create-oke 0.10.2 → 0.11.0
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/README.md +2 -2
- package/package.json +2 -2
- package/src/ai-setup/apply.ts +123 -31
- package/src/ai-setup/catalog.ts +3 -3
- package/src/ai-setup/from-pref.ts +1 -1
- package/src/ai-setup/prompts.ts +1 -1
- package/src/cli.test.ts +183 -142
- package/src/cli.ts +190 -32
- package/src/create-defaults.test.ts +21 -15
- package/src/create-defaults.ts +33 -13
- package/src/customize-flow.test.ts +30 -35
- package/src/customize-flow.ts +68 -229
- package/src/drivers-catalog.ts +42 -48
- package/src/local-okengine.test.ts +50 -41
- package/src/local-okengine.ts +28 -14
- package/src/locales.test.ts +128 -0
- package/src/locales.ts +321 -0
- package/src/scaffold.ts +277 -33
- package/src/templates.ts +2 -8
- package/src/transform.test.ts +102 -38
- package/src/transform.ts +378 -132
- package/templates/advanced/.env.example +23 -3
- package/templates/advanced/README.md +1 -1
- package/templates/advanced/drizzle.config.ts +10 -13
- package/templates/advanced/oke.config.ts +21 -62
- package/templates/advanced/package.json +3 -1
- package/templates/advanced/src/app.ts +3 -10
- package/templates/advanced/src/core.ts +58 -0
- package/templates/advanced/src/db/schema.decl.ts +1 -1
- package/templates/advanced/src/db/seed/index.ts +2 -2
- package/templates/advanced/src/flows/notes/index.ts +3 -15
- package/templates/advanced/src/locales/index.ts +6 -0
- package/templates/advanced/tests/advanced.test.ts +1 -1
- package/templates/advanced/tsconfig.json +3 -0
- package/templates/standard/.env.example +23 -3
- package/templates/standard/README.md +1 -1
- package/templates/standard/drizzle.config.ts +10 -13
- package/templates/standard/oke.config.ts +18 -60
- package/templates/standard/package.json +3 -1
- package/templates/standard/src/app.ts +3 -10
- package/templates/standard/src/core.ts +55 -0
- package/templates/standard/src/db/schema.decl.ts +1 -1
- package/templates/standard/src/db/seed/index.ts +2 -2
- package/templates/standard/src/flows/notes/index.ts +3 -12
- package/templates/standard/src/locales/index.ts +6 -0
- package/templates/standard/tests/standard.test.ts +1 -1
- package/templates/standard/tsconfig.json +3 -0
- package/templates/advanced/src/core/channels.ts +0 -13
- package/templates/advanced/src/core/gates.ts +0 -12
- package/templates/advanced/src/core/index.ts +0 -12
- package/templates/advanced/src/core/store.ts +0 -8
- package/templates/advanced/src/core/vault.ts +0 -7
- package/templates/advanced/src/locales/ar.ts +0 -18
- package/templates/standard/src/core/channels.ts +0 -13
- package/templates/standard/src/core/gates.ts +0 -12
- package/templates/standard/src/core/index.ts +0 -12
- package/templates/standard/src/core/store.ts +0 -5
- package/templates/standard/src/core/vault.ts +0 -7
- package/templates/standard/src/locales/ar.ts +0 -18
package/src/cli.test.ts
CHANGED
|
@@ -35,22 +35,15 @@ import {
|
|
|
35
35
|
toCreateDefaults,
|
|
36
36
|
writeCreateDefaults,
|
|
37
37
|
} from "./create-defaults.ts";
|
|
38
|
-
import { pinsDockerReady,
|
|
38
|
+
import { pinsDockerReady, recommendedDefaults } from "./drivers-catalog.ts";
|
|
39
39
|
import { listTemplateFiles, scaffold, targetDirectoryBlockReason } from "./scaffold.ts";
|
|
40
|
-
import {
|
|
41
|
-
DEFAULT_TEMPLATE,
|
|
42
|
-
TEMPLATE_DEFAULT_MODE,
|
|
43
|
-
TEMPLATES,
|
|
44
|
-
packageRoot,
|
|
45
|
-
resolveTemplateDir,
|
|
46
|
-
} from "./templates.ts";
|
|
40
|
+
import { DEFAULT_TEMPLATE, TEMPLATES, packageRoot, resolveTemplateDir } from "./templates.ts";
|
|
47
41
|
import {
|
|
48
42
|
applyCreateAnswers,
|
|
49
43
|
sanitizeProjectName,
|
|
50
44
|
shouldSkipTemplatePath,
|
|
51
45
|
transformConfigForSqlDriver,
|
|
52
46
|
transformPackageJson,
|
|
53
|
-
transformSchemaForSqlDriver,
|
|
54
47
|
} from "./transform.ts";
|
|
55
48
|
|
|
56
49
|
describe("defaultsBranchOptions", () => {
|
|
@@ -80,12 +73,12 @@ describe("parseArgs", () => {
|
|
|
80
73
|
expect(parseArgs(["x", "--template", "standard"]).templateExplicit).toBe(true);
|
|
81
74
|
});
|
|
82
75
|
|
|
83
|
-
test("accepts --sql sqlite
|
|
84
|
-
expect(parseArgs(["x"]).sqlDriver).toBe("
|
|
76
|
+
test("accepts --sql postgres (sqlite removed)", () => {
|
|
77
|
+
expect(parseArgs(["x"]).sqlDriver).toBe("postgres");
|
|
85
78
|
expect(parseArgs(["x"]).sqlDriverExplicit).toBe(false);
|
|
86
79
|
expect(parseArgs(["x", "--sql", "postgres"]).sqlDriver).toBe("postgres");
|
|
87
|
-
expect(parseArgs(["x", "--sql=sqlite"]).sqlDriver).toBe("sqlite");
|
|
88
80
|
expect(parseArgs(["x", "--sql", "postgres"]).sqlDriverExplicit).toBe(true);
|
|
81
|
+
expect(() => parseArgs(["x", "--sql=sqlite"])).toThrow(/sql/);
|
|
89
82
|
});
|
|
90
83
|
|
|
91
84
|
test("rejects unknown template, option, and sql", () => {
|
|
@@ -124,6 +117,19 @@ describe("parseArgs flags", () => {
|
|
|
124
117
|
expect(parseArgs(["x"]).agentsMd).toBe(true);
|
|
125
118
|
});
|
|
126
119
|
|
|
120
|
+
test("accepts --locales ar,fr", () => {
|
|
121
|
+
expect(parseArgs(["x", "--locales", "ar,fr"]).locales).toEqual(["ar", "fr"]);
|
|
122
|
+
expect(parseArgs(["x", "--locales=ar"]).locales).toEqual(["ar"]);
|
|
123
|
+
expect(parseArgs(["x"]).locales).toEqual([]);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("accepts --pgdog / --no-pgdog", () => {
|
|
127
|
+
expect(parseArgs(["x", "--pgdog"]).pgdog).toBe(true);
|
|
128
|
+
expect(parseArgs(["x", "--no-pgdog"]).pgdog).toBe(false);
|
|
129
|
+
expect(parseArgs(["x"]).pgdog).toBeUndefined();
|
|
130
|
+
expect(() => parseArgs(["x", "--pgdog", "--no-pgdog"])).toThrow(/pgdog/);
|
|
131
|
+
});
|
|
132
|
+
|
|
127
133
|
test("rejects --install with --no-install", () => {
|
|
128
134
|
expect(() => parseArgs(["x", "--install", "--no-install"])).toThrow(/install/);
|
|
129
135
|
});
|
|
@@ -146,6 +152,8 @@ function recommendedAnswers(overrides: Partial<InteractiveAnswers> = {}): Intera
|
|
|
146
152
|
agentsMd: true,
|
|
147
153
|
createDefaults: undefined,
|
|
148
154
|
aiApply: null,
|
|
155
|
+
locales: [],
|
|
156
|
+
pgdog: false,
|
|
149
157
|
...overrides,
|
|
150
158
|
};
|
|
151
159
|
}
|
|
@@ -165,8 +173,8 @@ describe("scaffoldArgsFromAnswers ≡ flag-driven", () => {
|
|
|
165
173
|
}
|
|
166
174
|
});
|
|
167
175
|
|
|
168
|
-
test("interactive
|
|
169
|
-
expect(scaffoldArgsFromAnswers(recommendedAnswers()).sqlDriver).toBe("
|
|
176
|
+
test("interactive defaults to postgres SQL", () => {
|
|
177
|
+
expect(scaffoldArgsFromAnswers(recommendedAnswers()).sqlDriver).toBe("postgres");
|
|
170
178
|
expect(
|
|
171
179
|
scaffoldArgsFromCli(parseArgs(["x", "--template", "standard", "--sql", "postgres"]))
|
|
172
180
|
.sqlDriver,
|
|
@@ -206,22 +214,20 @@ describe("wizard ← Back", () => {
|
|
|
206
214
|
});
|
|
207
215
|
|
|
208
216
|
describe("aiSetupProviderFor", () => {
|
|
209
|
-
test("prefers ollama when
|
|
217
|
+
test("prefers ollama when prod/dev is ollama even if menu is mock", () => {
|
|
210
218
|
expect(
|
|
211
219
|
aiSetupProviderFor("mock", {
|
|
212
|
-
|
|
213
|
-
docker: "ollama",
|
|
220
|
+
dev: "mock",
|
|
214
221
|
test: "mock",
|
|
215
222
|
prod: "ollama",
|
|
216
223
|
}),
|
|
217
224
|
).toBe("ollama");
|
|
218
225
|
});
|
|
219
226
|
|
|
220
|
-
test("keeps
|
|
227
|
+
test("keeps menu ollama", () => {
|
|
221
228
|
expect(
|
|
222
229
|
aiSetupProviderFor("ollama", {
|
|
223
|
-
|
|
224
|
-
docker: "anthropic",
|
|
230
|
+
dev: "ollama",
|
|
225
231
|
test: "mock",
|
|
226
232
|
prod: "anthropic",
|
|
227
233
|
}),
|
|
@@ -230,7 +236,7 @@ describe("aiSetupProviderFor", () => {
|
|
|
230
236
|
});
|
|
231
237
|
|
|
232
238
|
describe("interactive branches", () => {
|
|
233
|
-
test("recommended → no createDefaults, no
|
|
239
|
+
test("recommended → no createDefaults, Docker-first pins, no .oke/mode", async () => {
|
|
234
240
|
const dir = mkdtempSync(join(tmpdir(), "oke-rec-"));
|
|
235
241
|
rmSync(dir, { recursive: true, force: true });
|
|
236
242
|
try {
|
|
@@ -241,15 +247,17 @@ describe("interactive branches", () => {
|
|
|
241
247
|
});
|
|
242
248
|
expect(code).toBe(0);
|
|
243
249
|
const config = readFileSync(join(dir, "oke.config.ts"), "utf8");
|
|
244
|
-
expect(config).
|
|
245
|
-
expect(
|
|
250
|
+
expect(config).toMatch(/vault:\s*\{\s*dev: "vault"/);
|
|
251
|
+
expect(config).not.toMatch(/^\s*sql:\s*\{/m);
|
|
252
|
+
expect(existsSync(join(dir, ".oke", "mode"))).toBe(false);
|
|
253
|
+
expect(existsSync(join(dir, "docker", "docker-compose.yml"))).toBe(true);
|
|
246
254
|
expect(existsSync(join(dir, "src", "flows", "notes", "index.ts"))).toBe(true);
|
|
247
255
|
} finally {
|
|
248
256
|
rmSync(dir, { recursive: true, force: true });
|
|
249
257
|
}
|
|
250
258
|
});
|
|
251
259
|
|
|
252
|
-
test("recommended advanced →
|
|
260
|
+
test("recommended advanced → advanced Notes flows + compose", async () => {
|
|
253
261
|
const dir = mkdtempSync(join(tmpdir(), "oke-rec-adv-"));
|
|
254
262
|
rmSync(dir, { recursive: true, force: true });
|
|
255
263
|
try {
|
|
@@ -259,10 +267,13 @@ describe("interactive branches", () => {
|
|
|
259
267
|
ask: async () => recommendedAnswers({ name: dir, choice: "advanced" }),
|
|
260
268
|
});
|
|
261
269
|
expect(code).toBe(0);
|
|
262
|
-
expect(
|
|
270
|
+
expect(existsSync(join(dir, ".oke", "mode"))).toBe(false);
|
|
263
271
|
const notes = readFileSync(join(dir, "src", "flows", "notes", "index.ts"), "utf8");
|
|
264
272
|
expect(notes).toContain('flow("notes.digest"');
|
|
265
273
|
expect(notes).toContain('flow("notes.attach"');
|
|
274
|
+
expect(readFileSync(join(dir, "oke.config.ts"), "utf8")).toMatch(
|
|
275
|
+
/index:\s*\{\s*test:\s*"memory",\s*prod:\s*"meilisearch"/,
|
|
276
|
+
);
|
|
266
277
|
} finally {
|
|
267
278
|
rmSync(dir, { recursive: true, force: true });
|
|
268
279
|
}
|
|
@@ -271,7 +282,7 @@ describe("interactive branches", () => {
|
|
|
271
282
|
test("reuse previous settings round-trip", async () => {
|
|
272
283
|
const home = mkdtempSync(join(tmpdir(), "oke-reuse-"));
|
|
273
284
|
const path = createDefaultsPath(home);
|
|
274
|
-
const base = recommendedDefaults("
|
|
285
|
+
const base = recommendedDefaults("docker-ready");
|
|
275
286
|
const saved = {
|
|
276
287
|
...base,
|
|
277
288
|
drivers: {
|
|
@@ -279,9 +290,8 @@ describe("interactive branches", () => {
|
|
|
279
290
|
store: {
|
|
280
291
|
...base.drivers.store,
|
|
281
292
|
sql: {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
test: "memory",
|
|
293
|
+
dev: "postgres",
|
|
294
|
+
test: "pglite",
|
|
285
295
|
prod: "postgres",
|
|
286
296
|
},
|
|
287
297
|
},
|
|
@@ -305,7 +315,7 @@ describe("interactive branches", () => {
|
|
|
305
315
|
});
|
|
306
316
|
expect(code).toBe(0);
|
|
307
317
|
const config = readFileSync(join(dir, "oke.config.ts"), "utf8");
|
|
308
|
-
expect(config).
|
|
318
|
+
expect(config).toMatch(/vault:\s*\{\s*dev: "vault"/);
|
|
309
319
|
const templateConfig = readFileSync(
|
|
310
320
|
join(resolveTemplateDir("standard"), "oke.config.ts"),
|
|
311
321
|
"utf8",
|
|
@@ -322,21 +332,23 @@ describe("interactive branches", () => {
|
|
|
322
332
|
const path = createDefaultsPath(home);
|
|
323
333
|
const customized = toCreateDefaults({
|
|
324
334
|
template: "standard",
|
|
325
|
-
profile: "
|
|
335
|
+
profile: "docker-ready",
|
|
326
336
|
drivers: {
|
|
327
337
|
store: {
|
|
328
|
-
sql:
|
|
329
|
-
kv:
|
|
330
|
-
files:
|
|
338
|
+
sql: pinsDockerReady("postgres", "pglite"),
|
|
339
|
+
kv: pinsDockerReady("redis", "memory"),
|
|
340
|
+
files: pinsDockerReady("s3", "memory"),
|
|
331
341
|
index: null,
|
|
332
342
|
},
|
|
333
|
-
signal:
|
|
334
|
-
clock:
|
|
335
|
-
vault:
|
|
336
|
-
channel: { email:
|
|
343
|
+
signal: pinsDockerReady("redis", "memory"),
|
|
344
|
+
clock: pinsDockerReady("postgres", "frozen"),
|
|
345
|
+
vault: pinsDockerReady("vault", "memory"),
|
|
346
|
+
channel: { email: pinsDockerReady("smtp", "console") },
|
|
337
347
|
ai: null,
|
|
338
348
|
},
|
|
339
349
|
ai: { enabled: false, provider: null, driver: null },
|
|
350
|
+
locales: [],
|
|
351
|
+
pgdog: false,
|
|
340
352
|
});
|
|
341
353
|
|
|
342
354
|
const dir = mkdtempSync(join(tmpdir(), "oke-custom-app-"));
|
|
@@ -357,55 +369,17 @@ describe("interactive branches", () => {
|
|
|
357
369
|
});
|
|
358
370
|
expect(code).toBe(0);
|
|
359
371
|
expect(existsSync(path)).toBe(true);
|
|
360
|
-
expect(readCreateDefaults(path)?.drivers.store.sql.
|
|
361
|
-
expect(readFileSync(join(dir, "oke.config.ts"), "utf8")).
|
|
362
|
-
|
|
363
|
-
|
|
372
|
+
expect(readCreateDefaults(path)?.drivers.store.sql.dev).toBe("postgres");
|
|
373
|
+
expect(readFileSync(join(dir, "oke.config.ts"), "utf8")).toMatch(
|
|
374
|
+
/vault:\s*\{\s*dev: "vault"/,
|
|
375
|
+
);
|
|
376
|
+
expect(existsSync(join(dir, ".oke", "mode"))).toBe(false);
|
|
364
377
|
} finally {
|
|
365
378
|
rmSync(home, { recursive: true, force: true });
|
|
366
379
|
rmSync(dir, { recursive: true, force: true });
|
|
367
380
|
}
|
|
368
381
|
});
|
|
369
382
|
|
|
370
|
-
test("docker-ready profile seeds .oke/mode as docker", async () => {
|
|
371
|
-
const customized = toCreateDefaults({
|
|
372
|
-
template: "advanced",
|
|
373
|
-
profile: "docker-ready",
|
|
374
|
-
drivers: {
|
|
375
|
-
store: {
|
|
376
|
-
sql: pinsDockerReady("sqlite", "postgres", "memory"),
|
|
377
|
-
kv: pinsLocalOnly("memory", "redis", "memory"),
|
|
378
|
-
files: pinsLocalOnly("fs", "s3", "memory"),
|
|
379
|
-
index: null,
|
|
380
|
-
},
|
|
381
|
-
signal: pinsLocalOnly("memory", "redis", "memory"),
|
|
382
|
-
clock: pinsLocalOnly("memory", "file", "frozen"),
|
|
383
|
-
vault: pinsLocalOnly("env", "openbao", "memory"),
|
|
384
|
-
channel: { email: pinsLocalOnly("console", "smtp", "console") },
|
|
385
|
-
ai: null,
|
|
386
|
-
},
|
|
387
|
-
ai: { enabled: false, provider: null, driver: null },
|
|
388
|
-
});
|
|
389
|
-
const dir = mkdtempSync(join(tmpdir(), "oke-docker-mode-"));
|
|
390
|
-
rmSync(dir, { recursive: true, force: true });
|
|
391
|
-
try {
|
|
392
|
-
const code = await run([dir], {
|
|
393
|
-
stdinIsTTY: true,
|
|
394
|
-
runPostScaffold: false,
|
|
395
|
-
ask: async () =>
|
|
396
|
-
recommendedAnswers({
|
|
397
|
-
name: dir,
|
|
398
|
-
createDefaults: customized,
|
|
399
|
-
aiApply: null,
|
|
400
|
-
}),
|
|
401
|
-
});
|
|
402
|
-
expect(code).toBe(0);
|
|
403
|
-
expect(readFileSync(join(dir, ".oke", "mode"), "utf8").trim()).toBe("docker");
|
|
404
|
-
} finally {
|
|
405
|
-
rmSync(dir, { recursive: true, force: true });
|
|
406
|
-
}
|
|
407
|
-
});
|
|
408
|
-
|
|
409
383
|
test("non-TTY --yes stays zero-prompt recommended", async () => {
|
|
410
384
|
const dir = mkdtempSync(join(tmpdir(), "oke-yes-"));
|
|
411
385
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -422,7 +396,7 @@ describe("interactive branches", () => {
|
|
|
422
396
|
expect(code).toBe(0);
|
|
423
397
|
expect(askCalled).toBe(false);
|
|
424
398
|
const config = readFileSync(join(dir, "oke.config.ts"), "utf8");
|
|
425
|
-
expect(config).
|
|
399
|
+
expect(config).toMatch(/vault:\s*\{\s*dev: "vault"/);
|
|
426
400
|
expect(config).not.toMatch(/\bai:\s*\{/);
|
|
427
401
|
} finally {
|
|
428
402
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -452,45 +426,25 @@ describe("transformPackageJson", () => {
|
|
|
452
426
|
});
|
|
453
427
|
});
|
|
454
428
|
|
|
455
|
-
describe("transformSchemaForSqlDriver", () => {
|
|
456
|
-
const sqliteSchema = `import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
|
|
457
|
-
export const pings = sqliteTable("pings", {});
|
|
458
|
-
`;
|
|
459
|
-
|
|
460
|
-
test("postgres swaps dialect imports and table helper", () => {
|
|
461
|
-
const next = transformSchemaForSqlDriver(sqliteSchema, "postgres");
|
|
462
|
-
expect(next).toContain('from "drizzle-orm/pg-core"');
|
|
463
|
-
expect(next).toContain("pgTable(");
|
|
464
|
-
expect(next).not.toContain("sqliteTable");
|
|
465
|
-
expect(next).not.toContain("sqlite-core");
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
test("sqlite is idempotent on template sources", () => {
|
|
469
|
-
expect(transformSchemaForSqlDriver(sqliteSchema, "sqlite")).toBe(sqliteSchema);
|
|
470
|
-
});
|
|
471
|
-
});
|
|
472
|
-
|
|
473
429
|
describe("transformConfigForSqlDriver", () => {
|
|
474
430
|
const config = `export default defineConfig({
|
|
475
431
|
drivers: {
|
|
476
432
|
store: {
|
|
477
433
|
sql: {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
prod: "postgres",
|
|
434
|
+
dev: "memory",
|
|
435
|
+
test: "pglite",
|
|
436
|
+
prod: "memory",
|
|
482
437
|
},
|
|
483
438
|
},
|
|
484
439
|
},
|
|
485
440
|
});
|
|
486
441
|
`;
|
|
487
442
|
|
|
488
|
-
test("postgres pins
|
|
443
|
+
test("postgres pins dev/prod and leaves test pglite", () => {
|
|
489
444
|
const next = transformConfigForSqlDriver(config, "postgres");
|
|
490
|
-
expect(next).toContain('
|
|
491
|
-
expect(next).toContain('docker: "postgres"');
|
|
445
|
+
expect(next).toContain('dev: "postgres"');
|
|
492
446
|
expect(next).toContain('prod: "postgres"');
|
|
493
|
-
expect(next).toContain('test: "
|
|
447
|
+
expect(next).toContain('test: "pglite"');
|
|
494
448
|
});
|
|
495
449
|
});
|
|
496
450
|
|
|
@@ -517,28 +471,30 @@ describe("formatCdPath", () => {
|
|
|
517
471
|
});
|
|
518
472
|
|
|
519
473
|
describe("scaffold structure", () => {
|
|
520
|
-
test("each clean template produces its source tree (minus skips)", () => {
|
|
474
|
+
test("each clean template produces its source tree (minus skips)", async () => {
|
|
521
475
|
for (const id of TEMPLATES) {
|
|
522
476
|
const dir = mkdtempSync(join(tmpdir(), `create-oke-${id}-`));
|
|
523
477
|
try {
|
|
524
478
|
const templateDir = resolveTemplateDir(id);
|
|
525
479
|
const expected = listTemplateFiles(templateDir);
|
|
526
|
-
const result = scaffold({
|
|
480
|
+
const result = await scaffold({
|
|
527
481
|
targetDir: join(dir, id),
|
|
528
482
|
name: `app-${id}`,
|
|
529
483
|
source: { kind: "template", id },
|
|
530
484
|
});
|
|
531
|
-
const extras = ["AGENTS.md"
|
|
485
|
+
const extras = ["AGENTS.md"];
|
|
532
486
|
if (expected.includes(".env.example")) extras.push(".env.local");
|
|
533
|
-
|
|
534
|
-
expect(
|
|
535
|
-
|
|
536
|
-
);
|
|
487
|
+
const composeExtras = result.files.filter((f) => f.startsWith("docker/"));
|
|
488
|
+
expect(composeExtras.length).toBeGreaterThan(0);
|
|
489
|
+
expect(result.files).toContain("docker/docker-compose.yml");
|
|
490
|
+
expect(existsSync(join(result.targetDir, ".oke", "mode"))).toBe(false);
|
|
491
|
+
for (const f of expected) expect(result.files).toContain(f);
|
|
492
|
+
for (const f of extras) expect(result.files).toContain(f);
|
|
537
493
|
expect(result.files).toContain(".gitignore");
|
|
538
494
|
expect(result.files).toContain("README.md");
|
|
539
495
|
expect(result.files).toContain(".github/workflows/ci.yml");
|
|
540
496
|
expect(result.files).toContain("tsconfig.json");
|
|
541
|
-
expect(result.sqlDriver).toBe("
|
|
497
|
+
expect(result.sqlDriver).toBe("postgres");
|
|
542
498
|
expect(readFileSync(join(result.targetDir, "AGENTS.md"), "utf8")).toMatch(
|
|
543
499
|
/one law|on\(Trigger\)/i,
|
|
544
500
|
);
|
|
@@ -548,7 +504,10 @@ describe("scaffold structure", () => {
|
|
|
548
504
|
expect(readme).toMatch(/notes\.(create|attach|digest)|main\.health/);
|
|
549
505
|
expect(readme).toMatch(/scaffold|Included vs you build/i);
|
|
550
506
|
expect(readme).toMatch(/\.github\/workflows\/ci\.yml/);
|
|
551
|
-
|
|
507
|
+
const gitignore = readFileSync(join(result.targetDir, ".gitignore"), "utf8");
|
|
508
|
+
expect(gitignore).toMatch(/node_modules/);
|
|
509
|
+
expect(gitignore).toMatch(/\.env\.docker/);
|
|
510
|
+
expect(gitignore).not.toMatch(/^\/docker\/compose\.yml$/m);
|
|
552
511
|
const ciYml = readFileSync(join(result.targetDir, ".github/workflows/ci.yml"), "utf8");
|
|
553
512
|
expect(() => Bun.YAML.parse(ciYml)).not.toThrow();
|
|
554
513
|
const ci = Bun.YAML.parse(ciYml) as {
|
|
@@ -564,43 +523,53 @@ describe("scaffold structure", () => {
|
|
|
564
523
|
const appTs = readFileSync(join(result.targetDir, "src/app.ts"), "utf8");
|
|
565
524
|
expect(appTs).not.toMatch(/Object\.assign/);
|
|
566
525
|
expect(appTs).not.toMatch(/env:\s*["']test["']/);
|
|
567
|
-
// stores/secrets/signals/channel.templates auto-register — the
|
|
568
|
-
// minimal `oke({ name: "notes" })` shape carries no explicit arrays.
|
|
569
526
|
expect(appTs).not.toMatch(/stores:\s*\[/);
|
|
570
527
|
expect(appTs).toMatch(/oke\(\{\s*name:\s*["']notes["']\s*\}\)/);
|
|
571
528
|
const pkg = JSON.parse(readFileSync(join(result.targetDir, "package.json"), "utf8")) as {
|
|
572
529
|
name: string;
|
|
573
530
|
dependencies: { okengine: string };
|
|
574
531
|
scripts: { typecheck?: string; test?: string };
|
|
575
|
-
devDependencies: {
|
|
532
|
+
devDependencies: {
|
|
533
|
+
typescript?: string;
|
|
534
|
+
"@electric-sql/pglite"?: string;
|
|
535
|
+
"@electric-sql/pglite-pgvector"?: string;
|
|
536
|
+
};
|
|
576
537
|
};
|
|
577
538
|
expect(pkg.name).toBe(`app-${id}`);
|
|
578
539
|
expect(pkg.dependencies.okengine).not.toMatch(/^file:\.\./);
|
|
579
540
|
expect(pkg.scripts.typecheck).toBe("tsc --noEmit");
|
|
580
|
-
expect(pkg.scripts.test).toBe("
|
|
541
|
+
expect(pkg.scripts.test).toBe("oke test");
|
|
581
542
|
expect(pkg.devDependencies.typescript).toBeTruthy();
|
|
543
|
+
expect(pkg.devDependencies["@electric-sql/pglite"]).toBe("^0.5.4");
|
|
544
|
+
expect(pkg.devDependencies["@electric-sql/pglite-pgvector"]).toBe("^0.0.5");
|
|
545
|
+
const drizzle = readFileSync(join(result.targetDir, "drizzle.config.ts"), "utf8");
|
|
546
|
+
expect(drizzle).toContain('dialect: "postgresql"');
|
|
547
|
+
expect(drizzle).toContain("DATABASE_URL");
|
|
548
|
+
expect(drizzle).toContain("OKE_STORE_SQL_URL");
|
|
549
|
+
expect(drizzle).not.toContain("OKE_SQLITE_URL");
|
|
550
|
+
expect(drizzle).toContain(".oke/schema/oke.ts");
|
|
551
|
+
expect(existsSync(join(result.targetDir, ".oke/schema/oke.ts"))).toBe(true);
|
|
552
|
+
const sqlYml = readFileSync(join(result.targetDir, "docker/docker-compose.yml"), "utf8");
|
|
553
|
+
expect(sqlYml).toMatch(/healthcheck/);
|
|
554
|
+
expect(sqlYml).toMatch(/pg_isready/);
|
|
582
555
|
} finally {
|
|
583
556
|
rmSync(dir, { recursive: true, force: true });
|
|
584
557
|
}
|
|
585
558
|
}
|
|
586
559
|
});
|
|
587
560
|
|
|
588
|
-
test("standard has Notes layout (
|
|
561
|
+
test("standard has Notes layout (core.ts + notes flows)", async () => {
|
|
589
562
|
const dir = mkdtempSync(join(tmpdir(), "create-oke-standard-assert-"));
|
|
590
563
|
try {
|
|
591
|
-
const result = scaffold({
|
|
564
|
+
const result = await scaffold({
|
|
592
565
|
targetDir: join(dir, "standard"),
|
|
593
566
|
name: "standard-app",
|
|
594
567
|
source: { kind: "template", id: "standard" },
|
|
595
568
|
});
|
|
596
569
|
for (const path of [
|
|
597
|
-
"src/core
|
|
598
|
-
"src/core/vault.ts",
|
|
599
|
-
"src/core/channels.ts",
|
|
600
|
-
"src/core/store.ts",
|
|
601
|
-
"src/core/index.ts",
|
|
570
|
+
"src/core.ts",
|
|
602
571
|
"src/locales/en.ts",
|
|
603
|
-
"src/locales/
|
|
572
|
+
"src/locales/index.ts",
|
|
604
573
|
"src/flows/main/shapes.ts",
|
|
605
574
|
"src/flows/main/signals.ts",
|
|
606
575
|
"src/flows/notes/index.ts",
|
|
@@ -614,6 +583,19 @@ describe("scaffold structure", () => {
|
|
|
614
583
|
const notes = readFileSync(join(result.targetDir, "src/flows/notes/index.ts"), "utf8");
|
|
615
584
|
expect(notes).toContain('flow("notes.create"');
|
|
616
585
|
expect(notes).not.toContain('flow("notes.digest"');
|
|
586
|
+
expect(existsSync(join(result.targetDir, "src/locales/ar.ts"))).toBe(false);
|
|
587
|
+
expect(readFileSync(join(result.targetDir, "oke.config.ts"), "utf8")).toContain(
|
|
588
|
+
'locales: ["en"]',
|
|
589
|
+
);
|
|
590
|
+
expect(readFileSync(join(result.targetDir, "oke.config.ts"), "utf8")).not.toMatch(
|
|
591
|
+
/^\s*pgdog:\s*"/m,
|
|
592
|
+
);
|
|
593
|
+
const appTsStandard = readFileSync(join(result.targetDir, "src/app.ts"), "utf8");
|
|
594
|
+
expect(appTsStandard).toContain('import "@/core"');
|
|
595
|
+
expect(appTsStandard).not.toContain('import "@/locales/');
|
|
596
|
+
expect(readFileSync(join(result.targetDir, "src/core.ts"), "utf8")).toContain(
|
|
597
|
+
'import "@/locales"',
|
|
598
|
+
);
|
|
617
599
|
const all = result.files
|
|
618
600
|
.filter((f) => f.endsWith(".ts") || f.endsWith(".md"))
|
|
619
601
|
.map((f) => readFileSync(join(result.targetDir, f), "utf8"))
|
|
@@ -624,38 +606,96 @@ describe("scaffold structure", () => {
|
|
|
624
606
|
}
|
|
625
607
|
});
|
|
626
608
|
|
|
627
|
-
test("
|
|
609
|
+
test("locales ar,fr adds catalogs and keeps en default", async () => {
|
|
610
|
+
const dir = mkdtempSync(join(tmpdir(), "create-oke-locales-"));
|
|
611
|
+
try {
|
|
612
|
+
const result = await scaffold({
|
|
613
|
+
targetDir: join(dir, "i18n-app"),
|
|
614
|
+
name: "i18n-app",
|
|
615
|
+
source: { kind: "template", id: "standard" },
|
|
616
|
+
locales: ["ar", "fr"],
|
|
617
|
+
});
|
|
618
|
+
expect(result.locales).toEqual(["ar", "fr"]);
|
|
619
|
+
expect(existsSync(join(result.targetDir, "src/locales/ar.ts"))).toBe(true);
|
|
620
|
+
expect(existsSync(join(result.targetDir, "src/locales/fr.ts"))).toBe(true);
|
|
621
|
+
const config = readFileSync(join(result.targetDir, "oke.config.ts"), "utf8");
|
|
622
|
+
expect(config).toContain('"ar"');
|
|
623
|
+
expect(config).toContain('"fr"');
|
|
624
|
+
expect(config).toContain('"ar": "rtl"');
|
|
625
|
+
const app = readFileSync(join(result.targetDir, "src/app.ts"), "utf8");
|
|
626
|
+
expect(app).toContain('import "@/core"');
|
|
627
|
+
expect(app).not.toContain('import "@/locales/');
|
|
628
|
+
const localesIndex = readFileSync(join(result.targetDir, "src/locales/index.ts"), "utf8");
|
|
629
|
+
expect(localesIndex).toContain('import "./en";');
|
|
630
|
+
expect(localesIndex).toContain('import "./ar";');
|
|
631
|
+
expect(localesIndex).toContain('import "./fr";');
|
|
632
|
+
} finally {
|
|
633
|
+
rmSync(dir, { recursive: true, force: true });
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
test("pgdog opt-in pins images.pgdog and compose service", async () => {
|
|
638
|
+
const dir = mkdtempSync(join(tmpdir(), "create-oke-pgdog-"));
|
|
639
|
+
try {
|
|
640
|
+
const off = await scaffold({
|
|
641
|
+
targetDir: join(dir, "no-pool"),
|
|
642
|
+
name: "no-pool",
|
|
643
|
+
source: { kind: "template", id: "standard" },
|
|
644
|
+
pgdog: false,
|
|
645
|
+
});
|
|
646
|
+
expect(off.pgdog).toBe(false);
|
|
647
|
+
expect(readFileSync(join(off.targetDir, "oke.config.ts"), "utf8")).not.toMatch(
|
|
648
|
+
/^\s*pgdog:\s*"/m,
|
|
649
|
+
);
|
|
650
|
+
|
|
651
|
+
const on = await scaffold({
|
|
652
|
+
targetDir: join(dir, "with-pool"),
|
|
653
|
+
name: "with-pool",
|
|
654
|
+
source: { kind: "template", id: "standard" },
|
|
655
|
+
pgdog: true,
|
|
656
|
+
});
|
|
657
|
+
expect(on.pgdog).toBe(true);
|
|
658
|
+
const config = readFileSync(join(on.targetDir, "oke.config.ts"), "utf8");
|
|
659
|
+
expect(config).toMatch(/pgdog:\s*"ghcr\.io\/pgdogdev\/pgdog:/);
|
|
660
|
+
const compose = readFileSync(join(on.targetDir, "docker/docker-compose.yml"), "utf8");
|
|
661
|
+
expect(compose).toContain("pgdog:");
|
|
662
|
+
expect(existsSync(join(on.targetDir, "docker/pgdog/pgdog.toml"))).toBe(true);
|
|
663
|
+
} finally {
|
|
664
|
+
rmSync(dir, { recursive: true, force: true });
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
test("--sql postgres pins store.sql (abstract schema stays dialect-agnostic)", async () => {
|
|
628
669
|
const dir = mkdtempSync(join(tmpdir(), "create-oke-sql-pg-"));
|
|
629
670
|
try {
|
|
630
|
-
const result = scaffold({
|
|
671
|
+
const result = await scaffold({
|
|
631
672
|
targetDir: join(dir, "pg-app"),
|
|
632
673
|
name: "pg-app",
|
|
633
674
|
source: { kind: "template", id: "standard" },
|
|
634
675
|
sqlDriver: "postgres",
|
|
635
676
|
});
|
|
636
677
|
expect(result.sqlDriver).toBe("postgres");
|
|
637
|
-
// Abstract decl is dialect-agnostic — emit picks pgTable at sync time.
|
|
638
678
|
const decl = readFileSync(join(result.targetDir, "src/db/schema.decl.ts"), "utf8");
|
|
639
679
|
expect(decl).toContain("store.schema.table(");
|
|
640
680
|
expect(decl).not.toContain("sqliteTable");
|
|
641
681
|
expect(decl).not.toContain("pgTable");
|
|
642
682
|
const drizzle = readFileSync(join(result.targetDir, "drizzle.config.ts"), "utf8");
|
|
643
|
-
expect(drizzle).toContain("
|
|
644
|
-
expect(drizzle).toContain("schema.
|
|
683
|
+
expect(drizzle).toContain('dialect: "postgresql"');
|
|
684
|
+
expect(drizzle).toContain("schema.drizzle.ts");
|
|
685
|
+
expect(drizzle).toContain(".oke/schema/oke.ts");
|
|
645
686
|
const config = readFileSync(join(result.targetDir, "oke.config.ts"), "utf8");
|
|
646
|
-
|
|
647
|
-
expect(config).toMatch(
|
|
648
|
-
expect(config).toMatch(/
|
|
649
|
-
expect(config).toMatch(/sql:\s*\{[\s\S]*test:\s*"memory"/);
|
|
687
|
+
// postgres is DRIVER_DEFAULTS — sparse templates omit the sql map entirely.
|
|
688
|
+
expect(config).not.toMatch(/^\s*sql:\s*\{/m);
|
|
689
|
+
expect(config).toMatch(/vault:\s*\{\s*dev: "vault"/);
|
|
650
690
|
} finally {
|
|
651
691
|
rmSync(dir, { recursive: true, force: true });
|
|
652
692
|
}
|
|
653
693
|
});
|
|
654
694
|
|
|
655
|
-
test("--no-agents-md skips AGENTS.md", () => {
|
|
695
|
+
test("--no-agents-md skips AGENTS.md", async () => {
|
|
656
696
|
const dir = mkdtempSync(join(tmpdir(), "create-oke-no-agents-"));
|
|
657
697
|
try {
|
|
658
|
-
const result = scaffold({
|
|
698
|
+
const result = await scaffold({
|
|
659
699
|
targetDir: join(dir, "standard"),
|
|
660
700
|
name: "no-agents",
|
|
661
701
|
source: { kind: "template", id: "standard" },
|
|
@@ -742,7 +782,6 @@ describe("non-TTY CLI", () => {
|
|
|
742
782
|
const err = await new Response(proc.stderr).text();
|
|
743
783
|
expect(code).toBe(1);
|
|
744
784
|
expect(err).toMatch(/missing <name>/);
|
|
745
|
-
// Clack intro / select chrome must not appear.
|
|
746
785
|
expect(out + err).not.toMatch(/Project name|Start from a worked example/);
|
|
747
786
|
expect(out + err).not.toMatch(/│/);
|
|
748
787
|
});
|
|
@@ -765,7 +804,9 @@ describe("non-TTY CLI", () => {
|
|
|
765
804
|
label: "standard",
|
|
766
805
|
okengineDependency: "x",
|
|
767
806
|
files: [],
|
|
768
|
-
sqlDriver: "
|
|
807
|
+
sqlDriver: "postgres" as const,
|
|
808
|
+
locales: [],
|
|
809
|
+
pgdog: false,
|
|
769
810
|
};
|
|
770
811
|
expect(nextStepsText(result)).toContain("oke dev");
|
|
771
812
|
expect(nextStepsText(result)).toContain("bun install");
|