From be978df9736be93c953f2f1e7279973efde80770 Mon Sep 17 00:00:00 2001 From: Dominik Ferber Date: Mon, 25 May 2026 15:29:09 +0300 Subject: [PATCH 1/2] validate package.json fields --- .github/workflows/quality.yml | 14 ++++ scripts/validate-packages.mjs | 131 ++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 scripts/validate-packages.mjs diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 9142f516..4d1a551a 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -77,6 +77,20 @@ jobs: - name: Validate SKILL.md files run: node scripts/validate-skills.mjs + packages: + name: "Packages" + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - uses: actions/setup-node@v3 + with: + node-version-file: ".node-version" + + - name: Validate package.json files + run: node scripts/validate-packages.mjs + publint: name: "publint" runs-on: ubuntu-latest diff --git a/scripts/validate-packages.mjs b/scripts/validate-packages.mjs new file mode 100644 index 00000000..370f0b0c --- /dev/null +++ b/scripts/validate-packages.mjs @@ -0,0 +1,131 @@ +import { existsSync, readdirSync, readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +const GREEN = '\x1b[32m'; +const RED = '\x1b[31m'; +const BOLD = '\x1b[1m'; +const DIM = '\x1b[2m'; +const RESET = '\x1b[0m'; + +let hasErrors = false; + +const root = resolve(import.meta.dirname, '..'); +const packagesDir = resolve(root, 'packages'); + +const dirs = readdirSync(packagesDir, { withFileTypes: true }).filter((d) => + d.isDirectory(), +); + +const files = dirs + .map((d) => ({ + dir: d.name, + path: resolve(packagesDir, d.name, 'package.json'), + })) + .filter(({ path }) => existsSync(path)); + +if (files.length === 0) { + console.log('No package.json files found under packages/.'); + process.exit(0); +} + +function isPlainObject(value) { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +function checkPackage(pkg) { + const checks = []; + + if (typeof pkg.description !== 'string' || pkg.description.trim() === '') { + checks.push({ + ok: false, + label: 'description', + detail: 'must be a non-empty string', + }); + } else { + checks.push({ ok: true, label: 'description', detail: 'non-empty string' }); + } + + if (!Array.isArray(pkg.keywords) || pkg.keywords.length === 0) { + checks.push({ + ok: false, + label: 'keywords', + detail: 'must be a non-empty array', + }); + } else { + checks.push({ + ok: true, + label: 'keywords', + detail: `${pkg.keywords.length} entries`, + }); + } + + if (pkg.license !== 'MIT') { + checks.push({ + ok: false, + label: 'license', + detail: `must be "MIT" (got ${JSON.stringify(pkg.license)})`, + }); + } else { + checks.push({ ok: true, label: 'license', detail: '"MIT"' }); + } + + if (isPlainObject(pkg.author)) { + checks.push({ ok: true, label: 'author', detail: 'object' }); + } else if (typeof pkg.author === 'string' && pkg.author.trim() !== '') { + checks.push({ ok: true, label: 'author', detail: 'non-empty string' }); + } else { + checks.push({ + ok: false, + label: 'author', + detail: 'must be an object or non-empty string', + }); + } + + if (!isPlainObject(pkg.repository)) { + checks.push({ + ok: false, + label: 'repository', + detail: 'must be an object', + }); + } else { + checks.push({ ok: true, label: 'repository', detail: 'object' }); + } + + return checks; +} + +for (const { dir, path: file } of files) { + const relPath = file.replace(`${root}/`, ''); + + let pkg; + try { + pkg = JSON.parse(readFileSync(file, 'utf8')); + } catch (e) { + console.log(`\n${BOLD}${dir}${RESET} ${DIM}(${relPath})${RESET}`); + console.error(` ${RED}✗${RESET} Invalid JSON: ${e.message}`); + hasErrors = true; + continue; + } + + const checks = checkPackage(pkg); + const failed = checks.filter((c) => !c.ok).length; + const status = + failed === 0 + ? `${GREEN}all ${checks.length} checks passed${RESET}` + : `${RED}${failed}/${checks.length} failed${RESET}`; + + console.log(`\n${BOLD}${dir}${RESET} ${DIM}(${relPath})${RESET} — ${status}`); + for (const c of checks) { + const icon = c.ok ? `${GREEN}✓${RESET}` : `${RED}✗${RESET}`; + console.log(` ${icon} ${c.label}: ${c.detail}`); + } + + if (failed > 0) hasErrors = true; +} + +if (hasErrors) { + console.error(`\n${RED}Package validation failed.${RESET}`); + process.exit(1); +} else { + console.log(`\n${GREEN}All packages valid.${RESET}`); +} From a8b06f7af0d5756d6db126404f7529f4c2376e91 Mon Sep 17 00:00:00 2001 From: Dominik Ferber Date: Mon, 25 May 2026 15:30:01 +0300 Subject: [PATCH 2/2] update package.json fields --- package.json | 1 + packages/adapter-edge-config/package.json | 16 +++++++++++++--- packages/adapter-flagsmith/package.json | 2 +- packages/adapter-growthbook/package.json | 8 ++++++++ packages/adapter-hypertune/package.json | 16 +++++++++++++--- packages/adapter-launchdarkly/package.json | 2 +- packages/adapter-openfeature/package.json | 2 +- packages/adapter-optimizely/package.json | 13 ++++++++++--- packages/adapter-posthog/package.json | 2 +- packages/adapter-reflag/package.json | 2 +- packages/adapter-split/package.json | 16 +++++++++++++--- packages/adapter-statsig/package.json | 1 + packages/adapter-vercel/package.json | 16 +++++++++++++--- packages/flags/package.json | 1 + packages/prepare-flags-definitions/package.json | 15 ++++++++++++--- packages/vercel-flags-core/package.json | 16 +++++++++++++--- 16 files changed, 103 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index 9e4ec168..ef461f0d 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "test:e2e": "turbo test:e2e", "test:integration": "turbo test:integration", "type-check": "turbo type-check", + "validate-packages": "node scripts/validate-packages.mjs", "validate-skills": "node scripts/validate-skills.mjs", "version-packages": "changeset version && pnpm i --no-frozen-lockfile && git add ." }, diff --git a/packages/adapter-edge-config/package.json b/packages/adapter-edge-config/package.json index cbd07ced..c8abd869 100644 --- a/packages/adapter-edge-config/package.json +++ b/packages/adapter-edge-config/package.json @@ -1,10 +1,20 @@ { "name": "@flags-sdk/edge-config", "version": "0.1.2", - "description": "", - "keywords": [], + "description": "A Flags SDK adapter for Edge Config", + "keywords": [ + "vercel", + "flags", + "vercel flags", + "feature flags", + "flags sdk" + ], "license": "MIT", - "author": "", + "author": "Dominik Ferber ", + "repository": { + "type": "git", + "url": "git+https://github.com/vercel/flags.git" + }, "sideEffects": false, "type": "module", "exports": { diff --git a/packages/adapter-flagsmith/package.json b/packages/adapter-flagsmith/package.json index 6015a443..f4cd8d02 100644 --- a/packages/adapter-flagsmith/package.json +++ b/packages/adapter-flagsmith/package.json @@ -18,7 +18,7 @@ "url": "git+https://github.com/vercel/flags.git" }, "license": "MIT", - "author": "", + "author": "Dominik Ferber ", "sideEffects": false, "type": "module", "exports": { diff --git a/packages/adapter-growthbook/package.json b/packages/adapter-growthbook/package.json index a8439639..be2df0c7 100644 --- a/packages/adapter-growthbook/package.json +++ b/packages/adapter-growthbook/package.json @@ -10,6 +10,14 @@ "type": "git", "url": "git+https://github.com/vercel/flags.git" }, + "author": "Dominik Ferber ", + "keywords": [ + "growthbook", + "flags", + "flags sdk", + "experimentation", + "ab testing" + ], "license": "MIT", "sideEffects": false, "type": "module", diff --git a/packages/adapter-hypertune/package.json b/packages/adapter-hypertune/package.json index fb8fca00..dfacb73a 100644 --- a/packages/adapter-hypertune/package.json +++ b/packages/adapter-hypertune/package.json @@ -1,10 +1,19 @@ { "name": "@flags-sdk/hypertune", "version": "0.3.2", - "description": "", - "keywords": [], + "description": "A HyperTune adapter for the Flags SDK", + "keywords": [ + "hypertune", + "flags", + "flags sdk", + "experimentation", + "ab testing" + ], "license": "MIT", - "author": "", + "repository": { + "type": "git", + "url": "git+https://github.com/vercel/flags.git" + }, "sideEffects": false, "type": "module", "exports": { @@ -13,6 +22,7 @@ "require": "./dist/index.cjs" } }, + "author": "Miraan Tabrez ", "main": "./dist/index.js", "typesVersions": { "*": { diff --git a/packages/adapter-launchdarkly/package.json b/packages/adapter-launchdarkly/package.json index 2646141f..dfa58aad 100644 --- a/packages/adapter-launchdarkly/package.json +++ b/packages/adapter-launchdarkly/package.json @@ -19,7 +19,7 @@ "url": "git+https://github.com/vercel/flags.git" }, "license": "MIT", - "author": "", + "author": "Dominik Ferber ", "sideEffects": false, "type": "module", "exports": { diff --git a/packages/adapter-openfeature/package.json b/packages/adapter-openfeature/package.json index 5186d6af..c37b0953 100644 --- a/packages/adapter-openfeature/package.json +++ b/packages/adapter-openfeature/package.json @@ -18,7 +18,7 @@ "url": "git+https://github.com/vercel/flags.git" }, "license": "MIT", - "author": "", + "author": "Dominik Ferber ", "sideEffects": false, "type": "module", "exports": { diff --git a/packages/adapter-optimizely/package.json b/packages/adapter-optimizely/package.json index aba02250..e8247d05 100644 --- a/packages/adapter-optimizely/package.json +++ b/packages/adapter-optimizely/package.json @@ -1,12 +1,19 @@ { "name": "@flags-sdk/optimizely", "version": "0.1.1", - "description": "", - "keywords": [], + "description": "A provider for the Flags Explorer", + "keywords": [ + "optimizely", + "flags explorer" + ], "license": "MIT", - "author": "", + "author": "Dominik Ferber ", "sideEffects": false, "type": "module", + "repository": { + "type": "git", + "url": "git+https://github.com/vercel/flags.git" + }, "exports": { ".": { "import": "./dist/index.js", diff --git a/packages/adapter-posthog/package.json b/packages/adapter-posthog/package.json index a180390b..5aa5135c 100644 --- a/packages/adapter-posthog/package.json +++ b/packages/adapter-posthog/package.json @@ -19,7 +19,7 @@ "url": "git+https://github.com/vercel/flags.git" }, "license": "MIT", - "author": "", + "author": "Aaron Morris ", "sideEffects": false, "type": "module", "exports": { diff --git a/packages/adapter-reflag/package.json b/packages/adapter-reflag/package.json index 02cf3713..aa331bee 100644 --- a/packages/adapter-reflag/package.json +++ b/packages/adapter-reflag/package.json @@ -19,7 +19,7 @@ "url": "git+https://github.com/vercel/flags.git" }, "license": "MIT", - "author": "", + "author": "Ron Cohen ", "sideEffects": false, "type": "module", "exports": { diff --git a/packages/adapter-split/package.json b/packages/adapter-split/package.json index 99c0c342..1508f17c 100644 --- a/packages/adapter-split/package.json +++ b/packages/adapter-split/package.json @@ -1,12 +1,22 @@ { "name": "@flags-sdk/split", "version": "0.1.1", - "description": "", - "keywords": [], + "description": "A Split adapter for the Flags SDK", + "keywords": [ + "split", + "flags", + "flags sdk", + "experimentation", + "ab testing" + ], "license": "MIT", - "author": "", + "author": "Dominik Ferber ", "sideEffects": false, "type": "module", + "repository": { + "type": "git", + "url": "git+https://github.com/vercel/flags.git" + }, "exports": { ".": { "import": "./dist/index.js", diff --git a/packages/adapter-statsig/package.json b/packages/adapter-statsig/package.json index 4563bf50..4733e2ef 100644 --- a/packages/adapter-statsig/package.json +++ b/packages/adapter-statsig/package.json @@ -12,6 +12,7 @@ "experimentation", "ab testing" ], + "author": "Aaron Morris ", "homepage": "https://flags-sdk.dev", "bugs": { "url": "https://github.com/vercel/flags/issues" diff --git a/packages/adapter-vercel/package.json b/packages/adapter-vercel/package.json index 4e5ad9e5..de583dc4 100644 --- a/packages/adapter-vercel/package.json +++ b/packages/adapter-vercel/package.json @@ -1,10 +1,20 @@ { "name": "@flags-sdk/vercel", "version": "1.3.0", - "description": "", - "keywords": [], + "description": "A Flags SDK adapter for Vercel Flags", + "keywords": [ + "vercel", + "flags", + "vercel flags", + "feature flags", + "flags sdk" + ], "license": "MIT", - "author": "", + "repository": { + "type": "git", + "url": "git+https://github.com/vercel/flags.git" + }, + "author": "Dominik Ferber ", "sideEffects": false, "type": "module", "exports": { diff --git a/packages/flags/package.json b/packages/flags/package.json index c75f185d..b6982a56 100644 --- a/packages/flags/package.json +++ b/packages/flags/package.json @@ -10,6 +10,7 @@ "overrides", "SvelteKit" ], + "author": "Dominik Ferber ", "homepage": "https://flags-sdk.dev", "bugs": { "url": "https://github.com/vercel/flags/issues" diff --git a/packages/prepare-flags-definitions/package.json b/packages/prepare-flags-definitions/package.json index b4b9efdf..7b6bc95c 100644 --- a/packages/prepare-flags-definitions/package.json +++ b/packages/prepare-flags-definitions/package.json @@ -1,10 +1,19 @@ { "name": "@vercel/prepare-flags-definitions", "version": "0.2.1", - "description": "", - "keywords": [], + "description": "A utility for preparing flags definitions for embedding", + "keywords": [ + "flags", + "flags sdk", + "experimentation", + "ab testing" + ], "license": "MIT", - "author": "", + "author": "Dominik Ferber ", + "repository": { + "type": "git", + "url": "git+https://github.com/vercel/flags.git" + }, "sideEffects": false, "type": "module", "exports": { diff --git a/packages/vercel-flags-core/package.json b/packages/vercel-flags-core/package.json index b0350227..cc1a4c3f 100644 --- a/packages/vercel-flags-core/package.json +++ b/packages/vercel-flags-core/package.json @@ -1,10 +1,20 @@ { "name": "@vercel/flags-core", "version": "1.4.0", - "description": "", - "keywords": [], + "description": "A server-side client for Vercel Flags", + "keywords": [ + "vercel", + "flags", + "vercel flags", + "feature flags", + "flags sdk" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/vercel/flags.git" + }, "license": "MIT", - "author": "", + "author": "Dominik Ferber ", "sideEffects": false, "type": "module", "exports": {