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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Verify extension version
run: pnpm version:check

- name: Run lint
run: pnpm lint

Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Synchronize and verify release version
run: |
pnpm version:sync
node scripts/verify-extension-version.mjs "${GITHUB_REF_NAME}"

- name: Run lint
run: pnpm lint

Expand All @@ -44,6 +49,9 @@ jobs:
- name: Build extension
run: pnpm build

- name: Verify packaged extension version
run: node scripts/verify-extension-version.mjs "${GITHUB_REF_NAME}" out/manifest.json

- name: Verify extension build output
run: |
test -d out
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,14 @@ pnpm build
pnpm start
pnpm lint
pnpm typecheck
pnpm version:sync
pnpm version:check
```

`package.json`의 `version`을 릴리스 버전의 기준으로 사용합니다. 버전을 변경한 뒤
`pnpm version:sync`를 실행하면 원본 manifest가 동기화되며, 릴리스에서는 태그와 빌드
산출물의 버전까지 자동으로 검증합니다.

## 구조

- `src/app`: 팝업 UI를 만드는 Next.js App Router 엔트리
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
"type": "module",
"scripts": {
"dev": "next dev --turbopack",
"prebuild": "pnpm version:sync",
"build": "next build --webpack && node scripts/copy-extension.mjs",
"start": "next start",
"lint": "eslint .",
"typecheck": "tsc --noEmit"
"typecheck": "tsc --noEmit",
"version:sync": "node scripts/sync-extension-version.mjs",
"version:check": "node scripts/verify-extension-version.mjs"
},
"dependencies": {
"@hookform/resolvers": "^5.2.2",
Expand Down
3 changes: 3 additions & 0 deletions scripts/copy-extension.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
} from 'node:fs';
import { basename, dirname, join, relative } from 'node:path';

import { getPackageVersion } from './extension-version.mjs';

const root = process.cwd();
const outDir = join(root, 'out');
const nextDir = join(outDir, 'next', '_next');
Expand Down Expand Up @@ -164,6 +166,7 @@ function normalizeManifest() {
if (!existsSync(manifestPath)) return;

const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
manifest.version = getPackageVersion(root);
delete manifest.content_security_policy;
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
}
Expand Down
63 changes: 63 additions & 0 deletions scripts/extension-version.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';

export function readJson(filePath) {
return JSON.parse(readFileSync(filePath, 'utf8'));
}

export function assertValidChromeVersion(version, label = 'version') {
if (typeof version !== 'string' || !/^(0|[1-9]\d*)(\.(0|[1-9]\d*)){0,3}$/.test(version)) {
throw new Error(`${label} must contain one to four dot-separated integers: ${version}`);
}

const parts = version.split('.').map(Number);
if (parts.every(part => part === 0) || parts.some(part => part > 65_535)) {
throw new Error(`${label} is not a valid Chrome extension version: ${version}`);
}

return version;
}

export function getPackageVersion(root) {
const packageJson = readJson(join(root, 'package.json'));
return assertValidChromeVersion(packageJson.version, 'package.json version');
}

export function syncManifestVersion(root, manifestPath = join(root, 'extension', 'manifest.json')) {
const packageVersion = getPackageVersion(root);
const manifest = readJson(manifestPath);

if (manifest.version !== packageVersion) {
manifest.version = packageVersion;
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
}

return packageVersion;
}

export function verifyVersions(root, { tag, manifestPath } = {}) {
const packageVersion = getPackageVersion(root);
const resolvedManifestPath = manifestPath ?? join(root, 'extension', 'manifest.json');
const manifest = readJson(resolvedManifestPath);
const manifestVersion = assertValidChromeVersion(
manifest.version,
`${resolvedManifestPath} version`
);

if (manifestVersion !== packageVersion) {
throw new Error(
`Version mismatch: package.json=${packageVersion}, manifest=${manifestVersion}. ` +
'Run pnpm version:sync.'
);
}

if (tag) {
const tagVersion = tag.startsWith('v') ? tag.slice(1) : tag;
assertValidChromeVersion(tagVersion, 'release tag version');
if (tagVersion !== packageVersion) {
throw new Error(`Version mismatch: tag=${tag}, package.json=${packageVersion}`);
}
}

return packageVersion;
}
4 changes: 4 additions & 0 deletions scripts/sync-extension-version.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { syncManifestVersion } from './extension-version.mjs';

const version = syncManifestVersion(process.cwd());
console.log(`Extension manifest version synchronized to ${version}.`);
9 changes: 9 additions & 0 deletions scripts/verify-extension-version.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { resolve } from 'node:path';

import { verifyVersions } from './extension-version.mjs';

const [tag, manifestArgument] = process.argv.slice(2);
const manifestPath = manifestArgument ? resolve(process.cwd(), manifestArgument) : undefined;
const version = verifyVersions(process.cwd(), { tag, manifestPath });

console.log(`Extension version ${version} is valid and synchronized.`);
Loading