@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
|
@@ -3,7 +3,7 @@ import os from 'os';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import Database from 'better-sqlite3';
|
|
5
5
|
import { Kysely, SqliteDialect } from 'kysely';
|
|
6
|
-
import { deduplicateByName, generateScenarioSlug, convertIsoToSqliteTimestamp, determineCaptureUrl, resolvePreviewNavPath, clearEditorState, clearEditorUserPrompt, readDefaultScreenSize, readScreenSizes, resolveScenarioViewport, resolveViewportWithProjectDefault, upsertEditorScenario, cleanupScenarioFiles, readPreservedConfigProperties, validateStepTransition, slugifyDimension, isRowInFeatureSession, } from "../editorScenarios.js";
|
|
6
|
+
import { deduplicateByName, generateScenarioSlug, convertIsoToSqliteTimestamp, determineCaptureUrl, resolvePreviewNavPath, clearEditorState, clearEditorUserPrompt, readDefaultScreenSize, readScreenSizes, resolveScenarioViewport, resolveViewportWithProjectDefault, upsertEditorScenario, cleanupScenarioFiles, readPreservedConfigProperties, validateStepTransition, slugifyDimension, isRowInFeatureSession, backfillEntityShaOnScenarios, countScenariosNeedingEntityBackfill, validateEntityLinkageForAppScenario, } from "../editorScenarios.js";
|
|
7
7
|
describe('editorScenarios', () => {
|
|
8
8
|
describe('deduplicateByName', () => {
|
|
9
9
|
it('should keep only the last item for each key', () => {
|
|
@@ -497,7 +497,6 @@ describe('editorScenarios', () => {
|
|
|
497
497
|
type: null,
|
|
498
498
|
viewportWidth: 1280,
|
|
499
499
|
viewportHeight: 720,
|
|
500
|
-
dimension: null,
|
|
501
500
|
});
|
|
502
501
|
expect(result.isNew).toBe(true);
|
|
503
502
|
expect(result.cleanedUpIds).toEqual([]);
|
|
@@ -522,7 +521,6 @@ describe('editorScenarios', () => {
|
|
|
522
521
|
type: null,
|
|
523
522
|
viewportWidth: 1280,
|
|
524
523
|
viewportHeight: 720,
|
|
525
|
-
dimension: null,
|
|
526
524
|
});
|
|
527
525
|
// Second registration with same name
|
|
528
526
|
const second = await upsertEditorScenario(db, {
|
|
@@ -535,7 +533,6 @@ describe('editorScenarios', () => {
|
|
|
535
533
|
type: null,
|
|
536
534
|
viewportWidth: 800,
|
|
537
535
|
viewportHeight: 600,
|
|
538
|
-
dimension: null,
|
|
539
536
|
});
|
|
540
537
|
// Should reuse the same ID
|
|
541
538
|
expect(second.scenarioId).toBe(first.scenarioId);
|
|
@@ -576,7 +573,6 @@ describe('editorScenarios', () => {
|
|
|
576
573
|
type: null,
|
|
577
574
|
viewportWidth: 1280,
|
|
578
575
|
viewportHeight: 720,
|
|
579
|
-
dimension: null,
|
|
580
576
|
});
|
|
581
577
|
expect(result.isNew).toBe(false);
|
|
582
578
|
// Should return the IDs of cleaned-up duplicates
|
|
@@ -600,7 +596,6 @@ describe('editorScenarios', () => {
|
|
|
600
596
|
type: null,
|
|
601
597
|
viewportWidth: 1280,
|
|
602
598
|
viewportHeight: 720,
|
|
603
|
-
dimension: null,
|
|
604
599
|
});
|
|
605
600
|
await upsertEditorScenario(db, {
|
|
606
601
|
projectId,
|
|
@@ -612,7 +607,6 @@ describe('editorScenarios', () => {
|
|
|
612
607
|
type: null,
|
|
613
608
|
viewportWidth: 1280,
|
|
614
609
|
viewportHeight: 720,
|
|
615
|
-
dimension: null,
|
|
616
610
|
});
|
|
617
611
|
const rows = await db
|
|
618
612
|
.selectFrom('editor_scenarios')
|
|
@@ -631,7 +625,6 @@ describe('editorScenarios', () => {
|
|
|
631
625
|
type: 'application',
|
|
632
626
|
viewportWidth: 1280,
|
|
633
627
|
viewportHeight: 720,
|
|
634
|
-
dimension: null,
|
|
635
628
|
});
|
|
636
629
|
const result2 = await upsertEditorScenario(db, {
|
|
637
630
|
projectId: 'project-b',
|
|
@@ -643,7 +636,6 @@ describe('editorScenarios', () => {
|
|
|
643
636
|
type: 'application',
|
|
644
637
|
viewportWidth: 1280,
|
|
645
638
|
viewportHeight: 720,
|
|
646
|
-
dimension: null,
|
|
647
639
|
});
|
|
648
640
|
expect(result1.scenarioId).not.toBe(result2.scenarioId);
|
|
649
641
|
expect(result1.isNew).toBe(true);
|
|
@@ -848,7 +840,7 @@ describe('editorScenarios', () => {
|
|
|
848
840
|
afterEach(async () => {
|
|
849
841
|
await db.destroy();
|
|
850
842
|
});
|
|
851
|
-
it('should store dimensions as JSON array
|
|
843
|
+
it('should store dimensions as JSON array', async () => {
|
|
852
844
|
const result = await upsertEditorScenario(db, {
|
|
853
845
|
projectId,
|
|
854
846
|
name: 'Home Page',
|
|
@@ -859,7 +851,6 @@ describe('editorScenarios', () => {
|
|
|
859
851
|
type: 'application',
|
|
860
852
|
viewportWidth: 1440,
|
|
861
853
|
viewportHeight: 900,
|
|
862
|
-
dimension: 'Desktop',
|
|
863
854
|
dimensions: ['Desktop', 'Mobile'],
|
|
864
855
|
screenshotPaths: null,
|
|
865
856
|
});
|
|
@@ -873,8 +864,6 @@ describe('editorScenarios', () => {
|
|
|
873
864
|
'Desktop',
|
|
874
865
|
'Mobile',
|
|
875
866
|
]);
|
|
876
|
-
// dimension (singular) should be set to first element for backward compat
|
|
877
|
-
expect(row.dimension).toBe('Desktop');
|
|
878
867
|
});
|
|
879
868
|
it('should store screenshot_paths as JSON object', async () => {
|
|
880
869
|
const screenshotPaths = {
|
|
@@ -891,7 +880,6 @@ describe('editorScenarios', () => {
|
|
|
891
880
|
type: 'application',
|
|
892
881
|
viewportWidth: 1440,
|
|
893
882
|
viewportHeight: 900,
|
|
894
|
-
dimension: 'Desktop',
|
|
895
883
|
dimensions: ['Desktop', 'Mobile'],
|
|
896
884
|
screenshotPaths,
|
|
897
885
|
});
|
|
@@ -913,7 +901,6 @@ describe('editorScenarios', () => {
|
|
|
913
901
|
type: 'application',
|
|
914
902
|
viewportWidth: 1440,
|
|
915
903
|
viewportHeight: 900,
|
|
916
|
-
dimension: 'Desktop',
|
|
917
904
|
dimensions: ['Desktop'],
|
|
918
905
|
screenshotPaths: { Desktop: 'screenshots/old--desktop.png' },
|
|
919
906
|
});
|
|
@@ -927,7 +914,6 @@ describe('editorScenarios', () => {
|
|
|
927
914
|
type: 'application',
|
|
928
915
|
viewportWidth: 1440,
|
|
929
916
|
viewportHeight: 900,
|
|
930
|
-
dimension: 'Desktop',
|
|
931
917
|
dimensions: ['Desktop', 'Mobile'],
|
|
932
918
|
screenshotPaths: {
|
|
933
919
|
Desktop: 'screenshots/new--desktop.png',
|
|
@@ -960,7 +946,6 @@ describe('editorScenarios', () => {
|
|
|
960
946
|
type: null,
|
|
961
947
|
viewportWidth: 1280,
|
|
962
948
|
viewportHeight: 720,
|
|
963
|
-
dimension: null,
|
|
964
949
|
});
|
|
965
950
|
const row = await db
|
|
966
951
|
.selectFrom('editor_scenarios')
|
|
@@ -1099,7 +1084,6 @@ describe('editorScenarios', () => {
|
|
|
1099
1084
|
type: 'application',
|
|
1100
1085
|
viewportWidth: 400,
|
|
1101
1086
|
viewportHeight: 600,
|
|
1102
|
-
dimension: null,
|
|
1103
1087
|
entitySha: 'abc123',
|
|
1104
1088
|
displayName: 'Home',
|
|
1105
1089
|
});
|
|
@@ -1122,7 +1106,6 @@ describe('editorScenarios', () => {
|
|
|
1122
1106
|
type: 'application',
|
|
1123
1107
|
viewportWidth: 400,
|
|
1124
1108
|
viewportHeight: 600,
|
|
1125
|
-
dimension: null,
|
|
1126
1109
|
entitySha: 'old-sha',
|
|
1127
1110
|
displayName: 'OldName',
|
|
1128
1111
|
});
|
|
@@ -1137,7 +1120,6 @@ describe('editorScenarios', () => {
|
|
|
1137
1120
|
type: 'application',
|
|
1138
1121
|
viewportWidth: 400,
|
|
1139
1122
|
viewportHeight: 600,
|
|
1140
|
-
dimension: null,
|
|
1141
1123
|
entitySha: 'new-sha',
|
|
1142
1124
|
displayName: 'Home',
|
|
1143
1125
|
});
|
|
@@ -1160,7 +1142,6 @@ describe('editorScenarios', () => {
|
|
|
1160
1142
|
type: 'application',
|
|
1161
1143
|
viewportWidth: 400,
|
|
1162
1144
|
viewportHeight: 600,
|
|
1163
|
-
dimension: null,
|
|
1164
1145
|
entitySha: 'keep-this',
|
|
1165
1146
|
displayName: 'Home',
|
|
1166
1147
|
});
|
|
@@ -1175,7 +1156,6 @@ describe('editorScenarios', () => {
|
|
|
1175
1156
|
type: 'application',
|
|
1176
1157
|
viewportWidth: 400,
|
|
1177
1158
|
viewportHeight: 600,
|
|
1178
|
-
dimension: null,
|
|
1179
1159
|
});
|
|
1180
1160
|
const row = rawDb
|
|
1181
1161
|
.prepare('SELECT entity_sha, display_name FROM editor_scenarios WHERE id = ?')
|
|
@@ -1184,5 +1164,357 @@ describe('editorScenarios', () => {
|
|
|
1184
1164
|
expect(row.display_name).toBe('Home');
|
|
1185
1165
|
});
|
|
1186
1166
|
});
|
|
1167
|
+
describe('backfillEntityShaOnScenarios', () => {
|
|
1168
|
+
let db;
|
|
1169
|
+
let rawDb;
|
|
1170
|
+
const projectId = 'test-project-id';
|
|
1171
|
+
beforeEach(async () => {
|
|
1172
|
+
rawDb = new Database(':memory:');
|
|
1173
|
+
db = new Kysely({ dialect: new SqliteDialect({ database: rawDb }) });
|
|
1174
|
+
await db.schema
|
|
1175
|
+
.createTable('editor_scenarios')
|
|
1176
|
+
.addColumn('id', 'varchar', (col) => col.primaryKey())
|
|
1177
|
+
.addColumn('project_id', 'varchar', (col) => col.notNull())
|
|
1178
|
+
.addColumn('name', 'varchar', (col) => col.notNull())
|
|
1179
|
+
.addColumn('description', 'text')
|
|
1180
|
+
.addColumn('component_name', 'varchar')
|
|
1181
|
+
.addColumn('component_path', 'varchar')
|
|
1182
|
+
.addColumn('url', 'varchar')
|
|
1183
|
+
.addColumn('type', 'varchar')
|
|
1184
|
+
.addColumn('screenshot_path', 'varchar')
|
|
1185
|
+
.addColumn('viewport_width', 'integer')
|
|
1186
|
+
.addColumn('viewport_height', 'integer')
|
|
1187
|
+
.addColumn('dimension', 'varchar')
|
|
1188
|
+
.addColumn('dimensions', 'text')
|
|
1189
|
+
.addColumn('screenshot_paths', 'text')
|
|
1190
|
+
.addColumn('page_file_path', 'varchar')
|
|
1191
|
+
.addColumn('entity_sha', 'varchar')
|
|
1192
|
+
.addColumn('display_name', 'varchar')
|
|
1193
|
+
.addColumn('created_at', 'datetime', (col) => col.defaultTo(new Date().toISOString()))
|
|
1194
|
+
.addColumn('updated_at', 'datetime', (col) => col.defaultTo(new Date().toISOString()))
|
|
1195
|
+
.execute();
|
|
1196
|
+
});
|
|
1197
|
+
afterEach(async () => {
|
|
1198
|
+
await db.destroy();
|
|
1199
|
+
});
|
|
1200
|
+
it('should backfill component scenario by component_path match', async () => {
|
|
1201
|
+
// Insert a scenario with null entity_sha but with component_path
|
|
1202
|
+
rawDb
|
|
1203
|
+
.prepare(`INSERT INTO editor_scenarios (id, project_id, name, component_name, component_path, url, type, viewport_width, viewport_height)
|
|
1204
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1205
|
+
.run('sc-1', projectId, 'Header - Default', 'Header', 'src/components/Header.tsx', '/isolated-components/Header', 'component', 1280, 720);
|
|
1206
|
+
const result = await backfillEntityShaOnScenarios(db, [
|
|
1207
|
+
{
|
|
1208
|
+
sha: 'entity-sha-1',
|
|
1209
|
+
name: 'Header',
|
|
1210
|
+
filePath: 'src/components/Header.tsx',
|
|
1211
|
+
},
|
|
1212
|
+
]);
|
|
1213
|
+
expect(result.updated).toBe(1);
|
|
1214
|
+
const row = rawDb
|
|
1215
|
+
.prepare('SELECT entity_sha, display_name FROM editor_scenarios WHERE id = ?')
|
|
1216
|
+
.get('sc-1');
|
|
1217
|
+
expect(row.entity_sha).toBe('entity-sha-1');
|
|
1218
|
+
expect(row.display_name).toBe('Header');
|
|
1219
|
+
});
|
|
1220
|
+
it('should backfill page scenario by page_file_path match and derive display_name from route', async () => {
|
|
1221
|
+
// Insert a scenario with null entity_sha but with page_file_path
|
|
1222
|
+
rawDb
|
|
1223
|
+
.prepare(`INSERT INTO editor_scenarios (id, project_id, name, page_file_path, url, type, viewport_width, viewport_height)
|
|
1224
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1225
|
+
.run('sc-2', projectId, 'Feedback - Default', 'app/feedback/page.tsx', '/feedback', 'application', 1280, 720);
|
|
1226
|
+
const result = await backfillEntityShaOnScenarios(db, [
|
|
1227
|
+
{
|
|
1228
|
+
sha: 'entity-sha-2',
|
|
1229
|
+
name: 'Feedback',
|
|
1230
|
+
filePath: 'app/feedback/page.tsx',
|
|
1231
|
+
},
|
|
1232
|
+
]);
|
|
1233
|
+
expect(result.updated).toBe(1);
|
|
1234
|
+
const row = rawDb
|
|
1235
|
+
.prepare('SELECT entity_sha, display_name FROM editor_scenarios WHERE id = ?')
|
|
1236
|
+
.get('sc-2');
|
|
1237
|
+
expect(row.entity_sha).toBe('entity-sha-2');
|
|
1238
|
+
// display_name derived from page_file_path via routeDisplayName(buildRoutePattern())
|
|
1239
|
+
expect(row.display_name).toBe('Feedback');
|
|
1240
|
+
});
|
|
1241
|
+
it('should update scenarios with stale entity_sha to latest', async () => {
|
|
1242
|
+
// Margo bug: entity code changes create new versions with new SHAs,
|
|
1243
|
+
// but scenarios still point to the old SHA. Since journals preserve
|
|
1244
|
+
// screenshots and everything is in git, we always want scenarios
|
|
1245
|
+
// pointing to the latest entity version.
|
|
1246
|
+
rawDb
|
|
1247
|
+
.prepare(`INSERT INTO editor_scenarios (id, project_id, name, component_name, component_path, url, type, viewport_width, viewport_height, entity_sha, display_name)
|
|
1248
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1249
|
+
.run('sc-3', projectId, 'Header - Dark', 'Header', 'src/components/Header.tsx', '/isolated-components/Header', 'component', 1280, 720, 'old-sha', 'Header');
|
|
1250
|
+
const result = await backfillEntityShaOnScenarios(db, [
|
|
1251
|
+
{
|
|
1252
|
+
sha: 'latest-sha',
|
|
1253
|
+
name: 'Header',
|
|
1254
|
+
filePath: 'src/components/Header.tsx',
|
|
1255
|
+
},
|
|
1256
|
+
]);
|
|
1257
|
+
expect(result.updated).toBe(1);
|
|
1258
|
+
const row = rawDb
|
|
1259
|
+
.prepare('SELECT entity_sha FROM editor_scenarios WHERE id = ?')
|
|
1260
|
+
.get('sc-3');
|
|
1261
|
+
expect(row.entity_sha).toBe('latest-sha');
|
|
1262
|
+
});
|
|
1263
|
+
it('should not update scenarios already pointing to the latest SHA', async () => {
|
|
1264
|
+
rawDb
|
|
1265
|
+
.prepare(`INSERT INTO editor_scenarios (id, project_id, name, component_name, component_path, url, type, viewport_width, viewport_height, entity_sha, display_name)
|
|
1266
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1267
|
+
.run('sc-3b', projectId, 'Header - Dark', 'Header', 'src/components/Header.tsx', '/isolated-components/Header', 'component', 1280, 720, 'current-sha', 'Header');
|
|
1268
|
+
const result = await backfillEntityShaOnScenarios(db, [
|
|
1269
|
+
{
|
|
1270
|
+
sha: 'current-sha',
|
|
1271
|
+
name: 'Header',
|
|
1272
|
+
filePath: 'src/components/Header.tsx',
|
|
1273
|
+
},
|
|
1274
|
+
]);
|
|
1275
|
+
expect(result.updated).toBe(0);
|
|
1276
|
+
});
|
|
1277
|
+
it('should skip scenarios with no matching entity', async () => {
|
|
1278
|
+
rawDb
|
|
1279
|
+
.prepare(`INSERT INTO editor_scenarios (id, project_id, name, component_name, component_path, url, type, viewport_width, viewport_height)
|
|
1280
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1281
|
+
.run('sc-4', projectId, 'Footer - Default', 'Footer', 'src/components/Footer.tsx', '/isolated-components/Footer', 'component', 1280, 720);
|
|
1282
|
+
// Entities list doesn't include Footer.tsx
|
|
1283
|
+
const result = await backfillEntityShaOnScenarios(db, [
|
|
1284
|
+
{
|
|
1285
|
+
sha: 'entity-sha-1',
|
|
1286
|
+
name: 'Header',
|
|
1287
|
+
filePath: 'src/components/Header.tsx',
|
|
1288
|
+
},
|
|
1289
|
+
]);
|
|
1290
|
+
expect(result.updated).toBe(0);
|
|
1291
|
+
const row = rawDb
|
|
1292
|
+
.prepare('SELECT entity_sha FROM editor_scenarios WHERE id = ?')
|
|
1293
|
+
.get('sc-4');
|
|
1294
|
+
expect(row.entity_sha).toBeNull();
|
|
1295
|
+
});
|
|
1296
|
+
it('should match component scenarios by name+path, not just path (multi-entity files)', async () => {
|
|
1297
|
+
// A single file can export multiple components (e.g., FullPageLibrary.tsx
|
|
1298
|
+
// exports FullPageLibrary, FullPageEmptyState, FullPageArticleCard).
|
|
1299
|
+
// Each component scenario should get the SHA for its specific entity,
|
|
1300
|
+
// not whichever entity happens to be last in the map.
|
|
1301
|
+
rawDb
|
|
1302
|
+
.prepare(`INSERT INTO editor_scenarios (id, project_id, name, component_name, component_path, url, type, viewport_width, viewport_height, entity_sha)
|
|
1303
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1304
|
+
.run('sc-fplib', projectId, 'FullPageLibrary - Default', 'FullPageLibrary', 'src/library/FullPageLibrary.tsx', '/isolated-components/FullPageLibrary', 'component', 1280, 720, 'old-fplib-sha');
|
|
1305
|
+
rawDb
|
|
1306
|
+
.prepare(`INSERT INTO editor_scenarios (id, project_id, name, component_name, component_path, url, type, viewport_width, viewport_height, entity_sha)
|
|
1307
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1308
|
+
.run('sc-fpempty', projectId, 'FullPageEmptyState - Default', 'FullPageEmptyState', 'src/library/FullPageLibrary.tsx', '/isolated-components/FullPageEmptyState', 'component', 1280, 720, 'old-fpempty-sha');
|
|
1309
|
+
const result = await backfillEntityShaOnScenarios(db, [
|
|
1310
|
+
{
|
|
1311
|
+
sha: 'new-fplib-sha',
|
|
1312
|
+
name: 'FullPageLibrary',
|
|
1313
|
+
filePath: 'src/library/FullPageLibrary.tsx',
|
|
1314
|
+
},
|
|
1315
|
+
{
|
|
1316
|
+
sha: 'new-fpempty-sha',
|
|
1317
|
+
name: 'FullPageEmptyState',
|
|
1318
|
+
filePath: 'src/library/FullPageLibrary.tsx',
|
|
1319
|
+
},
|
|
1320
|
+
]);
|
|
1321
|
+
expect(result.updated).toBe(2);
|
|
1322
|
+
const fplib = rawDb
|
|
1323
|
+
.prepare('SELECT entity_sha FROM editor_scenarios WHERE id = ?')
|
|
1324
|
+
.get('sc-fplib');
|
|
1325
|
+
expect(fplib.entity_sha).toBe('new-fplib-sha');
|
|
1326
|
+
const fpempty = rawDb
|
|
1327
|
+
.prepare('SELECT entity_sha FROM editor_scenarios WHERE id = ?')
|
|
1328
|
+
.get('sc-fpempty');
|
|
1329
|
+
expect(fpempty.entity_sha).toBe('new-fpempty-sha');
|
|
1330
|
+
});
|
|
1331
|
+
it('should prefer default export entity for page scenarios when file has multiple entities', async () => {
|
|
1332
|
+
// Bug: src/popup/App.tsx has multiple entities (App visual, getInitialView library,
|
|
1333
|
+
// View type). Page scenarios should link to the default export (App), not whichever
|
|
1334
|
+
// entity happens to come first in the array.
|
|
1335
|
+
rawDb
|
|
1336
|
+
.prepare(`INSERT INTO editor_scenarios (id, project_id, name, page_file_path, url, type, viewport_width, viewport_height)
|
|
1337
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1338
|
+
.run('sc-page-1', projectId, 'Save View - First Time User', 'src/popup/App.tsx', '/', 'application', 400, 600);
|
|
1339
|
+
// Pass multiple entities for the same file — default export should win
|
|
1340
|
+
// regardless of order. Here the default export (App) appears first but
|
|
1341
|
+
// gets overwritten by later non-default entities in the naive set() impl.
|
|
1342
|
+
const result = await backfillEntityShaOnScenarios(db, [
|
|
1343
|
+
{
|
|
1344
|
+
sha: 'app-sha',
|
|
1345
|
+
name: 'App',
|
|
1346
|
+
filePath: 'src/popup/App.tsx',
|
|
1347
|
+
isDefaultExport: true,
|
|
1348
|
+
},
|
|
1349
|
+
{
|
|
1350
|
+
sha: 'get-initial-view-sha',
|
|
1351
|
+
name: 'getInitialView',
|
|
1352
|
+
filePath: 'src/popup/App.tsx',
|
|
1353
|
+
isDefaultExport: false,
|
|
1354
|
+
},
|
|
1355
|
+
{
|
|
1356
|
+
sha: 'view-type-sha',
|
|
1357
|
+
name: 'View',
|
|
1358
|
+
filePath: 'src/popup/App.tsx',
|
|
1359
|
+
isDefaultExport: false,
|
|
1360
|
+
},
|
|
1361
|
+
]);
|
|
1362
|
+
expect(result.updated).toBe(1);
|
|
1363
|
+
const row = rawDb
|
|
1364
|
+
.prepare('SELECT entity_sha FROM editor_scenarios WHERE id = ?')
|
|
1365
|
+
.get('sc-page-1');
|
|
1366
|
+
expect(row.entity_sha).toBe('app-sha');
|
|
1367
|
+
});
|
|
1368
|
+
});
|
|
1369
|
+
describe('countScenariosNeedingEntityBackfill', () => {
|
|
1370
|
+
let db;
|
|
1371
|
+
let rawDb;
|
|
1372
|
+
const projectId = 'test-project-id';
|
|
1373
|
+
beforeEach(async () => {
|
|
1374
|
+
rawDb = new Database(':memory:');
|
|
1375
|
+
db = new Kysely({ dialect: new SqliteDialect({ database: rawDb }) });
|
|
1376
|
+
await db.schema
|
|
1377
|
+
.createTable('editor_scenarios')
|
|
1378
|
+
.addColumn('id', 'varchar', (col) => col.primaryKey())
|
|
1379
|
+
.addColumn('project_id', 'varchar', (col) => col.notNull())
|
|
1380
|
+
.addColumn('name', 'varchar', (col) => col.notNull())
|
|
1381
|
+
.addColumn('description', 'text')
|
|
1382
|
+
.addColumn('component_name', 'varchar')
|
|
1383
|
+
.addColumn('component_path', 'varchar')
|
|
1384
|
+
.addColumn('url', 'varchar')
|
|
1385
|
+
.addColumn('type', 'varchar')
|
|
1386
|
+
.addColumn('screenshot_path', 'varchar')
|
|
1387
|
+
.addColumn('viewport_width', 'integer')
|
|
1388
|
+
.addColumn('viewport_height', 'integer')
|
|
1389
|
+
.addColumn('dimension', 'varchar')
|
|
1390
|
+
.addColumn('dimensions', 'text')
|
|
1391
|
+
.addColumn('screenshot_paths', 'text')
|
|
1392
|
+
.addColumn('page_file_path', 'varchar')
|
|
1393
|
+
.addColumn('entity_sha', 'varchar')
|
|
1394
|
+
.addColumn('display_name', 'varchar')
|
|
1395
|
+
.addColumn('created_at', 'datetime', (col) => col.defaultTo(new Date().toISOString()))
|
|
1396
|
+
.addColumn('updated_at', 'datetime', (col) => col.defaultTo(new Date().toISOString()))
|
|
1397
|
+
.execute();
|
|
1398
|
+
});
|
|
1399
|
+
afterEach(async () => {
|
|
1400
|
+
await db.destroy();
|
|
1401
|
+
});
|
|
1402
|
+
it('should NOT count scenarios that already have entity_sha', async () => {
|
|
1403
|
+
rawDb
|
|
1404
|
+
.prepare(`INSERT INTO editor_scenarios (id, project_id, name, component_name, component_path, url, type, viewport_width, viewport_height, entity_sha)
|
|
1405
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1406
|
+
.run('sc-1', projectId, 'Header - Default', 'Header', 'src/components/Header.tsx', '/isolated-components/Header', 'component', 1280, 720, 'existing-sha');
|
|
1407
|
+
const count = await countScenariosNeedingEntityBackfill(db);
|
|
1408
|
+
expect(count).toBe(0);
|
|
1409
|
+
});
|
|
1410
|
+
it('should count scenarios with null entity_sha and page_file_path', async () => {
|
|
1411
|
+
rawDb
|
|
1412
|
+
.prepare(`INSERT INTO editor_scenarios (id, project_id, name, page_file_path, url, type, viewport_width, viewport_height)
|
|
1413
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1414
|
+
.run('sc-2', projectId, 'Home - Default', 'app/page.tsx', '/', 'application', 1280, 720);
|
|
1415
|
+
const count = await countScenariosNeedingEntityBackfill(db);
|
|
1416
|
+
expect(count).toBe(1);
|
|
1417
|
+
});
|
|
1418
|
+
it('should count scenarios with null entity_sha and component_path', async () => {
|
|
1419
|
+
rawDb
|
|
1420
|
+
.prepare(`INSERT INTO editor_scenarios (id, project_id, name, component_name, component_path, url, type, viewport_width, viewport_height)
|
|
1421
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1422
|
+
.run('sc-3', projectId, 'Footer - Default', 'Footer', 'src/components/Footer.tsx', '/isolated-components/Footer', 'component', 1280, 720);
|
|
1423
|
+
const count = await countScenariosNeedingEntityBackfill(db);
|
|
1424
|
+
expect(count).toBe(1);
|
|
1425
|
+
});
|
|
1426
|
+
it('should NOT count scenarios with null entity_sha and no file paths', async () => {
|
|
1427
|
+
rawDb
|
|
1428
|
+
.prepare(`INSERT INTO editor_scenarios (id, project_id, name, url, type, viewport_width, viewport_height)
|
|
1429
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`)
|
|
1430
|
+
.run('sc-4', projectId, 'No File Path', '/', 'application', 1280, 720);
|
|
1431
|
+
const count = await countScenariosNeedingEntityBackfill(db);
|
|
1432
|
+
expect(count).toBe(0);
|
|
1433
|
+
});
|
|
1434
|
+
});
|
|
1435
|
+
describe('validateEntityLinkageForAppScenario', () => {
|
|
1436
|
+
let tmpDir;
|
|
1437
|
+
let glossaryPath;
|
|
1438
|
+
beforeEach(() => {
|
|
1439
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'entity-linkage-'));
|
|
1440
|
+
const codeyamDir = path.join(tmpDir, '.codeyam');
|
|
1441
|
+
fs.mkdirSync(codeyamDir, { recursive: true });
|
|
1442
|
+
glossaryPath = path.join(codeyamDir, 'glossary.json');
|
|
1443
|
+
});
|
|
1444
|
+
afterEach(() => {
|
|
1445
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
1446
|
+
});
|
|
1447
|
+
it('should return error when file is not in glossary', () => {
|
|
1448
|
+
// Write a glossary that does NOT include the target file
|
|
1449
|
+
fs.writeFileSync(glossaryPath, JSON.stringify([
|
|
1450
|
+
{ name: 'Header', filePath: 'src/components/Header.tsx' },
|
|
1451
|
+
]));
|
|
1452
|
+
const result = validateEntityLinkageForAppScenario({
|
|
1453
|
+
lookupFilePath: 'src/App.tsx',
|
|
1454
|
+
scenarioType: 'application',
|
|
1455
|
+
projectRoot: tmpDir,
|
|
1456
|
+
});
|
|
1457
|
+
expect(result.valid).toBe(false);
|
|
1458
|
+
expect(result.error).toContain('No glossary entry found');
|
|
1459
|
+
expect(result.error).toContain('src/App.tsx');
|
|
1460
|
+
});
|
|
1461
|
+
it('should return needsAnalysis when file IS in glossary but no entity exists', () => {
|
|
1462
|
+
// Write a glossary that includes the target file
|
|
1463
|
+
fs.writeFileSync(glossaryPath, JSON.stringify([
|
|
1464
|
+
{ name: 'App', filePath: 'src/App.tsx', description: 'Main app' },
|
|
1465
|
+
]));
|
|
1466
|
+
const result = validateEntityLinkageForAppScenario({
|
|
1467
|
+
lookupFilePath: 'src/App.tsx',
|
|
1468
|
+
scenarioType: 'application',
|
|
1469
|
+
projectRoot: tmpDir,
|
|
1470
|
+
});
|
|
1471
|
+
expect(result.valid).toBe(true);
|
|
1472
|
+
expect(result.needsAnalysis).toBe(true);
|
|
1473
|
+
});
|
|
1474
|
+
it('should skip validation for component-type scenarios', () => {
|
|
1475
|
+
// No glossary file exists at all
|
|
1476
|
+
const result = validateEntityLinkageForAppScenario({
|
|
1477
|
+
lookupFilePath: 'src/components/Header.tsx',
|
|
1478
|
+
scenarioType: 'component',
|
|
1479
|
+
projectRoot: tmpDir,
|
|
1480
|
+
});
|
|
1481
|
+
expect(result.valid).toBe(true);
|
|
1482
|
+
expect(result.needsAnalysis).toBeUndefined();
|
|
1483
|
+
});
|
|
1484
|
+
it('should skip validation when lookupFilePath is null', () => {
|
|
1485
|
+
const result = validateEntityLinkageForAppScenario({
|
|
1486
|
+
lookupFilePath: null,
|
|
1487
|
+
scenarioType: 'application',
|
|
1488
|
+
projectRoot: tmpDir,
|
|
1489
|
+
});
|
|
1490
|
+
expect(result.valid).toBe(true);
|
|
1491
|
+
expect(result.needsAnalysis).toBeUndefined();
|
|
1492
|
+
});
|
|
1493
|
+
it('should handle glossary wrapped in object format', () => {
|
|
1494
|
+
// LLMs sometimes write glossary as {"components": [...]}
|
|
1495
|
+
fs.writeFileSync(glossaryPath, JSON.stringify({
|
|
1496
|
+
components: [
|
|
1497
|
+
{ name: 'App', filePath: 'src/App.tsx', description: 'Main app' },
|
|
1498
|
+
],
|
|
1499
|
+
}));
|
|
1500
|
+
const result = validateEntityLinkageForAppScenario({
|
|
1501
|
+
lookupFilePath: 'src/App.tsx',
|
|
1502
|
+
scenarioType: 'user',
|
|
1503
|
+
projectRoot: tmpDir,
|
|
1504
|
+
});
|
|
1505
|
+
expect(result.valid).toBe(true);
|
|
1506
|
+
expect(result.needsAnalysis).toBe(true);
|
|
1507
|
+
});
|
|
1508
|
+
it('should return error when glossary file does not exist', () => {
|
|
1509
|
+
// Don't create a glossary file
|
|
1510
|
+
const result = validateEntityLinkageForAppScenario({
|
|
1511
|
+
lookupFilePath: 'src/App.tsx',
|
|
1512
|
+
scenarioType: 'application',
|
|
1513
|
+
projectRoot: tmpDir,
|
|
1514
|
+
});
|
|
1515
|
+
expect(result.valid).toBe(false);
|
|
1516
|
+
expect(result.error).toContain('No glossary entry found');
|
|
1517
|
+
});
|
|
1518
|
+
});
|
|
1187
1519
|
});
|
|
1188
1520
|
//# sourceMappingURL=editorScenarios.test.js.map
|