@codeyam/codeyam-cli 0.1.24 → 0.1.25
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 +277 -83
- package/codeyam-cli/src/commands/editor.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/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/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)-DLM1-ZMt.js +96 -0
- package/codeyam-cli/src/webserver/build/client/assets/globals-9EkC9j9I.css +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{manifest-694b698a.js → manifest-7e749098.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{root-DXjFYOxD.js → root-DGtly3mb.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/{analysisRunner-zEYtiv0T.js → analysisRunner-CO8xocj3.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/{index-CcHPEbhi.js → index-QKPqlUgg.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/{init-D68IyWbU.js → init-DlspChIk.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/server-build-ChzicV-B.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 +115 -0
- 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/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
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
-
"buildTimestamp": "2026-03-
|
|
3
|
-
"buildTime":
|
|
4
|
-
"gitCommit": "
|
|
2
|
+
"buildTimestamp": "2026-03-27T13:51:55.725Z",
|
|
3
|
+
"buildTime": 1774619515725,
|
|
4
|
+
"gitCommit": "cefcc7c0aa462f7267e8912e66de352da511a931",
|
|
5
5
|
"nodeVersion": "v20.20.1",
|
|
6
6
|
"contentHash": "c92230c027acb71cab56d2a696876a6a52206bfadd59fbc31a512b00a7ee8826",
|
|
7
|
-
"buildNumber":
|
|
8
|
-
"semanticVersion": "0.1.
|
|
9
|
-
"version": "0.1.
|
|
7
|
+
"buildNumber": 1256,
|
|
8
|
+
"semanticVersion": "0.1.1256",
|
|
9
|
+
"version": "0.1.1256 (2026-03-27T13:51+c92230c)"
|
|
10
10
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
|
-
[3/
|
|
3
|
-
[3/
|
|
2
|
+
[3/27/2026, 1:51:55 PM] > codeyam-combo@1.0.0 mergeDependencies
|
|
3
|
+
[3/27/2026, 1:51:55 PM] > node ./scripts/mergePackageJsonFiles.cjs
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
[3/
|
|
6
|
+
[3/27/2026, 1:51:55 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
|
|
@@ -281,7 +319,12 @@ function printAppScenarioInstructions(pageName, route) {
|
|
|
281
319
|
console.log(chalk.dim(' For external APIs: also add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
|
|
282
320
|
}
|
|
283
321
|
else {
|
|
322
|
+
const appCtx = getTechStackContext(root);
|
|
284
323
|
checkbox('Include data in every app scenario — without it the page will be empty:');
|
|
324
|
+
if (appCtx.isExpo) {
|
|
325
|
+
console.log(chalk.dim(' Use "localStorage":{"items":"[...]"} to pre-populate AsyncStorage (values are JSON strings)'));
|
|
326
|
+
console.log(chalk.dim(" AsyncStorage uses localStorage on web — CodeYam's injection works automatically."));
|
|
327
|
+
}
|
|
285
328
|
console.log(chalk.dim(' Use "mockData":{"routes":{"/api/...":{"body":[...]}}} to mock API responses'));
|
|
286
329
|
console.log(chalk.dim(' For external APIs: add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
|
|
287
330
|
}
|
|
@@ -356,9 +399,11 @@ function printExtractionPlanInstructions() {
|
|
|
356
399
|
* Shared component capture instructions used by editor Step 7 and migration Steps 3/8.
|
|
357
400
|
* Prints the full isolation route setup, codeyam-capture wrapper, register command, and error checking.
|
|
358
401
|
*/
|
|
359
|
-
function printComponentCaptureInstructions() {
|
|
402
|
+
function printComponentCaptureInstructions(root) {
|
|
403
|
+
const ctx = root ? getTechStackContext(root) : undefined;
|
|
404
|
+
const isExpo = ctx?.isExpo ?? false;
|
|
360
405
|
checkbox('Create isolation route dirs: `codeyam editor isolate ComponentA ComponentB ...`');
|
|
361
|
-
console.log(chalk.dim(
|
|
406
|
+
console.log(chalk.dim(` This creates app/isolated-components/layout.tsx (with production ${isExpo ? '__DEV__' : 'notFound()'} guard) and`));
|
|
362
407
|
console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
|
|
363
408
|
checkbox('For each visual component:');
|
|
364
409
|
console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
|
|
@@ -370,14 +415,28 @@ function printComponentCaptureInstructions() {
|
|
|
370
415
|
console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
|
|
371
416
|
console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
|
|
372
417
|
console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
|
|
373
|
-
|
|
374
|
-
|
|
418
|
+
if (isExpo) {
|
|
419
|
+
console.log(chalk.dim(' Expo: app/isolated-components/ComponentName.tsx → /isolated-components/ComponentName'));
|
|
420
|
+
console.log(chalk.dim(' Use useLocalSearchParams() from expo-router to read ?s=ScenarioName.'));
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
console.log(chalk.dim(' Remix: app/routes/isolated-components.ComponentName.tsx → /isolated-components/ComponentName'));
|
|
424
|
+
console.log(chalk.dim(' Next.js: app/isolated-components/ComponentName/page.tsx → /isolated-components/ComponentName'));
|
|
425
|
+
}
|
|
375
426
|
console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
|
|
376
427
|
console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
428
|
+
if (isExpo) {
|
|
429
|
+
console.log(chalk.dim(' Wrap the component in a capture container with nativeID="codeyam-capture":'));
|
|
430
|
+
console.log(chalk.dim(' <View nativeID="codeyam-capture" style={{ display:"flex" }}>'));
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
|
|
434
|
+
console.log(chalk.dim(' <div id="codeyam-capture" style={{ display:"inline-block" }}>'));
|
|
435
|
+
}
|
|
436
|
+
console.log(chalk.dim(isExpo
|
|
437
|
+
? ' <View style={{ width:"100%", maxWidth:... }}> ← match the app\'s container width'
|
|
438
|
+
: ' <div style={{ width:"100%", maxWidth:"..." }}> ← match the app\'s container width'));
|
|
439
|
+
console.log(chalk.dim(' e.g. card in a 3-col grid → maxWidth: 384, full-width component → omit maxWidth'));
|
|
381
440
|
console.log(chalk.dim(' The screenshot captures just this wrapper, so the component fills the image.'));
|
|
382
441
|
console.log(chalk.dim(' Center the wrapper on the page (flexbox center both axes) and set a page background'));
|
|
383
442
|
console.log(chalk.dim(' color that matches where the component normally appears (e.g. white for light UIs).'));
|
|
@@ -780,7 +839,7 @@ function printSetup(root) {
|
|
|
780
839
|
console.log(chalk.yellow(' ( ) Mobile') + chalk.dim(' — 375 × 667'));
|
|
781
840
|
console.log(chalk.yellow(' ( ) Custom') + chalk.dim(' — ask for width × height'));
|
|
782
841
|
console.log();
|
|
783
|
-
console.log(chalk.dim(' Pre-select based on app format: mobile-responsive-web-app/desktop-app → Desktop, mobile-app →
|
|
842
|
+
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
843
|
console.log(chalk.dim(' If only one obvious choice, confirm it rather than asking.'));
|
|
785
844
|
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
845
|
console.log();
|
|
@@ -989,33 +1048,64 @@ function printStep2(root, feature) {
|
|
|
989
1048
|
}
|
|
990
1049
|
console.log('Get the project ready to build.');
|
|
991
1050
|
console.log();
|
|
1051
|
+
const ctx = getTechStackContext(root);
|
|
992
1052
|
// If no project exists yet, include scaffolding instructions first
|
|
993
1053
|
if (!projectExists) {
|
|
994
1054
|
console.log(chalk.bold('Scaffold the project:'));
|
|
995
1055
|
checkbox('Run `codeyam editor template` to scaffold, install dependencies, init git, and configure CodeYam');
|
|
996
|
-
|
|
1056
|
+
if (ctx.isExpo) {
|
|
1057
|
+
console.log(chalk.dim(' This copies the Expo + React Native template, runs npm install,'));
|
|
1058
|
+
}
|
|
1059
|
+
else if (ctx.isChromeExt) {
|
|
1060
|
+
console.log(chalk.dim(' This copies the Chrome Extension + React template, runs npm install,'));
|
|
1061
|
+
}
|
|
1062
|
+
else {
|
|
1063
|
+
console.log(chalk.dim(' This copies the Next.js + Prisma 7 + SQLite template, runs npm install,'));
|
|
1064
|
+
}
|
|
997
1065
|
console.log(chalk.dim(' initializes git, runs codeyam init, and refreshes the editor — all in one command.'));
|
|
998
1066
|
console.log();
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1067
|
+
if (ctx.isExpo) {
|
|
1068
|
+
// Expo: no database, use AsyncStorage + theme
|
|
1069
|
+
checkbox('Define your data types in `lib/types.ts`');
|
|
1070
|
+
console.log(chalk.dim(" Create TypeScript interfaces for your app's data models"));
|
|
1071
|
+
console.log();
|
|
1072
|
+
checkbox('Set up initial data using the storage helper in `lib/storage.ts`');
|
|
1073
|
+
console.log(chalk.dim(' import { storage } from "@/lib/storage";'));
|
|
1074
|
+
console.log(chalk.dim(' await storage.set("items", [{ id: "1", title: "First item" }]);'));
|
|
1075
|
+
console.log();
|
|
1076
|
+
console.log(chalk.dim(' Read MOBILE_SETUP.md for data storage, navigation, and testing patterns.'));
|
|
1077
|
+
console.log();
|
|
1078
|
+
}
|
|
1079
|
+
else if (ctx.isChromeExt) {
|
|
1080
|
+
// Chrome Extension: use chrome.storage
|
|
1081
|
+
checkbox('Set up data storage using chrome.storage (with localStorage fallback for dev)');
|
|
1082
|
+
console.log();
|
|
1083
|
+
console.log(chalk.dim(' Read EXTENSION_SETUP.md for storage, messaging, and manifest patterns.'));
|
|
1084
|
+
console.log();
|
|
1085
|
+
}
|
|
1086
|
+
else {
|
|
1087
|
+
// Next.js: Prisma + database
|
|
1088
|
+
checkbox('Define your data models in `prisma/schema.prisma`');
|
|
1089
|
+
console.log(chalk.dim(" Replace the placeholder Todo model with your app's models"));
|
|
1090
|
+
console.log();
|
|
1091
|
+
checkbox('Push schema and seed the database');
|
|
1092
|
+
console.log(chalk.dim(' npm run db:push'));
|
|
1093
|
+
console.log(chalk.dim(' # Edit prisma/seed.ts with your seed data, then:'));
|
|
1094
|
+
console.log(chalk.dim(' npm run db:seed'));
|
|
1095
|
+
console.log(chalk.dim(` # After re-seeding, restart the dev server to pick up fresh data:`));
|
|
1096
|
+
console.log(chalk.dim(` # codeyam editor dev-server '{"action":"restart"}'`));
|
|
1097
|
+
console.log();
|
|
1098
|
+
console.log(chalk.yellow(' IMPORTANT: When adding new required columns to existing tables,'));
|
|
1099
|
+
console.log(chalk.yellow(' provide a @default(...) value so `db push` can fill existing rows.'));
|
|
1100
|
+
console.log(chalk.dim(' Example: userId String @default("anonymous") — existing rows get "anonymous"'));
|
|
1101
|
+
console.log(chalk.dim(' Without a default, Prisma requires --force-reset which drops ALL data.'));
|
|
1102
|
+
console.log(chalk.dim(' NEVER use --force-reset — it is blocked in this environment.'));
|
|
1103
|
+
console.log();
|
|
1104
|
+
console.log(chalk.dim(' See DATABASE.md for Prisma patterns and important warnings.'));
|
|
1105
|
+
console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
|
|
1106
|
+
console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
|
|
1107
|
+
console.log();
|
|
1108
|
+
}
|
|
1019
1109
|
printDataStructureInstructions();
|
|
1020
1110
|
}
|
|
1021
1111
|
else {
|
|
@@ -1057,26 +1147,42 @@ function printStep3(root, feature) {
|
|
|
1057
1147
|
if (isResuming) {
|
|
1058
1148
|
printResumptionHeader(3);
|
|
1059
1149
|
}
|
|
1150
|
+
const ctx = getTechStackContext(root);
|
|
1060
1151
|
console.log('Build fast with real data. Prioritize speed over quality.');
|
|
1061
1152
|
console.log();
|
|
1062
1153
|
console.log(chalk.bold('Checklist:'));
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1154
|
+
if (ctx.isExpo) {
|
|
1155
|
+
checkbox('Build screens that read from AsyncStorage via `lib/storage.ts` or fetch from APIs');
|
|
1156
|
+
if (!projectExists) {
|
|
1157
|
+
checkbox('Populate initial data in AsyncStorage for development');
|
|
1158
|
+
console.log(chalk.dim(' import { storage } from "@/lib/storage";'));
|
|
1159
|
+
console.log(chalk.dim(' await storage.set("items", [{ id: "1", title: "Buy groceries" }]);'));
|
|
1160
|
+
console.log();
|
|
1161
|
+
console.log(chalk.bold.cyan('Make data visually rich:'));
|
|
1162
|
+
console.log(chalk.cyan(' • Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
|
|
1163
|
+
console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
|
|
1164
|
+
console.log(chalk.cyan(' • Rich data makes the prototype look real and surfaces layout issues early'));
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
else {
|
|
1168
|
+
checkbox('Create API routes that read from the database via Prisma');
|
|
1169
|
+
if (!projectExists) {
|
|
1170
|
+
checkbox('Seed the database with demo data');
|
|
1171
|
+
checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
|
|
1172
|
+
console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
|
|
1173
|
+
console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
|
|
1174
|
+
console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
|
|
1175
|
+
console.log();
|
|
1176
|
+
console.log(chalk.bold.cyan('Make seed data visually rich:'));
|
|
1177
|
+
console.log(chalk.cyan(' • Use real placeholder images from Unsplash (https://images.unsplash.com/photo-<id>?w=400&h=300&fit=crop)'));
|
|
1178
|
+
console.log(chalk.cyan(' • Use avatar services like i.pravatar.cc for user profile photos'));
|
|
1179
|
+
console.log(chalk.cyan(' • Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
|
|
1180
|
+
console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
|
|
1181
|
+
console.log(chalk.cyan(' • Rich seed data makes the prototype look real and surfaces layout issues early'));
|
|
1182
|
+
}
|
|
1077
1183
|
}
|
|
1078
1184
|
checkbox('Verify the dev server shows the changes');
|
|
1079
|
-
checkbox(
|
|
1185
|
+
checkbox(`If the feature involves auth, email, payments, or other common patterns: read ${ctx.patternsFile}`);
|
|
1080
1186
|
// Responsive design guidance when building a mobile-responsive web app
|
|
1081
1187
|
if (prevState?.appFormats?.includes('mobile-responsive-web-app')) {
|
|
1082
1188
|
console.log();
|
|
@@ -1093,16 +1199,29 @@ function printStep3(root, feature) {
|
|
|
1093
1199
|
console.log();
|
|
1094
1200
|
console.log(designSystem);
|
|
1095
1201
|
console.log();
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1202
|
+
if (ctx.isExpo) {
|
|
1203
|
+
checkbox('Define ALL design tokens in `lib/theme.ts` — this is the single source of truth');
|
|
1204
|
+
console.log(chalk.dim(' Colors: theme.colors.bgSurface, theme.colors.textPrimary, etc.'));
|
|
1205
|
+
console.log(chalk.dim(' Typography: theme.fontSize.sm, theme.fontSize.lg, theme.fontFamily.mono'));
|
|
1206
|
+
console.log(chalk.dim(' Spacing: theme.spacing.sm, theme.spacing.md, theme.spacing.lg, etc.'));
|
|
1207
|
+
console.log(chalk.dim(' Border radius: theme.borderRadius.sm, theme.borderRadius.lg, etc.'));
|
|
1208
|
+
checkbox('Import theme in every component — ZERO hardcoded color strings or pixel values');
|
|
1209
|
+
console.log(chalk.dim(' Bad: color: "#333", fontSize: 14, padding: 12'));
|
|
1210
|
+
console.log(chalk.dim(' Good: color: theme.colors.textPrimary, fontSize: theme.fontSize.sm, padding: theme.spacing.md'));
|
|
1211
|
+
console.log(chalk.dim(' Do NOT use CSS custom properties (var(--token)) — they do not work in React Native.'));
|
|
1212
|
+
}
|
|
1213
|
+
else {
|
|
1214
|
+
checkbox('Define ALL design tokens as CSS custom properties in globals.css — not just colors');
|
|
1215
|
+
console.log(chalk.dim(' Colors: --bg-surface, --text-primary, --accent-green-a, etc.'));
|
|
1216
|
+
console.log(chalk.dim(' Typography: --text-xs, --text-sm, --text-lg, --text-2xl (font-size values)'));
|
|
1217
|
+
console.log(chalk.dim(' Font weights: --font-weight-normal, --font-weight-medium, --font-weight-semibold'));
|
|
1218
|
+
console.log(chalk.dim(' Spacing: --spacing-xs, --spacing-sm, --spacing-md, --spacing-lg, etc.'));
|
|
1219
|
+
console.log(chalk.dim(' Border radius, shadows, transitions — every value the design system defines.'));
|
|
1220
|
+
checkbox('Reference tokens from components — ZERO hardcoded px values for font-size, spacing, or colors');
|
|
1221
|
+
console.log(chalk.dim(' Bad: fontSize: 14, padding: "12px 16px", gap: 8'));
|
|
1222
|
+
console.log(chalk.dim(' Good: fontSize: "var(--text-sm)", padding: "var(--spacing-md) var(--spacing-lg)", gap: "var(--spacing-sm)"'));
|
|
1223
|
+
console.log(chalk.dim(' This ensures the entire app updates when the design system changes.'));
|
|
1224
|
+
}
|
|
1106
1225
|
}
|
|
1107
1226
|
console.log();
|
|
1108
1227
|
console.log(chalk.bold.cyan('Keep the preview moving:'));
|
|
@@ -1156,6 +1275,14 @@ function printStep4(root, feature) {
|
|
|
1156
1275
|
console.log(chalk.dim(' The user is looking at the preview — a blank page means something is broken.'));
|
|
1157
1276
|
console.log(chalk.dim(' If any check fails, fix the issue and re-verify before proceeding.'));
|
|
1158
1277
|
console.log();
|
|
1278
|
+
const ctx4 = getTechStackContext(root);
|
|
1279
|
+
if (ctx4.isExpo) {
|
|
1280
|
+
console.log(chalk.magenta.bold(' EXPO WEB PREVIEW NOTE:'));
|
|
1281
|
+
console.log(chalk.magenta(' The preview renders via react-native-web in a browser. Some differences'));
|
|
1282
|
+
console.log(chalk.magenta(' from native devices are expected (fonts, SafeAreaView, shadows, Platform.OS).'));
|
|
1283
|
+
console.log(chalk.magenta(' The preview is for layout and data verification. Test final polish on device.'));
|
|
1284
|
+
console.log();
|
|
1285
|
+
}
|
|
1159
1286
|
console.log(chalk.bold('Update README and setup script:'));
|
|
1160
1287
|
checkbox('Update `README.md`: set the project name, write a one-line description, and list any setup prerequisites');
|
|
1161
1288
|
checkbox('Update `npm run setup` in `package.json` if setup requires extra steps (e.g. env vars, external services)');
|
|
@@ -1268,6 +1395,7 @@ function printStep7(root, feature) {
|
|
|
1268
1395
|
if (isResuming) {
|
|
1269
1396
|
printResumptionHeader(7);
|
|
1270
1397
|
}
|
|
1398
|
+
const ctx = getTechStackContext(root);
|
|
1271
1399
|
console.log('Execute your extraction plan from step 6.');
|
|
1272
1400
|
console.log();
|
|
1273
1401
|
console.log(chalk.bold('Components:'));
|
|
@@ -1281,18 +1409,23 @@ function printStep7(root, feature) {
|
|
|
1281
1409
|
console.log(chalk.dim(' Cover: typical inputs, edge cases, empty/null inputs, error conditions'));
|
|
1282
1410
|
console.log(chalk.dim(' Aim for 3-8 test cases per function depending on complexity'));
|
|
1283
1411
|
console.log(chalk.dim(' Hooks count as functions — useDrinks, useAuth, etc. all need test files'));
|
|
1284
|
-
|
|
1412
|
+
if (ctx.isExpo) {
|
|
1413
|
+
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`');
|
|
1414
|
+
}
|
|
1415
|
+
else {
|
|
1416
|
+
checkbox('Place test files next to source: `app/lib/drinks.ts` → `app/lib/drinks.test.ts`');
|
|
1417
|
+
}
|
|
1285
1418
|
console.log(chalk.yellow(' Tests ARE the only coverage for library functions/hooks — step 9 only captures component screenshots.'));
|
|
1286
1419
|
console.log();
|
|
1287
1420
|
console.log(chalk.bold('Recursive pass:'));
|
|
1288
1421
|
checkbox('Re-read EVERY new file you just created — extract components from components, functions from functions');
|
|
1289
1422
|
checkbox('Keep going until every file is a thin shell: just imports and composition');
|
|
1290
|
-
console.log(chalk.yellow(
|
|
1423
|
+
console.log(chalk.yellow(` Check: does any file contain raw ${ctx.rawPrimitivesList}?`));
|
|
1291
1424
|
console.log(chalk.yellow(' If yes → that JSX section is a component waiting to be extracted.'));
|
|
1292
1425
|
console.log();
|
|
1293
1426
|
console.log(chalk.bold('Verify before proceeding:'));
|
|
1294
1427
|
checkbox('Run all tests and verify they pass');
|
|
1295
|
-
checkbox(
|
|
1428
|
+
checkbox(`Page files contain ONLY imports + component composition — no raw ${ctx.isExpo ? 'React Native primitives' : 'HTML tags'}`);
|
|
1296
1429
|
checkbox('Every component renders ONE thing or composes sub-components — no multi-section JSX');
|
|
1297
1430
|
checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
|
|
1298
1431
|
printDimensionGuidance(dim, dimNames);
|
|
@@ -1353,11 +1486,12 @@ function printStep9(root, feature) {
|
|
|
1353
1486
|
console.log(chalk.dim(' Reuse and improve existing scenarios where possible — update mock data'));
|
|
1354
1487
|
console.log(chalk.dim(' to reflect current changes. Add new scenarios only for genuinely new states.'));
|
|
1355
1488
|
console.log(chalk.dim(' Ensure at least one scenario clearly demonstrates what changed in this session.'));
|
|
1356
|
-
|
|
1489
|
+
const ctx9 = getTechStackContext(root);
|
|
1490
|
+
printComponentCaptureInstructions(root);
|
|
1357
1491
|
console.log();
|
|
1358
1492
|
console.log(chalk.bold('Library Functions — run tests:'));
|
|
1359
1493
|
checkbox('Run ALL test files created in step 7');
|
|
1360
|
-
console.log(chalk.dim(
|
|
1494
|
+
console.log(chalk.dim(` Example: ${ctx9.testRunCommand} app/lib/drinks.test.ts`));
|
|
1361
1495
|
checkbox('Verify every test passes');
|
|
1362
1496
|
checkbox('If any test fails, fix the source code and re-run');
|
|
1363
1497
|
console.log();
|
|
@@ -1838,7 +1972,7 @@ function printMigrateStep3(root) {
|
|
|
1838
1972
|
console.log(chalk.dim(' Component scenarios use isolation routes with a codeyam-capture wrapper for tight screenshots.'));
|
|
1839
1973
|
console.log(chalk.dim(' These supplement the app scenarios from step 2 with focused component-level views.'));
|
|
1840
1974
|
console.log();
|
|
1841
|
-
printComponentCaptureInstructions();
|
|
1975
|
+
printComponentCaptureInstructions(root);
|
|
1842
1976
|
console.log();
|
|
1843
1977
|
migrationStopGate(3, pageName, pageIndex, totalPages);
|
|
1844
1978
|
}
|
|
@@ -1981,7 +2115,7 @@ function printMigrateStep8(root) {
|
|
|
1981
2115
|
console.log();
|
|
1982
2116
|
console.log(chalk.bold('Component Scenarios:'));
|
|
1983
2117
|
console.log(chalk.dim(' For each visual component extracted in step 7, create isolation routes and register scenarios.'));
|
|
1984
|
-
printComponentCaptureInstructions();
|
|
2118
|
+
printComponentCaptureInstructions(root);
|
|
1985
2119
|
console.log();
|
|
1986
2120
|
console.log(chalk.bold('Verify:'));
|
|
1987
2121
|
checkbox('Run `codeyam editor analyze-imports` to populate import graph (or `codeyam editor analyze-imports path/to/file.tsx ...` for specific files)');
|
|
@@ -2744,41 +2878,67 @@ function handleDesignSystem(designSystemId) {
|
|
|
2744
2878
|
}
|
|
2745
2879
|
function handleIsolate(componentNames) {
|
|
2746
2880
|
const root = process.cwd();
|
|
2881
|
+
const ctx = getTechStackContext(root);
|
|
2747
2882
|
if (componentNames.length === 0) {
|
|
2748
2883
|
console.error(chalk.red('Usage: codeyam editor isolate "ComponentA ComponentB ..."'));
|
|
2749
2884
|
process.exit(1);
|
|
2750
2885
|
}
|
|
2751
2886
|
const isolateDir = path.join(root, 'app', 'isolated-components');
|
|
2752
2887
|
// Create layout.tsx with production guard if missing
|
|
2753
|
-
const layoutPath = path.join(isolateDir, 'layout.tsx');
|
|
2888
|
+
const layoutPath = path.join(isolateDir, ctx.isExpo ? '_layout.tsx' : 'layout.tsx');
|
|
2754
2889
|
if (!fs.existsSync(layoutPath)) {
|
|
2755
2890
|
fs.mkdirSync(isolateDir, { recursive: true });
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2891
|
+
if (ctx.isExpo) {
|
|
2892
|
+
fs.writeFileSync(layoutPath, [
|
|
2893
|
+
'import { Redirect, Slot } from "expo-router";',
|
|
2894
|
+
'',
|
|
2895
|
+
'export default function CaptureLayout() {',
|
|
2896
|
+
' if (!__DEV__) return <Redirect href="/" />;',
|
|
2897
|
+
' return <Slot />;',
|
|
2898
|
+
'}',
|
|
2899
|
+
'',
|
|
2900
|
+
].join('\n'), 'utf8');
|
|
2901
|
+
}
|
|
2902
|
+
else {
|
|
2903
|
+
fs.writeFileSync(layoutPath, [
|
|
2904
|
+
'import { notFound } from "next/navigation";',
|
|
2905
|
+
'',
|
|
2906
|
+
'export default function CaptureLayout({ children }: { children: React.ReactNode }) {',
|
|
2907
|
+
' if (process.env.NODE_ENV === "production") notFound();',
|
|
2908
|
+
' return <>{children}</>;',
|
|
2909
|
+
'}',
|
|
2910
|
+
'',
|
|
2911
|
+
].join('\n'), 'utf8');
|
|
2912
|
+
}
|
|
2913
|
+
console.log(chalk.green(`Created layout guard: app/isolated-components/${ctx.isExpo ? '_layout.tsx' : 'layout.tsx'}`));
|
|
2914
|
+
}
|
|
2915
|
+
// Create isolation route for each component.
|
|
2916
|
+
// Expo Router uses flat files (ComponentName.tsx), Next.js uses subdirectories (ComponentName/page.tsx).
|
|
2768
2917
|
const created = [];
|
|
2769
2918
|
const existed = [];
|
|
2770
2919
|
for (const name of componentNames) {
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2920
|
+
if (ctx.isExpo) {
|
|
2921
|
+
const filePath = path.join(isolateDir, `${name}.tsx`);
|
|
2922
|
+
if (fs.existsSync(filePath)) {
|
|
2923
|
+
existed.push(name);
|
|
2924
|
+
}
|
|
2925
|
+
else {
|
|
2926
|
+
created.push(name);
|
|
2927
|
+
}
|
|
2774
2928
|
}
|
|
2775
2929
|
else {
|
|
2776
|
-
|
|
2777
|
-
|
|
2930
|
+
const dir = path.join(isolateDir, name);
|
|
2931
|
+
if (fs.existsSync(dir)) {
|
|
2932
|
+
existed.push(name);
|
|
2933
|
+
}
|
|
2934
|
+
else {
|
|
2935
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
2936
|
+
created.push(name);
|
|
2937
|
+
}
|
|
2778
2938
|
}
|
|
2779
2939
|
}
|
|
2780
2940
|
if (created.length > 0) {
|
|
2781
|
-
console.log(chalk.green(`Created ${created.length} isolation route
|
|
2941
|
+
console.log(chalk.green(`Created ${created.length} isolation route(s): ${created.join(', ')}`));
|
|
2782
2942
|
}
|
|
2783
2943
|
if (existed.length > 0) {
|
|
2784
2944
|
console.log(chalk.dim(`Already existed: ${existed.join(', ')}`));
|
|
@@ -2832,6 +2992,12 @@ function formatApiSubcommandResult(subcommand, data) {
|
|
|
2832
2992
|
parts.push(`scenarioId="${data.scenarioId}"`);
|
|
2833
2993
|
if (data.sessionsNotified != null)
|
|
2834
2994
|
parts.push(`notified=${data.sessionsNotified}`);
|
|
2995
|
+
// Surface the HTTP health check from the dev server
|
|
2996
|
+
if (data.preview) {
|
|
2997
|
+
parts.push(`healthy=${data.preview.healthy}`);
|
|
2998
|
+
if (data.preview.error)
|
|
2999
|
+
parts.push(`error="${data.preview.error}"`);
|
|
3000
|
+
}
|
|
2835
3001
|
return parts.join(' ');
|
|
2836
3002
|
}
|
|
2837
3003
|
case 'dev-server': {
|
|
@@ -4371,7 +4537,7 @@ async function handleTemplate() {
|
|
|
4371
4537
|
_: [],
|
|
4372
4538
|
});
|
|
4373
4539
|
console.log(chalk.green(' CodeYam initialized.'));
|
|
4374
|
-
// 5. Verify config has startCommand
|
|
4540
|
+
// 5. Verify config has startCommand and set format-specific defaults
|
|
4375
4541
|
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
4376
4542
|
if (fs.existsSync(configPath)) {
|
|
4377
4543
|
try {
|
|
@@ -4382,6 +4548,26 @@ async function handleTemplate() {
|
|
|
4382
4548
|
console.log(chalk.yellow(' Warning: No startCommand found in .codeyam/config.json webapps.'));
|
|
4383
4549
|
console.log(chalk.dim(' You may need to add: "startCommand": { "command": "sh", "args": ["-c", "npm run dev -- --port $PORT"] }'));
|
|
4384
4550
|
}
|
|
4551
|
+
// Store appFormats from the tech stack so the editor UI can adapt
|
|
4552
|
+
if (stack?.supportedFormats) {
|
|
4553
|
+
config.appFormats = stack.supportedFormats;
|
|
4554
|
+
}
|
|
4555
|
+
// Set mobile-first defaults for mobile-app projects
|
|
4556
|
+
if (stack?.supportedFormats?.includes('mobile-app')) {
|
|
4557
|
+
config.defaultScreenSize = {
|
|
4558
|
+
name: 'iPhone 16',
|
|
4559
|
+
width: 393,
|
|
4560
|
+
height: 852,
|
|
4561
|
+
};
|
|
4562
|
+
config.screenSizes = {
|
|
4563
|
+
'iPhone 16': { width: 393, height: 852 },
|
|
4564
|
+
'iPhone 16 Pro Max': { width: 430, height: 932 },
|
|
4565
|
+
'iPhone SE': { width: 375, height: 667 },
|
|
4566
|
+
'Pixel 8': { width: 412, height: 915 },
|
|
4567
|
+
'iPad mini': { width: 744, height: 1133 },
|
|
4568
|
+
};
|
|
4569
|
+
}
|
|
4570
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
4385
4571
|
}
|
|
4386
4572
|
catch {
|
|
4387
4573
|
// Config parse error is non-fatal
|
|
@@ -4416,7 +4602,15 @@ async function handleTemplate() {
|
|
|
4416
4602
|
}
|
|
4417
4603
|
console.log();
|
|
4418
4604
|
console.log(chalk.green.bold('Project scaffolded and ready!'));
|
|
4419
|
-
|
|
4605
|
+
if (stack?.id === 'expo-react-native') {
|
|
4606
|
+
console.log(chalk.dim('Next: Set up your data types, configure the theme in lib/theme.ts, and build your feature.'));
|
|
4607
|
+
}
|
|
4608
|
+
else if (stack?.id === 'chrome-extension-react') {
|
|
4609
|
+
console.log(chalk.dim('Next: Configure your extension manifest and build your feature.'));
|
|
4610
|
+
}
|
|
4611
|
+
else {
|
|
4612
|
+
console.log(chalk.dim('Next: Define your Prisma models, push the schema, seed the database, and build your feature.'));
|
|
4613
|
+
}
|
|
4420
4614
|
}
|
|
4421
4615
|
// ─── Sync subcommand ─────────────────────────────────────────────────
|
|
4422
4616
|
/**
|