@codeyam/codeyam-cli 0.1.0-staging.b6c4c78 → 0.1.0-staging.b8b17a5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/analyzer-template/.build-info.json +8 -8
- package/analyzer-template/log.txt +3 -3
- package/analyzer-template/packages/database/src/lib/kysely/tables/editorScenariosTable.ts +26 -16
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.d.ts +0 -1
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.d.ts.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.js +28 -16
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
- package/codeyam-cli/src/cli.js +9 -0
- package/codeyam-cli/src/cli.js.map +1 -1
- package/codeyam-cli/src/commands/__tests__/editor.isolateArgs.test.js +51 -0
- package/codeyam-cli/src/commands/__tests__/editor.isolateArgs.test.js.map +1 -0
- package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js +11 -0
- package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js.map +1 -1
- package/codeyam-cli/src/commands/editor.js +460 -65
- package/codeyam-cli/src/commands/editor.js.map +1 -1
- package/codeyam-cli/src/commands/editorIsolateArgs.js +25 -0
- package/codeyam-cli/src/commands/editorIsolateArgs.js.map +1 -0
- package/codeyam-cli/src/commands/telemetry.js +37 -0
- package/codeyam-cli/src/commands/telemetry.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +994 -1
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorDeleteScenario.test.js +100 -0
- package/codeyam-cli/src/utils/__tests__/editorDeleteScenario.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorEntityChangeStatus.test.js +70 -0
- package/codeyam-cli/src/utils/__tests__/editorEntityChangeStatus.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js +97 -5
- package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorLoaderHelpers.test.js +40 -1
- package/codeyam-cli/src/utils/__tests__/editorLoaderHelpers.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorMigration.test.js +5 -0
- package/codeyam-cli/src/utils/__tests__/editorMigration.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +354 -22
- package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorSeedAdapterPrismaValidation.test.js +143 -0
- package/codeyam-cli/src/utils/__tests__/editorSeedAdapterPrismaValidation.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js +28 -0
- package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js +40 -1
- package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/telemetry.test.js +159 -0
- package/codeyam-cli/src/utils/__tests__/telemetry.test.js.map +1 -0
- package/codeyam-cli/src/utils/editorAudit.js +179 -1
- package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
- package/codeyam-cli/src/utils/editorDeleteScenario.js +67 -0
- package/codeyam-cli/src/utils/editorDeleteScenario.js.map +1 -0
- package/codeyam-cli/src/utils/editorEntityChangeStatus.js +13 -7
- package/codeyam-cli/src/utils/editorEntityChangeStatus.js.map +1 -1
- package/codeyam-cli/src/utils/editorEntityHelpers.js +18 -3
- package/codeyam-cli/src/utils/editorEntityHelpers.js.map +1 -1
- package/codeyam-cli/src/utils/editorLoaderHelpers.js +14 -2
- package/codeyam-cli/src/utils/editorLoaderHelpers.js.map +1 -1
- package/codeyam-cli/src/utils/editorMigration.js +1 -1
- package/codeyam-cli/src/utils/editorMigration.js.map +1 -1
- package/codeyam-cli/src/utils/editorScenarios.js +150 -2
- package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
- package/codeyam-cli/src/utils/editorSeedAdapter.js +70 -0
- package/codeyam-cli/src/utils/editorSeedAdapter.js.map +1 -1
- package/codeyam-cli/src/utils/entityChangeStatus.js +8 -2
- package/codeyam-cli/src/utils/entityChangeStatus.js.map +1 -1
- package/codeyam-cli/src/utils/fileWatcher.js +38 -0
- package/codeyam-cli/src/utils/fileWatcher.js.map +1 -1
- package/codeyam-cli/src/utils/scenariosManifest.js +15 -10
- package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -1
- package/codeyam-cli/src/utils/telemetry.js +106 -0
- package/codeyam-cli/src/utils/telemetry.js.map +1 -0
- package/codeyam-cli/src/utils/telemetryMiddleware.js +22 -0
- package/codeyam-cli/src/utils/telemetryMiddleware.js.map +1 -0
- package/codeyam-cli/src/webserver/__tests__/buildPtyEnv.test.js +35 -0
- package/codeyam-cli/src/webserver/__tests__/buildPtyEnv.test.js.map +1 -0
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js +61 -0
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-DYqG1D_d.js +58 -0
- package/codeyam-cli/src/webserver/build/client/assets/editorPreview-DggyRwOr.js +41 -0
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.dev-BOi8kpwd.js → entity._sha.scenarios._scenarioId.dev-D1eikpe1.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/globals-DRvOjyO3.css +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{manifest-5f1c29f5.js → manifest-f4212c17.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{root-BBCQJ_ZM.js → root-F-k2uYj5.js} +15 -15
- package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-if8kM_1Q.js +13 -0
- package/codeyam-cli/src/webserver/build/server/assets/{index-BLKsJR3o.js → index-CHymws6l.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/init-D3HkMDbI.js +10 -0
- package/codeyam-cli/src/webserver/build/server/assets/progress-CHTtrxFG.js +1 -0
- package/codeyam-cli/src/webserver/build/server/assets/server-build-DTCzJQiH.js +551 -0
- package/codeyam-cli/src/webserver/build/server/index.js +1 -1
- package/codeyam-cli/src/webserver/build-info.json +5 -5
- package/codeyam-cli/src/webserver/editorProxy.js +78 -3
- package/codeyam-cli/src/webserver/editorProxy.js.map +1 -1
- package/codeyam-cli/src/webserver/server.js +32 -0
- package/codeyam-cli/src/webserver/server.js.map +1 -1
- package/codeyam-cli/src/webserver/terminalServer.js +7 -2
- package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
- package/codeyam-cli/templates/editor-step-hook.py +7 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/package.json +1 -1
- package/codeyam-cli/templates/nextjs-prisma-supabase/package.json +1 -1
- package/package.json +2 -1
- package/packages/database/src/lib/kysely/tables/editorScenariosTable.js +28 -16
- package/packages/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-y_5LB2iU.js +0 -58
- package/codeyam-cli/src/webserver/build/client/assets/editorPreview-DBa7T2FK.js +0 -41
- package/codeyam-cli/src/webserver/build/client/assets/globals-BCTpZEY8.css +0 -1
- package/codeyam-cli/src/webserver/build/server/assets/init-C2iMAqYu.js +0 -10
- package/codeyam-cli/src/webserver/build/server/assets/server-build-DR42Xd5a.js +0 -489
|
@@ -16,8 +16,9 @@ import { APP_FORMATS, TECH_STACKS } from "../data/techStacks.js";
|
|
|
16
16
|
import { getProjectRoot as getStateProjectRoot } from "../state.js";
|
|
17
17
|
import initCommand from "./init.js";
|
|
18
18
|
import { scanScenarioFiles, syncScenarioFilesToDatabase, backfillScenarioMetadata, migrateScenarioFormats, } from "../utils/scenariosManifest.js";
|
|
19
|
-
import { clearEditorState, clearEditorUserPrompt, validateStepTransition, } from "../utils/editorScenarios.js";
|
|
20
|
-
import { validateSeedData, detectSeedAdapter, } from "../utils/editorSeedAdapter.js";
|
|
19
|
+
import { clearEditorState, clearEditorUserPrompt, validateStepTransition, backfillEntityShaOnScenarios, } from "../utils/editorScenarios.js";
|
|
20
|
+
import { validateSeedData, detectSeedAdapter, validateSeedKeysAgainstPrisma, } from "../utils/editorSeedAdapter.js";
|
|
21
|
+
import { deleteScenarioViaCli } from "../utils/editorDeleteScenario.js";
|
|
21
22
|
import { buildEditorApiRequest, callEditorApi, EDITOR_API_SUBCOMMANDS, } from "../utils/editorApi.js";
|
|
22
23
|
import { parseRegisterArg } from "../utils/parseRegisterArg.js";
|
|
23
24
|
import { sanitizeGlossaryEntries } from "../utils/editorLoaderHelpers.js";
|
|
@@ -207,20 +208,27 @@ function printAppScenarioInstructions(pageName, route) {
|
|
|
207
208
|
checkbox('Check existing scenarios: `codeyam editor scenarios`');
|
|
208
209
|
console.log(chalk.dim(' Review existing scenarios — reuse and update their data rather than'));
|
|
209
210
|
console.log(chalk.dim(' creating duplicates. Re-register with the same name to update a scenario.'));
|
|
210
|
-
|
|
211
|
-
console.log(chalk.yellow('
|
|
211
|
+
console.log();
|
|
212
|
+
console.log(chalk.bold.yellow('App scenarios vs component scenarios — these are DIFFERENT:'));
|
|
213
|
+
console.log(chalk.yellow(' App scenarios: show the FULL PAGE with seeded data at a real route (/, /library, etc.)'));
|
|
214
|
+
console.log(chalk.yellow(' Component scenarios: show ONE COMPONENT in isolation at /isolated-components/...'));
|
|
215
|
+
console.log(chalk.yellow(' This step is about APP scenarios. Do NOT set "componentName" — that makes it a component scenario.'));
|
|
216
|
+
console.log();
|
|
217
|
+
checkbox('Identify every page/route in the app and ensure each has app-level scenarios');
|
|
218
|
+
console.log(chalk.dim(" Check the app's router/entry points for all distinct pages"));
|
|
219
|
+
console.log(chalk.yellow(' Every page needs at least 2-3 app scenarios — not just the main page'));
|
|
212
220
|
if (pageName) {
|
|
213
221
|
console.log(chalk.dim(` Example: "${pageName} - Full Data", "${pageName} - Empty State"`));
|
|
214
222
|
}
|
|
215
223
|
else {
|
|
216
|
-
console.log(chalk.dim('
|
|
217
|
-
console.log(chalk.dim(' Detail: "Detail - With Reviews", "Detail - No Reviews", "Detail - High Rated"'));
|
|
224
|
+
console.log(chalk.dim(' Example: "Page - Full Data", "Page - Empty State", "Page - Error State"'));
|
|
218
225
|
}
|
|
219
226
|
console.log();
|
|
220
|
-
checkbox('Register each scenario with type "application"
|
|
221
|
-
console.log(chalk.dim(` codeyam editor register '{"name":"${pageName || 'Page'} - Full Data"
|
|
222
|
-
console.log(chalk.
|
|
223
|
-
console.log(chalk.yellow('
|
|
227
|
+
checkbox('Register each scenario with type "application", the real page URL, and pageFilePath:');
|
|
228
|
+
console.log(chalk.dim(` codeyam editor register '{"name":"${pageName || 'Page'} - Full Data",`));
|
|
229
|
+
console.log(chalk.dim(` "type":"application","url":"${route || '/'}","pageFilePath":"src/path/to/Page.tsx",...}'`));
|
|
230
|
+
console.log(chalk.yellow(' Required fields: "type":"application", "url" (real route), "pageFilePath" (source file)'));
|
|
231
|
+
console.log(chalk.yellow(' Do NOT include "componentName" — that would make it a component scenario'));
|
|
224
232
|
console.log();
|
|
225
233
|
checkbox('Choose the right data approach for scenarios:');
|
|
226
234
|
console.log(chalk.dim(' With seed adapter: add "seed":{...} to populate the database for each state'));
|
|
@@ -323,7 +331,8 @@ function printComponentCaptureInstructions() {
|
|
|
323
331
|
console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
|
|
324
332
|
console.log(chalk.dim(` "url":"/codeyam-isolate/ComponentName?s=Scenario",`));
|
|
325
333
|
console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
|
|
326
|
-
console.log(chalk.
|
|
334
|
+
console.log(chalk.yellow(' IMPORTANT: url MUST be an isolation route (/codeyam-isolate/... or /isolated-components/...).'));
|
|
335
|
+
console.log(chalk.yellow(' Do NOT use real page URLs (like /library) for component scenarios.'));
|
|
327
336
|
console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
|
|
328
337
|
console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
|
|
329
338
|
console.log(chalk.yellow(' 5. IMPORTANT: Check the register response for `clientErrors` array'));
|
|
@@ -930,13 +939,29 @@ function printStep2(root, feature) {
|
|
|
930
939
|
console.log();
|
|
931
940
|
console.log(chalk.bold('Build the feature:'));
|
|
932
941
|
}
|
|
942
|
+
else {
|
|
943
|
+
console.log(chalk.bold('Prepare the database for this feature:'));
|
|
944
|
+
checkbox('List existing scenarios: `codeyam editor scenarios`');
|
|
945
|
+
console.log(chalk.dim(' Review existing scenarios to find seed data that best demonstrates the feature.'));
|
|
946
|
+
console.log(chalk.dim(" Don't create throwaway seed data — use what's already been curated."));
|
|
947
|
+
checkbox('Pick the scenario with seed data most relevant to this feature');
|
|
948
|
+
console.log(chalk.dim(" Think: which existing data state best exercises the feature you're building?"));
|
|
949
|
+
console.log(chalk.dim(' A scenario with diverse, realistic data is usually the best starting point.'));
|
|
950
|
+
checkbox('Load that scenario to seed the database and preview it:');
|
|
951
|
+
console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
|
|
952
|
+
console.log(chalk.dim(' This switches the active scenario, runs the seed adapter, and refreshes the preview.'));
|
|
953
|
+
console.log(chalk.dim(' If no existing scenario fits, use the default seed: npm run db:seed'));
|
|
954
|
+
console.log();
|
|
955
|
+
}
|
|
933
956
|
console.log(chalk.bold('Checklist:'));
|
|
934
957
|
checkbox('Create API routes that read from the database via Prisma');
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
958
|
+
if (!projectExists) {
|
|
959
|
+
checkbox('Seed the database with demo data');
|
|
960
|
+
checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
|
|
961
|
+
console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
|
|
962
|
+
console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
|
|
963
|
+
console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
|
|
964
|
+
}
|
|
940
965
|
checkbox('Verify the dev server shows the changes');
|
|
941
966
|
checkbox('If the feature involves auth, email, payments, or other common patterns: read FEATURE_PATTERNS.md');
|
|
942
967
|
// Responsive design guidance when building a mobile-responsive web app
|
|
@@ -1419,8 +1444,6 @@ function printStep12(root, feature) {
|
|
|
1419
1444
|
}
|
|
1420
1445
|
// ─── Step 13: Present ─────────────────────────────────────────────────
|
|
1421
1446
|
function printStep13(root, feature) {
|
|
1422
|
-
const port = getServerPort();
|
|
1423
|
-
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1424
1447
|
const prevState = readState(root);
|
|
1425
1448
|
const isResuming = prevState?.step === 13;
|
|
1426
1449
|
const now = new Date().toISOString();
|
|
@@ -1439,18 +1462,9 @@ function printStep13(root, feature) {
|
|
|
1439
1462
|
console.log('Present the results to the user and get their approval.');
|
|
1440
1463
|
console.log();
|
|
1441
1464
|
console.log(chalk.bold('Checklist:'));
|
|
1442
|
-
checkbox('Fetch all scenarios: `codeyam editor scenarios`');
|
|
1443
|
-
console.log(chalk.dim(' Each scenario has a `changeStatus` field: "new", "edited", "impacted", or null.'));
|
|
1444
|
-
checkbox('Pick the best scenario to showcase from this working session:');
|
|
1445
|
-
console.log(chalk.dim(' Prefer scenarios with changeStatus "new" or "edited" (directly changed in this session).'));
|
|
1446
|
-
console.log(chalk.dim(' For app-level scenarios, prefer those showing the new/changed functionality.'));
|
|
1447
|
-
console.log(chalk.dim(' NEVER pick a scenario with changeStatus null — those are unchanged from a previous commit.'));
|
|
1448
|
-
checkbox('Switch the preview to that scenario using its `id` and `dimension`:');
|
|
1449
|
-
console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
|
|
1450
|
-
console.log(chalk.dim(' Use the dimension that matches the scenario — check its registered dimensions.'));
|
|
1451
|
-
printDimensionGuidance(dim, dimNames);
|
|
1452
1465
|
checkbox(`Show the results panel: \`codeyam editor show-results\``);
|
|
1453
1466
|
console.log(chalk.dim(' This opens a visual panel below the terminal showing all scenarios with screenshots.'));
|
|
1467
|
+
console.log(chalk.dim(' The preview automatically switches to the first app-level scenario.'));
|
|
1454
1468
|
console.log(chalk.dim(' The user can click scenarios to switch the live preview.'));
|
|
1455
1469
|
checkbox('Write a 1-2 sentence summary of what was built');
|
|
1456
1470
|
checkbox('Report test count and audit status (one line)');
|
|
@@ -1689,10 +1703,8 @@ function printMigrateStep4(root) {
|
|
|
1689
1703
|
checkbox('Review all scenario screenshots for this page — do they look correct?');
|
|
1690
1704
|
checkbox('Check for client errors: `codeyam editor client-errors`');
|
|
1691
1705
|
checkbox('If any issues: fix the scenario data, re-register affected scenarios');
|
|
1692
|
-
checkbox('Fetch all scenarios: `codeyam editor scenarios`');
|
|
1693
|
-
checkbox('Pick the best scenario to showcase:');
|
|
1694
|
-
console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>"}'`));
|
|
1695
1706
|
checkbox('Show results: `codeyam editor show-results`');
|
|
1707
|
+
console.log(chalk.dim(' The preview automatically switches to the first app-level scenario.'));
|
|
1696
1708
|
console.log();
|
|
1697
1709
|
console.log(chalk.dim('The user should now see their existing page with live screenshots in the preview.'));
|
|
1698
1710
|
migrationStopGate(4, pageName, pageIndex, totalPages);
|
|
@@ -1858,10 +1870,8 @@ function printMigrateStep10(root) {
|
|
|
1858
1870
|
console.log("Show results and commit this page's migration.");
|
|
1859
1871
|
console.log();
|
|
1860
1872
|
console.log(chalk.bold('Checklist:'));
|
|
1861
|
-
checkbox('Fetch all scenarios: `codeyam editor scenarios`');
|
|
1862
|
-
checkbox('Pick the best scenario to showcase:');
|
|
1863
|
-
console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>"}'`));
|
|
1864
1873
|
checkbox('Show results: `codeyam editor show-results`');
|
|
1874
|
+
console.log(chalk.dim(' The preview automatically switches to the first app-level scenario.'));
|
|
1865
1875
|
checkbox('Write a 1-2 sentence summary of what was migrated');
|
|
1866
1876
|
console.log();
|
|
1867
1877
|
console.log(chalk.bold('Present a selection menu (AskUserQuestion with these EXACT option labels):'));
|
|
@@ -2324,6 +2334,26 @@ async function handleAnalyzeImports(options = {}) {
|
|
|
2324
2334
|
});
|
|
2325
2335
|
console.log(summary || JSON.stringify({ imports, entities: entityData }));
|
|
2326
2336
|
}
|
|
2337
|
+
// Backfill entity_sha on scenarios registered before entities existed.
|
|
2338
|
+
// This fixes the "missing data" UI state when scenarios were registered
|
|
2339
|
+
// while analyze-imports was still running in the background.
|
|
2340
|
+
try {
|
|
2341
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
2342
|
+
const db = getDatabase();
|
|
2343
|
+
const backfillResult = await backfillEntityShaOnScenarios(db, latestEntities.map((e) => ({
|
|
2344
|
+
sha: e.sha,
|
|
2345
|
+
name: e.name,
|
|
2346
|
+
filePath: e.filePath || '',
|
|
2347
|
+
isDefaultExport: e.metadata?.notExported === false &&
|
|
2348
|
+
e.metadata?.namedExport === false,
|
|
2349
|
+
})));
|
|
2350
|
+
if (backfillResult.updated > 0 && !options.silent) {
|
|
2351
|
+
console.log(chalk.green(`Linked ${backfillResult.updated} scenario(s) to their entities.`));
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
catch {
|
|
2355
|
+
/* non-fatal */
|
|
2356
|
+
}
|
|
2327
2357
|
}
|
|
2328
2358
|
// ─── Validate-seed subcommand ─────────────────────────────────────────
|
|
2329
2359
|
/**
|
|
@@ -2362,6 +2392,24 @@ function handleValidateSeed(jsonArg) {
|
|
|
2362
2392
|
: 0;
|
|
2363
2393
|
console.log(chalk.dim(` ${table}: ${rows} row(s)`));
|
|
2364
2394
|
}
|
|
2395
|
+
// Check seed keys against Prisma schema if available
|
|
2396
|
+
const schemaPath = path.join(root, 'prisma', 'schema.prisma');
|
|
2397
|
+
try {
|
|
2398
|
+
if (fs.existsSync(schemaPath)) {
|
|
2399
|
+
const schemaContent = fs.readFileSync(schemaPath, 'utf-8');
|
|
2400
|
+
const warnings = validateSeedKeysAgainstPrisma(tables, schemaContent);
|
|
2401
|
+
if (warnings.length > 0) {
|
|
2402
|
+
console.log();
|
|
2403
|
+
console.log(chalk.yellow('Prisma model name warnings:'));
|
|
2404
|
+
for (const w of warnings) {
|
|
2405
|
+
console.log(chalk.yellow(` ⚠ ${w}`));
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
catch {
|
|
2411
|
+
// Schema not readable — skip Prisma validation
|
|
2412
|
+
}
|
|
2365
2413
|
}
|
|
2366
2414
|
else {
|
|
2367
2415
|
console.error(chalk.red('Seed data validation failed:'));
|
|
@@ -2371,6 +2419,27 @@ function handleValidateSeed(jsonArg) {
|
|
|
2371
2419
|
process.exit(1);
|
|
2372
2420
|
}
|
|
2373
2421
|
}
|
|
2422
|
+
// ─── Delete subcommand ────────────────────────────────────────────────
|
|
2423
|
+
/**
|
|
2424
|
+
* `codeyam editor delete <scenarioId>`
|
|
2425
|
+
*
|
|
2426
|
+
* Delete a scenario by ID via the running editor server.
|
|
2427
|
+
*/
|
|
2428
|
+
async function handleDelete(scenarioId) {
|
|
2429
|
+
if (!scenarioId) {
|
|
2430
|
+
console.error(chalk.red('Error: Missing scenario ID. Usage: codeyam editor delete <scenarioId>'));
|
|
2431
|
+
process.exit(1);
|
|
2432
|
+
}
|
|
2433
|
+
const port = getServerPort();
|
|
2434
|
+
const result = await deleteScenarioViaCli(scenarioId, port);
|
|
2435
|
+
if (result.success) {
|
|
2436
|
+
console.log(chalk.green(result.message));
|
|
2437
|
+
}
|
|
2438
|
+
else {
|
|
2439
|
+
console.error(chalk.red(result.message));
|
|
2440
|
+
process.exit(1);
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2374
2443
|
// ─── Isolate subcommand ───────────────────────────────────────────────
|
|
2375
2444
|
/**
|
|
2376
2445
|
* `codeyam editor isolate "StarRating CategoryBadge DrinkCard"`
|
|
@@ -2845,23 +2914,108 @@ function handleChange(feature) {
|
|
|
2845
2914
|
}
|
|
2846
2915
|
// ─── Audit gate ─────────────────────────────────────────────────────
|
|
2847
2916
|
/**
|
|
2848
|
-
*
|
|
2849
|
-
*
|
|
2850
|
-
* Used as a hard gate before steps 8+.
|
|
2917
|
+
* Fetch the audit result from the server.
|
|
2918
|
+
* Returns null if the server is unreachable.
|
|
2851
2919
|
*/
|
|
2852
|
-
async function
|
|
2920
|
+
async function fetchAuditResult() {
|
|
2853
2921
|
const port = getServerPort();
|
|
2854
2922
|
try {
|
|
2855
2923
|
const res = await fetch(`http://localhost:${port}/api/editor-audit`);
|
|
2856
2924
|
if (!res.ok)
|
|
2857
|
-
return
|
|
2858
|
-
|
|
2859
|
-
return data?.summary?.allPassing === true;
|
|
2925
|
+
return null;
|
|
2926
|
+
return await res.json();
|
|
2860
2927
|
}
|
|
2861
2928
|
catch {
|
|
2862
|
-
|
|
2929
|
+
return null;
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
/**
|
|
2933
|
+
* Silently check whether the audit passes. Returns true if allPassing,
|
|
2934
|
+
* false if any issues remain or the server is unreachable.
|
|
2935
|
+
* Used as a hard gate before steps 8+.
|
|
2936
|
+
*
|
|
2937
|
+
* Auto-runs analyze-imports if the only failure is incomplete entities,
|
|
2938
|
+
* then re-checks once.
|
|
2939
|
+
*/
|
|
2940
|
+
async function checkAuditGate() {
|
|
2941
|
+
const data = await fetchAuditResult();
|
|
2942
|
+
if (!data)
|
|
2943
|
+
return true; // Server unreachable — don't block
|
|
2944
|
+
if (data.summary?.allPassing === true)
|
|
2863
2945
|
return true;
|
|
2946
|
+
// If incomplete entities are the only issue, auto-fix with analyze-imports (once)
|
|
2947
|
+
const { isAutoRemediable } = await import('../utils/editorAudit.js');
|
|
2948
|
+
if (isAutoRemediable(data.summary, false)) {
|
|
2949
|
+
try {
|
|
2950
|
+
await handleAnalyzeImports({ silent: true });
|
|
2951
|
+
}
|
|
2952
|
+
catch {
|
|
2953
|
+
return false;
|
|
2954
|
+
}
|
|
2955
|
+
// Re-check after fix
|
|
2956
|
+
const retry = await fetchAuditResult();
|
|
2957
|
+
if (retry?.summary?.allPassing === true)
|
|
2958
|
+
return true;
|
|
2959
|
+
}
|
|
2960
|
+
// Print specific failures so Claude knows what to fix without running audit separately
|
|
2961
|
+
printAuditGateFailures(data);
|
|
2962
|
+
return false;
|
|
2963
|
+
}
|
|
2964
|
+
/**
|
|
2965
|
+
* Print a concise summary of audit failures for the gate block message.
|
|
2966
|
+
* This gives Claude immediate context about what to fix without needing
|
|
2967
|
+
* to run `codeyam editor audit` as a separate step.
|
|
2968
|
+
*/
|
|
2969
|
+
function printAuditGateFailures(data) {
|
|
2970
|
+
const s = data.summary;
|
|
2971
|
+
if (!s)
|
|
2972
|
+
return;
|
|
2973
|
+
const issues = [];
|
|
2974
|
+
if (s.componentsMissing > 0)
|
|
2975
|
+
issues.push(`${s.componentsMissing} component(s) missing scenarios`);
|
|
2976
|
+
if (s.componentsWithErrors > 0)
|
|
2977
|
+
issues.push(`${s.componentsWithErrors} component(s) with client errors (browser API or runtime errors in captured scenarios)`);
|
|
2978
|
+
if (s.functionsMissing > 0)
|
|
2979
|
+
issues.push(`${s.functionsMissing} function(s) missing test files`);
|
|
2980
|
+
if (s.functionsFailing > 0)
|
|
2981
|
+
issues.push(`${s.functionsFailing} function(s) with failing tests`);
|
|
2982
|
+
if (s.functionsRunnerError > 0)
|
|
2983
|
+
issues.push(`${s.functionsRunnerError} function(s) with test runner errors (the runner crashed — not a test failure)`);
|
|
2984
|
+
if (s.functionsNameMismatch > 0)
|
|
2985
|
+
issues.push(`${s.functionsNameMismatch} function(s) with test name mismatch`);
|
|
2986
|
+
if (s.missingFromGlossary > 0)
|
|
2987
|
+
issues.push(`${s.missingFromGlossary} file(s) with scenarios not in glossary`);
|
|
2988
|
+
if (s.incompleteEntities > 0)
|
|
2989
|
+
issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
|
|
2990
|
+
if (s.miscategorizedScenarios > 0)
|
|
2991
|
+
issues.push(`${s.miscategorizedScenarios} component(s) using page URLs instead of isolation routes`);
|
|
2992
|
+
if (issues.length > 0) {
|
|
2993
|
+
console.error(chalk.yellow('\nAudit failures:'));
|
|
2994
|
+
for (const issue of issues) {
|
|
2995
|
+
console.error(chalk.yellow(` • ${issue}`));
|
|
2996
|
+
}
|
|
2864
2997
|
}
|
|
2998
|
+
// Surface client error details inline — these are the most common cause of
|
|
2999
|
+
// Claude looping because the errors require code fixes, not audit re-runs.
|
|
3000
|
+
if (data.components) {
|
|
3001
|
+
const withErrors = data.components.filter((c) => c.status === 'has_errors' && c.clientErrors?.length > 0);
|
|
3002
|
+
if (withErrors.length > 0) {
|
|
3003
|
+
console.error(chalk.yellow('\nClient errors found:'));
|
|
3004
|
+
for (const c of withErrors) {
|
|
3005
|
+
console.error(chalk.red(` ${c.name}:`));
|
|
3006
|
+
for (const err of c.clientErrors.slice(0, 3)) {
|
|
3007
|
+
console.error(chalk.red(` → ${err}`));
|
|
3008
|
+
}
|
|
3009
|
+
if (c.clientErrors.length > 3) {
|
|
3010
|
+
console.error(chalk.dim(` … and ${c.clientErrors.length - 3} more`));
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
console.error(chalk.yellow('\nFix: Fix the code errors above, then re-capture the affected scenarios.'));
|
|
3014
|
+
console.error(chalk.yellow('If errors reference browser APIs (localStorage, sessionStorage, window, document),'));
|
|
3015
|
+
console.error(chalk.yellow('create a universal mock: codeyam detect-universal-mocks'));
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
3018
|
+
console.error(chalk.dim('\nRun `codeyam editor audit` for full details.\n'));
|
|
2865
3019
|
}
|
|
2866
3020
|
// ─── Audit subcommand ────────────────────────────────────────────────
|
|
2867
3021
|
/**
|
|
@@ -2872,22 +3026,38 @@ async function checkAuditGate() {
|
|
|
2872
3026
|
* have test files. Exits with code 1 if anything is missing.
|
|
2873
3027
|
*/
|
|
2874
3028
|
async function handleAudit() {
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
let data;
|
|
2878
|
-
try {
|
|
2879
|
-
const res = await fetch(url);
|
|
2880
|
-
if (!res.ok) {
|
|
2881
|
-
console.error(chalk.red(`Error: Audit endpoint returned ${res.status}`));
|
|
2882
|
-
process.exit(1);
|
|
2883
|
-
}
|
|
2884
|
-
data = await res.json();
|
|
2885
|
-
}
|
|
2886
|
-
catch (err) {
|
|
3029
|
+
let data = await fetchAuditResult();
|
|
3030
|
+
if (!data) {
|
|
2887
3031
|
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
2888
|
-
console.error(chalk.dim(` ${err.message}`));
|
|
2889
3032
|
process.exit(1);
|
|
2890
3033
|
}
|
|
3034
|
+
// Auto-fix incomplete entities — but only once.
|
|
3035
|
+
// If analyze-imports runs and entities are STILL incomplete, report clearly
|
|
3036
|
+
// instead of retrying. The analysis may have errors (e.g., stale entity
|
|
3037
|
+
// references from refactoring) that can't be fixed by re-running.
|
|
3038
|
+
const incompleteBeforeFix = data.incompleteEntities || [];
|
|
3039
|
+
let autoRemediationFailed = false;
|
|
3040
|
+
if (incompleteBeforeFix.length > 0) {
|
|
3041
|
+
console.log(chalk.dim(`Running import analysis for ${incompleteBeforeFix.length} incomplete entit${incompleteBeforeFix.length !== 1 ? 'ies' : 'y'}...`));
|
|
3042
|
+
try {
|
|
3043
|
+
await handleAnalyzeImports({ silent: true });
|
|
3044
|
+
}
|
|
3045
|
+
catch {
|
|
3046
|
+
// Fall through — the audit will still report them as incomplete
|
|
3047
|
+
}
|
|
3048
|
+
// Re-fetch audit results after the fix
|
|
3049
|
+
data = await fetchAuditResult();
|
|
3050
|
+
if (!data) {
|
|
3051
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server after analyze-imports.'));
|
|
3052
|
+
process.exit(1);
|
|
3053
|
+
}
|
|
3054
|
+
// If entities are still incomplete after running analyze-imports,
|
|
3055
|
+
// flag it so we can show a clear message instead of looping
|
|
3056
|
+
const incompleteAfterFix = data.incompleteEntities || [];
|
|
3057
|
+
if (incompleteAfterFix.length > 0) {
|
|
3058
|
+
autoRemediationFailed = true;
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
2891
3061
|
const { components, functions, summary } = data;
|
|
2892
3062
|
console.log();
|
|
2893
3063
|
console.log(chalk.bold.cyan('━━━ Editor Audit ━━━'));
|
|
@@ -2915,6 +3085,14 @@ async function handleAudit() {
|
|
|
2915
3085
|
if (c.clientErrors.length > 3) {
|
|
2916
3086
|
console.log(chalk.dim(` … and ${c.clientErrors.length - 3} more`));
|
|
2917
3087
|
}
|
|
3088
|
+
// Detect browser API errors and provide actionable guidance
|
|
3089
|
+
const browserApiPattern = /\b(localStorage|sessionStorage|window\.|document\.|navigator\.|indexedDB|matchMedia|ResizeObserver|IntersectionObserver|MutationObserver)\b/;
|
|
3090
|
+
const hasBrowserApiErrors = c.clientErrors.some((err) => browserApiPattern.test(err));
|
|
3091
|
+
if (hasBrowserApiErrors) {
|
|
3092
|
+
console.log(chalk.yellow(` ⚠ These errors are caused by browser APIs that don't exist during server-side analysis.`));
|
|
3093
|
+
console.log(chalk.yellow(` Fix: Create a universal mock to stub the missing API. Run: codeyam detect-universal-mocks`));
|
|
3094
|
+
console.log(chalk.yellow(` DO NOT re-run the audit or analyze-imports — the error will persist until a mock is created.`));
|
|
3095
|
+
}
|
|
2918
3096
|
}
|
|
2919
3097
|
}
|
|
2920
3098
|
console.log();
|
|
@@ -2929,6 +3107,14 @@ async function handleAudit() {
|
|
|
2929
3107
|
case 'ok':
|
|
2930
3108
|
detail = chalk.dim(` (${f.testFile})`);
|
|
2931
3109
|
break;
|
|
3110
|
+
case 'runner_error':
|
|
3111
|
+
detail = chalk.red(` — test runner crashed: ${f.testFile}`);
|
|
3112
|
+
if (f.errorMessage) {
|
|
3113
|
+
detail += `\n ${chalk.red(f.errorMessage)}`;
|
|
3114
|
+
detail += `\n ${chalk.yellow('Note: This is NOT a test failure — the test runner itself could not execute.')}`;
|
|
3115
|
+
detail += `\n ${chalk.yellow('Try running the test manually: npx vitest run ' + f.testFile)}`;
|
|
3116
|
+
}
|
|
3117
|
+
break;
|
|
2932
3118
|
case 'failing':
|
|
2933
3119
|
detail = chalk.red(` — tests failing: ${f.testFile}`);
|
|
2934
3120
|
break;
|
|
@@ -2956,6 +3142,43 @@ async function handleAudit() {
|
|
|
2956
3142
|
console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
|
|
2957
3143
|
console.log();
|
|
2958
3144
|
}
|
|
3145
|
+
// Incomplete entities (scenarios without analyses)
|
|
3146
|
+
const incompleteEntities = data.incompleteEntities || [];
|
|
3147
|
+
if (incompleteEntities.length > 0) {
|
|
3148
|
+
console.log(chalk.bold('Incomplete entities (need import analysis):'));
|
|
3149
|
+
for (const e of incompleteEntities) {
|
|
3150
|
+
console.log(` ${chalk.red('✗')} ${e.name} — ${e.scenarioCount} scenario${e.scenarioCount !== 1 ? 's' : ''} but entity not analyzed`);
|
|
3151
|
+
}
|
|
3152
|
+
if (autoRemediationFailed) {
|
|
3153
|
+
console.log(chalk.red(' analyze-imports was run automatically but these entities are STILL incomplete.'));
|
|
3154
|
+
console.log(chalk.red(' DO NOT re-run analyze-imports or the audit in a loop — the result will be the same.'));
|
|
3155
|
+
console.log(chalk.yellow(' This means the analysis failed for these entities. Common causes:'));
|
|
3156
|
+
console.log(chalk.yellow(' • Entity code uses browser APIs (localStorage, window, document) — create a universal mock'));
|
|
3157
|
+
console.log(chalk.yellow(' • Source file was renamed/deleted but glossary still references the old path'));
|
|
3158
|
+
console.log(chalk.yellow(' • Entity has import errors that prevent analysis'));
|
|
3159
|
+
console.log(chalk.yellow(' To investigate: run `codeyam editor analyze-imports` (without --silent) to see the full error.'));
|
|
3160
|
+
console.log(chalk.yellow(' To fix browser API issues: run `codeyam detect-universal-mocks`'));
|
|
3161
|
+
}
|
|
3162
|
+
else {
|
|
3163
|
+
console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to analyze these entities'));
|
|
3164
|
+
}
|
|
3165
|
+
console.log();
|
|
3166
|
+
}
|
|
3167
|
+
// Miscategorized scenarios (component scenarios using real page URLs)
|
|
3168
|
+
const miscategorizedScenarios = data.miscategorizedScenarios || [];
|
|
3169
|
+
if (miscategorizedScenarios.length > 0) {
|
|
3170
|
+
console.log(chalk.bold('Miscategorized scenarios (component with page URL):'));
|
|
3171
|
+
for (const m of miscategorizedScenarios) {
|
|
3172
|
+
console.log(` ${chalk.red('✗')} ${m.componentName} at ${chalk.dim(m.url)} — ${m.scenarioNames.length} scenario${m.scenarioNames.length !== 1 ? 's' : ''}`);
|
|
3173
|
+
for (const name of m.scenarioNames) {
|
|
3174
|
+
console.log(chalk.dim(` "${name}"`));
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3177
|
+
console.log(chalk.yellow(' Component scenarios must use isolation routes (/isolated-components/...).'));
|
|
3178
|
+
console.log(chalk.yellow(' Either re-register as app scenarios (remove componentName, add pageFilePath)'));
|
|
3179
|
+
console.log(chalk.yellow(' or re-register with an isolation URL.'));
|
|
3180
|
+
console.log();
|
|
3181
|
+
}
|
|
2959
3182
|
// Summary
|
|
2960
3183
|
const allOk = summary.allPassing;
|
|
2961
3184
|
if (allOk) {
|
|
@@ -2976,12 +3199,21 @@ async function handleAudit() {
|
|
|
2976
3199
|
if (summary.functionsFailing > 0) {
|
|
2977
3200
|
parts.push(`${summary.functionsFailing} function${summary.functionsFailing !== 1 ? 's' : ''} with failing tests`);
|
|
2978
3201
|
}
|
|
3202
|
+
if (summary.functionsRunnerError > 0) {
|
|
3203
|
+
parts.push(`${summary.functionsRunnerError} function${summary.functionsRunnerError !== 1 ? 's' : ''} with test runner errors (not test failures — see details above)`);
|
|
3204
|
+
}
|
|
2979
3205
|
if (summary.functionsNameMismatch > 0) {
|
|
2980
3206
|
parts.push(`${summary.functionsNameMismatch} function${summary.functionsNameMismatch !== 1 ? 's' : ''} with test name mismatch (missing top-level describe)`);
|
|
2981
3207
|
}
|
|
2982
3208
|
if (summary.missingFromGlossary > 0) {
|
|
2983
3209
|
parts.push(`${summary.missingFromGlossary} file${summary.missingFromGlossary !== 1 ? 's' : ''} with scenarios not in glossary`);
|
|
2984
3210
|
}
|
|
3211
|
+
if (summary.incompleteEntities > 0) {
|
|
3212
|
+
parts.push(`${summary.incompleteEntities} entit${summary.incompleteEntities !== 1 ? 'ies' : 'y'} need import analysis`);
|
|
3213
|
+
}
|
|
3214
|
+
if (summary.miscategorizedScenarios > 0) {
|
|
3215
|
+
parts.push(`${summary.miscategorizedScenarios} component${summary.miscategorizedScenarios !== 1 ? 's' : ''} using page URLs instead of isolation routes`);
|
|
3216
|
+
}
|
|
2985
3217
|
console.log(chalk.red.bold('Audit failed: ') + parts.join(', '));
|
|
2986
3218
|
}
|
|
2987
3219
|
console.log();
|
|
@@ -3099,6 +3331,32 @@ async function handleScenarios() {
|
|
|
3099
3331
|
}
|
|
3100
3332
|
// ─── Scenario Coverage subcommand ───────────────────────────────────
|
|
3101
3333
|
async function handleScenarioCoverage() {
|
|
3334
|
+
// Safety net: heal any scenarios with null entity_sha before checking coverage
|
|
3335
|
+
try {
|
|
3336
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
3337
|
+
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
|
|
3338
|
+
const db = getDatabase();
|
|
3339
|
+
const backfillCount = await countScenariosNeedingEntityBackfill(db);
|
|
3340
|
+
if (backfillCount > 0) {
|
|
3341
|
+
console.log(chalk.dim(` Healing ${backfillCount} scenario(s) with unlinked entities...`));
|
|
3342
|
+
await handleAnalyzeImports({ silent: true });
|
|
3343
|
+
// Run backfill after analysis
|
|
3344
|
+
const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios');
|
|
3345
|
+
const entities = await loadEntities({});
|
|
3346
|
+
if (entities && entities.length > 0) {
|
|
3347
|
+
await backfillEntityShaOnScenarios(db, entities.map((e) => ({
|
|
3348
|
+
sha: e.sha,
|
|
3349
|
+
name: e.name,
|
|
3350
|
+
filePath: e.filePath || '',
|
|
3351
|
+
isDefaultExport: e.metadata?.notExported === false &&
|
|
3352
|
+
e.metadata?.namedExport === false,
|
|
3353
|
+
})));
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
catch {
|
|
3358
|
+
// Non-fatal — proceed with coverage check regardless
|
|
3359
|
+
}
|
|
3102
3360
|
const port = getServerPort();
|
|
3103
3361
|
const url = `http://localhost:${port}/api/editor-scenario-coverage`;
|
|
3104
3362
|
let data;
|
|
@@ -3246,7 +3504,20 @@ async function handleTemplate() {
|
|
|
3246
3504
|
// Config parse error is non-fatal
|
|
3247
3505
|
}
|
|
3248
3506
|
}
|
|
3249
|
-
// 5b.
|
|
3507
|
+
// 5b. Mark the project as template-scaffolded so migration detection
|
|
3508
|
+
// doesn't treat it as a pre-existing project that needs migration.
|
|
3509
|
+
const now = new Date().toISOString();
|
|
3510
|
+
const existingState = readState(root);
|
|
3511
|
+
writeState(root, {
|
|
3512
|
+
feature: '',
|
|
3513
|
+
step: 0,
|
|
3514
|
+
label: '',
|
|
3515
|
+
startedAt: now,
|
|
3516
|
+
featureStartedAt: now,
|
|
3517
|
+
...existingState,
|
|
3518
|
+
scaffolded: true,
|
|
3519
|
+
});
|
|
3520
|
+
// 5c. Write a fresh prototypeId so the proxy clears stale localStorage
|
|
3250
3521
|
const activeScenarioPath = path.join(root, '.codeyam', 'active-scenario.json');
|
|
3251
3522
|
fs.writeFileSync(activeScenarioPath, JSON.stringify({ prototypeId: Date.now().toString() }), 'utf-8');
|
|
3252
3523
|
// 6. Trigger editor-refresh so the server picks up the new project
|
|
@@ -3624,7 +3895,9 @@ const editorCommand = {
|
|
|
3624
3895
|
describe: 'Debug: output directory for the bundle',
|
|
3625
3896
|
});
|
|
3626
3897
|
}
|
|
3627
|
-
|
|
3898
|
+
// Allow extra positional args for subcommands like `isolate A B C`
|
|
3899
|
+
// without yargs rejecting them as unknown.
|
|
3900
|
+
return builder.strict(false);
|
|
3628
3901
|
},
|
|
3629
3902
|
handler: async (argv) => {
|
|
3630
3903
|
const root = getProjectRoot();
|
|
@@ -3703,14 +3976,16 @@ const editorCommand = {
|
|
|
3703
3976
|
await handleValidateSeed(argv.json || '');
|
|
3704
3977
|
return;
|
|
3705
3978
|
}
|
|
3979
|
+
// Subcommand: codeyam editor delete <scenarioId>
|
|
3980
|
+
if (argv.step === 'delete') {
|
|
3981
|
+
await handleDelete(argv.json || '');
|
|
3982
|
+
return;
|
|
3983
|
+
}
|
|
3706
3984
|
// Subcommand: codeyam editor isolate "StarRating CategoryBadge DrinkCard"
|
|
3985
|
+
// Also supports: codeyam editor isolate StarRating CategoryBadge DrinkCard
|
|
3707
3986
|
if (argv.step === 'isolate') {
|
|
3708
|
-
const
|
|
3709
|
-
|
|
3710
|
-
names.push(...argv.json.split(/[\s,]+/).filter(Boolean));
|
|
3711
|
-
// Collect any extra positional args (yargs puts them in argv._)
|
|
3712
|
-
const extras = argv._.filter((a) => typeof a === 'string' && a !== 'editor');
|
|
3713
|
-
names.push(...extras);
|
|
3987
|
+
const { parseIsolateArgs } = await import('./editorIsolateArgs.js');
|
|
3988
|
+
const names = parseIsolateArgs(argv.json, argv._);
|
|
3714
3989
|
handleIsolate(names);
|
|
3715
3990
|
return;
|
|
3716
3991
|
}
|
|
@@ -3807,6 +4082,9 @@ const editorCommand = {
|
|
|
3807
4082
|
'screenshot_path',
|
|
3808
4083
|
'viewport_width',
|
|
3809
4084
|
'viewport_height',
|
|
4085
|
+
'dimensions',
|
|
4086
|
+
'screenshot_paths',
|
|
4087
|
+
'page_file_path',
|
|
3810
4088
|
'created_at',
|
|
3811
4089
|
'updated_at',
|
|
3812
4090
|
])
|
|
@@ -3895,6 +4173,41 @@ const editorCommand = {
|
|
|
3895
4173
|
catch {
|
|
3896
4174
|
// Non-fatal — migration failure shouldn't block editor startup
|
|
3897
4175
|
}
|
|
4176
|
+
// Auto-seed on fresh clone: if no scenario has ever been activated
|
|
4177
|
+
// (active-scenario.json doesn't exist), seed the application database
|
|
4178
|
+
// with the first application scenario that has seed data.
|
|
4179
|
+
const activeScenarioPath = path.join(projectRoot, '.codeyam', 'active-scenario.json');
|
|
4180
|
+
if (!fs.existsSync(activeScenarioPath)) {
|
|
4181
|
+
try {
|
|
4182
|
+
const { getDatabase: getDb } = await import('../../../packages/database/index.js');
|
|
4183
|
+
const seedDb = getDb();
|
|
4184
|
+
const appScenario = await seedDb
|
|
4185
|
+
.selectFrom('editor_scenarios')
|
|
4186
|
+
.select(['id', 'name', 'type'])
|
|
4187
|
+
.where('project_id', '=', project.id)
|
|
4188
|
+
.where('type', '=', 'application')
|
|
4189
|
+
.orderBy('created_at', 'asc')
|
|
4190
|
+
.executeTakeFirst();
|
|
4191
|
+
if (appScenario) {
|
|
4192
|
+
const seedFile = path.join(projectRoot, '.codeyam', 'editor-scenarios', `${appScenario.id}.seed.json`);
|
|
4193
|
+
if (fs.existsSync(seedFile)) {
|
|
4194
|
+
const { switchActiveScenario } = await import('../utils/editorScenarioSwitch.js');
|
|
4195
|
+
const seedResult = await switchActiveScenario({
|
|
4196
|
+
scenarioId: appScenario.id,
|
|
4197
|
+
scenarioName: appScenario.name || undefined,
|
|
4198
|
+
scenarioType: appScenario.type || undefined,
|
|
4199
|
+
projectRoot,
|
|
4200
|
+
});
|
|
4201
|
+
if (seedResult.seedResult?.success) {
|
|
4202
|
+
console.log(chalk.green(` Auto-seeded database with scenario: ${appScenario.name || appScenario.id}`));
|
|
4203
|
+
}
|
|
4204
|
+
}
|
|
4205
|
+
}
|
|
4206
|
+
}
|
|
4207
|
+
catch {
|
|
4208
|
+
// Non-fatal — auto-seed is best-effort
|
|
4209
|
+
}
|
|
4210
|
+
}
|
|
3898
4211
|
// `codeyam editor` (no step) always implies editor mode.
|
|
3899
4212
|
// The empty-folder heuristic is no longer needed here — running this
|
|
3900
4213
|
// command IS the signal. We still detect empty folders so that
|
|
@@ -4013,6 +4326,22 @@ const editorCommand = {
|
|
|
4013
4326
|
// Non-fatal
|
|
4014
4327
|
}
|
|
4015
4328
|
}
|
|
4329
|
+
// Check if any scenarios have null entity_sha with file paths — heal on startup
|
|
4330
|
+
if (!needsAnalysis) {
|
|
4331
|
+
try {
|
|
4332
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
4333
|
+
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
|
|
4334
|
+
const db = getDatabase();
|
|
4335
|
+
const backfillCount = await countScenariosNeedingEntityBackfill(db);
|
|
4336
|
+
if (backfillCount > 0) {
|
|
4337
|
+
console.log(chalk.dim(` Found ${backfillCount} scenario(s) with unlinked entities — running import analysis...`));
|
|
4338
|
+
needsAnalysis = true;
|
|
4339
|
+
}
|
|
4340
|
+
}
|
|
4341
|
+
catch {
|
|
4342
|
+
// Non-fatal
|
|
4343
|
+
}
|
|
4344
|
+
}
|
|
4016
4345
|
}
|
|
4017
4346
|
if (needsAnalysis) {
|
|
4018
4347
|
try {
|
|
@@ -4023,6 +4352,71 @@ const editorCommand = {
|
|
|
4023
4352
|
}
|
|
4024
4353
|
}
|
|
4025
4354
|
}
|
|
4355
|
+
// Backfill page_file_path for application scenarios that have a URL but
|
|
4356
|
+
// no page_file_path. This resolves the file from the URL using Next.js
|
|
4357
|
+
// routing conventions, enabling syncScenarioEntityShas to link them to
|
|
4358
|
+
// entities. Covers fresh clones where JSON files lack pageFilePath.
|
|
4359
|
+
try {
|
|
4360
|
+
const { getDatabase: getDb } = await import('../../../packages/database/index.js');
|
|
4361
|
+
const pfpDb = getDb();
|
|
4362
|
+
const unresolved = await pfpDb
|
|
4363
|
+
.selectFrom('editor_scenarios')
|
|
4364
|
+
.select(['id', 'url'])
|
|
4365
|
+
.where('project_id', '=', project.id)
|
|
4366
|
+
.where('component_name', 'is', null)
|
|
4367
|
+
.where('page_file_path', 'is', null)
|
|
4368
|
+
.where('url', 'is not', null)
|
|
4369
|
+
.execute();
|
|
4370
|
+
if (unresolved.length > 0) {
|
|
4371
|
+
const { scanPageFilePaths: scanPfp } = await import('../utils/entityChangeStatus.server.js');
|
|
4372
|
+
const { matchUrlToPageFile } = await import('../utils/routePatternMatching');
|
|
4373
|
+
const { allFiles: pfpFiles } = scanPfp(projectRoot);
|
|
4374
|
+
let pfpResolved = 0;
|
|
4375
|
+
for (const row of unresolved) {
|
|
4376
|
+
const r = row;
|
|
4377
|
+
if (!r.url)
|
|
4378
|
+
continue;
|
|
4379
|
+
const matched = matchUrlToPageFile(r.url, pfpFiles);
|
|
4380
|
+
if (matched) {
|
|
4381
|
+
await pfpDb
|
|
4382
|
+
.updateTable('editor_scenarios')
|
|
4383
|
+
.set({ page_file_path: matched })
|
|
4384
|
+
.where('id', '=', r.id)
|
|
4385
|
+
.execute();
|
|
4386
|
+
pfpResolved++;
|
|
4387
|
+
}
|
|
4388
|
+
}
|
|
4389
|
+
if (pfpResolved > 0) {
|
|
4390
|
+
console.log(chalk.green(` Resolved page_file_path for ${pfpResolved} scenario(s) from URL`));
|
|
4391
|
+
}
|
|
4392
|
+
}
|
|
4393
|
+
}
|
|
4394
|
+
catch {
|
|
4395
|
+
/* Non-fatal — page_file_path backfill from URL */
|
|
4396
|
+
}
|
|
4397
|
+
// Backfill entity_sha on scenarios that were synced before entities existed.
|
|
4398
|
+
// This runs independently of analyze-imports so fresh clones and file-synced
|
|
4399
|
+
// scenarios get linked to their entities even when auto-analyze doesn't trigger.
|
|
4400
|
+
try {
|
|
4401
|
+
const entities = await loadEntities({});
|
|
4402
|
+
if (entities && entities.length > 0) {
|
|
4403
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
4404
|
+
const db = getDatabase();
|
|
4405
|
+
const result = await backfillEntityShaOnScenarios(db, entities.map((e) => ({
|
|
4406
|
+
sha: e.sha,
|
|
4407
|
+
name: e.name,
|
|
4408
|
+
filePath: e.filePath || '',
|
|
4409
|
+
isDefaultExport: e.metadata?.notExported === false &&
|
|
4410
|
+
e.metadata?.namedExport === false,
|
|
4411
|
+
})));
|
|
4412
|
+
if (result.updated > 0) {
|
|
4413
|
+
console.log(chalk.green(` Linked ${result.updated} scenario(s) to their entities`));
|
|
4414
|
+
}
|
|
4415
|
+
}
|
|
4416
|
+
}
|
|
4417
|
+
catch {
|
|
4418
|
+
/* Non-fatal */
|
|
4419
|
+
}
|
|
4026
4420
|
console.log();
|
|
4027
4421
|
console.log(` Dashboard: ${url}`);
|
|
4028
4422
|
console.log(' Run "codeyam --help" for all commands');
|
|
@@ -4108,8 +4502,9 @@ const editorCommand = {
|
|
|
4108
4502
|
if (step >= 8) {
|
|
4109
4503
|
const auditOk = await checkAuditGate();
|
|
4110
4504
|
if (!auditOk) {
|
|
4111
|
-
|
|
4112
|
-
console.error(chalk.
|
|
4505
|
+
// checkAuditGate() already printed specific failure details above
|
|
4506
|
+
console.error(chalk.red.bold('BLOCKED: The audit has not passed. Fix the issues listed above before proceeding.'));
|
|
4507
|
+
console.error(chalk.yellow('DO NOT re-run the audit in a loop — fix the underlying issues first.'));
|
|
4113
4508
|
process.exit(1);
|
|
4114
4509
|
}
|
|
4115
4510
|
}
|