Simplify validation scripts

This commit is contained in:
murashit 2026-06-20 08:18:19 +09:00
parent 880ef3f35f
commit b14969cbfb
6 changed files with 93 additions and 57 deletions

View file

@ -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.

View file

@ -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",

View file

@ -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);

View file

@ -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 = [];

60
scripts/check.mjs Normal file
View file

@ -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" },
];
}

View file

@ -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<string> {
return mkdtemp(path.join(tmpdir(), "codex-panel-scripts-"));
}
async function styleOrderFixture(): Promise<string> {
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<string, string>): Promise<void> {
await writeJson(path.join(cwd, "tsconfig.json"), {
compilerOptions: {