Organize development scripts

This commit is contained in:
murashit 2026-05-30 10:01:34 +09:00
parent fa904e4fad
commit 062f775c41
10 changed files with 124 additions and 73 deletions

View file

@ -18,24 +18,24 @@
"build:prod": "node esbuild.config.mjs --production",
"dev": "node esbuild.config.mjs --watch",
"format": "node scripts/format.mjs",
"format:check": "node scripts/format.mjs --check --cache --cache-location node_modules/.cache/prettier/.prettier-cache",
"format:check": "node scripts/format.mjs --check --cache",
"format:check:ci": "node scripts/format.mjs --check",
"api:baseline": "node scripts/report-api-baseline.mjs",
"api:baseline:check": "node scripts/report-api-baseline.mjs --check",
"generate:app-server-types": "node scripts/clean-generated-types.mjs && codex app-server generate-ts --experimental --out src/generated/app-server && node scripts/normalize-generated-types.mjs",
"api:baseline": "node scripts/api-baseline.mjs",
"api:baseline:check": "node scripts/api-baseline.mjs --check",
"generate:app-server-types": "node scripts/generate-app-server-types.mjs",
"lint": "node scripts/run-parallel.mjs lint:ts lint:css",
"lint:css": "stylelint \"*.css\" \"src/**/*.css\" --max-warnings=0",
"lint:ts": "eslint src tests scripts \"*.config.ts\" \"*.config.mjs\" --max-warnings=0 --cache --cache-location node_modules/.cache/eslint/ --cache-strategy content",
"lint:ts:ci": "eslint src tests scripts \"*.config.ts\" \"*.config.mjs\" --max-warnings=0",
"release:check": "node scripts/check-release.mjs",
"release:preflight": "node scripts/preflight-release.mjs",
"release:prepare": "node scripts/prepare-release.mjs",
"release:check": "node scripts/release/check.mjs",
"release:preflight": "node scripts/release/preflight.mjs",
"release:prepare": "node scripts/release/prepare.mjs",
"test": "vitest run --cache",
"test:ci": "vitest run",
"typecheck": "tsc -p tsconfig.json --noEmit --incremental --tsBuildInfoFile node_modules/.cache/typescript/tsconfig.tsbuildinfo",
"typecheck:ci": "tsc -p tsconfig.json --noEmit",
"check": "node scripts/run-parallel.mjs typecheck test lint:ts lint:css format:check && npm run build:prod",
"check:ci": "npm run typecheck:ci && npm run test:ci && npm run lint:ts:ci && npm run lint:css && npm run format:check:ci && npm run build:prod"
"check": "node scripts/check.mjs",
"check:ci": "node scripts/check.mjs --ci"
},
"devDependencies": {
"@eslint/js": "^10.0.1",

View file

@ -1,6 +1,6 @@
import { readFile } from "node:fs/promises";
import { spawnSync } from "node:child_process";
import { readJson } from "./release-utils.mjs";
import { readJson } from "./release/utils.mjs";
const args = new Set(process.argv.slice(2));
const shouldCheck = args.has("--check");
@ -118,6 +118,7 @@ const manifestJson = await readJson("manifest.json");
const versionsJson = await readJson("versions.json");
const readme = await readFile("README.md", "utf8");
const clientSource = await readFile("src/app-server/client.ts", "utf8");
const appServerGenerateSource = await readFile("scripts/generate-app-server-types.mjs", "utf8");
const readmeBaselines = readCompatibilityBaselines(readme);
const codexReadmeVersion = readmeBaselines.codexTestedCliVersion;
@ -134,9 +135,10 @@ const obsidianSpecSemver = parseSemver(obsidianSpec);
const obsidianLockSemver = parseSemver(obsidianLockVersion);
const obsidianMinSemver = parseSemver(obsidianMinVersion);
const appServerGenerateScript = packageJson.scripts?.["generate:app-server-types"] ?? "";
const appServerGenerationExperimental =
appServerGenerateScript.includes("codex app-server generate-ts") && appServerGenerateScript.includes("--experimental");
appServerGenerateSource.includes("app-server") &&
appServerGenerateSource.includes("generate-ts") &&
appServerGenerateSource.includes("--experimental");
const initializeExperimentalApi = /experimentalApi:\s*true/.test(clientSource);
const initializeRequestAttestationDisabled = /requestAttestation:\s*false/.test(clientSource);

32
scripts/check.mjs Normal file
View file

@ -0,0 +1,32 @@
import { spawnSync } from "node:child_process";
const args = new Set(process.argv.slice(2));
const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
for (const arg of args) {
if (arg !== "--ci") {
console.error("Usage: node scripts/check.mjs [--ci]");
process.exit(1);
}
}
if (args.has("--ci")) {
for (const script of ["typecheck:ci", "test:ci", "lint:ts:ci", "lint:css", "format:check:ci", "build:prod"]) {
run(npmCommand, ["run", script]);
}
} else {
run("node", ["scripts/run-parallel.mjs", "typecheck", "test", "lint:ts", "lint:css", "format:check"]);
run(npmCommand, ["run", "build:prod"]);
}
function run(command, args) {
const result = spawnSync(command, args, {
stdio: "inherit",
shell: false,
});
if (result.error) {
console.error(`Failed to run ${command} ${args.join(" ")}: ${result.error.message}`);
process.exit(1);
}
if (result.status !== 0) process.exit(result.status ?? 1);
}

View file

@ -1,7 +0,0 @@
import { mkdir, rm } from "node:fs/promises";
import path from "node:path";
const generatedDir = path.resolve("src/generated/app-server");
await rm(generatedDir, { recursive: true, force: true });
await mkdir(generatedDir, { recursive: true });

View file

@ -0,0 +1,68 @@
import { spawnSync } from "node:child_process";
import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
import path from "node:path";
const generatedDir = path.resolve("src/generated/app-server");
const generatedHeader = "// GENERATED CODE! DO NOT MODIFY BY HAND!";
const normalizationNotice = "// This file was mechanically normalized after generation by scripts/generate-app-server-types.mjs.";
await cleanGeneratedTypes();
run("codex", ["app-server", "generate-ts", "--experimental", "--out", "src/generated/app-server"]);
await normalizeGeneratedTypes();
async function cleanGeneratedTypes() {
await rm(generatedDir, { recursive: true, force: true });
await mkdir(generatedDir, { recursive: true });
}
async function normalizeGeneratedTypes() {
const files = await listTypeScriptFiles(generatedDir);
await Promise.all(
files.map(async (file) => {
const source = await readFile(file, "utf8");
const normalized = normalizeSource(source);
const nextSource = normalized === source ? source : addNormalizationNotice(normalized);
if (nextSource !== source) await writeFile(file, nextSource);
}),
);
}
async function listTypeScriptFiles(dir) {
const entries = await readdir(dir, { withFileTypes: true });
const files = await Promise.all(
entries.map((entry) => {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) return listTypeScriptFiles(fullPath);
return entry.isFile() && entry.name.endsWith(".ts") ? [fullPath] : [];
}),
);
return files.flat();
}
function normalizeSource(source) {
let normalized = source;
do {
source = normalized;
normalized = source.replaceAll("| null | null", "| null");
} while (normalized !== source);
return normalized;
}
function addNormalizationNotice(source) {
if (source.includes(normalizationNotice)) return source;
if (!source.startsWith(generatedHeader)) return source;
return source.replace(generatedHeader, `${generatedHeader}\n${normalizationNotice}`);
}
function run(command, args) {
const result = spawnSync(command, args, {
stdio: "inherit",
shell: false,
});
if (result.error) {
console.error(`Failed to run ${command} ${args.join(" ")}: ${result.error.message}`);
process.exit(1);
}
if (result.status !== 0) process.exit(result.status ?? 1);
}

View file

@ -1,44 +0,0 @@
import { readdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
const generatedDir = path.resolve("src/generated/app-server");
const generatedHeader = "// GENERATED CODE! DO NOT MODIFY BY HAND!";
const normalizationNotice = "// This file was mechanically normalized after generation by scripts/normalize-generated-types.mjs.";
async function listTypeScriptFiles(dir) {
const entries = await readdir(dir, { withFileTypes: true });
const files = await Promise.all(
entries.map((entry) => {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) return listTypeScriptFiles(fullPath);
return entry.isFile() && entry.name.endsWith(".ts") ? [fullPath] : [];
}),
);
return files.flat();
}
function normalizeSource(source) {
let normalized = source;
do {
source = normalized;
normalized = source.replaceAll("| null | null", "| null");
} while (normalized !== source);
return normalized;
}
function addNormalizationNotice(source) {
if (source.includes(normalizationNotice)) return source;
if (!source.startsWith(generatedHeader)) return source;
return source.replace(generatedHeader, `${generatedHeader}\n${normalizationNotice}`);
}
const files = await listTypeScriptFiles(generatedDir);
await Promise.all(
files.map(async (file) => {
const source = await readFile(file, "utf8");
const normalized = normalizeSource(source);
const nextSource = normalized === source ? source : addNormalizationNotice(normalized);
if (nextSource !== source) await writeFile(file, nextSource);
}),
);

View file

@ -1,6 +1,6 @@
import { readFile } from "node:fs/promises";
import path from "node:path";
import { compareVersions, isExpectedNextVersion, parseVersion, readJson } from "./release-utils.mjs";
import { compareVersions, isExpectedNextVersion, parseVersion, readJson } from "./utils.mjs";
function fail(message) {
console.error(`release check failed: ${message}`);

View file

@ -1,6 +1,6 @@
import { mkdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { compareVersions, isExpectedNextVersion, parseVersion, readJson, writeJson } from "./release-utils.mjs";
import { compareVersions, isExpectedNextVersion, parseVersion, readJson, writeJson } from "./utils.mjs";
function fail(message) {
console.error(`release prepare failed: ${message}`);
@ -17,6 +17,8 @@ const packageJson = await readJson("package.json");
const packageLockJson = await readJson("package-lock.json");
const manifestJson = await readJson("manifest.json");
const versionsJson = await readJson("versions.json");
const notesDir = path.join(".github", "release-notes");
const notesPath = path.join(notesDir, `${releaseVersion}.md`);
const versionKeys = Object.keys(versionsJson);
const previousVersionKey = versionKeys.at(-1);
@ -29,6 +31,12 @@ if (!isExpectedNextVersion(previousVersion, currentVersion)) {
fail(`release version ${releaseVersion} is not the expected next version after ${previousVersionKey}`);
}
if (versionsJson[releaseVersion] !== undefined) fail(`versions.json already contains ${releaseVersion}`);
try {
await readFile(notesPath, "utf8");
fail(`${notesPath} already exists`);
} catch (error) {
if (error.code !== "ENOENT") throw error;
}
packageJson.version = releaseVersion;
packageLockJson.version = releaseVersion;
@ -42,15 +50,7 @@ await writeJson("package-lock.json", packageLockJson);
await writeJson("manifest.json", manifestJson);
await writeJson("versions.json", versionsJson);
const notesDir = path.join(".github", "release-notes");
const notesPath = path.join(notesDir, `${releaseVersion}.md`);
await mkdir(notesDir, { recursive: true });
try {
await readFile(notesPath, "utf8");
fail(`${notesPath} already exists`);
} catch (error) {
if (error.code !== "ENOENT") throw error;
}
await writeFile(notesPath, "## Changes\n\n- \n");
console.log(`prepared release ${releaseVersion}`);