@codeyam/codeyam-cli 0.1.0-staging.78c1cad → 0.1.0-staging.7973139
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 +7 -7
- package/analyzer-template/log.txt +3 -3
- package/analyzer-template/package.json +2 -2
- package/analyzer-template/packages/aws/package.json +5 -5
- package/analyzer-template/packages/database/package.json +1 -1
- package/codeyam-cli/src/commands/__tests__/editor.statePersistence.test.js +55 -0
- package/codeyam-cli/src/commands/__tests__/editor.statePersistence.test.js.map +1 -0
- package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js +39 -3
- package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js.map +1 -1
- package/codeyam-cli/src/commands/editor.js +395 -5
- package/codeyam-cli/src/commands/editor.js.map +1 -1
- package/codeyam-cli/src/commands/init.js +20 -0
- package/codeyam-cli/src/commands/init.js.map +1 -1
- package/codeyam-cli/src/data/techStacks.js +1 -1
- package/codeyam-cli/src/utils/__tests__/devServerState.test.js +93 -1
- package/codeyam-cli/src/utils/__tests__/devServerState.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +136 -0
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js +98 -1
- package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorRoadmap.test.js +1108 -0
- package/codeyam-cli/src/utils/__tests__/editorRoadmap.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js +120 -0
- package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js +84 -0
- package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/envFile.test.js +125 -0
- package/codeyam-cli/src/utils/__tests__/envFile.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/handoffContext.test.js +500 -0
- package/codeyam-cli/src/utils/__tests__/handoffContext.test.js.map +1 -0
- package/codeyam-cli/src/utils/designSystemShowcase.js +810 -0
- package/codeyam-cli/src/utils/designSystemShowcase.js.map +1 -0
- package/codeyam-cli/src/utils/devServerState.js +32 -0
- package/codeyam-cli/src/utils/devServerState.js.map +1 -1
- package/codeyam-cli/src/utils/editorAudit.js +6 -1
- package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
- package/codeyam-cli/src/utils/editorRoadmap.js +574 -0
- package/codeyam-cli/src/utils/editorRoadmap.js.map +1 -0
- package/codeyam-cli/src/utils/editorScenarioSwitch.js +27 -12
- package/codeyam-cli/src/utils/editorScenarioSwitch.js.map +1 -1
- package/codeyam-cli/src/utils/editorScenarios.js +10 -0
- package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
- package/codeyam-cli/src/utils/editorSeedAdapter.js +27 -14
- package/codeyam-cli/src/utils/editorSeedAdapter.js.map +1 -1
- package/codeyam-cli/src/utils/envFile.js +90 -0
- package/codeyam-cli/src/utils/envFile.js.map +1 -0
- package/codeyam-cli/src/utils/handoffContext.js +257 -0
- package/codeyam-cli/src/utils/handoffContext.js.map +1 -0
- package/codeyam-cli/src/utils/install-skills.js +36 -6
- package/codeyam-cli/src/utils/install-skills.js.map +1 -1
- package/codeyam-cli/src/utils/techStackConfig.js +38 -0
- package/codeyam-cli/src/utils/techStackConfig.js.map +1 -0
- package/codeyam-cli/src/utils/techStackConfig.test.js +85 -0
- package/codeyam-cli/src/utils/techStackConfig.test.js.map +1 -0
- package/codeyam-cli/src/webserver/__tests__/api.interactive-switch-scenario.test.js +1 -0
- package/codeyam-cli/src/webserver/__tests__/api.interactive-switch-scenario.test.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/buildPtyEnv.test.js +119 -1
- package/codeyam-cli/src/webserver/__tests__/buildPtyEnv.test.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js +354 -1
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js.map +1 -1
- package/codeyam-cli/src/webserver/app/lib/database.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{CopyButton-CLe80MMu.js → CopyButton-DTBZZfSk.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{EntityItem-Crt_KN_U.js → EntityItem-BxclONWq.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeIcon-CD7lGABo.js → EntityTypeIcon-BsnEOJZ_.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{InlineSpinner-CgTNOhnu.js → InlineSpinner-ByaELMbv.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{InteractivePreview-DtYTSPL2.js → InteractivePreview-6WjVfhxX.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/{LibraryFunctionPreview-D3s1MFkb.js → LibraryFunctionPreview-ChX-Hp7W.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{LogViewer-CM5zg40N.js → LogViewer-C-9zQdXg.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{MiniClaudeChat-CQENLSrF.js → MiniClaudeChat-Bs2_Oua4.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/{ReportIssueModal-C2PLkej3.js → ReportIssueModal-DQsceHVv.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{SafeScreenshot-DanvyBPb.js → SafeScreenshot-DThcm_9M.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-CefgqbCr.js → ScenarioViewer-Cl4oOA3A.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{Spinner-Bc8BG-Lw.js → Spinner-CIil5-gb.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{ViewportInspectBar-BA_Ry-rs.js → ViewportInspectBar-BqkA9zyZ.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{_index-C1YkzTAV.js → _index-DnOgyseQ.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-yH46LLUz.js → activity.(_tab)-DqM9hbNE.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{addon-web-links-CHx25PAe.js → addon-web-links-C58dYPwR.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{agent-transcripts-Bg3e7q4S.js → agent-transcripts-B8NCeOrm.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-database-verify-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-github-verify-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-handoff-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-hosting-verify-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-roadmap-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{book-open-CL-lMgHh.js → book-open-BFSIqZgO.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{chevron-down-GmAjGS9-.js → chevron-down-B9fDzFVh.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/chunk-UVKPFVEO-Bmq2apuh.js +43 -0
- package/codeyam-cli/src/webserver/build/client/assets/{circle-check-DFcQkN5j.js → circle-check-DLPObLUx.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{copy-C6iF61Xs.js → copy-DXEmO0TD.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{createLucideIcon-4ImjHTVC.js → createLucideIcon-BwyFiRot.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{dev.empty-CRepiabR.js → dev.empty-iRhRIFlp.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor._tab-BZPBzV73.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-DhtVC4aI.js +161 -0
- package/codeyam-cli/src/webserver/build/client/assets/{editorPreview-CluPkvXJ.js → editorPreview-C6fEYHrh.js} +6 -6
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-DYJRGiDI.js → entity._sha._-pc-vc6wO.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.dev-wdiwx5-Z.js → entity._sha.scenarios._scenarioId.dev-C8AyYgYT.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-BrkN-40Y.js → entity._sha.scenarios._scenarioId.fullscreen-DziaVQX1.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.create-scenario-DxfhekTZ.js → entity._sha_.create-scenario-BTcpgIpC.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.edit._scenarioId-CRXJWmpB.js → entity._sha_.edit._scenarioId-D_O_ajfZ.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entry.client-SuW9syRS.js → entry.client-j1Vi0bco.js} +6 -6
- package/codeyam-cli/src/webserver/build/client/assets/{files-D-xGrg29.js → files-kuny2Q_s.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{git-Bq_fbXP5.js → git-DgCZPMie.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/globals-L-aUIeux.css +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{index-Bp1l4hSv.js → index-BliGSSpl.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{index-DE3jI_dv.js → index-SqjQKTdH.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{index-CWV9XZiG.js → index-vyrZD2g4.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{labs-B_IX45ih.js → labs-c3yLxSEp.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{loader-circle-De-7qQ2u.js → loader-circle-D-q28GLF.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/manifest-79d0d81a.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{memory-Cx2xEx7s.js → memory-CEWIUC4t.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{pause-CFxEKL1u.js → pause-BP6fitdh.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{root-dKFRTYcy.js → root-L2V0jea7.js} +6 -6
- package/codeyam-cli/src/webserver/build/client/assets/{search-BdBb5aqc.js → search-BooqacKS.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{settings-DdE-Untf.js → settings-BM0nbryO.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{simulations-DSCdE99u.js → simulations-ovy6FjRY.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{terminal-CrplD4b1.js → terminal-DHemCJIs.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{triangle-alert-DqJ0j69l.js → triangle-alert-D87ekDl8.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{useCustomSizes-DhXHbEjP.js → useCustomSizes-Dk0Tciqg.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{useLastLogLine-D9QZKaLJ.js → useLastLogLine-C8QvIe05.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{useReportContext-Cy5Qg_UR.js → useReportContext-jkCytuYz.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{useToast-5HR2j9ZE.js → useToast-BgqkixU9.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/{analysisRunner-B6HnVI5u.js → analysisRunner-QgInFGdU.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/{index-rV_xLS1u.js → index-zblh9auj.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/init-DaE0CBjk.js +14 -0
- package/codeyam-cli/src/webserver/build/server/assets/server-build-CNvgz1cC.js +853 -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 +255 -20
- package/codeyam-cli/src/webserver/editorProxy.js.map +1 -1
- package/codeyam-cli/src/webserver/server.js +67 -0
- package/codeyam-cli/src/webserver/server.js.map +1 -1
- package/codeyam-cli/src/webserver/terminalServer.js +102 -11
- package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
- package/codeyam-cli/templates/codeyam-editor-codex.md +61 -0
- package/codeyam-cli/templates/codeyam-editor-gemini.md +59 -0
- package/codeyam-cli/templates/editor-step-hook.py +4 -4
- package/codeyam-cli/templates/expo-react-native/MOBILE_SETUP.md +85 -0
- package/codeyam-cli/templates/expo-react-native/app/_layout.tsx +4 -2
- package/codeyam-cli/templates/expo-react-native/app.json +11 -0
- package/codeyam-cli/templates/expo-react-native/babel.config.js +1 -0
- package/codeyam-cli/templates/expo-react-native/gitignore +2 -0
- package/codeyam-cli/templates/expo-react-native/package.json +24 -18
- package/codeyam-cli/templates/expo-react-native/patches/expo-modules-autolinking+3.0.24.patch +29 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/gitignore +1 -0
- package/codeyam-cli/templates/seed-adapters/supabase.ts +261 -77
- package/package.json +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/chunk-JZWAC4HX-BAdwhyCx.js +0 -43
- package/codeyam-cli/src/webserver/build/client/assets/editor._tab-Gbk_i5Js.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-CRxPi2BB.js +0 -96
- package/codeyam-cli/src/webserver/build/client/assets/globals-BsGHu8WX.css +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/manifest-9032538f.js +0 -1
- package/codeyam-cli/src/webserver/build/server/assets/init-BdWDvetv.js +0 -10
- package/codeyam-cli/src/webserver/build/server/assets/server-build-B_jdq5dT.js +0 -689
|
@@ -10,28 +10,39 @@
|
|
|
10
10
|
* "tableName": [{ "column": "value", ... }, ...],
|
|
11
11
|
* "_auth": { // optional
|
|
12
12
|
* "email": "alice@example.com",
|
|
13
|
-
* "password": "test123"
|
|
13
|
+
* "password": "test123" // optional — default used if omitted
|
|
14
14
|
* }
|
|
15
15
|
* }
|
|
16
16
|
*
|
|
17
17
|
* When _auth is present, the adapter:
|
|
18
18
|
* 1. Creates the user if they don't exist (auto-confirms email)
|
|
19
|
-
* 2.
|
|
20
|
-
* 3. Writes session cookies to .codeyam/tmp/seed-session.json
|
|
19
|
+
* 2. Builds a synthetic JWT with far-future expiry (no real sign-in needed)
|
|
20
|
+
* 3. Writes session cookies + auth mocks to .codeyam/tmp/seed-session.json
|
|
21
21
|
* so the CodeYam proxy can inject them into the browser
|
|
22
22
|
*
|
|
23
|
+
* The password field is optional — if omitted, a default dev password is used.
|
|
24
|
+
* No real JWTs are stored; the preload module intercepts all auth API calls.
|
|
25
|
+
*
|
|
26
|
+
* Data operations use direct PostgreSQL (pg) for speed — TRUNCATE CASCADE
|
|
27
|
+
* and batched INSERT are ~2x faster than individual PostgREST HTTP calls.
|
|
28
|
+
*
|
|
23
29
|
* Requirements:
|
|
30
|
+
* - DATABASE_URL env var with a PostgreSQL connection string (session pooler recommended)
|
|
24
31
|
* - A Supabase project URL (https://<ref>.supabase.co) in any env var
|
|
25
32
|
* - A secret key (sb_secret_... or legacy eyJ... service_role JWT) in any env var
|
|
26
33
|
*
|
|
27
|
-
* The adapter scans ALL env vars by value pattern
|
|
34
|
+
* The adapter scans ALL env vars by value pattern for Supabase URL and secret key —
|
|
35
|
+
* no specific naming required. DATABASE_URL must be set explicitly.
|
|
28
36
|
*/
|
|
29
37
|
|
|
30
38
|
import { createClient } from '@supabase/supabase-js';
|
|
31
39
|
import { Prisma } from '@prisma/client';
|
|
40
|
+
import pg from 'pg';
|
|
32
41
|
import * as fs from 'fs';
|
|
33
42
|
import * as path from 'path';
|
|
34
43
|
|
|
44
|
+
const { Client } = pg;
|
|
45
|
+
|
|
35
46
|
/**
|
|
36
47
|
* Scan all env vars for values matching a pattern.
|
|
37
48
|
* Returns ALL unique matches (not just the first) so callers can disambiguate.
|
|
@@ -66,7 +77,6 @@ const supabaseUrl = findAllEnvByPattern(
|
|
|
66
77
|
|
|
67
78
|
// New-format keys are unambiguous by prefix
|
|
68
79
|
const newSecretKey = findAllEnvByPattern(/^sb_secret_/)[0];
|
|
69
|
-
const newAnonKey = findAllEnvByPattern(/^sb_publishable_/)[0];
|
|
70
80
|
|
|
71
81
|
// Legacy JWT keys — disambiguate by decoding the role claim
|
|
72
82
|
const legacyJwts = findAllEnvByPattern(
|
|
@@ -75,10 +85,8 @@ const legacyJwts = findAllEnvByPattern(
|
|
|
75
85
|
const legacyServiceRole = legacyJwts.find(
|
|
76
86
|
(jwt) => getJwtRole(jwt) === 'service_role',
|
|
77
87
|
);
|
|
78
|
-
const legacyAnon = legacyJwts.find((jwt) => getJwtRole(jwt) === 'anon');
|
|
79
88
|
|
|
80
89
|
const secretKey = newSecretKey || legacyServiceRole;
|
|
81
|
-
const anonKey = newAnonKey || legacyAnon;
|
|
82
90
|
|
|
83
91
|
if (!supabaseUrl || !secretKey) {
|
|
84
92
|
console.error(
|
|
@@ -93,7 +101,17 @@ if (!supabaseUrl || !secretKey) {
|
|
|
93
101
|
process.exit(1);
|
|
94
102
|
}
|
|
95
103
|
|
|
96
|
-
//
|
|
104
|
+
// DATABASE_URL for direct PostgreSQL access (session pooler recommended for speed)
|
|
105
|
+
const databaseUrl = process.env.DATABASE_URL;
|
|
106
|
+
if (!databaseUrl || !databaseUrl.startsWith('postgresql://')) {
|
|
107
|
+
console.error('DATABASE_URL must be set to a PostgreSQL connection string.');
|
|
108
|
+
console.error(
|
|
109
|
+
'Use the session pooler connection string (port 5432) from your Supabase dashboard.',
|
|
110
|
+
);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Admin client for user management only (uses secret key to bypass RLS)
|
|
97
115
|
const supabase = createClient(supabaseUrl, secretKey, {
|
|
98
116
|
auth: { autoRefreshToken: false, persistSession: false },
|
|
99
117
|
});
|
|
@@ -109,61 +127,213 @@ function getProjectRef(): string {
|
|
|
109
127
|
}
|
|
110
128
|
}
|
|
111
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Build a mapping from lowercased seed-data keys to actual PostgreSQL table names.
|
|
132
|
+
* Prisma creates tables with the model name (PascalCase) unless @@map is used.
|
|
133
|
+
* PostgreSQL requires the exact table name, so we need this translation.
|
|
134
|
+
*/
|
|
135
|
+
function buildTableNameMap(): Record<string, string> {
|
|
136
|
+
const map: Record<string, string> = {};
|
|
137
|
+
for (const model of Prisma.dmmf.datamodel.models) {
|
|
138
|
+
// dbName is set when @@map is used, otherwise null → use model name
|
|
139
|
+
const dbName = (model as any).dbName || model.name;
|
|
140
|
+
// Map lowercased model name → actual table name
|
|
141
|
+
const lowered = model.name.charAt(0).toLowerCase() + model.name.slice(1);
|
|
142
|
+
map[lowered] = dbName;
|
|
143
|
+
map[model.name] = dbName; // Also map PascalCase for safety
|
|
144
|
+
}
|
|
145
|
+
return map;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Build column name mapping from Prisma field names to database column names.
|
|
150
|
+
* Prisma uses camelCase field names but generates snake_case columns unless @map is used.
|
|
151
|
+
*/
|
|
152
|
+
function buildColumnMap(tableName: string): Record<string, string> {
|
|
153
|
+
const map: Record<string, string> = {};
|
|
154
|
+
for (const model of Prisma.dmmf.datamodel.models) {
|
|
155
|
+
const dbName = (model as any).dbName || model.name;
|
|
156
|
+
if (dbName !== tableName) continue;
|
|
157
|
+
for (const field of model.fields) {
|
|
158
|
+
if (field.kind === 'object') continue; // Skip relation fields
|
|
159
|
+
const colName = (field as any).dbName || field.name;
|
|
160
|
+
map[field.name] = colName;
|
|
161
|
+
}
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
return map;
|
|
165
|
+
}
|
|
166
|
+
|
|
112
167
|
async function seedTables(seed: Record<string, unknown[]>) {
|
|
113
168
|
if (Object.keys(seed).length === 0) return;
|
|
114
169
|
|
|
170
|
+
const tableMap = buildTableNameMap();
|
|
171
|
+
|
|
115
172
|
// Discover ALL models from the Prisma schema — not just the tables in the seed data.
|
|
116
173
|
// This ensures FK-dependent tables are cleared even when the seed only contains
|
|
117
|
-
// the parent table's data.
|
|
118
|
-
const
|
|
119
|
-
(m) => m
|
|
174
|
+
// the parent table's data.
|
|
175
|
+
const allTables = Prisma.dmmf.datamodel.models.map(
|
|
176
|
+
(m) => (m as any).dbName || m.name,
|
|
120
177
|
);
|
|
121
178
|
|
|
122
179
|
console.log(
|
|
123
|
-
`Clearing ${
|
|
180
|
+
`Clearing ${allTables.length} tables, seeding: ${Object.keys(seed).join(', ')}`,
|
|
124
181
|
);
|
|
125
182
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
183
|
+
const client = new Client({
|
|
184
|
+
connectionString: databaseUrl,
|
|
185
|
+
ssl: { rejectUnauthorized: false },
|
|
186
|
+
});
|
|
187
|
+
await client.connect();
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
// TRUNCATE all tables in one statement — CASCADE handles FK dependencies
|
|
191
|
+
// automatically, so table order doesn't matter. Much faster than
|
|
192
|
+
// individual DELETE FROM queries via PostgREST.
|
|
193
|
+
const quoted = allTables.map((t) => `"${t}"`).join(', ');
|
|
194
|
+
await client.query(`TRUNCATE ${quoted} CASCADE`);
|
|
195
|
+
console.log(` Cleared ${allTables.length} tables`);
|
|
196
|
+
|
|
197
|
+
// Reset auto-increment sequences so IDs start from 1 on each scenario switch.
|
|
198
|
+
// Without this, IDs keep climbing and hardcoded URLs like /drinks/1 break.
|
|
199
|
+
for (const table of allTables) {
|
|
200
|
+
try {
|
|
201
|
+
await client.query(
|
|
202
|
+
`SELECT setval(pg_get_serial_sequence('"${table}"', 'id'), 1, false)`,
|
|
203
|
+
);
|
|
204
|
+
} catch {
|
|
205
|
+
// Table may not have an 'id' serial column — skip
|
|
138
206
|
}
|
|
139
|
-
} else {
|
|
140
|
-
console.log(` Cleared ${table}`);
|
|
141
207
|
}
|
|
208
|
+
|
|
209
|
+
// Insert seed data using batched INSERT for speed
|
|
210
|
+
for (const [seedKey, rows] of Object.entries(seed)) {
|
|
211
|
+
if (!Array.isArray(rows) || rows.length === 0) continue;
|
|
212
|
+
const table = tableMap[seedKey] || seedKey;
|
|
213
|
+
const columnMap = buildColumnMap(table);
|
|
214
|
+
|
|
215
|
+
// Get column names from the first row's keys, mapped to DB column names
|
|
216
|
+
const fieldNames = Object.keys(rows[0] as Record<string, unknown>);
|
|
217
|
+
const dbColumns = fieldNames.map((f) => columnMap[f] || f);
|
|
218
|
+
const quotedCols = dbColumns.map((c) => `"${c}"`).join(', ');
|
|
219
|
+
|
|
220
|
+
// Build parameterized VALUES clause for all rows at once
|
|
221
|
+
const params: unknown[] = [];
|
|
222
|
+
const valueClauses: string[] = [];
|
|
223
|
+
for (let i = 0; i < rows.length; i++) {
|
|
224
|
+
const row = rows[i] as Record<string, unknown>;
|
|
225
|
+
const placeholders: string[] = [];
|
|
226
|
+
for (const field of fieldNames) {
|
|
227
|
+
params.push(row[field]);
|
|
228
|
+
placeholders.push(`$${params.length}`);
|
|
229
|
+
}
|
|
230
|
+
valueClauses.push(`(${placeholders.join(', ')})`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
await client.query(
|
|
234
|
+
`INSERT INTO "${table}" (${quotedCols}) VALUES ${valueClauses.join(', ')}`,
|
|
235
|
+
params,
|
|
236
|
+
);
|
|
237
|
+
console.log(` Seeded ${rows.length} rows into ${table}`);
|
|
238
|
+
}
|
|
239
|
+
} finally {
|
|
240
|
+
await client.end();
|
|
142
241
|
}
|
|
242
|
+
}
|
|
143
243
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
244
|
+
/**
|
|
245
|
+
* Export mode: dump current database state to a JSON file.
|
|
246
|
+
* Used by CodeYam to save interactive changes back to scenario seed data.
|
|
247
|
+
*
|
|
248
|
+
* Usage: npx tsx .codeyam/seed-adapter.ts --export <output-path.json>
|
|
249
|
+
*/
|
|
250
|
+
async function exportData(outputPath: string) {
|
|
251
|
+
const tableMap = buildTableNameMap();
|
|
252
|
+
const modelNames = Prisma.dmmf.datamodel.models.map((m) => m.name);
|
|
253
|
+
|
|
254
|
+
const client = new Client({
|
|
255
|
+
connectionString: databaseUrl,
|
|
256
|
+
ssl: { rejectUnauthorized: false },
|
|
257
|
+
});
|
|
258
|
+
await client.connect();
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
const seed: Record<string, unknown[]> = {};
|
|
262
|
+
for (const model of modelNames) {
|
|
263
|
+
const camelCase = model.charAt(0).toLowerCase() + model.slice(1);
|
|
264
|
+
const table = tableMap[camelCase] || model;
|
|
265
|
+
try {
|
|
266
|
+
const result = await client.query(`SELECT * FROM "${table}"`);
|
|
267
|
+
if (result.rows.length > 0) {
|
|
268
|
+
seed[camelCase] = result.rows;
|
|
269
|
+
}
|
|
270
|
+
} catch {
|
|
271
|
+
// Skip tables that can't be queried
|
|
272
|
+
}
|
|
151
273
|
}
|
|
152
|
-
|
|
274
|
+
|
|
275
|
+
fs.writeFileSync(outputPath, JSON.stringify(seed, null, 2));
|
|
276
|
+
console.log(`Exported ${Object.keys(seed).length} tables`);
|
|
277
|
+
} finally {
|
|
278
|
+
await client.end();
|
|
153
279
|
}
|
|
154
280
|
}
|
|
155
281
|
|
|
156
282
|
/**
|
|
157
|
-
*
|
|
283
|
+
* Build a minimal JWT with a far-future expiry.
|
|
284
|
+
* The signature is fake — it doesn't matter because the CodeYam preload module
|
|
285
|
+
* intercepts the Supabase auth validation endpoint and returns mock data.
|
|
286
|
+
*/
|
|
287
|
+
function buildFakeJwt(userId: string, email: string): string {
|
|
288
|
+
const header = { alg: 'HS256', typ: 'JWT' };
|
|
289
|
+
const payload = {
|
|
290
|
+
sub: userId,
|
|
291
|
+
email,
|
|
292
|
+
role: 'authenticated',
|
|
293
|
+
aud: 'authenticated',
|
|
294
|
+
exp: 9999999999,
|
|
295
|
+
iat: Math.floor(Date.now() / 1000),
|
|
296
|
+
};
|
|
297
|
+
const encode = (obj: object) =>
|
|
298
|
+
Buffer.from(JSON.stringify(obj)).toString('base64url');
|
|
299
|
+
return `${encode(header)}.${encode(payload)}.codeyam-mock-signature`;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Build a Supabase user response object matching the /auth/v1/user endpoint format.
|
|
304
|
+
*/
|
|
305
|
+
function buildUserResponse(user: { id: string; email?: string }) {
|
|
306
|
+
return {
|
|
307
|
+
id: user.id,
|
|
308
|
+
aud: 'authenticated',
|
|
309
|
+
role: 'authenticated',
|
|
310
|
+
email: user.email || '',
|
|
311
|
+
email_confirmed_at: new Date().toISOString(),
|
|
312
|
+
app_metadata: { provider: 'email', providers: ['email'] },
|
|
313
|
+
user_metadata: {},
|
|
314
|
+
created_at: new Date().toISOString(),
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const DEFAULT_AUTH_PASSWORD = 'codeyam-dev-password-123!';
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Handle auth: create user, build synthetic session, write session cookies.
|
|
158
322
|
* Returns the user ID so callers can replace __AUTH_USER_ID__ placeholders in seed data.
|
|
323
|
+
*
|
|
324
|
+
* No real sign-in is performed — the session cookie uses a synthetic JWT with
|
|
325
|
+
* far-future expiry, and externalApis mocks intercept all Supabase auth API calls.
|
|
326
|
+
* This avoids storing real tokens (security risk + expiration) in scenario files.
|
|
159
327
|
*/
|
|
160
328
|
async function handleAuth(auth: {
|
|
161
329
|
email: string;
|
|
162
|
-
password
|
|
330
|
+
password?: string;
|
|
163
331
|
}): Promise<string> {
|
|
164
|
-
const
|
|
332
|
+
const email = auth.email;
|
|
333
|
+
const password = auth.password || DEFAULT_AUTH_PASSWORD;
|
|
165
334
|
|
|
166
|
-
// Create user if they don't exist (auto-confirm email
|
|
335
|
+
// Create user if they don't exist (auto-confirm email)
|
|
336
|
+
// We still need the auth.users row for PostgREST FK relationships.
|
|
167
337
|
const { data: createData, error: createError } =
|
|
168
338
|
await supabase.auth.admin.createUser({
|
|
169
339
|
email,
|
|
@@ -171,72 +341,79 @@ async function handleAuth(auth: {
|
|
|
171
341
|
email_confirm: true,
|
|
172
342
|
});
|
|
173
343
|
|
|
344
|
+
let userId: string;
|
|
345
|
+
|
|
174
346
|
if (createError) {
|
|
175
347
|
if (createError.message.includes('already been registered')) {
|
|
176
|
-
// User exists —
|
|
177
|
-
// scenario specifies a different password than a previous run.
|
|
348
|
+
// User exists — look up their ID
|
|
178
349
|
const { data: listData } = await supabase.auth.admin.listUsers();
|
|
179
350
|
const existingUser = listData?.users?.find((u) => u.email === email);
|
|
180
351
|
if (existingUser) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
console.
|
|
352
|
+
userId = existingUser.id;
|
|
353
|
+
console.log(` Found existing user ${email} (user: ${userId})`);
|
|
354
|
+
} else {
|
|
355
|
+
console.error(
|
|
356
|
+
` User ${email} reported as registered but not found in listUsers`,
|
|
357
|
+
);
|
|
358
|
+
process.exit(1);
|
|
185
359
|
}
|
|
186
360
|
} else {
|
|
187
361
|
console.error(` Failed to create auth user: ${createError.message}`);
|
|
188
362
|
process.exit(1);
|
|
189
363
|
}
|
|
364
|
+
} else {
|
|
365
|
+
userId = createData!.user.id;
|
|
366
|
+
console.log(` Created user ${email} (user: ${userId})`);
|
|
190
367
|
}
|
|
191
368
|
|
|
192
|
-
//
|
|
193
|
-
//
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
auth: { autoRefreshToken: false, persistSession: false },
|
|
198
|
-
})
|
|
199
|
-
: supabase;
|
|
200
|
-
|
|
201
|
-
const { data: signInData, error: signInError } =
|
|
202
|
-
await signInClient.auth.signInWithPassword({ email, password });
|
|
203
|
-
|
|
204
|
-
if (signInError || !signInData.session) {
|
|
205
|
-
console.error(
|
|
206
|
-
` Failed to sign in as ${email}: ${signInError?.message || 'no session returned'}`,
|
|
207
|
-
);
|
|
208
|
-
process.exit(1);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const userId = signInData.user.id;
|
|
212
|
-
console.log(` Signed in as ${email} (user: ${userId})`);
|
|
369
|
+
// Build synthetic JWT and mock responses — no real sign-in needed.
|
|
370
|
+
// The preload module intercepts server-side getUser() calls so Next.js
|
|
371
|
+
// middleware returns the mock user without hitting real Supabase.
|
|
372
|
+
const fakeJwt = buildFakeJwt(userId!, email);
|
|
373
|
+
const userResponse = buildUserResponse({ id: userId!, email });
|
|
213
374
|
|
|
214
|
-
// Write session cookies for the proxy to inject
|
|
215
375
|
const projectRef = getProjectRef();
|
|
216
376
|
const sessionOutput = {
|
|
217
377
|
cookies: [
|
|
218
378
|
{
|
|
219
379
|
name: `sb-${projectRef}-auth-token`,
|
|
220
380
|
value: JSON.stringify({
|
|
221
|
-
access_token:
|
|
222
|
-
refresh_token:
|
|
381
|
+
access_token: fakeJwt,
|
|
382
|
+
refresh_token: 'codeyam-mock-refresh-token',
|
|
223
383
|
token_type: 'bearer',
|
|
224
|
-
expires_in:
|
|
225
|
-
expires_at:
|
|
384
|
+
expires_in: 315360000,
|
|
385
|
+
expires_at: 9999999999,
|
|
226
386
|
}),
|
|
227
387
|
path: '/',
|
|
228
388
|
sameSite: 'Lax' as const,
|
|
229
389
|
},
|
|
230
390
|
],
|
|
391
|
+
externalApis: {
|
|
392
|
+
[`${supabaseUrl}/auth/v1/user`]: {
|
|
393
|
+
body: userResponse,
|
|
394
|
+
status: 200,
|
|
395
|
+
},
|
|
396
|
+
[`${supabaseUrl}/auth/v1/token`]: {
|
|
397
|
+
body: {
|
|
398
|
+
access_token: fakeJwt,
|
|
399
|
+
token_type: 'bearer',
|
|
400
|
+
expires_in: 315360000,
|
|
401
|
+
expires_at: 9999999999,
|
|
402
|
+
refresh_token: 'codeyam-mock-refresh-token',
|
|
403
|
+
user: userResponse,
|
|
404
|
+
},
|
|
405
|
+
status: 200,
|
|
406
|
+
},
|
|
407
|
+
},
|
|
231
408
|
};
|
|
232
409
|
|
|
233
410
|
const outputDir = path.join(process.cwd(), '.codeyam', 'tmp');
|
|
234
411
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
235
412
|
const outputPath = path.join(outputDir, 'seed-session.json');
|
|
236
413
|
fs.writeFileSync(outputPath, JSON.stringify(sessionOutput, null, 2));
|
|
237
|
-
console.log(` Session cookies written to ${outputPath}`);
|
|
414
|
+
console.log(` Session cookies + auth mocks written to ${outputPath}`);
|
|
238
415
|
|
|
239
|
-
return userId
|
|
416
|
+
return userId!;
|
|
240
417
|
}
|
|
241
418
|
|
|
242
419
|
/**
|
|
@@ -275,7 +452,7 @@ async function main() {
|
|
|
275
452
|
// in seed data (e.g. for user_id foreign key columns with Supabase RLS)
|
|
276
453
|
if (auth) {
|
|
277
454
|
const userId = await handleAuth(
|
|
278
|
-
auth as { email: string; password
|
|
455
|
+
auth as { email: string; password?: string },
|
|
279
456
|
);
|
|
280
457
|
seed = replaceAuthPlaceholders(seed, userId);
|
|
281
458
|
}
|
|
@@ -285,7 +462,14 @@ async function main() {
|
|
|
285
462
|
console.log('Seed complete');
|
|
286
463
|
}
|
|
287
464
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
465
|
+
if (process.argv[2] === '--export') {
|
|
466
|
+
exportData(process.argv[3]).catch((e) => {
|
|
467
|
+
console.error('Seed adapter export error:', e);
|
|
468
|
+
process.exit(1);
|
|
469
|
+
});
|
|
470
|
+
} else {
|
|
471
|
+
main().catch((e) => {
|
|
472
|
+
console.error('Seed adapter error:', e);
|
|
473
|
+
process.exit(1);
|
|
474
|
+
});
|
|
475
|
+
}
|