Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,4 @@ jobs:
node-version: 22
cache: npm
- run: npm ci
- run: npm run typecheck
- run: npm run test
- run: npm run build
- run: npm run verify
4 changes: 1 addition & 3 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ jobs:
registry-url: https://registry.npmjs.org

- run: npm ci
- run: npm run typecheck
- run: npm run test
- run: npm run build
- run: npm run verify

- name: Check release version matches package.json
run: |
Expand Down
6 changes: 6 additions & 0 deletions README.ko.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# numkey

<p align="center">
<a href="https://devslab.kr/brand/open-source/"><img src="docs/assets/brand/readme-header.png" alt="numkey - 묶인 숫자 단위를 가로지르는 안정된 캐럿" width="960"></a>
</p>

[DevsLab 오픈소스](https://devslab.kr/brand/open-source/)

[![npm](https://img.shields.io/npm/v/%40devslab%2Fnumkey)](https://www.npmjs.com/package/@devslab/numkey)
[![CI](https://github.com/devslab-kr/numkey/actions/workflows/ci.yml/badge.svg)](https://github.com/devslab-kr/numkey/actions/workflows/ci.yml)
![TypeScript](https://img.shields.io/badge/TypeScript-Ready-3178C6?logo=typescript&logoColor=white)
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# numkey

<p align="center">
<a href="https://devslab.kr/brand/open-source/"><img src="docs/assets/brand/readme-header.png" alt="numkey - a stable caret across grouped numeric units" width="960"></a>
</p>

Open source by [DevsLab](https://devslab.kr/brand/open-source/)

[![npm](https://img.shields.io/npm/v/%40devslab%2Fnumkey)](https://www.npmjs.com/package/@devslab/numkey)
[![CI](https://github.com/devslab-kr/numkey/actions/workflows/ci.yml/badge.svg)](https://github.com/devslab-kr/numkey/actions/workflows/ci.yml)
![TypeScript](https://img.shields.io/badge/TypeScript-Ready-3178C6?logo=typescript&logoColor=white)
Expand Down
21 changes: 21 additions & 0 deletions docs/assets/brand/checksums.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
298658c5507dd8acacfa0b2c26b0cd00b612f0932698e4223f0a8a97957fafed apple-touch-icon.png
4205e96a25955ce59c730fd380e45db18b153e152424661df1822ed296ccaf1c favicon.ico
4ec733f9542323f6d2840ba743c4a11df4d416a7efa1d214567d62b58653619b favicon.svg
4ec733f9542323f6d2840ba743c4a11df4d416a7efa1d214567d62b58653619b glyph-color.svg
03f6342931d1660554b44bb5704aeb4639fa5fb6d12dbf6286418ecfaeddc654 glyph-monochrome.svg
dc08a6ba51a2a8e9cb82d56f900ef2de9330ac72fbb3c727e0436a4ab2f3944f glyph-reversed.svg
f5cba92cab71ae542c297205daca2f50bf7a64279da964a0239f85b062b7e36d icons/icon-16.png
298658c5507dd8acacfa0b2c26b0cd00b612f0932698e4223f0a8a97957fafed icons/icon-180.png
8e9148efe85095d304c182fe0fe3fa2a2b44bcfe2a4e26077c21947baf92be96 icons/icon-192.png
3c696ba85751c9a9e805141adf07242f495d8ffc115b2477074862a3843a19fa icons/icon-32.png
bfa28751bd6e26e47dfb8458a23d309082bb7db6e293957438ac02fdd4af0fa8 icons/icon-48.png
3004387fcbc066076f70f383e74e11e9a94f6cd771e032ca8ff993daede614dc icons/icon-512.png
905cf0a05ab5d3787bc02ff7d4591bfa020d72aefdd0e0d0c53271da6d3f383c lockup-endorsed.svg
ee4822f0054f88b5a386dc53d5034af1577309b0425e78ed2af85167e8467aa2 lockup.svg
90062f969fca1da22b05ec5d3f92bbc0f46e736b473351078e54317df19c41be maskable-192.png
18b8c7957b532b78ef631fca894a245bc4bd1eb7a36255e6ec1a7da857b698c0 maskable-512.png
affd93edb1d511780bad26de47cc673a5e93014967916029dd8db68ef9ec394e og.png
8e9148efe85095d304c182fe0fe3fa2a2b44bcfe2a4e26077c21947baf92be96 pwa-192.png
3004387fcbc066076f70f383e74e11e9a94f6cd771e032ca8ff993daede614dc pwa-512.png
481391d02b1f50ed6dba1790c0899bcc7d16948421bba66778756a89921b5dbb readme-header.png
27a520d8daf6752076ac90d52b611034af0d2e29a8167e3e1ef7d4f0246b52e9 wordmark.svg
1 change: 1 addition & 0 deletions docs/assets/brand/project-lockup.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/assets/brand/project-mark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/brand/readme-header.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,12 @@
"sideEffects": false,
"scripts": {
"build": "tsup",
"check:brand": "node scripts/check-brand-assets.mjs",
"typecheck": "tsc --noEmit",
"test": "vitest run",
"test:watch": "vitest",
"prepublishOnly": "npm run typecheck && npm run test && npm run build"
"verify": "npm run check:brand && npm run typecheck && npm run test && npm run build",
"prepublishOnly": "npm run verify"
},
"peerDependencies": {
"react": ">=18",
Expand Down
70 changes: 70 additions & 0 deletions scripts/check-brand-assets.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { createHash } from 'node:crypto'
import { readFile } from 'node:fs/promises'
import { resolve } from 'node:path'
import assert from 'node:assert/strict'

const root = resolve(import.meta.dirname, '..')
const read = (file) => readFile(resolve(root, file), 'utf8')
const hash = async (file) => createHash('sha256').update(await readFile(resolve(root, file))).digest('hex')

const assets = {
'docs/assets/brand/project-mark.svg': '4ec733f9542323f6d2840ba743c4a11df4d416a7efa1d214567d62b58653619b',
'docs/assets/brand/project-lockup.svg': '905cf0a05ab5d3787bc02ff7d4591bfa020d72aefdd0e0d0c53271da6d3f383c',
'docs/assets/brand/readme-header.png': '481391d02b1f50ed6dba1790c0899bcc7d16948421bba66778756a89921b5dbb',
'site/favicon.svg': '4ec733f9542323f6d2840ba743c4a11df4d416a7efa1d214567d62b58653619b',
'site/favicon.ico': '4205e96a25955ce59c730fd380e45db18b153e152424661df1822ed296ccaf1c',
'site/apple-touch-icon.png': '298658c5507dd8acacfa0b2c26b0cd00b612f0932698e4223f0a8a97957fafed',
'site/og.png': 'affd93edb1d511780bad26de47cc673a5e93014967916029dd8db68ef9ec394e',
'site/project-mark-color.svg': '4ec733f9542323f6d2840ba743c4a11df4d416a7efa1d214567d62b58653619b',
'site/project-mark-reversed.svg': 'dc08a6ba51a2a8e9cb82d56f900ef2de9330ac72fbb3c727e0436a4ab2f3944f',
}

for (const [file, expected] of Object.entries(assets)) {
assert.equal(await hash(file), expected, `${file} must be the approved O04 asset`)
}

const checksumFile = 'docs/assets/brand/checksums.txt'
assert.equal(await hash(checksumFile), 'f1c266d086750eed1fc053e10e858ad9d33c3227d468b823cc7c7d8ca2f4a1ed', 'vendored O04 checksums.txt must remain exact')
const checksums = await read(checksumFile)
for (const [asset, expected] of Object.entries({
'glyph-color.svg': '4ec733f9542323f6d2840ba743c4a11df4d416a7efa1d214567d62b58653619b',
'lockup-endorsed.svg': '905cf0a05ab5d3787bc02ff7d4591bfa020d72aefdd0e0d0c53271da6d3f383c',
'readme-header.png': '481391d02b1f50ed6dba1790c0899bcc7d16948421bba66778756a89921b5dbb',
})) {
assert.match(checksums, new RegExp(`${expected} ${asset}`), `checksums.txt must declare ${asset}`)
}

const preview = 'https://raw.githubusercontent.com/devslab-kr/numkey/main/docs/preview.png'
assert.equal(await hash('docs/preview.png'), 'c26e0695abc6122c508d1af04173ad95c864b8c82965b827b210f4e5a7b67715', 'docs/preview.png must be preserved byte-for-byte')
for (const [file, endorsement] of [
['README.md', /Open source by \[DevsLab\]\(https:\/\/devslab\.kr\/brand\/open-source\/\)/],
['README.ko.md', /\[DevsLab 오픈소스\]\(https:\/\/devslab\.kr\/brand\/open-source\/\)/],
]) {
const content = await read(file)
assert.match(content, endorsement, `${file} needs its localized DevsLab endorsement`)
assert.match(content, /docs\/assets\/brand\/readme-header\.png/, `${file} needs the O04 README header`)
assert.match(content, new RegExp(preview.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), `${file} must retain the preview reference`)
}

const site = await read('site/index.html')
assert.match(site, /srcset="project-mark-reversed\.svg"/, 'dark mode must use the explicit O04 reversed mark')
assert.match(site, /src="project-mark-color\.svg"/, 'light mode must use the explicit O04 color mark')
assert.doesNotMatch(site, /src="project-lockup\.svg"/, 'site must not render an unstyleable currentColor lockup through img')
assert.match(site, /rel="canonical" href="https:\/\/devslab-kr\.github\.io\/numkey\/"/, 'site needs a canonical URL')
assert.match(site, /property="og:title" content="numkey/, 'site needs an Open Graph title')
assert.match(site, /property="og:image" content="https:\/\/devslab-kr\.github\.io\/numkey\/og\.png"/, 'site needs the O04 Open Graph image')
assert.match(site, /name="twitter:card" content="summary_large_image"/, 'site needs a Twitter card')
assert.match(site, /name="twitter:image:alt" content="numkey - a stable caret across grouped numeric units"/, 'site needs Twitter image alt text')
const lightGlow = site.indexOf('rgb(6 182 212 / .11)')
const darkGlow = site.lastIndexOf('@media (prefers-color-scheme: dark)')
assert.ok(darkGlow > lightGlow, 'dark atmosphere override must follow the light glow in source order')
assert.match(site.slice(darkGlow), /rgb\(6 182 212 \/ \.10\)/, 'dark atmosphere must cap cyan opacity at .10')

const pkg = JSON.parse(await read('package.json'))
assert.equal(pkg.scripts.verify, 'npm run check:brand && npm run typecheck && npm run test && npm run build', 'routine verification must include the brand check')
assert.equal(pkg.scripts.prepublishOnly, 'npm run verify', 'publishing must use routine verification')
for (const workflow of ['.github/workflows/ci.yml', '.github/workflows/publish.yml']) {
assert.match(await read(workflow), /npm run verify/, `${workflow} must use the routine verification gate`)
}

console.log(`Verified ${Object.keys(assets).length} approved O04 assets and numkey brand surfaces.`)
Binary file added site/apple-touch-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added site/favicon.ico
Binary file not shown.
1 change: 1 addition & 0 deletions site/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading