diff --git a/AGENTS.md b/AGENTS.md index d3c0027..6dbfe77 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -73,16 +73,19 @@ Doğrulama: npm run check # orbit:validate + astro check npm run test:d1 # D1/workerd test paketi npm run orbit:test # içerik + referans istemci testleri -npm run build # tam hat: test:d1, orbit:test, validate, og, astro, site, browser +npm run build # production frontend artifact + site/browser doğrulaması +npm run verify # tam kapı: 342 D1 testi + build ``` -`npm run build` içerik, D1, Astro, paylaşım görselleri, statik sayfa ve gerçek -tarayıcı regresyonlarını birlikte çalıştırır. Production credential'ı olmadan -çalışır. +`npm run build` içerik, referans istemciler, production config, paylaşım +görselleri, Astro çıktısı, statik sayfa ve gerçek tarayıcı regresyonlarını +çalıştırır. Ağır D1 regresyonu ayrı `npm run test:d1` kapısıdır; ikisini birlikte +çalıştıran `npm run verify` eski tam doğrulama yüzeyini korur. Production +credential'ı olmadan çalışırlar. **`browser:test`'i tek başına çalıştırma.** Derlenmiş `dist/` dizinini okur; -yeniden derlemeden çalıştırırsan eski markup hakkında yeşil verir. Yalnız -`npm run build` gerçekten değişikliği test eder. +yeniden derlemeden çalıştırırsan eski markup hakkında yeşil verir. `npm run build` +ve `npm run verify` önce artifact'i yeniden üretir. Worker paketi dağıtmadan doğrulama: diff --git a/README.md b/README.md index 8067b8b..ede4ca7 100644 --- a/README.md +++ b/README.md @@ -79,11 +79,13 @@ npm run check npm run test:d1 npm run orbit:test npm run build +npm run verify ``` -Tam `build` komutu içerik, D1, Astro, paylaşım görselleri, statik sayfa ve gerçek -tarayıcı regresyonlarını birlikte çalıştırır. Production credential'ı olmadan -yerel geliştirme ve test yapılabilir. +`build` production frontend artifact'ini üretir ve içerik, istemci, config, OG, +site-integrity ve gerçek tarayıcı kontrollerini çalıştırır. Ağır D1 regresyonu +`test:d1` altında ayrıdır; `verify` ikisini art arda çalıştırarak eski tam kalite +kapısını korur. Production credential'ı olmadan geliştirme ve test yapılabilir. ## Repo haritası diff --git a/docs/PUBLISHING.md b/docs/PUBLISHING.md index 7be5d41..69c192c 100644 --- a/docs/PUBLISHING.md +++ b/docs/PUBLISHING.md @@ -301,7 +301,7 @@ Komut şu kontrolleri uygular: - Yanıt hedefinin gerçekten var olması - Aynı ajanın mükerrer reaksiyon vermemesi - `npm run check` -- `npm run build` +- `npm run verify` - Gönderiye özel 1200×630 paylaşım görselinin üretilmesi Check veya build başarısız olursa yeni oluşturulan public dosya geri alınır ve diff --git a/docs/V6_PROJECT_LEDGER.md b/docs/V6_PROJECT_LEDGER.md index c9b8957..1456899 100644 --- a/docs/V6_PROJECT_LEDGER.md +++ b/docs/V6_PROJECT_LEDGER.md @@ -2345,3 +2345,33 @@ Anything under `docs/archive/` describes an earlier state and is frozen. Read it contract, and staging rehearsal concurrently. - Performance claims for Actions are now based on workflow trigger-to-complete wall time, not the duration of individual jobs. + +### 2026-09-18 — Local build and full-verification separation + +- Baseline `npm run build` wall time on the local Mac mini was measured at + **207.45 seconds**. The command was doing two different jobs under one name: + all 342 D1 regression tests plus production frontend artifact verification. +- Concurrency-only experiments were measured and rejected: launching every CI + split beside the frontend reduced the full command only to 190.79s and made + browser verification balloon to 105.87s; a more balanced overlap still took + 185.52s. Those versions were not shipped. +- The final contract separates concerns without deleting coverage. `npm run build` + owns content/reference-client/config/OG preflight, Astro build, and the + built-artifact site/browser checks. `npm run test:d1` remains the exhaustive + backend regression gate, and new `npm run verify` runs D1 plus build to retain + the historical full local gate. +- `orbit:post` and `orbit:publish` now call `verify`, so editorial publication + cannot bypass D1 just because the everyday build command became focused. CI, + production deploy, and nightly regression already run the exhaustive D1 + partitions independently. +- A custom heavy-first D1 scheduler was also benchmarked. It improved one warm + isolated run from 156.93s to 142.77s, but became materially slower after a + clean install, so it was rejected and not shipped. `test:d1` keeps the + established Node/tsx runner; the build speedup does not depend on changing D1. +- Performance claims use `/usr/bin/time -p` wall time. The focused everyday + `npm run build` measured **46.87s**, down from the old overloaded build's + **207.45s**: 160.58s less wall time, or about **77.4% faster** on the same + Mac mini. With the final established D1 runner restored, `npm run verify` + completed the full historical quality surface successfully in **203.23s**; + that deliberately heavy command is reserved for explicit full verification + and editorial publish/post gates rather than every artifact build. diff --git a/package.json b/package.json index 00bd181..8dddd41 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "type": "module", "scripts": { "dev": "astro dev", - "build": "npm run test:d1 && npm run orbit:test && npm run orbit:validate && npm run production:config:check && npm run og:generate && astro build && npm run site:test && npm run browser:test", + "build": "node scripts/orbit-build.mjs", "worker:build": "astro build --config astro.worker.config.mjs && wrangler deploy --dry-run --config wrangler.jsonc --outdir dist/worker", "worker:build:staging": "ORBIT_SITE_URL=https://orbit-v6-staging.samett33710.workers.dev ORBIT_NOINDEX=true astro build --config astro.worker.config.mjs && node scripts/orbit-staging-assets.mjs && wrangler deploy --dry-run --config wrangler.staging.jsonc --outdir dist/worker-staging", "worker:assets:production:live": "ORBIT_SITE_URL=https://orbit.sametbasbug.dev ORBIT_NOINDEX=false astro build --config astro.worker.config.mjs", @@ -61,7 +61,9 @@ "orbit:publish": "node scripts/orbit-publish.mjs", "test:d1:core:foundation": "tsx --test --test-concurrency=5 scripts/orbit-test-ports-tests.ts scripts/orbit-agent-identity-tests.ts scripts/orbit-api-contract-tests.ts scripts/orbit-handle-policy-tests.ts scripts/orbit-d1-tests.ts scripts/orbit-email-tests.ts scripts/orbit-mcp-authorization-tests.ts", "test:d1:core:site": "tsx --test --test-concurrency=5 scripts/orbit-site-authorization-tests.ts scripts/orbit-site-action-tests.ts scripts/orbit-site-client-register-tests.ts scripts/orbit-site-client-registration-tests.ts scripts/orbit-site-signin-e2e-tests.ts", - "test:d1:core:public": "tsx --test --test-concurrency=5 scripts/orbit-public-page-tests.ts scripts/orbit-public-repository-tests.ts scripts/orbit-slice1-tests.ts scripts/orbit-slice1-worker-tests.ts scripts/orbit-slice3-tests.ts" + "test:d1:core:public": "tsx --test --test-concurrency=5 scripts/orbit-public-page-tests.ts scripts/orbit-public-repository-tests.ts scripts/orbit-slice1-tests.ts scripts/orbit-slice1-worker-tests.ts scripts/orbit-slice3-tests.ts", + "astro:build": "astro build", + "verify": "npm run test:d1 && npm run build" }, "dependencies": { "@astrojs/cloudflare": "14.3.2", diff --git a/scripts/orbit-actions-scope-tests.mjs b/scripts/orbit-actions-scope-tests.mjs index d004bfe..9e810b1 100644 --- a/scripts/orbit-actions-scope-tests.mjs +++ b/scripts/orbit-actions-scope-tests.mjs @@ -35,12 +35,22 @@ test('escalates mixed and unknown changes to full', () => { * yetkilendirme, tek-renderer kilidi, skala kilidi) hiçbir listede değildi. * Yani `main`'e doğrudan push edilen bir değişiklik o üç dosya hiç * çalışmadan canlıya gidiyordu. Testler PR'da ve gece koşusunda çalıştığı için - * ("npm run build" tam listeyi çağırıyor) dışarıdan hiçbir şey bozuk + * ("npm run test:d1" tam listeyi çağırıyor) dışarıdan hiçbir şey bozuk * görünmüyordu. * * Asıl kusur listelerin yanlış olması değil, yanlış olduklarının hiçbir yerde * görünmemesiydi: bir test dosyası eklemek iki yeri güncellemeyi gerektiriyor * ve ikincisini unutmak sessiz kalıyor. Bu test o sessizliği kaldırıyor. */ +test('editorial commands keep the full verification gate', async () => { + for (const relative of ['./orbit-post.mjs', './orbit-publish.mjs']) { + const source = await readFile(new URL(relative, import.meta.url), 'utf8'); + assert.ok( + source.includes("['npm', ['run', 'verify']]") && !source.includes("['npm', ['run', 'build']]"), + `${relative} bypasses the full verification gate`, + ); + } +}); + test('every D1 test file the full suite runs is also in exactly one split', async () => { const packageJson = JSON.parse( await readFile(new URL('../package.json', import.meta.url), 'utf8'), diff --git a/scripts/orbit-build.mjs b/scripts/orbit-build.mjs new file mode 100644 index 0000000..1dfa08f --- /dev/null +++ b/scripts/orbit-build.mjs @@ -0,0 +1,81 @@ +import { spawn } from 'node:child_process'; +import { performance } from 'node:perf_hooks'; + +const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'; +const startedAt = performance.now(); + +function seconds(ms) { + return `${(ms / 1000).toFixed(2)}s`; +} + +function runScript(script) { + const start = performance.now(); + process.stdout.write(`\n[build] ▶ ${script}\n`); + + return new Promise((resolve, reject) => { + const child = spawn(npm, ['run', script], { + cwd: process.cwd(), + env: process.env, + stdio: 'inherit', + }); + + child.once('error', reject); + child.once('exit', (code, signal) => { + const elapsed = seconds(performance.now() - start); + if (code === 0) { + process.stdout.write(`[build] ✓ ${script} (${elapsed})\n`); + resolve(); + return; + } + + reject( + new Error( + `${script} failed after ${elapsed}` + + (signal ? ` (signal ${signal})` : ` (exit ${code ?? 'unknown'})`), + ), + ); + }); + }); +} + +async function runParallel(label, scripts) { + const start = performance.now(); + process.stdout.write(`\n[build] ${label}: ${scripts.join(', ')}\n`); + const results = await Promise.allSettled(scripts.map(runScript)); + const failures = results.filter((result) => result.status === 'rejected'); + + if (failures.length > 0) { + throw new AggregateError( + failures.map((failure) => failure.reason), + `${label} failed`, + ); + } + + process.stdout.write(`[build] ${label} complete (${seconds(performance.now() - start)})\n`); +} + +async function frontendPipeline() { + await runParallel('frontend preflight', [ + 'orbit:test:content', + 'orbit:test:clients', + 'orbit:validate', + 'production:config:check', + 'og:generate', + ]); + + await runScript('astro:build'); + await runParallel('built artifact verification', ['site:test', 'browser:test']); +} + +try { + await frontendPipeline(); + process.stdout.write(`\n[build] ✓ production frontend build (${seconds(performance.now() - startedAt)})\n`); +} catch (error) { + console.error('\n[build] ✗ production frontend build failed'); + if (error instanceof AggregateError) { + for (const failure of error.errors) console.error(failure); + } else { + console.error(error); + } + process.exitCode = 1; +} diff --git a/scripts/orbit-post.mjs b/scripts/orbit-post.mjs index 8a537a0..38ea396 100644 --- a/scripts/orbit-post.mjs +++ b/scripts/orbit-post.mjs @@ -85,7 +85,7 @@ if (dryRun) process.exit(0); fs.mkdirSync(destinationDir, { recursive: true }); fs.writeFileSync(destination, output, { encoding: 'utf8', flag: 'wx' }); -for (const command of [['npm', ['run', 'check']], ['npm', ['run', 'build']]]) { +for (const command of [['npm', ['run', 'check']], ['npm', ['run', 'verify']]]) { const result = spawnSync(command[0], command[1], { cwd: ROOT, stdio: 'inherit' }); if (result.status !== 0) { fs.unlinkSync(destination); diff --git a/scripts/orbit-publish.mjs b/scripts/orbit-publish.mjs index 625b79c..280f5ea 100644 --- a/scripts/orbit-publish.mjs +++ b/scripts/orbit-publish.mjs @@ -118,7 +118,7 @@ try { throw error; } -for (const command of [['npm', ['run', 'check']], ['npm', ['run', 'build']]]) { +for (const command of [['npm', ['run', 'check']], ['npm', ['run', 'verify']]]) { const result = spawnSync(command[0], command[1], { cwd: ROOT, stdio: 'inherit' }); if (result.status !== 0) { removePublishedFile();