@codeyam/codeyam-cli 0.1.29 → 0.1.31
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 +1 -1
- package/analyzer-template/packages/aws/package.json +1 -1
- package/analyzer-template/packages/database/package.json +1 -1
- 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 +133 -0
- 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/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 +712 -2
- package/codeyam-cli/src/utils/__tests__/editorRoadmap.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/editorRoadmap.js +290 -17
- package/codeyam-cli/src/utils/editorRoadmap.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__/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 +115 -0
- 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/{MiniClaudeChat-BusrvT2F.js → MiniClaudeChat-Bs2_Oua4.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/editor.entity.(_sha)-DOXe0Qx7.js +161 -0
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-Ce1s4OQ1.js → entity._sha._-pc-vc6wO.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/manifest-30c44d84.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{root-CVjDQwjJ.js → root-CLedrjXQ.js} +7 -7
- package/codeyam-cli/src/webserver/build/server/assets/{analysisRunner-CTJYMVFP.js → analysisRunner-CuR5TvUx.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/{index-CCth4Hgw.js → index-D4MWAsqb.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/init-JObA4lXD.js +14 -0
- package/codeyam-cli/src/webserver/build/server/assets/server-build-i8OXK4oL.js +765 -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 +77 -4
- package/codeyam-cli/src/webserver/editorProxy.js.map +1 -1
- package/codeyam-cli/src/webserver/terminalServer.js +81 -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/nextjs-prisma-sqlite/gitignore +1 -0
- package/codeyam-cli/templates/seed-adapters/supabase.ts +185 -84
- package/package.json +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-785deXbZ.js +0 -147
- package/codeyam-cli/src/webserver/build/client/assets/globals-Bt7TsgQz.css +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/manifest-3d8cde80.js +0 -1
- package/codeyam-cli/src/webserver/build/server/assets/init-UXl-3vVp.js +0 -10
- package/codeyam-cli/src/webserver/build/server/assets/server-build-DSW2mE30.js +0 -741
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# CodeYam Editor Project (Gemini)
|
|
2
|
+
|
|
3
|
+
This project is being built with CodeYam's Code + Data Editor. The core principle is **code and data are developed together** — every component should have scenario data that drives what it renders.
|
|
4
|
+
|
|
5
|
+
## Architecture: Components + Scenarios
|
|
6
|
+
|
|
7
|
+
### Component Structure
|
|
8
|
+
|
|
9
|
+
Build with a clear component hierarchy so individual components can be previewed in isolation:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
app/
|
|
13
|
+
components/
|
|
14
|
+
Dashboard.tsx ← page-level component
|
|
15
|
+
TaskList.tsx ← displays a list, gets data from API or props
|
|
16
|
+
TaskCard.tsx ← single item, pure props
|
|
17
|
+
EmptyState.tsx ← shown when no data
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Scenarios
|
|
21
|
+
|
|
22
|
+
A **scenario** is a named set of data that drives how the app renders. Scenarios exist at two levels:
|
|
23
|
+
|
|
24
|
+
- **App-level scenarios**: Full application state.
|
|
25
|
+
- **Component-level scenarios**: Props for a single component in isolation.
|
|
26
|
+
|
|
27
|
+
**Always register scenarios with CodeYam** using the CLI:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
codeyam editor register @.codeyam/tmp/scenario.json
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**To list existing scenarios:** `codeyam editor scenarios`
|
|
34
|
+
|
|
35
|
+
## Workflow
|
|
36
|
+
|
|
37
|
+
1. Build a component
|
|
38
|
+
2. Create scenarios with rich, realistic data.
|
|
39
|
+
3. Register the scenarios with CodeYam.
|
|
40
|
+
4. Preview each scenario to verify the component handles all states.
|
|
41
|
+
5. Repeat for the next component.
|
|
42
|
+
|
|
43
|
+
## Key Conventions
|
|
44
|
+
|
|
45
|
+
- Component files go in `app/components/` (or `src/components/`)
|
|
46
|
+
- One component per file, default export.
|
|
47
|
+
- Components accept props that can be provided by scenarios.
|
|
48
|
+
|
|
49
|
+
## AI Handoff
|
|
50
|
+
|
|
51
|
+
Since you are working in an AI-assisted workflow, you must maintain a handoff summary for other AI agents (or yourself in a later session).
|
|
52
|
+
|
|
53
|
+
**At the end of every step, update the handoff summary:**
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
codeyam editor handoff '{"summary": "Built the X component and registered 3 scenarios. Ready to start on Y."}'
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
This summary will be displayed when starting a new session or switching between AI providers (Claude <-> Gemini).
|
|
@@ -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
|
});
|
|
@@ -112,7 +130,7 @@ function getProjectRef(): string {
|
|
|
112
130
|
/**
|
|
113
131
|
* Build a mapping from lowercased seed-data keys to actual PostgreSQL table names.
|
|
114
132
|
* Prisma creates tables with the model name (PascalCase) unless @@map is used.
|
|
115
|
-
*
|
|
133
|
+
* PostgreSQL requires the exact table name, so we need this translation.
|
|
116
134
|
*/
|
|
117
135
|
function buildTableNameMap(): Record<string, string> {
|
|
118
136
|
const map: Record<string, string> = {};
|
|
@@ -127,6 +145,25 @@ function buildTableNameMap(): Record<string, string> {
|
|
|
127
145
|
return map;
|
|
128
146
|
}
|
|
129
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
|
+
|
|
130
167
|
async function seedTables(seed: Record<string, unknown[]>) {
|
|
131
168
|
if (Object.keys(seed).length === 0) return;
|
|
132
169
|
|
|
@@ -134,7 +171,7 @@ async function seedTables(seed: Record<string, unknown[]>) {
|
|
|
134
171
|
|
|
135
172
|
// Discover ALL models from the Prisma schema — not just the tables in the seed data.
|
|
136
173
|
// This ensures FK-dependent tables are cleared even when the seed only contains
|
|
137
|
-
// the parent table's data.
|
|
174
|
+
// the parent table's data.
|
|
138
175
|
const allTables = Prisma.dmmf.datamodel.models.map(
|
|
139
176
|
(m) => (m as any).dbName || m.name,
|
|
140
177
|
);
|
|
@@ -143,34 +180,102 @@ async function seedTables(seed: Record<string, unknown[]>) {
|
|
|
143
180
|
`Clearing ${allTables.length} tables, seeding: ${Object.keys(seed).join(', ')}`,
|
|
144
181
|
);
|
|
145
182
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
|
158
206
|
}
|
|
159
|
-
} else {
|
|
160
|
-
console.log(` Cleared ${table}`);
|
|
161
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();
|
|
162
241
|
}
|
|
242
|
+
}
|
|
163
243
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
+
}
|
|
172
273
|
}
|
|
173
|
-
|
|
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();
|
|
174
279
|
}
|
|
175
280
|
}
|
|
176
281
|
|
|
@@ -210,17 +315,25 @@ function buildUserResponse(user: { id: string; email?: string }) {
|
|
|
210
315
|
};
|
|
211
316
|
}
|
|
212
317
|
|
|
318
|
+
const DEFAULT_AUTH_PASSWORD = 'codeyam-dev-password-123!';
|
|
319
|
+
|
|
213
320
|
/**
|
|
214
|
-
* Handle auth: create user,
|
|
321
|
+
* Handle auth: create user, build synthetic session, write session cookies.
|
|
215
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.
|
|
216
327
|
*/
|
|
217
328
|
async function handleAuth(auth: {
|
|
218
329
|
email: string;
|
|
219
|
-
password
|
|
330
|
+
password?: string;
|
|
220
331
|
}): Promise<string> {
|
|
221
|
-
const
|
|
332
|
+
const email = auth.email;
|
|
333
|
+
const password = auth.password || DEFAULT_AUTH_PASSWORD;
|
|
222
334
|
|
|
223
|
-
// 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.
|
|
224
337
|
const { data: createData, error: createError } =
|
|
225
338
|
await supabase.auth.admin.createUser({
|
|
226
339
|
email,
|
|
@@ -228,52 +341,36 @@ async function handleAuth(auth: {
|
|
|
228
341
|
email_confirm: true,
|
|
229
342
|
});
|
|
230
343
|
|
|
344
|
+
let userId: string;
|
|
345
|
+
|
|
231
346
|
if (createError) {
|
|
232
347
|
if (createError.message.includes('already been registered')) {
|
|
233
|
-
// User exists —
|
|
234
|
-
// scenario specifies a different password than a previous run.
|
|
348
|
+
// User exists — look up their ID
|
|
235
349
|
const { data: listData } = await supabase.auth.admin.listUsers();
|
|
236
350
|
const existingUser = listData?.users?.find((u) => u.email === email);
|
|
237
351
|
if (existingUser) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
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);
|
|
242
359
|
}
|
|
243
360
|
} else {
|
|
244
361
|
console.error(` Failed to create auth user: ${createError.message}`);
|
|
245
362
|
process.exit(1);
|
|
246
363
|
}
|
|
364
|
+
} else {
|
|
365
|
+
userId = createData!.user.id;
|
|
366
|
+
console.log(` Created user ${email} (user: ${userId})`);
|
|
247
367
|
}
|
|
248
368
|
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
auth: { autoRefreshToken: false, persistSession: false },
|
|
255
|
-
})
|
|
256
|
-
: supabase;
|
|
257
|
-
|
|
258
|
-
const { data: signInData, error: signInError } =
|
|
259
|
-
await signInClient.auth.signInWithPassword({ email, password });
|
|
260
|
-
|
|
261
|
-
if (signInError || !signInData.session) {
|
|
262
|
-
console.error(
|
|
263
|
-
` Failed to sign in as ${email}: ${signInError?.message || 'no session returned'}`,
|
|
264
|
-
);
|
|
265
|
-
process.exit(1);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
const userId = signInData.user.id;
|
|
269
|
-
console.log(` Signed in as ${email} (user: ${userId})`);
|
|
270
|
-
|
|
271
|
-
// Use the REAL JWT from Supabase sign-in for the session cookie so auth
|
|
272
|
-
// works immediately against the real Supabase API. The externalApis below
|
|
273
|
-
// provide a fallback for when the CLI supports preload-based auth
|
|
274
|
-
// interception (no real Supabase calls needed at render time).
|
|
275
|
-
const fakeJwt = buildFakeJwt(userId, email);
|
|
276
|
-
const userResponse = buildUserResponse(signInData.user);
|
|
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 });
|
|
277
374
|
|
|
278
375
|
const projectRef = getProjectRef();
|
|
279
376
|
const sessionOutput = {
|
|
@@ -281,19 +378,16 @@ async function handleAuth(auth: {
|
|
|
281
378
|
{
|
|
282
379
|
name: `sb-${projectRef}-auth-token`,
|
|
283
380
|
value: JSON.stringify({
|
|
284
|
-
access_token:
|
|
285
|
-
refresh_token:
|
|
381
|
+
access_token: fakeJwt,
|
|
382
|
+
refresh_token: 'codeyam-mock-refresh-token',
|
|
286
383
|
token_type: 'bearer',
|
|
287
|
-
expires_in:
|
|
288
|
-
expires_at:
|
|
384
|
+
expires_in: 315360000,
|
|
385
|
+
expires_at: 9999999999,
|
|
289
386
|
}),
|
|
290
387
|
path: '/',
|
|
291
388
|
sameSite: 'Lax' as const,
|
|
292
389
|
},
|
|
293
390
|
],
|
|
294
|
-
// External API mocks — when the CLI supports propagating these, the
|
|
295
|
-
// preload module will intercept server-side getUser() calls so Next.js
|
|
296
|
-
// middleware returns the mock user without hitting real Supabase.
|
|
297
391
|
externalApis: {
|
|
298
392
|
[`${supabaseUrl}/auth/v1/user`]: {
|
|
299
393
|
body: userResponse,
|
|
@@ -319,7 +413,7 @@ async function handleAuth(auth: {
|
|
|
319
413
|
fs.writeFileSync(outputPath, JSON.stringify(sessionOutput, null, 2));
|
|
320
414
|
console.log(` Session cookies + auth mocks written to ${outputPath}`);
|
|
321
415
|
|
|
322
|
-
return userId
|
|
416
|
+
return userId!;
|
|
323
417
|
}
|
|
324
418
|
|
|
325
419
|
/**
|
|
@@ -358,7 +452,7 @@ async function main() {
|
|
|
358
452
|
// in seed data (e.g. for user_id foreign key columns with Supabase RLS)
|
|
359
453
|
if (auth) {
|
|
360
454
|
const userId = await handleAuth(
|
|
361
|
-
auth as { email: string; password
|
|
455
|
+
auth as { email: string; password?: string },
|
|
362
456
|
);
|
|
363
457
|
seed = replaceAuthPlaceholders(seed, userId);
|
|
364
458
|
}
|
|
@@ -368,7 +462,14 @@ async function main() {
|
|
|
368
462
|
console.log('Seed complete');
|
|
369
463
|
}
|
|
370
464
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
+
}
|