@codeyam/codeyam-cli 0.1.24 → 0.1.26
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 +6 -6
- package/analyzer-template/log.txt +3 -3
- package/codeyam-cli/src/commands/editor.js +306 -89
- package/codeyam-cli/src/commands/editor.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +10 -0
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/testRunner.test.js +0 -1
- package/codeyam-cli/src/utils/__tests__/testRunner.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/webappDetection.test.js +6 -0
- package/codeyam-cli/src/utils/__tests__/webappDetection.test.js.map +1 -1
- package/codeyam-cli/src/utils/editorAudit.js +22 -5
- package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
- package/codeyam-cli/src/utils/editorScenarios.js +1 -0
- package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
- package/codeyam-cli/src/utils/entityChangeStatus.server.js +15 -0
- package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -1
- package/codeyam-cli/src/utils/scenariosManifest.js +8 -2
- package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -1
- package/codeyam-cli/src/utils/testResultCache.js +53 -0
- package/codeyam-cli/src/utils/testResultCache.js.map +1 -0
- package/codeyam-cli/src/utils/testResultCache.server.js +81 -0
- package/codeyam-cli/src/utils/testResultCache.server.js.map +1 -0
- package/codeyam-cli/src/utils/testResultCache.server.test.js +187 -0
- package/codeyam-cli/src/utils/testResultCache.server.test.js.map +1 -0
- package/codeyam-cli/src/utils/testResultCache.test.js +230 -0
- package/codeyam-cli/src/utils/testResultCache.test.js.map +1 -0
- package/codeyam-cli/src/utils/testRunner.js +1 -7
- package/codeyam-cli/src/utils/testRunner.js.map +1 -1
- package/codeyam-cli/src/utils/webappDetection.js +4 -2
- package/codeyam-cli/src/utils/webappDetection.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +32 -0
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-CRxPi2BB.js +96 -0
- package/codeyam-cli/src/webserver/build/client/assets/globals-BsGHu8WX.css +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{manifest-694b698a.js → manifest-9032538f.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{root-DXjFYOxD.js → root-dKFRTYcy.js} +5 -5
- package/codeyam-cli/src/webserver/build/server/assets/{analysisRunner-zEYtiv0T.js → analysisRunner-B6HnVI5u.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/{index-CcHPEbhi.js → index-rV_xLS1u.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/{init-D68IyWbU.js → init-BdWDvetv.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/server-build-B_jdq5dT.js +689 -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/idleDetector.js +12 -3
- package/codeyam-cli/src/webserver/idleDetector.js.map +1 -1
- package/codeyam-cli/src/webserver/terminalServer.js +3 -1
- package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
- package/codeyam-cli/templates/expo-react-native/MOBILE_SETUP.md +119 -5
- package/codeyam-cli/templates/expo-react-native/__tests__/.gitkeep +0 -0
- package/codeyam-cli/templates/expo-react-native/app/_layout.tsx +3 -2
- package/codeyam-cli/templates/expo-react-native/app/index.tsx +36 -0
- package/codeyam-cli/templates/expo-react-native/global.css +7 -0
- package/codeyam-cli/templates/expo-react-native/lib/theme.ts +73 -0
- package/codeyam-cli/templates/expo-react-native/package.json +16 -6
- package/codeyam-cli/templates/isolation-route/expo-router.tsx.template +54 -0
- package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +15 -0
- package/package.json +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-oBrbke_R.js +0 -96
- package/codeyam-cli/src/webserver/build/client/assets/globals-oyPmV37k.css +0 -1
- package/codeyam-cli/src/webserver/build/server/assets/server-build-Cxzo0Zp2.js +0 -688
- package/codeyam-cli/templates/expo-react-native/app/(tabs)/_layout.tsx +0 -33
- package/codeyam-cli/templates/expo-react-native/app/(tabs)/index.tsx +0 -12
- package/codeyam-cli/templates/expo-react-native/app/(tabs)/settings.tsx +0 -12
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
-
"buildTimestamp": "2026-03-
|
|
3
|
-
"buildTime":
|
|
4
|
-
"gitCommit": "
|
|
2
|
+
"buildTimestamp": "2026-03-27T20:00:52.922Z",
|
|
3
|
+
"buildTime": 1774641652922,
|
|
4
|
+
"gitCommit": "2a3a412834b1cc8d46883263d3695bd29b8157dc",
|
|
5
5
|
"nodeVersion": "v20.20.1",
|
|
6
6
|
"contentHash": "c92230c027acb71cab56d2a696876a6a52206bfadd59fbc31a512b00a7ee8826",
|
|
7
|
-
"buildNumber":
|
|
8
|
-
"semanticVersion": "0.1.
|
|
9
|
-
"version": "0.1.
|
|
7
|
+
"buildNumber": 1262,
|
|
8
|
+
"semanticVersion": "0.1.1262",
|
|
9
|
+
"version": "0.1.1262 (2026-03-27T20:00+c92230c)"
|
|
10
10
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
|
-
[3/
|
|
3
|
-
[3/
|
|
2
|
+
[3/27/2026, 8:00:52 PM] > codeyam-combo@1.0.0 mergeDependencies
|
|
3
|
+
[3/27/2026, 8:00:52 PM] > node ./scripts/mergePackageJsonFiles.cjs
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
[3/
|
|
6
|
+
[3/27/2026, 8:00:52 PM] Merged dependencies into root package.json
|
|
7
7
|
|
|
@@ -189,6 +189,44 @@ function getProjectDimensions(root) {
|
|
|
189
189
|
return { defaultName: 'Desktop', names: [] };
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
|
+
function getTechStackContext(root) {
|
|
193
|
+
const state = readState(root);
|
|
194
|
+
const techStackId = state?.techStackId || '';
|
|
195
|
+
const stack = TECH_STACKS.find((s) => s.id === techStackId);
|
|
196
|
+
const isExpo = techStackId === 'expo-react-native';
|
|
197
|
+
const isChromeExt = techStackId === 'chrome-extension-react';
|
|
198
|
+
const isNextjs = techStackId.startsWith('nextjs-') || (!isExpo && !isChromeExt);
|
|
199
|
+
return {
|
|
200
|
+
id: techStackId || 'nextjs-prisma-sqlite',
|
|
201
|
+
isExpo,
|
|
202
|
+
isNextjs,
|
|
203
|
+
isChromeExt,
|
|
204
|
+
hasDatabase: isNextjs,
|
|
205
|
+
testRunner: isNextjs ? 'vitest' : 'jest',
|
|
206
|
+
testRunCommand: isNextjs ? 'npx vitest run' : 'npx jest',
|
|
207
|
+
storageType: isExpo
|
|
208
|
+
? 'asyncStorage'
|
|
209
|
+
: isChromeExt
|
|
210
|
+
? 'chromeStorage'
|
|
211
|
+
: 'prisma',
|
|
212
|
+
routerImport: isExpo
|
|
213
|
+
? 'expo-router'
|
|
214
|
+
: isChromeExt
|
|
215
|
+
? 'react-router-dom'
|
|
216
|
+
: 'next/navigation',
|
|
217
|
+
componentPrimitives: isExpo
|
|
218
|
+
? '<View>, <Text>, <ScrollView>'
|
|
219
|
+
: '<div>, <span>, <h1>',
|
|
220
|
+
rawPrimitivesList: isExpo
|
|
221
|
+
? '<View>, <Text>, <Image>, <ScrollView>, <FlatList>'
|
|
222
|
+
: '<div>, <span>, <h1>, <p>, <img>, <ul>',
|
|
223
|
+
patternsFile: isExpo
|
|
224
|
+
? 'MOBILE_SETUP.md'
|
|
225
|
+
: isChromeExt
|
|
226
|
+
? 'EXTENSION_SETUP.md'
|
|
227
|
+
: 'FEATURE_PATTERNS.md',
|
|
228
|
+
};
|
|
229
|
+
}
|
|
192
230
|
/**
|
|
193
231
|
* Print dimension guidance when the project has multiple screen sizes.
|
|
194
232
|
* Tells Claude to pick the right dimension for the content being previewed
|
|
@@ -267,6 +305,10 @@ function printAppScenarioInstructions(pageName, route) {
|
|
|
267
305
|
console.log(chalk.dim(' Example: "Page - Full Data", "Page - Empty State", "Page - Error State"'));
|
|
268
306
|
}
|
|
269
307
|
console.log();
|
|
308
|
+
checkbox('Ensure every page file used in app scenarios has a glossary entry with its filePath:');
|
|
309
|
+
console.log(chalk.dim(' The register command validates pageFilePath against the glossary — add missing pages now.'));
|
|
310
|
+
console.log(chalk.dim(' Example: {"name":"CounterScreen","filePath":"app/(tabs)/index.tsx","type":"page","description":"Main counter page"}'));
|
|
311
|
+
console.log();
|
|
270
312
|
checkbox('Register each scenario with type "application", the real page URL, and pageFilePath:');
|
|
271
313
|
console.log(chalk.dim(` codeyam editor register '{"name":"${pageName || 'Page'} - Full Data",`));
|
|
272
314
|
console.log(chalk.dim(` "type":"application","url":"${route || '/'}","pageFilePath":"src/path/to/Page.tsx",...}'`));
|
|
@@ -281,7 +323,12 @@ function printAppScenarioInstructions(pageName, route) {
|
|
|
281
323
|
console.log(chalk.dim(' For external APIs: also add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
|
|
282
324
|
}
|
|
283
325
|
else {
|
|
326
|
+
const appCtx = getTechStackContext(root);
|
|
284
327
|
checkbox('Include data in every app scenario — without it the page will be empty:');
|
|
328
|
+
if (appCtx.isExpo) {
|
|
329
|
+
console.log(chalk.dim(' Use "localStorage":{"items":"[...]"} to pre-populate AsyncStorage (values are JSON strings)'));
|
|
330
|
+
console.log(chalk.dim(" AsyncStorage uses localStorage on web — CodeYam's injection works automatically."));
|
|
331
|
+
}
|
|
285
332
|
console.log(chalk.dim(' Use "mockData":{"routes":{"/api/...":{"body":[...]}}} to mock API responses'));
|
|
286
333
|
console.log(chalk.dim(' For external APIs: add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
|
|
287
334
|
}
|
|
@@ -356,10 +403,18 @@ function printExtractionPlanInstructions() {
|
|
|
356
403
|
* Shared component capture instructions used by editor Step 7 and migration Steps 3/8.
|
|
357
404
|
* Prints the full isolation route setup, codeyam-capture wrapper, register command, and error checking.
|
|
358
405
|
*/
|
|
359
|
-
function printComponentCaptureInstructions() {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
406
|
+
function printComponentCaptureInstructions(root) {
|
|
407
|
+
const ctx = root ? getTechStackContext(root) : undefined;
|
|
408
|
+
const isExpo = ctx?.isExpo ?? false;
|
|
409
|
+
checkbox('Set up isolation routes: `codeyam editor isolate ComponentA ComponentB ...`');
|
|
410
|
+
if (isExpo) {
|
|
411
|
+
console.log(chalk.dim(' This creates app/isolated-components/_layout.tsx (with __DEV__ guard).'));
|
|
412
|
+
console.log(chalk.dim(' You then create a flat .tsx file per component (NOT subdirectories — Expo Router uses flat files).'));
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
console.log(chalk.dim(` This creates app/isolated-components/layout.tsx (with notFound() guard) and`));
|
|
416
|
+
console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
|
|
417
|
+
}
|
|
363
418
|
checkbox('For each visual component:');
|
|
364
419
|
console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
|
|
365
420
|
console.log(chalk.dim(' — Props/interface'));
|
|
@@ -370,14 +425,28 @@ function printComponentCaptureInstructions() {
|
|
|
370
425
|
console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
|
|
371
426
|
console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
|
|
372
427
|
console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
|
|
373
|
-
|
|
374
|
-
|
|
428
|
+
if (isExpo) {
|
|
429
|
+
console.log(chalk.dim(' Expo: app/isolated-components/ComponentName.tsx → /isolated-components/ComponentName'));
|
|
430
|
+
console.log(chalk.dim(' Use useLocalSearchParams() from expo-router to read ?s=ScenarioName.'));
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
console.log(chalk.dim(' Remix: app/routes/isolated-components.ComponentName.tsx → /isolated-components/ComponentName'));
|
|
434
|
+
console.log(chalk.dim(' Next.js: app/isolated-components/ComponentName/page.tsx → /isolated-components/ComponentName'));
|
|
435
|
+
}
|
|
375
436
|
console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
|
|
376
437
|
console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
438
|
+
if (isExpo) {
|
|
439
|
+
console.log(chalk.dim(' Wrap the component in a capture container with nativeID="codeyam-capture":'));
|
|
440
|
+
console.log(chalk.dim(' <View nativeID="codeyam-capture" style={{ display:"flex" }}>'));
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
|
|
444
|
+
console.log(chalk.dim(' <div id="codeyam-capture" style={{ display:"inline-block" }}>'));
|
|
445
|
+
}
|
|
446
|
+
console.log(chalk.dim(isExpo
|
|
447
|
+
? ' <View style={{ width:"100%", maxWidth:... }}> ← match the app\'s container width'
|
|
448
|
+
: ' <div style={{ width:"100%", maxWidth:"..." }}> ← match the app\'s container width'));
|
|
449
|
+
console.log(chalk.dim(' e.g. card in a 3-col grid → maxWidth: 384, full-width component → omit maxWidth'));
|
|
381
450
|
console.log(chalk.dim(' The screenshot captures just this wrapper, so the component fills the image.'));
|
|
382
451
|
console.log(chalk.dim(' Center the wrapper on the page (flexbox center both axes) and set a page background'));
|
|
383
452
|
console.log(chalk.dim(' color that matches where the component normally appears (e.g. white for light UIs).'));
|
|
@@ -746,8 +815,8 @@ function printSetup(root) {
|
|
|
746
815
|
console.log(chalk.bold('Tech Stack Selection:'));
|
|
747
816
|
console.log(chalk.dim(' Based on the selected formats, present ONLY the matching tech stacks.'));
|
|
748
817
|
console.log(chalk.dim(' A stack matches if ANY of the user\'s selected formats appears in its "Supports" list.'));
|
|
749
|
-
console.log(chalk.dim('
|
|
750
|
-
console.log(chalk.dim('
|
|
818
|
+
console.log(chalk.dim(' If only ONE stack matches, confirm it directly (e.g. "Expo + React Native is the only stack for mobile apps — using that.").'));
|
|
819
|
+
console.log(chalk.dim(' If MULTIPLE stacks match, use AskUserQuestion to let the user pick one.'));
|
|
751
820
|
console.log();
|
|
752
821
|
console.log(chalk.bold(' Available Tech Stacks:'));
|
|
753
822
|
for (const stack of TECH_STACKS) {
|
|
@@ -780,7 +849,7 @@ function printSetup(root) {
|
|
|
780
849
|
console.log(chalk.yellow(' ( ) Mobile') + chalk.dim(' — 375 × 667'));
|
|
781
850
|
console.log(chalk.yellow(' ( ) Custom') + chalk.dim(' — ask for width × height'));
|
|
782
851
|
console.log();
|
|
783
|
-
console.log(chalk.dim(' Pre-select based on app format: mobile-responsive-web-app/desktop-app → Desktop, mobile-app →
|
|
852
|
+
console.log(chalk.dim(' Pre-select based on app format: mobile-responsive-web-app/desktop-app → Desktop, mobile-app → iPhone 16 (393×852), chrome-extension → Custom (400×600).'));
|
|
784
853
|
console.log(chalk.dim(' If only one obvious choice, confirm it rather than asking.'));
|
|
785
854
|
console.log(chalk.dim(` Save the choice via: curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" -d '{"defaultScreenSize":{"name":"Desktop","width":1440,"height":900}}'`));
|
|
786
855
|
console.log();
|
|
@@ -989,33 +1058,64 @@ function printStep2(root, feature) {
|
|
|
989
1058
|
}
|
|
990
1059
|
console.log('Get the project ready to build.');
|
|
991
1060
|
console.log();
|
|
1061
|
+
const ctx = getTechStackContext(root);
|
|
992
1062
|
// If no project exists yet, include scaffolding instructions first
|
|
993
1063
|
if (!projectExists) {
|
|
994
1064
|
console.log(chalk.bold('Scaffold the project:'));
|
|
995
1065
|
checkbox('Run `codeyam editor template` to scaffold, install dependencies, init git, and configure CodeYam');
|
|
996
|
-
|
|
1066
|
+
if (ctx.isExpo) {
|
|
1067
|
+
console.log(chalk.dim(' This copies the Expo + React Native template, runs npm install,'));
|
|
1068
|
+
}
|
|
1069
|
+
else if (ctx.isChromeExt) {
|
|
1070
|
+
console.log(chalk.dim(' This copies the Chrome Extension + React template, runs npm install,'));
|
|
1071
|
+
}
|
|
1072
|
+
else {
|
|
1073
|
+
console.log(chalk.dim(' This copies the Next.js + Prisma 7 + SQLite template, runs npm install,'));
|
|
1074
|
+
}
|
|
997
1075
|
console.log(chalk.dim(' initializes git, runs codeyam init, and refreshes the editor — all in one command.'));
|
|
998
1076
|
console.log();
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1077
|
+
if (ctx.isExpo) {
|
|
1078
|
+
// Expo: no database, use AsyncStorage + theme
|
|
1079
|
+
checkbox('Define your data types in `lib/types.ts`');
|
|
1080
|
+
console.log(chalk.dim(" Create TypeScript interfaces for your app's data models"));
|
|
1081
|
+
console.log();
|
|
1082
|
+
checkbox('Set up initial data using the storage helper in `lib/storage.ts`');
|
|
1083
|
+
console.log(chalk.dim(' import { storage } from "@/lib/storage";'));
|
|
1084
|
+
console.log(chalk.dim(' await storage.set("items", [{ id: "1", title: "First item" }]);'));
|
|
1085
|
+
console.log();
|
|
1086
|
+
console.log(chalk.dim(' Read MOBILE_SETUP.md for data storage, navigation, and testing patterns.'));
|
|
1087
|
+
console.log();
|
|
1088
|
+
}
|
|
1089
|
+
else if (ctx.isChromeExt) {
|
|
1090
|
+
// Chrome Extension: use chrome.storage
|
|
1091
|
+
checkbox('Set up data storage using chrome.storage (with localStorage fallback for dev)');
|
|
1092
|
+
console.log();
|
|
1093
|
+
console.log(chalk.dim(' Read EXTENSION_SETUP.md for storage, messaging, and manifest patterns.'));
|
|
1094
|
+
console.log();
|
|
1095
|
+
}
|
|
1096
|
+
else {
|
|
1097
|
+
// Next.js: Prisma + database
|
|
1098
|
+
checkbox('Define your data models in `prisma/schema.prisma`');
|
|
1099
|
+
console.log(chalk.dim(" Replace the placeholder Todo model with your app's models"));
|
|
1100
|
+
console.log();
|
|
1101
|
+
checkbox('Push schema and seed the database');
|
|
1102
|
+
console.log(chalk.dim(' npm run db:push'));
|
|
1103
|
+
console.log(chalk.dim(' # Edit prisma/seed.ts with your seed data, then:'));
|
|
1104
|
+
console.log(chalk.dim(' npm run db:seed'));
|
|
1105
|
+
console.log(chalk.dim(` # After re-seeding, restart the dev server to pick up fresh data:`));
|
|
1106
|
+
console.log(chalk.dim(` # codeyam editor dev-server '{"action":"restart"}'`));
|
|
1107
|
+
console.log();
|
|
1108
|
+
console.log(chalk.yellow(' IMPORTANT: When adding new required columns to existing tables,'));
|
|
1109
|
+
console.log(chalk.yellow(' provide a @default(...) value so `db push` can fill existing rows.'));
|
|
1110
|
+
console.log(chalk.dim(' Example: userId String @default("anonymous") — existing rows get "anonymous"'));
|
|
1111
|
+
console.log(chalk.dim(' Without a default, Prisma requires --force-reset which drops ALL data.'));
|
|
1112
|
+
console.log(chalk.dim(' NEVER use --force-reset — it is blocked in this environment.'));
|
|
1113
|
+
console.log();
|
|
1114
|
+
console.log(chalk.dim(' See DATABASE.md for Prisma patterns and important warnings.'));
|
|
1115
|
+
console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
|
|
1116
|
+
console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
|
|
1117
|
+
console.log();
|
|
1118
|
+
}
|
|
1019
1119
|
printDataStructureInstructions();
|
|
1020
1120
|
}
|
|
1021
1121
|
else {
|
|
@@ -1057,26 +1157,42 @@ function printStep3(root, feature) {
|
|
|
1057
1157
|
if (isResuming) {
|
|
1058
1158
|
printResumptionHeader(3);
|
|
1059
1159
|
}
|
|
1160
|
+
const ctx = getTechStackContext(root);
|
|
1060
1161
|
console.log('Build fast with real data. Prioritize speed over quality.');
|
|
1061
1162
|
console.log();
|
|
1062
1163
|
console.log(chalk.bold('Checklist:'));
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1164
|
+
if (ctx.isExpo) {
|
|
1165
|
+
checkbox('Build screens that read from AsyncStorage via `lib/storage.ts` or fetch from APIs');
|
|
1166
|
+
if (!projectExists) {
|
|
1167
|
+
checkbox('Populate initial data in AsyncStorage for development');
|
|
1168
|
+
console.log(chalk.dim(' import { storage } from "@/lib/storage";'));
|
|
1169
|
+
console.log(chalk.dim(' await storage.set("items", [{ id: "1", title: "Buy groceries" }]);'));
|
|
1170
|
+
console.log();
|
|
1171
|
+
console.log(chalk.bold.cyan('Make data visually rich:'));
|
|
1172
|
+
console.log(chalk.cyan(' • Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
|
|
1173
|
+
console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
|
|
1174
|
+
console.log(chalk.cyan(' • Rich data makes the prototype look real and surfaces layout issues early'));
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
else {
|
|
1178
|
+
checkbox('Create API routes that read from the database via Prisma');
|
|
1179
|
+
if (!projectExists) {
|
|
1180
|
+
checkbox('Seed the database with demo data');
|
|
1181
|
+
checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
|
|
1182
|
+
console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
|
|
1183
|
+
console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
|
|
1184
|
+
console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
|
|
1185
|
+
console.log();
|
|
1186
|
+
console.log(chalk.bold.cyan('Make seed data visually rich:'));
|
|
1187
|
+
console.log(chalk.cyan(' • Use real placeholder images from Unsplash (https://images.unsplash.com/photo-<id>?w=400&h=300&fit=crop)'));
|
|
1188
|
+
console.log(chalk.cyan(' • Use avatar services like i.pravatar.cc for user profile photos'));
|
|
1189
|
+
console.log(chalk.cyan(' • Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
|
|
1190
|
+
console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
|
|
1191
|
+
console.log(chalk.cyan(' • Rich seed data makes the prototype look real and surfaces layout issues early'));
|
|
1192
|
+
}
|
|
1077
1193
|
}
|
|
1078
1194
|
checkbox('Verify the dev server shows the changes');
|
|
1079
|
-
checkbox(
|
|
1195
|
+
checkbox(`If the feature involves auth, email, payments, or other common patterns: read ${ctx.patternsFile}`);
|
|
1080
1196
|
// Responsive design guidance when building a mobile-responsive web app
|
|
1081
1197
|
if (prevState?.appFormats?.includes('mobile-responsive-web-app')) {
|
|
1082
1198
|
console.log();
|
|
@@ -1093,16 +1209,29 @@ function printStep3(root, feature) {
|
|
|
1093
1209
|
console.log();
|
|
1094
1210
|
console.log(designSystem);
|
|
1095
1211
|
console.log();
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1212
|
+
if (ctx.isExpo) {
|
|
1213
|
+
checkbox('Define ALL design tokens in `lib/theme.ts` — this is the single source of truth');
|
|
1214
|
+
console.log(chalk.dim(' Colors: theme.colors.bgSurface, theme.colors.textPrimary, etc.'));
|
|
1215
|
+
console.log(chalk.dim(' Typography: theme.fontSize.sm, theme.fontSize.lg, theme.fontFamily.mono'));
|
|
1216
|
+
console.log(chalk.dim(' Spacing: theme.spacing.sm, theme.spacing.md, theme.spacing.lg, etc.'));
|
|
1217
|
+
console.log(chalk.dim(' Border radius: theme.borderRadius.sm, theme.borderRadius.lg, etc.'));
|
|
1218
|
+
checkbox('Import theme in every component — ZERO hardcoded color strings or pixel values');
|
|
1219
|
+
console.log(chalk.dim(' Bad: color: "#333", fontSize: 14, padding: 12'));
|
|
1220
|
+
console.log(chalk.dim(' Good: color: theme.colors.textPrimary, fontSize: theme.fontSize.sm, padding: theme.spacing.md'));
|
|
1221
|
+
console.log(chalk.dim(' Do NOT use CSS custom properties (var(--token)) — they do not work in React Native.'));
|
|
1222
|
+
}
|
|
1223
|
+
else {
|
|
1224
|
+
checkbox('Define ALL design tokens as CSS custom properties in globals.css — not just colors');
|
|
1225
|
+
console.log(chalk.dim(' Colors: --bg-surface, --text-primary, --accent-green-a, etc.'));
|
|
1226
|
+
console.log(chalk.dim(' Typography: --text-xs, --text-sm, --text-lg, --text-2xl (font-size values)'));
|
|
1227
|
+
console.log(chalk.dim(' Font weights: --font-weight-normal, --font-weight-medium, --font-weight-semibold'));
|
|
1228
|
+
console.log(chalk.dim(' Spacing: --spacing-xs, --spacing-sm, --spacing-md, --spacing-lg, etc.'));
|
|
1229
|
+
console.log(chalk.dim(' Border radius, shadows, transitions — every value the design system defines.'));
|
|
1230
|
+
checkbox('Reference tokens from components — ZERO hardcoded px values for font-size, spacing, or colors');
|
|
1231
|
+
console.log(chalk.dim(' Bad: fontSize: 14, padding: "12px 16px", gap: 8'));
|
|
1232
|
+
console.log(chalk.dim(' Good: fontSize: "var(--text-sm)", padding: "var(--spacing-md) var(--spacing-lg)", gap: "var(--spacing-sm)"'));
|
|
1233
|
+
console.log(chalk.dim(' This ensures the entire app updates when the design system changes.'));
|
|
1234
|
+
}
|
|
1106
1235
|
}
|
|
1107
1236
|
console.log();
|
|
1108
1237
|
console.log(chalk.bold.cyan('Keep the preview moving:'));
|
|
@@ -1156,6 +1285,14 @@ function printStep4(root, feature) {
|
|
|
1156
1285
|
console.log(chalk.dim(' The user is looking at the preview — a blank page means something is broken.'));
|
|
1157
1286
|
console.log(chalk.dim(' If any check fails, fix the issue and re-verify before proceeding.'));
|
|
1158
1287
|
console.log();
|
|
1288
|
+
const ctx4 = getTechStackContext(root);
|
|
1289
|
+
if (ctx4.isExpo) {
|
|
1290
|
+
console.log(chalk.magenta.bold(' EXPO WEB PREVIEW NOTE:'));
|
|
1291
|
+
console.log(chalk.magenta(' The preview renders via react-native-web in a browser. Some differences'));
|
|
1292
|
+
console.log(chalk.magenta(' from native devices are expected (fonts, SafeAreaView, shadows, Platform.OS).'));
|
|
1293
|
+
console.log(chalk.magenta(' The preview is for layout and data verification. Test final polish on device.'));
|
|
1294
|
+
console.log();
|
|
1295
|
+
}
|
|
1159
1296
|
console.log(chalk.bold('Update README and setup script:'));
|
|
1160
1297
|
checkbox('Update `README.md`: set the project name, write a one-line description, and list any setup prerequisites');
|
|
1161
1298
|
checkbox('Update `npm run setup` in `package.json` if setup requires extra steps (e.g. env vars, external services)');
|
|
@@ -1268,6 +1405,7 @@ function printStep7(root, feature) {
|
|
|
1268
1405
|
if (isResuming) {
|
|
1269
1406
|
printResumptionHeader(7);
|
|
1270
1407
|
}
|
|
1408
|
+
const ctx = getTechStackContext(root);
|
|
1271
1409
|
console.log('Execute your extraction plan from step 6.');
|
|
1272
1410
|
console.log();
|
|
1273
1411
|
console.log(chalk.bold('Components:'));
|
|
@@ -1281,18 +1419,24 @@ function printStep7(root, feature) {
|
|
|
1281
1419
|
console.log(chalk.dim(' Cover: typical inputs, edge cases, empty/null inputs, error conditions'));
|
|
1282
1420
|
console.log(chalk.dim(' Aim for 3-8 test cases per function depending on complexity'));
|
|
1283
1421
|
console.log(chalk.dim(' Hooks count as functions — useDrinks, useAuth, etc. all need test files'));
|
|
1284
|
-
|
|
1422
|
+
console.log(chalk.dim(' Wrap all tests in a describe("FunctionName", ...) block — the audit matches on this name'));
|
|
1423
|
+
if (ctx.isExpo) {
|
|
1424
|
+
checkbox('Place test files next to source but OUTSIDE `app/` (Expo Router treats all files in app/ as routes): `lib/storage.ts` → `lib/storage.test.ts`, `app/hooks/useCounter.ts` → `__tests__/hooks/useCounter.test.ts`');
|
|
1425
|
+
}
|
|
1426
|
+
else {
|
|
1427
|
+
checkbox('Place test files next to source: `app/lib/drinks.ts` → `app/lib/drinks.test.ts`');
|
|
1428
|
+
}
|
|
1285
1429
|
console.log(chalk.yellow(' Tests ARE the only coverage for library functions/hooks — step 9 only captures component screenshots.'));
|
|
1286
1430
|
console.log();
|
|
1287
1431
|
console.log(chalk.bold('Recursive pass:'));
|
|
1288
1432
|
checkbox('Re-read EVERY new file you just created — extract components from components, functions from functions');
|
|
1289
1433
|
checkbox('Keep going until every file is a thin shell: just imports and composition');
|
|
1290
|
-
console.log(chalk.yellow(
|
|
1434
|
+
console.log(chalk.yellow(` Check: does any file contain raw ${ctx.rawPrimitivesList}?`));
|
|
1291
1435
|
console.log(chalk.yellow(' If yes → that JSX section is a component waiting to be extracted.'));
|
|
1292
1436
|
console.log();
|
|
1293
1437
|
console.log(chalk.bold('Verify before proceeding:'));
|
|
1294
1438
|
checkbox('Run all tests and verify they pass');
|
|
1295
|
-
checkbox(
|
|
1439
|
+
checkbox(`Page files contain ONLY imports + component composition — no raw ${ctx.isExpo ? 'React Native primitives' : 'HTML tags'}`);
|
|
1296
1440
|
checkbox('Every component renders ONE thing or composes sub-components — no multi-section JSX');
|
|
1297
1441
|
checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
|
|
1298
1442
|
printDimensionGuidance(dim, dimNames);
|
|
@@ -1353,11 +1497,12 @@ function printStep9(root, feature) {
|
|
|
1353
1497
|
console.log(chalk.dim(' Reuse and improve existing scenarios where possible — update mock data'));
|
|
1354
1498
|
console.log(chalk.dim(' to reflect current changes. Add new scenarios only for genuinely new states.'));
|
|
1355
1499
|
console.log(chalk.dim(' Ensure at least one scenario clearly demonstrates what changed in this session.'));
|
|
1356
|
-
|
|
1500
|
+
const ctx9 = getTechStackContext(root);
|
|
1501
|
+
printComponentCaptureInstructions(root);
|
|
1357
1502
|
console.log();
|
|
1358
1503
|
console.log(chalk.bold('Library Functions — run tests:'));
|
|
1359
1504
|
checkbox('Run ALL test files created in step 7');
|
|
1360
|
-
console.log(chalk.dim(
|
|
1505
|
+
console.log(chalk.dim(` Example: ${ctx9.testRunCommand} app/lib/drinks.test.ts`));
|
|
1361
1506
|
checkbox('Verify every test passes');
|
|
1362
1507
|
checkbox('If any test fails, fix the source code and re-run');
|
|
1363
1508
|
console.log();
|
|
@@ -1838,7 +1983,7 @@ function printMigrateStep3(root) {
|
|
|
1838
1983
|
console.log(chalk.dim(' Component scenarios use isolation routes with a codeyam-capture wrapper for tight screenshots.'));
|
|
1839
1984
|
console.log(chalk.dim(' These supplement the app scenarios from step 2 with focused component-level views.'));
|
|
1840
1985
|
console.log();
|
|
1841
|
-
printComponentCaptureInstructions();
|
|
1986
|
+
printComponentCaptureInstructions(root);
|
|
1842
1987
|
console.log();
|
|
1843
1988
|
migrationStopGate(3, pageName, pageIndex, totalPages);
|
|
1844
1989
|
}
|
|
@@ -1981,7 +2126,7 @@ function printMigrateStep8(root) {
|
|
|
1981
2126
|
console.log();
|
|
1982
2127
|
console.log(chalk.bold('Component Scenarios:'));
|
|
1983
2128
|
console.log(chalk.dim(' For each visual component extracted in step 7, create isolation routes and register scenarios.'));
|
|
1984
|
-
printComponentCaptureInstructions();
|
|
2129
|
+
printComponentCaptureInstructions(root);
|
|
1985
2130
|
console.log();
|
|
1986
2131
|
console.log(chalk.bold('Verify:'));
|
|
1987
2132
|
checkbox('Run `codeyam editor analyze-imports` to populate import graph (or `codeyam editor analyze-imports path/to/file.tsx ...` for specific files)');
|
|
@@ -2744,41 +2889,78 @@ function handleDesignSystem(designSystemId) {
|
|
|
2744
2889
|
}
|
|
2745
2890
|
function handleIsolate(componentNames) {
|
|
2746
2891
|
const root = process.cwd();
|
|
2892
|
+
const ctx = getTechStackContext(root);
|
|
2747
2893
|
if (componentNames.length === 0) {
|
|
2748
2894
|
console.error(chalk.red('Usage: codeyam editor isolate "ComponentA ComponentB ..."'));
|
|
2749
2895
|
process.exit(1);
|
|
2750
2896
|
}
|
|
2751
2897
|
const isolateDir = path.join(root, 'app', 'isolated-components');
|
|
2752
|
-
// Create
|
|
2753
|
-
|
|
2898
|
+
// Create the framework-appropriate layout guard if missing.
|
|
2899
|
+
// Clean up wrong-framework layout from a previous CLI version.
|
|
2900
|
+
const layoutPath = path.join(isolateDir, ctx.isExpo ? '_layout.tsx' : 'layout.tsx');
|
|
2901
|
+
const wrongLayoutPath = path.join(isolateDir, ctx.isExpo ? 'layout.tsx' : '_layout.tsx');
|
|
2902
|
+
if (fs.existsSync(wrongLayoutPath)) {
|
|
2903
|
+
fs.unlinkSync(wrongLayoutPath);
|
|
2904
|
+
}
|
|
2754
2905
|
if (!fs.existsSync(layoutPath)) {
|
|
2755
2906
|
fs.mkdirSync(isolateDir, { recursive: true });
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2907
|
+
if (ctx.isExpo) {
|
|
2908
|
+
fs.writeFileSync(layoutPath, [
|
|
2909
|
+
'import { Redirect, Slot } from "expo-router";',
|
|
2910
|
+
'',
|
|
2911
|
+
'export default function CaptureLayout() {',
|
|
2912
|
+
' if (!__DEV__) return <Redirect href="/" />;',
|
|
2913
|
+
' return <Slot />;',
|
|
2914
|
+
'}',
|
|
2915
|
+
'',
|
|
2916
|
+
].join('\n'), 'utf8');
|
|
2917
|
+
}
|
|
2918
|
+
else {
|
|
2919
|
+
fs.writeFileSync(layoutPath, [
|
|
2920
|
+
'import { notFound } from "next/navigation";',
|
|
2921
|
+
'',
|
|
2922
|
+
'export default function CaptureLayout({ children }: { children: React.ReactNode }) {',
|
|
2923
|
+
' if (process.env.NODE_ENV === "production") notFound();',
|
|
2924
|
+
' return <>{children}</>;',
|
|
2925
|
+
'}',
|
|
2926
|
+
'',
|
|
2927
|
+
].join('\n'), 'utf8');
|
|
2928
|
+
}
|
|
2929
|
+
console.log(chalk.green(`Created layout guard: app/isolated-components/${ctx.isExpo ? '_layout.tsx' : 'layout.tsx'}`));
|
|
2930
|
+
}
|
|
2931
|
+
// Create isolation route for each component.
|
|
2932
|
+
// Expo Router uses flat files (ComponentName.tsx), Next.js uses subdirectories (ComponentName/page.tsx).
|
|
2768
2933
|
const created = [];
|
|
2769
2934
|
const existed = [];
|
|
2770
2935
|
for (const name of componentNames) {
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2936
|
+
if (ctx.isExpo) {
|
|
2937
|
+
const filePath = path.join(isolateDir, `${name}.tsx`);
|
|
2938
|
+
if (fs.existsSync(filePath)) {
|
|
2939
|
+
existed.push(name);
|
|
2940
|
+
}
|
|
2941
|
+
else {
|
|
2942
|
+
created.push(name);
|
|
2943
|
+
}
|
|
2774
2944
|
}
|
|
2775
2945
|
else {
|
|
2776
|
-
|
|
2777
|
-
|
|
2946
|
+
const dir = path.join(isolateDir, name);
|
|
2947
|
+
if (fs.existsSync(dir)) {
|
|
2948
|
+
existed.push(name);
|
|
2949
|
+
}
|
|
2950
|
+
else {
|
|
2951
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
2952
|
+
created.push(name);
|
|
2953
|
+
}
|
|
2778
2954
|
}
|
|
2779
2955
|
}
|
|
2780
2956
|
if (created.length > 0) {
|
|
2781
|
-
|
|
2957
|
+
if (ctx.isExpo) {
|
|
2958
|
+
console.log(chalk.green(`Ready for ${created.length} isolation route(s): ${created.map((n) => `app/isolated-components/${n}.tsx`).join(', ')}`));
|
|
2959
|
+
console.log(chalk.dim(' Create each file with useLocalSearchParams, a scenarios map, and nativeID="codeyam-capture"'));
|
|
2960
|
+
}
|
|
2961
|
+
else {
|
|
2962
|
+
console.log(chalk.green(`Created ${created.length} isolation route dir(s): ${created.join(', ')}`));
|
|
2963
|
+
}
|
|
2782
2964
|
}
|
|
2783
2965
|
if (existed.length > 0) {
|
|
2784
2966
|
console.log(chalk.dim(`Already existed: ${existed.join(', ')}`));
|
|
@@ -2832,6 +3014,12 @@ function formatApiSubcommandResult(subcommand, data) {
|
|
|
2832
3014
|
parts.push(`scenarioId="${data.scenarioId}"`);
|
|
2833
3015
|
if (data.sessionsNotified != null)
|
|
2834
3016
|
parts.push(`notified=${data.sessionsNotified}`);
|
|
3017
|
+
// Surface the HTTP health check from the dev server
|
|
3018
|
+
if (data.preview) {
|
|
3019
|
+
parts.push(`healthy=${data.preview.healthy}`);
|
|
3020
|
+
if (data.preview.error)
|
|
3021
|
+
parts.push(`error="${data.preview.error}"`);
|
|
3022
|
+
}
|
|
2835
3023
|
return parts.join(' ');
|
|
2836
3024
|
}
|
|
2837
3025
|
case 'dev-server': {
|
|
@@ -3788,7 +3976,8 @@ async function handleAudit(options) {
|
|
|
3788
3976
|
if (f.errorMessage) {
|
|
3789
3977
|
detail += `\n ${chalk.red(f.errorMessage)}`;
|
|
3790
3978
|
detail += `\n ${chalk.yellow('Note: This is NOT a test failure — the test runner itself could not execute.')}`;
|
|
3791
|
-
|
|
3979
|
+
const ctx = getTechStackContext(process.cwd());
|
|
3980
|
+
detail += `\n ${chalk.yellow('Try running the test manually: ' + ctx.testRunCommand + ' ' + f.testFile)}`;
|
|
3792
3981
|
}
|
|
3793
3982
|
break;
|
|
3794
3983
|
case 'failing':
|
|
@@ -4371,7 +4560,7 @@ async function handleTemplate() {
|
|
|
4371
4560
|
_: [],
|
|
4372
4561
|
});
|
|
4373
4562
|
console.log(chalk.green(' CodeYam initialized.'));
|
|
4374
|
-
// 5. Verify config has startCommand
|
|
4563
|
+
// 5. Verify config has startCommand and set format-specific defaults
|
|
4375
4564
|
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
4376
4565
|
if (fs.existsSync(configPath)) {
|
|
4377
4566
|
try {
|
|
@@ -4382,6 +4571,26 @@ async function handleTemplate() {
|
|
|
4382
4571
|
console.log(chalk.yellow(' Warning: No startCommand found in .codeyam/config.json webapps.'));
|
|
4383
4572
|
console.log(chalk.dim(' You may need to add: "startCommand": { "command": "sh", "args": ["-c", "npm run dev -- --port $PORT"] }'));
|
|
4384
4573
|
}
|
|
4574
|
+
// Store appFormats from the tech stack so the editor UI can adapt
|
|
4575
|
+
if (stack?.supportedFormats) {
|
|
4576
|
+
config.appFormats = stack.supportedFormats;
|
|
4577
|
+
}
|
|
4578
|
+
// Set mobile-first defaults for mobile-app projects
|
|
4579
|
+
if (stack?.supportedFormats?.includes('mobile-app')) {
|
|
4580
|
+
config.defaultScreenSize = {
|
|
4581
|
+
name: 'iPhone 16',
|
|
4582
|
+
width: 393,
|
|
4583
|
+
height: 852,
|
|
4584
|
+
};
|
|
4585
|
+
config.screenSizes = {
|
|
4586
|
+
'iPhone 16': { width: 393, height: 852 },
|
|
4587
|
+
'iPhone 16 Pro Max': { width: 430, height: 932 },
|
|
4588
|
+
'iPhone SE': { width: 375, height: 667 },
|
|
4589
|
+
'Pixel 8': { width: 412, height: 915 },
|
|
4590
|
+
'iPad mini': { width: 744, height: 1133 },
|
|
4591
|
+
};
|
|
4592
|
+
}
|
|
4593
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
4385
4594
|
}
|
|
4386
4595
|
catch {
|
|
4387
4596
|
// Config parse error is non-fatal
|
|
@@ -4416,7 +4625,15 @@ async function handleTemplate() {
|
|
|
4416
4625
|
}
|
|
4417
4626
|
console.log();
|
|
4418
4627
|
console.log(chalk.green.bold('Project scaffolded and ready!'));
|
|
4419
|
-
|
|
4628
|
+
if (stack?.id === 'expo-react-native') {
|
|
4629
|
+
console.log(chalk.dim('Next: Set up your data types, configure the theme in lib/theme.ts, and build your feature.'));
|
|
4630
|
+
}
|
|
4631
|
+
else if (stack?.id === 'chrome-extension-react') {
|
|
4632
|
+
console.log(chalk.dim('Next: Configure your extension manifest and build your feature.'));
|
|
4633
|
+
}
|
|
4634
|
+
else {
|
|
4635
|
+
console.log(chalk.dim('Next: Define your Prisma models, push the schema, seed the database, and build your feature.'));
|
|
4636
|
+
}
|
|
4420
4637
|
}
|
|
4421
4638
|
// ─── Sync subcommand ─────────────────────────────────────────────────
|
|
4422
4639
|
/**
|