create-oke 0.9.1 → 0.10.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 +7 -5
- package/package.json +2 -3
- package/src/agents-md.ts +1 -1
- package/src/ai-setup/apply.ts +66 -23
- package/src/ai-setup/catalog.ts +1316 -35
- package/src/ai-setup/detect-ollama.ts +48 -0
- package/src/ai-setup/from-pref.ts +32 -0
- package/src/ai-setup/prompts.ts +430 -486
- package/src/ai-setup/recommend.ts +118 -101
- package/src/cli.test.ts +36 -7
- package/src/cli.ts +21 -16
- package/src/customize-flow.test.ts +43 -13
- package/src/customize-flow.ts +136 -61
- package/src/drivers-catalog.ts +29 -12
- package/src/local-okengine.test.ts +59 -0
- package/src/local-okengine.ts +137 -0
- package/src/scaffold.ts +1 -1
- package/src/transform.test.ts +54 -32
- package/src/transform.ts +46 -9
- package/src/wizard-select.ts +5 -6
- package/templates/advanced/.github/workflows/ci.yml +24 -0
- package/templates/advanced/.vscode/settings.json +15 -0
- package/templates/advanced/README.md +15 -4
- package/templates/advanced/drizzle.config.ts +6 -4
- package/templates/advanced/oke.config.ts +6 -2
- package/templates/advanced/package.json +5 -2
- package/templates/advanced/src/app.ts +2 -14
- package/templates/advanced/src/core/index.ts +12 -0
- package/templates/advanced/src/{core.ts → core/store.ts} +1 -1
- package/templates/advanced/src/db/migrations/.gitkeep +0 -0
- package/templates/{standard/src → advanced/src/db}/schema.decl.ts +1 -1
- package/templates/advanced/src/{seed → db/seed}/index.ts +2 -2
- package/templates/advanced/src/flows/notes/index.ts +2 -4
- package/templates/advanced/tests/advanced.test.ts +10 -9
- package/templates/advanced/tsconfig.json +23 -0
- package/templates/standard/.github/workflows/ci.yml +24 -0
- package/templates/standard/.vscode/settings.json +15 -0
- package/templates/standard/README.md +20 -9
- package/templates/standard/drizzle.config.ts +6 -4
- package/templates/standard/oke.config.ts +4 -0
- package/templates/standard/package.json +5 -2
- package/templates/standard/src/app.ts +2 -14
- package/templates/standard/src/core/index.ts +12 -0
- package/templates/standard/src/{core.ts → core/store.ts} +1 -1
- package/templates/standard/src/db/migrations/.gitkeep +0 -0
- package/templates/{advanced/src → standard/src/db}/schema.decl.ts +1 -1
- package/templates/standard/src/{seed → db/seed}/index.ts +2 -2
- package/templates/standard/src/flows/notes/index.ts +2 -4
- package/templates/standard/tests/standard.test.ts +13 -10
- package/templates/standard/tsconfig.json +23 -0
- /package/templates/advanced/src/{channels.ts → core/channels.ts} +0 -0
- /package/templates/advanced/src/{gates.ts → core/gates.ts} +0 -0
- /package/templates/advanced/src/{vault.ts → core/vault.ts} +0 -0
- /package/templates/standard/src/{channels.ts → core/channels.ts} +0 -0
- /package/templates/standard/src/{gates.ts → core/gates.ts} +0 -0
- /package/templates/standard/src/{vault.ts → core/vault.ts} +0 -0
package/src/transform.test.ts
CHANGED
|
@@ -11,8 +11,23 @@ import { resolveTemplateDir } from "./templates.ts";
|
|
|
11
11
|
import { applyCreateAnswers, upsertAiDrivers } from "./transform.ts";
|
|
12
12
|
import type { EnvDriverPins } from "./create-defaults.ts";
|
|
13
13
|
|
|
14
|
-
function templateConfig(): string {
|
|
15
|
-
return readFileSync(join(resolveTemplateDir(
|
|
14
|
+
function templateConfig(id: "standard" | "advanced" = "advanced"): string {
|
|
15
|
+
return readFileSync(join(resolveTemplateDir(id), "oke.config.ts"), "utf8");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Evaluate `oke.config.ts` source via identity `defineConfig` (no okengine import). */
|
|
19
|
+
function evalConfig(source: string): {
|
|
20
|
+
drivers?: {
|
|
21
|
+
ai?: unknown;
|
|
22
|
+
channel?: { ai?: unknown; email?: unknown };
|
|
23
|
+
};
|
|
24
|
+
images?: Record<string, string>;
|
|
25
|
+
} {
|
|
26
|
+
const body = source
|
|
27
|
+
.replace(/^import\s+[\s\S]*?from\s+["'][^"']+["'];?\s*/m, "")
|
|
28
|
+
.replace(/export\s+default\s+/, "return ");
|
|
29
|
+
const defineConfig = <T>(c: T): T => c;
|
|
30
|
+
return new Function("defineConfig", body)(defineConfig) as ReturnType<typeof evalConfig>;
|
|
16
31
|
}
|
|
17
32
|
|
|
18
33
|
function defaultsWithIndex(indexLocal: string, indexDocker: string) {
|
|
@@ -67,39 +82,46 @@ describe("upsertAiDrivers", () => {
|
|
|
67
82
|
};
|
|
68
83
|
|
|
69
84
|
test("inserts drivers.ai as sibling of channel (not inside email)", () => {
|
|
70
|
-
const next = upsertAiDrivers(templateConfig(), ollamaPins);
|
|
71
|
-
|
|
72
|
-
expect(
|
|
73
|
-
|
|
74
|
-
expect(channel).
|
|
75
|
-
expect(channel).not.toContain("ai:");
|
|
85
|
+
const next = upsertAiDrivers(templateConfig("advanced"), ollamaPins);
|
|
86
|
+
const config = evalConfig(next);
|
|
87
|
+
expect(config.drivers?.ai).toEqual(ollamaPins);
|
|
88
|
+
expect(config.drivers?.channel?.ai).toBeUndefined();
|
|
89
|
+
expect(config.drivers?.channel?.email).toBeDefined();
|
|
76
90
|
});
|
|
77
91
|
|
|
78
92
|
test("applyCreateAnswers with ai pins keeps top-level drivers.ai", () => {
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
const llamaPins = pinsDockerReady("openai-compatible", "openai-compatible", "mock");
|
|
94
|
+
for (const id of ["standard", "advanced"] as const) {
|
|
95
|
+
const next = applyCreateAnswers(
|
|
96
|
+
templateConfig(id),
|
|
97
|
+
toCreateDefaults({
|
|
98
|
+
template: "advanced",
|
|
99
|
+
profile: "docker-ready",
|
|
100
|
+
drivers: {
|
|
101
|
+
store: {
|
|
102
|
+
sql: pinsDockerReady("libsql", "postgres", "memory"),
|
|
103
|
+
kv: pinsLocalOnly("memory", "redis", "memory"),
|
|
104
|
+
files: pinsLocalOnly("fs", "s3", "memory"),
|
|
105
|
+
index: null,
|
|
106
|
+
},
|
|
107
|
+
signal: pinsLocalOnly("memory", "redis", "memory"),
|
|
108
|
+
clock: pinsLocalOnly("memory", "file", "frozen"),
|
|
109
|
+
vault: pinsLocalOnly("env", "openbao", "memory"),
|
|
110
|
+
channel: { email: pinsLocalOnly("console", "smtp", "console") },
|
|
111
|
+
ai: llamaPins,
|
|
90
112
|
},
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
113
|
+
ai: { enabled: true, provider: "llama-cpp", driver: "openai-compatible" },
|
|
114
|
+
}),
|
|
115
|
+
);
|
|
116
|
+
const config = evalConfig(next);
|
|
117
|
+
expect(config.drivers?.ai, id).toEqual({
|
|
118
|
+
local: "openai-compatible",
|
|
119
|
+
docker: "openai-compatible",
|
|
120
|
+
test: "mock",
|
|
121
|
+
prod: "openai-compatible",
|
|
122
|
+
});
|
|
123
|
+
expect(config.drivers?.channel?.ai, id).toBeUndefined();
|
|
124
|
+
expect(config.images?.ai, id).toBe("ghcr.io/ggml-org/llama.cpp:server-b10290");
|
|
125
|
+
}
|
|
104
126
|
});
|
|
105
127
|
});
|
package/src/transform.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* Exactly:
|
|
6
6
|
* 1. `package.json` `"name"` → the user-provided project name
|
|
7
7
|
* 2. `package.json` `"okengine": "file:../.."` → an installable reference
|
|
8
|
-
* (
|
|
8
|
+
* (staged `file:~/.oke/create-oke/okengine` in the monorepo — not the
|
|
9
|
+
* workspace root; registry version otherwise)
|
|
9
10
|
* 3. Drop monorepo-only files that import paths outside the source tree
|
|
10
11
|
* (today: `tests/docker.test.ts`)
|
|
11
12
|
* 4. Optional `--sql` / wizard choice → Drizzle dialect + `store.sql` pins
|
|
@@ -14,7 +15,14 @@
|
|
|
14
15
|
import { readFileSync } from "node:fs";
|
|
15
16
|
import { join } from "node:path";
|
|
16
17
|
import type { CreateDefaults, EnvDriverPins } from "./create-defaults.ts";
|
|
17
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
DEFAULT_IMAGES,
|
|
20
|
+
LLAMA_CPP_IMAGE,
|
|
21
|
+
OLLAMA_IMAGE,
|
|
22
|
+
SGLANG_IMAGE,
|
|
23
|
+
VLLM_IMAGE,
|
|
24
|
+
} from "./drivers-catalog.ts";
|
|
25
|
+
import { materializeLocalOkengineDependency } from "./local-okengine.ts";
|
|
18
26
|
import { packageRoot } from "./templates.ts";
|
|
19
27
|
|
|
20
28
|
/** SQL store drivers selectable at scaffold time. */
|
|
@@ -49,13 +57,15 @@ export type ScaffoldPackageJson = {
|
|
|
49
57
|
/**
|
|
50
58
|
* Resolve the `okengine` dependency string written into the scaffolded package.json.
|
|
51
59
|
*
|
|
52
|
-
* - Monorepo / local: `file
|
|
60
|
+
* - Monorepo / local: staged publish-shaped `file:` package (no workspaces /
|
|
61
|
+
* no monorepo `devDependencies` — those pull drizzle-zod and trip Bun’s RC
|
|
62
|
+
* peer check)
|
|
53
63
|
* - Published create-oke: the version of this package (kept in lockstep with okengine)
|
|
54
64
|
*
|
|
55
65
|
* @param localOkengineRoot - Absolute path when available
|
|
56
66
|
*/
|
|
57
67
|
export function resolveOkengineDependency(localOkengineRoot: string | null): string {
|
|
58
|
-
if (localOkengineRoot) return
|
|
68
|
+
if (localOkengineRoot) return materializeLocalOkengineDependency(localOkengineRoot);
|
|
59
69
|
const pkgPath = join(packageRoot(), "package.json");
|
|
60
70
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf8")) as { version: string };
|
|
61
71
|
return pkg.version;
|
|
@@ -269,11 +279,14 @@ export function upsertAiDrivers(source: string, pins: EnvDriverPins): string {
|
|
|
269
279
|
* Format an env driver map literal.
|
|
270
280
|
*
|
|
271
281
|
* @param pins - Pins
|
|
272
|
-
* @param indentLevel - Indent
|
|
282
|
+
* @param indentLevel - Indent of the map key (`signal` → 2, `sql`/`email` → 3;
|
|
283
|
+
* two spaces each). Inner fields use `indentLevel + 1`; the closing `}`
|
|
284
|
+
* aligns with the key. An off-by-one here made `email`'s `},` look like
|
|
285
|
+
* `channel`'s close, so {@link upsertAiDrivers} nested `ai` under `channel`.
|
|
273
286
|
*/
|
|
274
287
|
function formatEnvMap(pins: EnvDriverPins, indentLevel: number): string {
|
|
275
|
-
const pad = " ".repeat(indentLevel);
|
|
276
|
-
const close = " ".repeat(indentLevel
|
|
288
|
+
const pad = " ".repeat(indentLevel + 1);
|
|
289
|
+
const close = " ".repeat(indentLevel);
|
|
277
290
|
return `{
|
|
278
291
|
${pad}local: "${pins.local}",
|
|
279
292
|
${pad}docker: "${pins.docker}",
|
|
@@ -329,13 +342,37 @@ function syncImages(source: string, defaults: CreateDefaults): string {
|
|
|
329
342
|
if (d.store.index?.docker === "meilisearch") {
|
|
330
343
|
pin("store.index", DEFAULT_IMAGES["store.index"]!);
|
|
331
344
|
}
|
|
332
|
-
if (
|
|
333
|
-
|
|
345
|
+
if (
|
|
346
|
+
d.ai &&
|
|
347
|
+
(d.ai.docker === "ollama" ||
|
|
348
|
+
d.ai.local === "ollama" ||
|
|
349
|
+
d.ai.docker === "openai-compatible" ||
|
|
350
|
+
d.ai.local === "openai-compatible")
|
|
351
|
+
) {
|
|
352
|
+
pin("ai", aiImageForDefaults(defaults));
|
|
334
353
|
}
|
|
335
354
|
|
|
336
355
|
return replaceImagesBlock(source, images);
|
|
337
356
|
}
|
|
338
357
|
|
|
358
|
+
/**
|
|
359
|
+
* Resolve `images.ai` from create-defaults provider / driver pins.
|
|
360
|
+
*
|
|
361
|
+
* @param defaults - Create answers
|
|
362
|
+
*/
|
|
363
|
+
function aiImageForDefaults(defaults: CreateDefaults): string {
|
|
364
|
+
const provider = defaults.ai.provider;
|
|
365
|
+
if (provider === "ollama" || defaults.drivers.ai?.local === "ollama") {
|
|
366
|
+
return OLLAMA_IMAGE;
|
|
367
|
+
}
|
|
368
|
+
if (provider === "vllm") return VLLM_IMAGE;
|
|
369
|
+
if (provider === "sglang") return SGLANG_IMAGE;
|
|
370
|
+
if (provider === "llama-cpp" || defaults.drivers.ai?.local === "openai-compatible") {
|
|
371
|
+
return LLAMA_CPP_IMAGE;
|
|
372
|
+
}
|
|
373
|
+
return DEFAULT_IMAGES.ai!;
|
|
374
|
+
}
|
|
375
|
+
|
|
339
376
|
/**
|
|
340
377
|
* Parse role→image pins from an `images` block.
|
|
341
378
|
*
|
package/src/wizard-select.ts
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared Clack select helpers with optional
|
|
2
|
+
* Shared Clack select helpers with optional Back.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { isCancel, select } from "@clack/prompts";
|
|
6
6
|
|
|
7
|
-
/** Sentinel — go back one wizard step (shown as "
|
|
7
|
+
/** Sentinel — go back one wizard step (shown as "Back" in selects). */
|
|
8
8
|
export const WIZARD_BACK = "__back__" as const;
|
|
9
|
-
/** {@link WIZARD_BACK} type alias. */
|
|
10
9
|
export type WizardBack = typeof WIZARD_BACK;
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
|
-
* Append "
|
|
12
|
+
* Append "Back" when allowed — pure helper for tests + {@link selectWithBack}.
|
|
14
13
|
*
|
|
15
14
|
* @param options - Choices
|
|
16
15
|
* @param allowBack - Whether to append Back
|
|
@@ -20,11 +19,11 @@ export function withBackOption(
|
|
|
20
19
|
allowBack: boolean,
|
|
21
20
|
): readonly { value: string; label: string; hint?: string }[] {
|
|
22
21
|
if (!allowBack) return options;
|
|
23
|
-
return [...options, { value: WIZARD_BACK, label: "
|
|
22
|
+
return [...options, { value: WIZARD_BACK, label: "Back" }];
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
/**
|
|
27
|
-
* Select with an optional trailing "
|
|
26
|
+
* Select with an optional trailing "Back" option.
|
|
28
27
|
*
|
|
29
28
|
* @param message - Prompt
|
|
30
29
|
* @param options - Choices (without back)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Minimal CI for an okengine app — typecheck + test on every push/PR.
|
|
2
|
+
# Expand later (lint, docker, deploy); keep this green from day one.
|
|
3
|
+
|
|
4
|
+
name: CI
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
pull_request:
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
check:
|
|
12
|
+
name: typecheck · test
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v6
|
|
16
|
+
- uses: oven-sh/setup-bun@v2.2.0
|
|
17
|
+
with:
|
|
18
|
+
bun-version: latest
|
|
19
|
+
- name: Install
|
|
20
|
+
run: bun install
|
|
21
|
+
- name: Typecheck
|
|
22
|
+
run: bun run typecheck
|
|
23
|
+
- name: Test
|
|
24
|
+
run: bun test
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"files.exclude": {
|
|
3
|
+
"**/dist": true,
|
|
4
|
+
"**/.next": true,
|
|
5
|
+
"**/.turbo": true,
|
|
6
|
+
"**/bun.lock": true,
|
|
7
|
+
"**/node_modules": true,
|
|
8
|
+
"**/next-env.d.ts": true,
|
|
9
|
+
"**/worker-configuration.d.ts": true
|
|
10
|
+
},
|
|
11
|
+
"editor.formatOnSave": true,
|
|
12
|
+
"files.associations": {
|
|
13
|
+
"*.css": "tailwindcss"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Docker-ready [okengine](https://oke.omqkhafi.dev) **Notes** starter — same domain as
|
|
4
4
|
`standard`, plus files attach, a daily digest clock, and an AI summarize stub.
|
|
5
|
+
Scaffold, not a finished product: keep the Flows you want, replace the rest.
|
|
5
6
|
|
|
6
7
|
```bash
|
|
7
8
|
bun install
|
|
8
9
|
oke mode docker # seeded by create-oke recommended path
|
|
9
|
-
oke dev
|
|
10
|
-
oke db seed # essential welcome note + local/docker sample notes
|
|
10
|
+
oke dev # auto db push; asks once whether to seed
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
| Surface | URL |
|
|
@@ -24,14 +24,25 @@ oke db seed # essential welcome note + local/docker sample notes
|
|
|
24
24
|
| POST | `/notes/:id/summarize` | `notes.summarize` — `fx.ask` when AI is configured |
|
|
25
25
|
| clock | `every("1d")` | `notes.digest` — active note count |
|
|
26
26
|
|
|
27
|
-
Configure AI with `oke ai setup` or `create-oke --ai` (writes `src/ai.ts` + `drivers.ai`).
|
|
27
|
+
Configure AI with `oke ai setup` or `create-oke --ai` (writes `src/core/ai.ts` + `drivers.ai`).
|
|
28
28
|
Without AI, summarize returns a local excerpt (`via: "fallback"`).
|
|
29
29
|
|
|
30
|
+
## Included vs you build
|
|
31
|
+
|
|
32
|
+
| Ships ready | You still own |
|
|
33
|
+
| ----------- | ------------- |
|
|
34
|
+
| standard Notes surface + attach / digest / summarize | your domain beyond notes |
|
|
35
|
+
| files Store pin + digest Clock | production file policy and schedules |
|
|
36
|
+
| AI summarize stub (fallback without AI) | real prompts / models via `oke ai setup` |
|
|
37
|
+
| `.github/workflows/ci.yml` (typecheck + test) | lint, docker, deploy when you need them |
|
|
38
|
+
|
|
30
39
|
## Layout extras
|
|
31
40
|
|
|
32
41
|
| Path | Role |
|
|
33
42
|
| ---- | ---- |
|
|
34
|
-
| `src/
|
|
43
|
+
| `src/core/` | `store` (db + files) · gates · vault · channels |
|
|
44
|
+
| `src/db/` | schema · seed · migrations |
|
|
45
|
+
| `.github/workflows/ci.yml` | `bun run typecheck` + `bun test` on push/PR |
|
|
35
46
|
|
|
36
47
|
## Drivers
|
|
37
48
|
|
|
@@ -5,17 +5,19 @@ import { defineConfig } from "drizzle-kit";
|
|
|
5
5
|
*
|
|
6
6
|
* Dialect comes from OKE's `store.sql` driver map (resolved by the CLI and
|
|
7
7
|
* injected as `OKE_DRIZZLE_DIALECT`) — never inferred from `DATABASE_URL`
|
|
8
|
-
* presence. `src/schema.generated.ts` is emitted from `src/schema.decl.ts`
|
|
8
|
+
* presence. `src/db/schema.generated.ts` is emitted from `src/db/schema.decl.ts`
|
|
9
9
|
* for the active dialect as a pre-step of `oke db` / `oke dev`.
|
|
10
10
|
*
|
|
11
|
-
* Versioned prod migrations land in `./
|
|
11
|
+
* Versioned prod migrations land in `./src/db/migrations` — never mix with `.oke/`.
|
|
12
12
|
*/
|
|
13
13
|
const dialect = (process.env.OKE_DRIZZLE_DIALECT ?? "sqlite") as "sqlite" | "postgresql";
|
|
14
14
|
|
|
15
15
|
export default defineConfig({
|
|
16
16
|
dialect,
|
|
17
|
-
schema: "./src/schema.generated.ts",
|
|
18
|
-
out: "./
|
|
17
|
+
schema: "./src/db/schema.generated.ts",
|
|
18
|
+
out: "./src/db/migrations",
|
|
19
|
+
// Core runtime tables (oke_crons, …) are created by drivers — not domain schema.
|
|
20
|
+
tablesFilter: ["!oke_*"],
|
|
19
21
|
dbCredentials: {
|
|
20
22
|
url:
|
|
21
23
|
dialect === "postgresql"
|
|
@@ -5,6 +5,10 @@ import { defineConfig } from "okengine/config";
|
|
|
5
5
|
* Recommended create path seeds `.oke/mode` as docker.
|
|
6
6
|
*/
|
|
7
7
|
export default defineConfig({
|
|
8
|
+
db: {
|
|
9
|
+
declare: "src/db/schema.decl.ts",
|
|
10
|
+
generated: "src/db/schema.generated.ts",
|
|
11
|
+
},
|
|
8
12
|
drivers: {
|
|
9
13
|
store: {
|
|
10
14
|
sql: {
|
|
@@ -66,7 +70,7 @@ export default defineConfig({
|
|
|
66
70
|
prod: "smtp",
|
|
67
71
|
},
|
|
68
72
|
},
|
|
69
|
-
// Opt in: create-oke --ai / oke ai setup writes drivers.ai + src/ai.ts
|
|
73
|
+
// Opt in: create-oke --ai / oke ai setup writes drivers.ai + src/core/ai.ts
|
|
70
74
|
},
|
|
71
75
|
images: {
|
|
72
76
|
"store.sql": "postgres:18-alpine",
|
|
@@ -76,7 +80,7 @@ export default defineConfig({
|
|
|
76
80
|
"channel.email": "axllent/mailpit:v1.22.3",
|
|
77
81
|
vault: "openbao/openbao:2.6.1",
|
|
78
82
|
// "store.index": "getmeili/meilisearch:v1.37",
|
|
79
|
-
// ai: "ollama/ollama:
|
|
83
|
+
// ai: "ghcr.io/ggml-org/llama.cpp:server-b10290", // or ollama/ollama:0.32.6
|
|
80
84
|
},
|
|
81
85
|
i18n: { locales: ["en", "ar"], default: "en", dir: { ar: "rtl" } },
|
|
82
86
|
});
|
|
@@ -5,13 +5,16 @@
|
|
|
5
5
|
"type": "module",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"okengine": "file:../../../..",
|
|
8
|
-
"drizzle-orm": "
|
|
8
|
+
"drizzle-orm": "1.0.0-rc.4",
|
|
9
9
|
"zod": "^4.4.3"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
|
-
"
|
|
12
|
+
"@types/bun": "latest",
|
|
13
|
+
"drizzle-kit": "1.0.0-rc.4",
|
|
14
|
+
"typescript": "^7.0.2"
|
|
13
15
|
},
|
|
14
16
|
"scripts": {
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
15
18
|
"test": "bun test",
|
|
16
19
|
"dev": "oke dev"
|
|
17
20
|
}
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import { db, files } from "./core";
|
|
2
|
-
import "./gates";
|
|
3
|
-
import "./vault";
|
|
4
|
-
import "./channels";
|
|
1
|
+
import { db, files, noteCreatedMail, webhookSecret } from "./core";
|
|
5
2
|
import "./locales/en";
|
|
6
3
|
import "./locales/ar";
|
|
7
4
|
|
|
@@ -9,22 +6,13 @@ import { oke } from "okengine";
|
|
|
9
6
|
import * as main from "./flows/main";
|
|
10
7
|
import * as notes from "./flows/notes";
|
|
11
8
|
import { noteCreated } from "./flows/notes/signals";
|
|
12
|
-
import { noteCreatedMail } from "./channels";
|
|
13
|
-
import { webhookSecret } from "./vault";
|
|
14
9
|
|
|
15
10
|
export const app = oke({
|
|
16
11
|
name: "notes",
|
|
12
|
+
stores: [db, files],
|
|
17
13
|
secrets: [webhookSecret],
|
|
18
14
|
signals: [noteCreated],
|
|
19
15
|
channel: { templates: [noteCreatedMail] },
|
|
20
16
|
}).adopt({ main, notes });
|
|
21
17
|
|
|
22
18
|
export type App = typeof app;
|
|
23
|
-
|
|
24
|
-
Object.assign(app.$options, {
|
|
25
|
-
env: "test",
|
|
26
|
-
stores: [db, files],
|
|
27
|
-
secrets: [webhookSecret],
|
|
28
|
-
signals: [noteCreated],
|
|
29
|
-
channel: { templates: [noteCreatedMail] },
|
|
30
|
-
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core wiring — stores, gates, vault, channels (and AI when configured).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { db, files } from "./store";
|
|
6
|
+
export { notesWrite } from "./gates";
|
|
7
|
+
export { webhookSecret } from "./vault";
|
|
8
|
+
export { noteCreatedMail } from "./channels";
|
|
9
|
+
|
|
10
|
+
import "./gates";
|
|
11
|
+
import "./vault";
|
|
12
|
+
import "./channels";
|
|
File without changes
|
|
@@ -2,7 +2,7 @@ import { store, field, id, now } from "okengine";
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Notes domain — abstract declarations, emitted to
|
|
5
|
-
* `src/schema.generated.ts` for the active dialect by `oke db` / `oke dev`.
|
|
5
|
+
* `src/db/schema.generated.ts` for the active dialect by `oke db` / `oke dev`.
|
|
6
6
|
*/
|
|
7
7
|
export const notes = store.schema.table("notes", {
|
|
8
8
|
id: field.text().primaryKey().defaultFn(id),
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { defineSeed, type Fx } from "okengine";
|
|
2
|
-
import { db } from "
|
|
2
|
+
import { db } from "../../core";
|
|
3
3
|
import { notes } from "../schema.decl";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Seed data — run explicitly with `oke db seed` (never at boot).
|
|
7
7
|
*
|
|
8
8
|
* Categories: `essential` (every env) · `dev` (local|docker) · `prod` (prod only).
|
|
9
|
-
* For multi-file composition (`src/seed/essential/*.ts` + arrays here), see
|
|
9
|
+
* For multi-file composition (`src/db/seed/essential/*.ts` + arrays here), see
|
|
10
10
|
* Store docs → Seeding.
|
|
11
11
|
*/
|
|
12
12
|
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { on, flow, http, every, gate, fail } from "okengine";
|
|
2
2
|
import { eq, isNull } from "drizzle-orm";
|
|
3
3
|
|
|
4
|
-
import { db, files } from "../../core";
|
|
5
|
-
import { notes } from "../../schema.decl";
|
|
6
|
-
import { webhookSecret } from "../../vault";
|
|
7
|
-
import { noteCreatedMail } from "../../channels";
|
|
4
|
+
import { db, files, noteCreatedMail, webhookSecret } from "../../core";
|
|
5
|
+
import { notes } from "../../db/schema.decl";
|
|
8
6
|
import {
|
|
9
7
|
NoteAttachIn,
|
|
10
8
|
NoteAttachOut,
|
|
@@ -13,30 +13,31 @@ afterAll(async () => {
|
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
test("boots — health flow is named main.health", async () => {
|
|
16
|
-
const { data, error } = await t.api.main
|
|
16
|
+
const { data, error } = await t.api.main!.health!({});
|
|
17
17
|
expect(error).toBeNull();
|
|
18
18
|
expect(data).toEqual({ ok: true });
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
test("notes create → attach → summarize fallback → archive", async () => {
|
|
22
|
-
const created = await t.api.notes
|
|
22
|
+
const created = await t.api.notes!.create!({
|
|
23
23
|
title: "Advanced",
|
|
24
24
|
body: "Body long enough to exercise attach and summarize paths in the advanced starter.",
|
|
25
25
|
});
|
|
26
26
|
expect(created.error).toBeNull();
|
|
27
|
-
const id = created.data
|
|
27
|
+
const id = (created.data as { id: string }).id;
|
|
28
28
|
|
|
29
29
|
await t.signals.drain();
|
|
30
30
|
|
|
31
|
-
const attached = await t.api.notes
|
|
31
|
+
const attached = await t.api.notes!.attach!({ id, text: "attachment body" });
|
|
32
32
|
expect(attached.error).toBeNull();
|
|
33
|
-
expect(attached.data
|
|
33
|
+
expect((attached.data as { key: string }).key).toBe(`notes/${id}/attachment.txt`);
|
|
34
34
|
|
|
35
|
-
const summary = await t.api.notes
|
|
35
|
+
const summary = await t.api.notes!.summarize!({ id });
|
|
36
36
|
expect(summary.error).toBeNull();
|
|
37
|
-
|
|
38
|
-
expect(
|
|
37
|
+
const out = summary.data as { via: string; summary: string };
|
|
38
|
+
expect(out.via).toBe("fallback");
|
|
39
|
+
expect(out.summary.length).toBeGreaterThan(0);
|
|
39
40
|
|
|
40
|
-
const archived = await t.api.notes
|
|
41
|
+
const archived = await t.api.notes!.archive!({ id });
|
|
41
42
|
expect(archived.error).toBeNull();
|
|
42
43
|
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"lib": ["ESNext"],
|
|
4
|
+
"target": "ESNext",
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleDetection": "force",
|
|
7
|
+
"types": ["bun"],
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"verbatimModuleSyntax": true,
|
|
11
|
+
"noEmit": true,
|
|
12
|
+
"rewriteRelativeImportExtensions": true,
|
|
13
|
+
"strict": true,
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
"noFallthroughCasesInSwitch": true,
|
|
16
|
+
"noUncheckedIndexedAccess": true,
|
|
17
|
+
"noImplicitOverride": true,
|
|
18
|
+
"noUnusedLocals": true,
|
|
19
|
+
"noUnusedParameters": true,
|
|
20
|
+
"erasableSyntaxOnly": true
|
|
21
|
+
},
|
|
22
|
+
"include": ["src/**/*.ts", "tests/**/*.ts", "oke.config.ts", "drizzle.config.ts"]
|
|
23
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Minimal CI for an okengine app — typecheck + test on every push/PR.
|
|
2
|
+
# Expand later (lint, docker, deploy); keep this green from day one.
|
|
3
|
+
|
|
4
|
+
name: CI
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
pull_request:
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
check:
|
|
12
|
+
name: typecheck · test
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v6
|
|
16
|
+
- uses: oven-sh/setup-bun@v2.2.0
|
|
17
|
+
with:
|
|
18
|
+
bun-version: latest
|
|
19
|
+
- name: Install
|
|
20
|
+
run: bun install
|
|
21
|
+
- name: Typecheck
|
|
22
|
+
run: bun run typecheck
|
|
23
|
+
- name: Test
|
|
24
|
+
run: bun test
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"files.exclude": {
|
|
3
|
+
"**/dist": true,
|
|
4
|
+
"**/.next": true,
|
|
5
|
+
"**/.turbo": true,
|
|
6
|
+
"**/bun.lock": true,
|
|
7
|
+
"**/node_modules": true,
|
|
8
|
+
"**/next-env.d.ts": true,
|
|
9
|
+
"**/worker-configuration.d.ts": true
|
|
10
|
+
},
|
|
11
|
+
"editor.formatOnSave": true,
|
|
12
|
+
"files.associations": {
|
|
13
|
+
"*.css": "tailwindcss"
|
|
14
|
+
}
|
|
15
|
+
}
|