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/README.md
CHANGED
|
@@ -50,8 +50,8 @@ model selection runs in the wizard **before** `bun install` (writes `.env.local`
|
|
|
50
50
|
|
|
51
51
|
Store-bearing scaffolds ship `drizzle.config.ts` and `.env.example`. Local
|
|
52
52
|
`oke dev` auto-runs `oke db push` on schema change and asks once whether to
|
|
53
|
-
seed;
|
|
54
|
-
|
|
53
|
+
seed; compose stack passwords are generated and preserved per project
|
|
54
|
+
(built-in vault needs no extra container). Both
|
|
55
55
|
templates include `.github/workflows/ci.yml` (typecheck + `bun test` on push/PR).
|
|
56
56
|
|
|
57
57
|
Every scaffold writes an `AGENTS.md` so coding agents know the OKE contract
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-oke",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Scaffold an okengine app — bunx create-oke@latest <name>",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
24
|
"typecheck": "tsc --noEmit",
|
|
25
|
-
"test": "bun test"
|
|
25
|
+
"test": "bun test src"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@clack/prompts": "^1.7.0"
|
package/src/ai-setup/apply.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Write AI driver config, env, and `src/core
|
|
2
|
+
* Write AI driver config, env, and AI models into `src/core.ts` for `oke ai setup`.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
7
|
import { OLLAMA_IMAGE } from "../drivers-catalog.ts";
|
|
8
|
+
import { extractImages, findImagesBlock, replaceImagesBlock } from "../transform.ts";
|
|
8
9
|
|
|
9
10
|
/** Choices applied to the project. */
|
|
10
11
|
export type AiSetupApplyInput = {
|
|
@@ -69,13 +70,15 @@ export function applyAiSetup(
|
|
|
69
70
|
: existsSync(envExample)
|
|
70
71
|
? readFileSync(envExample, "utf8")
|
|
71
72
|
: "";
|
|
72
|
-
// Driver / URL stay commented so docker mode can hydrate
|
|
73
|
-
// `docker/.env.docker` without being shadowed. The
|
|
74
|
-
// written
|
|
75
|
-
//
|
|
73
|
+
// Driver / URL / model stay commented so docker mode can hydrate from
|
|
74
|
+
// `docker/.env.docker` without being shadowed. The chosen model is still
|
|
75
|
+
// written into `.env.local` as a hint (and into `.env.docker` when present);
|
|
76
|
+
// `loadExistingStackControls` seeds commented `OKE_AI_MODEL` on first boot.
|
|
76
77
|
env = upsertEnv(env, "OKE_AI_DRIVER", input.driver, { comment: true });
|
|
77
78
|
if (input.baseUrl) env = upsertEnv(env, "OKE_AI_URL", input.baseUrl, { comment: true });
|
|
78
|
-
if (input.chatModel)
|
|
79
|
+
if (input.chatModel) {
|
|
80
|
+
env = upsertEnv(env, "OKE_AI_MODEL", input.chatModel, { comment: true });
|
|
81
|
+
}
|
|
79
82
|
if (input.visionModel) {
|
|
80
83
|
env = upsertEnv(env, "OKE_AI_VISION_MODEL", input.visionModel, { comment: true });
|
|
81
84
|
}
|
|
@@ -91,10 +94,7 @@ export function applyAiSetup(
|
|
|
91
94
|
}
|
|
92
95
|
writeFileSync(envPath, env.endsWith("\n") ? env : `${env}\n`, "utf8");
|
|
93
96
|
|
|
94
|
-
const aiTsPath =
|
|
95
|
-
mkdirSync(dirname(aiTsPath), { recursive: true });
|
|
96
|
-
writeFileSync(aiTsPath, renderAiTs(input), "utf8");
|
|
97
|
-
ensureAiImported(cwd);
|
|
97
|
+
const aiTsPath = writeAiModels(cwd, input);
|
|
98
98
|
|
|
99
99
|
return { configPath, envPath, aiTsPath };
|
|
100
100
|
}
|
|
@@ -104,11 +104,11 @@ export function applyAiSetup(
|
|
|
104
104
|
* @param driver - AI driver id
|
|
105
105
|
*/
|
|
106
106
|
export function upsertAiDrivers(source: string, driver: string): string {
|
|
107
|
+
const runtime = driver === "mock" ? "mock" : driver;
|
|
107
108
|
const block = `{
|
|
108
|
-
|
|
109
|
-
docker: "${driver === "mock" ? "mock" : driver}",
|
|
109
|
+
dev: "${runtime}",
|
|
110
110
|
test: "mock",
|
|
111
|
-
prod: "${
|
|
111
|
+
prod: "${runtime}",
|
|
112
112
|
}`;
|
|
113
113
|
// Top-level drivers.ai only (4-space indent) — never nest under channel.email.
|
|
114
114
|
const aiRe = /^( {4})ai:\s*\{[\s\S]*?\n\1\}/m;
|
|
@@ -127,24 +127,19 @@ export function upsertAiDrivers(source: string, driver: string): string {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
/**
|
|
130
|
+
* Set one dotted role's image pin, preserving every other pin (including
|
|
131
|
+
* `store.*` / `channel.*` nesting) via {@link extractImages} /
|
|
132
|
+
* {@link replaceImagesBlock} — a parse/set/render round-trip rather than a
|
|
133
|
+
* single-line regex, so nested sub-objects are never corrupted.
|
|
134
|
+
*
|
|
130
135
|
* @param source - Config source
|
|
131
|
-
* @param key - Image role
|
|
136
|
+
* @param key - Image role (dotted for `store.*` / `channel.*`, flat otherwise)
|
|
132
137
|
* @param image - Image ref
|
|
133
138
|
*/
|
|
134
139
|
export function upsertImage(source: string, key: string, image: string): string {
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
const m = imagesRe.exec(source);
|
|
139
|
-
if (!m) return source;
|
|
140
|
-
const body = m[1]!;
|
|
141
|
-
if (new RegExp(`${keyLit}\\s*:`).test(body) || new RegExp(`"${key}"\\s*:`).test(body)) {
|
|
142
|
-
return source.replace(
|
|
143
|
-
new RegExp(`(["']?${key.replace(".", "\\.")}["']?\\s*:\\s*)"[^"]*"`),
|
|
144
|
-
`$1"${image}"`,
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
return source.replace(imagesRe, `images: {${body}\n${line}\n }`);
|
|
140
|
+
if (!findImagesBlock(source)) return source;
|
|
141
|
+
const images = { ...extractImages(source), [key]: image };
|
|
142
|
+
return replaceImagesBlock(source, images);
|
|
148
143
|
}
|
|
149
144
|
|
|
150
145
|
/** Options for {@link upsertEnv}. */
|
|
@@ -230,16 +225,113 @@ export function renderAiTs(input: AiSetupApplyInput): string {
|
|
|
230
225
|
}
|
|
231
226
|
|
|
232
227
|
/**
|
|
233
|
-
*
|
|
228
|
+
* Write AI model declarations into `src/core.ts` (preferred) or legacy `src/core/ai.ts`.
|
|
229
|
+
*
|
|
230
|
+
* @param cwd - Project root
|
|
231
|
+
* @param input - Setup choices
|
|
232
|
+
* @returns Path written
|
|
233
|
+
*/
|
|
234
|
+
function writeAiModels(cwd: string, input: AiSetupApplyInput): string {
|
|
235
|
+
const rendered = renderAiTs(input);
|
|
236
|
+
const coreTsPath = join(cwd, "src", "core.ts");
|
|
237
|
+
const legacyIndex = join(cwd, "src", "core", "index.ts");
|
|
238
|
+
|
|
239
|
+
// Folder layout still in the wild — keep writing a sidecar.
|
|
240
|
+
if (!existsSync(coreTsPath) && existsSync(legacyIndex)) {
|
|
241
|
+
const aiTsPath = join(cwd, "src", "core", "ai.ts");
|
|
242
|
+
mkdirSync(dirname(aiTsPath), { recursive: true });
|
|
243
|
+
writeFileSync(aiTsPath, rendered, "utf8");
|
|
244
|
+
ensureLegacyAiImported(cwd);
|
|
245
|
+
return aiTsPath;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
mkdirSync(dirname(coreTsPath), { recursive: true });
|
|
249
|
+
if (existsSync(coreTsPath)) {
|
|
250
|
+
const existing = readFileSync(coreTsPath, "utf8");
|
|
251
|
+
if (hasAiModels(existing)) {
|
|
252
|
+
return coreTsPath;
|
|
253
|
+
}
|
|
254
|
+
writeFileSync(coreTsPath, mergeAiIntoCore(existing, rendered), "utf8");
|
|
255
|
+
} else {
|
|
256
|
+
writeFileSync(coreTsPath, rendered, "utf8");
|
|
257
|
+
}
|
|
258
|
+
ensureCoreImported(cwd);
|
|
259
|
+
return coreTsPath;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* @param source - Existing TypeScript
|
|
264
|
+
*/
|
|
265
|
+
function hasAiModels(source: string): boolean {
|
|
266
|
+
return (
|
|
267
|
+
/\bai\.model\s*\(/.test(source) ||
|
|
268
|
+
/from\s+["']\.\/(?:core\/)?ai["']/.test(source) ||
|
|
269
|
+
/import\s+["']\.\/(?:core\/)?ai["']/.test(source)
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Merge rendered AI module into an existing `src/core.ts`.
|
|
275
|
+
*
|
|
276
|
+
* @param existing - Current core.ts
|
|
277
|
+
* @param rendered - Output of {@link renderAiTs}
|
|
278
|
+
*/
|
|
279
|
+
export function mergeAiIntoCore(existing: string, rendered: string): string {
|
|
280
|
+
const body = rendered.replace(/^import\s*\{\s*ai\s*\}\s*from\s*["']okengine["'];\s*\n*/m, "");
|
|
281
|
+
const next = ensureNamedOkengineImport(existing, "ai");
|
|
282
|
+
return `${next.trimEnd()}\n\n${body.trimStart()}`;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Ensure a named binding is imported from `"okengine"`.
|
|
287
|
+
*
|
|
288
|
+
* @param source - Module source
|
|
289
|
+
* @param name - Binding to add
|
|
290
|
+
*/
|
|
291
|
+
export function ensureNamedOkengineImport(source: string, name: string): string {
|
|
292
|
+
const re = /import\s*\{([^}]*)\}\s*from\s*["']okengine["']\s*;/;
|
|
293
|
+
const m = re.exec(source);
|
|
294
|
+
if (!m) {
|
|
295
|
+
return `import { ${name} } from "okengine";\n${source}`;
|
|
296
|
+
}
|
|
297
|
+
const names = m[1]!
|
|
298
|
+
.split(",")
|
|
299
|
+
.map((s) => s.trim())
|
|
300
|
+
.filter(Boolean);
|
|
301
|
+
if (names.includes(name)) return source;
|
|
302
|
+
const sorted = [...names, name].sort((a, b) => a.localeCompare(b));
|
|
303
|
+
return source.replace(re, `import { ${sorted.join(", ")} } from "okengine";`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Ensure `src/app.ts` loads `@/core` / `./core` so merged AI registers.
|
|
308
|
+
*
|
|
309
|
+
* @param cwd - Project root
|
|
310
|
+
*/
|
|
311
|
+
function ensureCoreImported(cwd: string): void {
|
|
312
|
+
const appPath = join(cwd, "src", "app.ts");
|
|
313
|
+
if (!existsSync(appPath)) return;
|
|
314
|
+
const src = readFileSync(appPath, "utf8");
|
|
315
|
+
if (
|
|
316
|
+
/from\s+["']@\/core["']/.test(src) ||
|
|
317
|
+
/import\s+["']@\/core["']/.test(src) ||
|
|
318
|
+
/from\s+["']\.\/core(?:\.ts)?["']/.test(src) ||
|
|
319
|
+
/import\s+["']\.\/core(?:\.ts)?["']/.test(src)
|
|
320
|
+
) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
writeFileSync(appPath, `import "@/core";\n${src}`, "utf8");
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Legacy `src/core/*` layout — side-effect import the AI sidecar.
|
|
234
328
|
*
|
|
235
329
|
* @param cwd - Project root
|
|
236
330
|
*/
|
|
237
|
-
function
|
|
331
|
+
function ensureLegacyAiImported(cwd: string): void {
|
|
238
332
|
const candidates: ReadonlyArray<{ readonly rel: string; readonly importLine: string }> = [
|
|
239
333
|
{ rel: "src/app.ts", importLine: `import "./core/ai";\n` },
|
|
240
334
|
{ rel: "src/core/index.ts", importLine: `import "./ai";\n` },
|
|
241
|
-
// Legacy layout
|
|
242
|
-
{ rel: "src/core.ts", importLine: `import "./ai";\n` },
|
|
243
335
|
];
|
|
244
336
|
for (const { rel, importLine } of candidates) {
|
|
245
337
|
const path = join(cwd, rel);
|
package/src/ai-setup/catalog.ts
CHANGED
|
@@ -788,9 +788,8 @@ export const LLAMA_CPP_CHAT_MODELS: readonly CatalogModel[] = [
|
|
|
788
788
|
{
|
|
789
789
|
id: "smollm2",
|
|
790
790
|
label: "SmolLM2",
|
|
791
|
-
hint: "
|
|
791
|
+
hint: "Lightest",
|
|
792
792
|
role: "chat",
|
|
793
|
-
recommended: true,
|
|
794
793
|
ramGb: 1,
|
|
795
794
|
tier: "ultra-fast",
|
|
796
795
|
modalities: ["text"],
|
|
@@ -951,8 +950,9 @@ export const LLAMA_CPP_CHAT_MODELS: readonly CatalogModel[] = [
|
|
|
951
950
|
{
|
|
952
951
|
id: "granite3.3:2b",
|
|
953
952
|
label: "Granite 3.3 2B",
|
|
954
|
-
hint: "IBM 2B",
|
|
953
|
+
hint: "Default · IBM 2B",
|
|
955
954
|
role: "chat",
|
|
955
|
+
recommended: true,
|
|
956
956
|
ramGb: 2,
|
|
957
957
|
tier: "ultra-fast",
|
|
958
958
|
modalities: ["text", "code"],
|
|
@@ -68,7 +68,7 @@ export function nonInteractiveAiApply(provider: string): AiSetupApplyInput {
|
|
|
68
68
|
return {
|
|
69
69
|
driver: "openai-compatible",
|
|
70
70
|
baseUrl: process.env.OKE_AI_URL ?? "http://127.0.0.1:8080/v1",
|
|
71
|
-
chatModel: "
|
|
71
|
+
chatModel: "granite3.3:2b",
|
|
72
72
|
visionModel: null,
|
|
73
73
|
embedModel: null,
|
|
74
74
|
image: LLAMA_CPP_IMAGE,
|
package/src/ai-setup/prompts.ts
CHANGED
|
@@ -154,7 +154,7 @@ async function askLlamaCppPath(options: {
|
|
|
154
154
|
if (mode === null) return null;
|
|
155
155
|
|
|
156
156
|
if (mode === "manual") {
|
|
157
|
-
const id = await askOtherModelId("
|
|
157
|
+
const id = await askOtherModelId("granite3.3:2b");
|
|
158
158
|
if (id === null) return null;
|
|
159
159
|
return finishLlamaCpp(id);
|
|
160
160
|
}
|