From b14969cbfbb03257f925b703aceb655e324ab7d6 Mon Sep 17 00:00:00 2001 From: murashit Date: Sat, 20 Jun 2026 08:18:19 +0900 Subject: [PATCH] Simplify validation scripts --- docs/development.md | 7 ++- package.json | 16 ++---- scripts/api-baseline.mjs | 27 ++++------ scripts/build-styles.mjs | 23 ++------- scripts/check.mjs | 60 +++++++++++++++++++++++ tests/scripts/development-scripts.test.ts | 17 ++++--- 6 files changed, 93 insertions(+), 57 deletions(-) create mode 100644 scripts/check.mjs diff --git a/docs/development.md b/docs/development.md index 1dff1f69..7633e034 100644 --- a/docs/development.md +++ b/docs/development.md @@ -11,13 +11,13 @@ npm run check npm run build ``` -Run `npm run format` after edits and before `npm run check` so Prettier-only issues are fixed upfront. `npm run check` runs TypeScript type checking, unit tests, lint checks, Prettier check, CSS build verification, and a production esbuild bundle. The local `npm run check` path runs checks in parallel and uses local caches where available. Use `npm run check:ci` when you need to reproduce the sequential, non-cached CI validation path. +Run `npm run format` after edits and before `npm run check` so Prettier-only issues are fixed upfront. `npm run check` runs TypeScript type checking, unit tests, lint checks, and Prettier check in parallel, then runs the CSS build verification and production esbuild bundle as the final step. `npm run check` and `npm run check:ci` use the same script runner; the local path uses caches where available, while `npm run check:ci` uses the non-cached validation commands used by CI. ## Generated and Loaded Files `main.js`, `styles.css`, `data.json`, and `node_modules/` are ignored by Git. `main.js` and `styles.css` are still the files Obsidian loads, so run `npm run build` after source changes. -CSS is authored in `src/styles/` and generated into the ignored root `styles.css` release asset. Use `npm run build:styles` when only regenerating CSS, or `npm run build:styles:check` to verify that the authored CSS can be rendered. +CSS is authored in `src/styles/` and generated into the ignored root `styles.css` release asset. Use `npm run build:styles` when only regenerating CSS; it also verifies the authored CSS order before writing `styles.css`. The app-server TypeScript bindings in `src/generated/app-server/` are generated from the installed Codex CLI: @@ -72,9 +72,8 @@ Use the local API baseline report when checking whether the development environm ```sh npm run api:baseline -npm run api:baseline:check ``` -Obsidian runtime compatibility is declared through `manifest.json` and `versions.json`. The `obsidian` npm package provides compile-time TypeScript API definitions; it is not runtime validation for an Obsidian app-version matrix. Because the project does not run app-version smoke tests, keep the API type package in the same minor as `manifest.minAppVersion` and use the latest patch in that minor for local type checking. Raise `manifest.minAppVersion` only when intentionally adopting a newer Obsidian app/API minor. +Obsidian runtime compatibility is declared through `manifest.json` and `versions.json`. The `obsidian` npm package provides compile-time TypeScript API definitions; it is not runtime validation for an Obsidian app-version matrix. Because the project does not run app-version smoke tests, keep the API type package in the same minor as `manifest.minAppVersion` and use the latest patch in that minor for local type checking. `npm run api:baseline` exits non-zero when the local environment or recorded baselines drift. Raise `manifest.minAppVersion` only when intentionally adopting a newer Obsidian app/API minor. Codex app-server compatibility is managed by Codex CLI minor version. README records the tested Codex CLI patch version, and the baseline check verifies that the local `codex --version` is in the same minor before app-server binding or compatibility work. diff --git a/package.json b/package.json index 9e169188..dd45b892 100644 --- a/package.json +++ b/package.json @@ -15,24 +15,16 @@ "homepage": "https://github.com/murashit/codex-panel#readme", "scripts": { "api:baseline": "node scripts/api-baseline.mjs", - "api:baseline:check": "node scripts/api-baseline.mjs --check", "build": "node esbuild.config.mjs", "build:styles": "node scripts/build-styles.mjs", - "build:styles:check": "node scripts/build-styles.mjs --check", - "check": "concurrently --group --pad-prefix --names typecheck,test,lint:ts,lint:css,lint:css-usage,lint:deps,lint:unused,format:check,build:styles:check,build \"npm run --silent typecheck\" \"npm run --silent test\" \"npm run --silent lint:ts\" \"npm run --silent lint:css\" \"npm run --silent lint:css-usage\" \"npm run --silent lint:deps\" \"npm run --silent lint:unused\" \"npm run --silent format:check\" \"npm run --silent build:styles:check\" \"npm run --silent build\"", - "check:ci": "npm run typecheck:ci && npm run test:ci && npm run lint:ci && npm run format:check:ci && npm run build:styles:check && npm run build", + "check": "node scripts/check.mjs", + "check:ci": "node scripts/check.mjs --ci", "format": "node scripts/format.mjs", "format:check": "node scripts/format.mjs --check --cache", "format:check:ci": "node scripts/format.mjs --check", "generate:app-server-types": "node scripts/generate-app-server-types.mjs", - "lint": "concurrently --group --pad-prefix --names lint:ts,lint:css,lint:css-usage,lint:deps,lint:unused \"npm run --silent lint:ts\" \"npm run --silent lint:css\" \"npm run --silent lint:css-usage\" \"npm run --silent lint:deps\" \"npm run --silent lint:unused\"", - "lint:ci": "npm run lint:ts:ci && npm run lint:css && npm run lint:css-usage && npm run lint:deps && npm run lint:unused", - "lint:css": "stylelint \"src/**/*.css\" --max-warnings=0", - "lint:css-usage": "node scripts/lint/check-css-usage.mjs --fail-on-candidates", - "lint:deps": "node scripts/lint/check-import-cycles.mjs", - "lint:ts": "eslint src tests scripts \"*.config.ts\" \"*.config.mjs\" --max-warnings=0 --cache --cache-strategy content --cache-location node_modules/.cache/eslint/.eslintcache", - "lint:ts:ci": "eslint src tests scripts \"*.config.ts\" \"*.config.mjs\" --max-warnings=0", - "lint:unused": "knip --no-progress", + "lint": "node scripts/check.mjs --lint", + "lint:ci": "node scripts/check.mjs --ci --lint", "release:check": "node scripts/release/check.mjs", "release:preflight": "node scripts/release/preflight.mjs", "release:prepare": "node scripts/release/prepare.mjs", diff --git a/scripts/api-baseline.mjs b/scripts/api-baseline.mjs index 1916f81a..cbeb72fd 100644 --- a/scripts/api-baseline.mjs +++ b/scripts/api-baseline.mjs @@ -3,17 +3,20 @@ import { spawnSync } from "node:child_process"; import { readJson } from "./utils.mjs"; const args = new Set(process.argv.slice(2)); -const shouldCheck = args.has("--check"); const asJson = args.has("--json"); +const validArgs = new Set(["--json"]); + +for (const arg of args) { + if (!validArgs.has(arg)) { + console.error("Usage: node scripts/api-baseline.mjs [--json]"); + process.exit(1); + } +} function fail(message) { failures.push(message); } -function warn(message) { - warnings.push(message); -} - function parseSemver(value) { const match = String(value ?? "").match(/\b(\d+)\.(\d+)\.(\d+)\b/); if (!match) return null; @@ -111,8 +114,6 @@ function displayValue(value) { } const failures = []; -const warnings = []; - const packageJson = await readJson("package.json"); const packageLockJson = await readJson("package-lock.json"); const manifestJson = await readJson("manifest.json"); @@ -148,9 +149,7 @@ if (!codexReadmeSemver) { fail("README.md Compatibility table must define `codex.testedCliVersion` as X.Y.Z."); } if (!codexLocalSemver) { - const message = "local codex --version could not be read."; - if (shouldCheck) fail(message); - else warn(message); + fail("local codex --version could not be read."); } if (codexReadmeSemver && codexLocalSemver && minorKey(codexReadmeSemver) !== minorKey(codexLocalSemver)) { fail(`local Codex CLI minor ${minorKey(codexLocalSemver)} does not match compatibility table minor ${minorKey(codexReadmeSemver)}.`); @@ -215,7 +214,6 @@ const report = { lockedPackageVersion: obsidianLockVersion, lockedPackageMinor: minorKey(obsidianLockSemver), }, - warnings, failures, }; @@ -243,11 +241,6 @@ if (asJson) { console.log(` package obsidian: ${displayValue(report.obsidian.packageDependency)}`); console.log(` package range: ${report.obsidian.packageDependencyRange}`); console.log(` package-lock obsidian: ${displayValue(report.obsidian.lockedPackageVersion)}`); - if (warnings.length > 0) { - console.log(""); - console.log("Warnings"); - for (const message of warnings) console.log(` - ${message}`); - } if (failures.length > 0) { console.log(""); console.log("Failures"); @@ -255,4 +248,4 @@ if (asJson) { } } -if (shouldCheck && failures.length > 0) process.exit(1); +if (failures.length > 0) process.exit(1); diff --git a/scripts/build-styles.mjs b/scripts/build-styles.mjs index 442fcc86..e5abe584 100644 --- a/scripts/build-styles.mjs +++ b/scripts/build-styles.mjs @@ -5,34 +5,21 @@ import { fileURLToPath } from "node:url"; const sourceDir = path.join("src", "styles"); const orderPath = path.join(sourceDir, "order.json"); const outputPath = "styles.css"; -const checkMode = process.argv.includes("--check"); -const validArgs = new Set(["--check"]); if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { - for (const arg of process.argv.slice(2)) { - if (!validArgs.has(arg)) { - console.error("Usage: node scripts/build-styles.mjs [--check]"); - process.exit(1); - } - } - - if (checkMode) { - await checkStyles(); - } else { - await buildStyles(); + if (process.argv.length > 2) { + console.error("Usage: node scripts/build-styles.mjs"); + process.exit(1); } + await buildStyles(); } export async function buildStyles() { + await checkStyleOrder(); await mkdir(path.dirname(outputPath), { recursive: true }); await writeFile(outputPath, await renderStyles()); } -async function checkStyles() { - await checkStyleOrder(); - await renderStyles(); -} - export async function renderStyles() { const parts = []; diff --git a/scripts/check.mjs b/scripts/check.mjs new file mode 100644 index 00000000..d0f262a8 --- /dev/null +++ b/scripts/check.mjs @@ -0,0 +1,60 @@ +import concurrently from "concurrently"; + +const args = process.argv.slice(2); +const validArgs = new Set(["--ci", "--lint"]); +const unknownArgs = args.filter((arg) => !validArgs.has(arg)); + +if (unknownArgs.length > 0) { + console.error("Usage: node scripts/check.mjs [--ci] [--lint]"); + process.exit(1); +} + +const ciMode = args.includes("--ci"); +const lintOnly = args.includes("--lint"); +const concurrentOptions = { + group: true, + padPrefix: true, + prefix: "name", +}; + +try { + if (lintOnly) { + await runConcurrently(lintCommands({ ciMode })); + } else { + await runConcurrently(checkCommands({ ciMode })); + await runConcurrently([{ name: "build", command: "node esbuild.config.mjs" }]); + } +} catch { + process.exit(1); +} + +async function runConcurrently(commands) { + await concurrently(commands, concurrentOptions).result; +} + +function checkCommands({ ciMode }) { + const suffix = ciMode ? ":ci" : ""; + const formatArgs = ciMode ? "--check" : "--check --cache"; + + return [ + { name: "typecheck", command: `npm run --silent typecheck${suffix}` }, + { name: "test", command: `npm run --silent test${suffix}` }, + ...lintCommands({ ciMode, namePrefix: "lint:" }), + { name: "format:check", command: `node scripts/format.mjs ${formatArgs}` }, + ]; +} + +function lintCommands({ ciMode, namePrefix = "" }) { + const eslintCacheArgs = ciMode ? "" : " --cache --cache-strategy content --cache-location node_modules/.cache/eslint/.eslintcache"; + + return [ + { + name: `${namePrefix}ts`, + command: `eslint src tests scripts "*.config.ts" "*.config.mjs" --max-warnings=0${eslintCacheArgs}`, + }, + { name: `${namePrefix}css`, command: 'stylelint "src/**/*.css" --max-warnings=0' }, + { name: `${namePrefix}css-usage`, command: "node scripts/lint/check-css-usage.mjs --fail-on-candidates" }, + { name: `${namePrefix}deps`, command: "node scripts/lint/check-import-cycles.mjs" }, + { name: `${namePrefix}unused`, command: "knip --no-progress" }, + ]; +} diff --git a/tests/scripts/development-scripts.test.ts b/tests/scripts/development-scripts.test.ts index eb53a181..c95adbc2 100644 --- a/tests/scripts/development-scripts.test.ts +++ b/tests/scripts/development-scripts.test.ts @@ -8,13 +8,9 @@ const repoRoot = process.cwd(); describe("development scripts", () => { it("fails style builds when CSS files are missing from the style order file", async () => { - const cwd = await tempWorkspace(); - await mkdir(path.join(cwd, "src", "styles"), { recursive: true }); - await writeJson(path.join(cwd, "src", "styles", "order.json"), ["00-tokens.css"]); - await writeFile(path.join(cwd, "src", "styles", "00-tokens.css"), ".codex-panel { color: var(--text-normal); }\n"); - await writeFile(path.join(cwd, "src", "styles", "10-unlisted.css"), ".codex-panel__extra { display: block; }\n"); + const cwd = await styleOrderFixture(); - const result = runNodeScript("scripts/build-styles.mjs", ["--check"], cwd); + const result = runNodeScript("scripts/build-styles.mjs", [], cwd); expect(result.status).toBe(1); expect(result.stderr).toContain("CSS files missing from src/styles/order.json: 10-unlisted.css"); @@ -209,6 +205,15 @@ async function tempWorkspace(): Promise { return mkdtemp(path.join(tmpdir(), "codex-panel-scripts-")); } +async function styleOrderFixture(): Promise { + const cwd = await tempWorkspace(); + await mkdir(path.join(cwd, "src", "styles"), { recursive: true }); + await writeJson(path.join(cwd, "src", "styles", "order.json"), ["00-tokens.css"]); + await writeFile(path.join(cwd, "src", "styles", "00-tokens.css"), ".codex-panel { color: var(--text-normal); }\n"); + await writeFile(path.join(cwd, "src", "styles", "10-unlisted.css"), ".codex-panel__extra { display: block; }\n"); + return cwd; +} + async function writeImportCycleFixture(cwd: string, files: Record): Promise { await writeJson(path.join(cwd, "tsconfig.json"), { compilerOptions: {