create-oke 0.2.8 → 0.3.4
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 +11 -32
- package/package.json +2 -4
- package/src/cli.test.ts +29 -117
- package/src/cli.ts +16 -139
- package/src/docs-origin.ts +2 -3
- package/src/index.ts +1 -2
- package/src/scaffold.ts +13 -31
- package/src/templates.ts +12 -90
- package/src/transform.ts +1 -1
- package/template/.env.example +56 -0
- package/{templates/standard → template}/README.md +25 -12
- package/template/bun.lock +845 -0
- package/template/drizzle.config.ts +25 -0
- package/{templates/full → template}/oke.config.ts +11 -7
- package/{templates/minimal → template}/src/core.ts +1 -1
- package/template/src/schema.decl.ts +12 -0
- package/examples/linkly/.env.example +0 -15
- package/examples/linkly/drizzle.config.ts +0 -11
- package/examples/linkly/oke.config.ts +0 -12
- package/examples/linkly/package.json +0 -18
- package/examples/linkly/src/app.ts +0 -21
- package/examples/linkly/src/core.ts +0 -4
- package/examples/linkly/src/flows/analytics/index.ts +0 -39
- package/examples/linkly/src/flows/links/index.ts +0 -82
- package/examples/linkly/src/flows/links/shapes.ts +0 -22
- package/examples/linkly/src/flows/links/signals.ts +0 -14
- package/examples/linkly/src/gates.ts +0 -10
- package/examples/linkly/src/schema.decl.ts +0 -39
- package/examples/linkly/src/schema.generated.ts +0 -30
- package/examples/linkly/src/schema.ts +0 -2
- package/examples/linkly/tests/linkly.test.ts +0 -19
- package/examples/notes/.env.example +0 -12
- package/examples/notes/drizzle.config.ts +0 -11
- package/examples/notes/oke.config.ts +0 -14
- package/examples/notes/package.json +0 -19
- package/examples/notes/src/app.ts +0 -13
- package/examples/notes/src/core.ts +0 -4
- package/examples/notes/src/flows/notes/index.ts +0 -58
- package/examples/notes/src/schema.ts +0 -9
- package/examples/notes/tests/notes.test.ts +0 -10
- package/examples/provisions/.env.example +0 -19
- package/examples/provisions/drizzle.config.ts +0 -11
- package/examples/provisions/oke.config.ts +0 -19
- package/examples/provisions/package.json +0 -18
- package/examples/provisions/src/app.ts +0 -60
- package/examples/provisions/src/channels.ts +0 -15
- package/examples/provisions/src/core.ts +0 -4
- package/examples/provisions/src/flows/notifications/index.ts +0 -25
- package/examples/provisions/src/flows/orders/index.ts +0 -97
- package/examples/provisions/src/flows/orders/shapes.ts +0 -21
- package/examples/provisions/src/flows/orders/signals.ts +0 -19
- package/examples/provisions/src/flows/payments/index.ts +0 -43
- package/examples/provisions/src/flows/payments/shapes.ts +0 -3
- package/examples/provisions/src/flows/payments/stripe.ts +0 -18
- package/examples/provisions/src/gates.ts +0 -4
- package/examples/provisions/src/locales/ar.ts +0 -1
- package/examples/provisions/src/locales/en.ts +0 -1
- package/examples/provisions/src/plugins/audit-schema.ts +0 -2
- package/examples/provisions/src/plugins/audit.ts +0 -14
- package/examples/provisions/src/schema.ts +0 -16
- package/examples/provisions/src/vault.ts +0 -16
- package/examples/provisions/tests/orders.test.ts +0 -19
- package/examples/skyport/.env.example +0 -34
- package/examples/skyport/drizzle.config.ts +0 -11
- package/examples/skyport/evals/triage.jsonl +0 -1
- package/examples/skyport/oke.config.ts +0 -47
- package/examples/skyport/oke.images.lock +0 -4
- package/examples/skyport/package.json +0 -19
- package/examples/skyport/src/ai.ts +0 -25
- package/examples/skyport/src/app.ts +0 -46
- package/examples/skyport/src/channels.ts +0 -12
- package/examples/skyport/src/core.ts +0 -4
- package/examples/skyport/src/flows/bookings/index.ts +0 -73
- package/examples/skyport/src/flows/bookings/shapes.ts +0 -6
- package/examples/skyport/src/flows/bookings/signals.ts +0 -13
- package/examples/skyport/src/flows/notifications/index.ts +0 -16
- package/examples/skyport/src/flows/payments/index.ts +0 -14
- package/examples/skyport/src/flows/payments/shapes.ts +0 -5
- package/examples/skyport/src/flows/support/index.ts +0 -50
- package/examples/skyport/src/flows/users/elements.ts +0 -2
- package/examples/skyport/src/flows/users/index.ts +0 -12
- package/examples/skyport/src/flows/users/shapes.ts +0 -6
- package/examples/skyport/src/gates.ts +0 -10
- package/examples/skyport/src/journeys.ts +0 -9
- package/examples/skyport/src/locales/ar.ts +0 -4
- package/examples/skyport/src/locales/en.ts +0 -4
- package/examples/skyport/src/plugins/audit-schema.ts +0 -7
- package/examples/skyport/src/plugins/audit.ts +0 -14
- package/examples/skyport/src/plugins/panel.tsx +0 -4
- package/examples/skyport/src/schema.ts +0 -25
- package/examples/skyport/src/vault.ts +0 -24
- package/examples/skyport/tests/support.test.ts +0 -16
- package/src/sync-templates.ts +0 -77
- package/templates/full/.env.example +0 -24
- package/templates/full/README.md +0 -65
- package/templates/full/drizzle.config.ts +0 -14
- package/templates/full/package.json +0 -18
- package/templates/full/src/ai.ts +0 -11
- package/templates/full/src/app.ts +0 -34
- package/templates/full/src/channels.ts +0 -9
- package/templates/full/src/core.ts +0 -4
- package/templates/full/src/flows/main/index.ts +0 -45
- package/templates/full/src/flows/main/shapes.ts +0 -3
- package/templates/full/src/flows/main/signals.ts +0 -8
- package/templates/full/src/gates.ts +0 -4
- package/templates/full/src/locales/ar.ts +0 -3
- package/templates/full/src/locales/en.ts +0 -3
- package/templates/full/src/schema.ts +0 -8
- package/templates/full/src/vault.ts +0 -9
- package/templates/full/tests/full.test.ts +0 -11
- package/templates/hello/.env.example +0 -9
- package/templates/hello/README.md +0 -40
- package/templates/hello/oke.config.ts +0 -3
- package/templates/hello/package.json +0 -14
- package/templates/hello/src/app.ts +0 -10
- package/templates/hello/src/flows/hello/index.ts +0 -27
- package/templates/hello/tests/hello.test.ts +0 -10
- package/templates/minimal/.env.example +0 -13
- package/templates/minimal/README.md +0 -49
- package/templates/minimal/drizzle.config.ts +0 -14
- package/templates/minimal/oke.config.ts +0 -14
- package/templates/minimal/package.json +0 -18
- package/templates/minimal/src/app.ts +0 -13
- package/templates/minimal/src/flows/main/index.ts +0 -49
- package/templates/minimal/src/schema.ts +0 -8
- package/templates/minimal/tests/minimal.test.ts +0 -13
- package/templates/standard/.env.example +0 -21
- package/templates/standard/drizzle.config.ts +0 -14
- package/templates/standard/oke.config.ts +0 -51
- package/templates/standard/src/core.ts +0 -4
- package/templates/standard/src/schema.ts +0 -9
- /package/{templates/standard → template}/package.json +0 -0
- /package/{templates/standard → template}/src/app.ts +0 -0
- /package/{templates/standard → template}/src/channels.ts +0 -0
- /package/{templates/standard → template}/src/flows/main/index.ts +0 -0
- /package/{templates/standard → template}/src/flows/main/shapes.ts +0 -0
- /package/{templates/standard → template}/src/flows/main/signals.ts +0 -0
- /package/{templates/standard → template}/src/gates.ts +0 -0
- /package/{templates/standard → template}/src/locales/ar.ts +0 -0
- /package/{templates/standard → template}/src/locales/en.ts +0 -0
- /package/{templates/standard → template}/src/vault.ts +0 -0
- /package/{templates/standard → template}/tests/standard.test.ts +0 -0
package/README.md
CHANGED
|
@@ -8,9 +8,8 @@ cd my-app
|
|
|
8
8
|
oke dev
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
On a TTY, create-oke opens a Clack wizard
|
|
12
|
-
|
|
13
|
-
`--from-example` for non-interactive (CI / agents).
|
|
11
|
+
On a TTY, create-oke opens a Clack wizard to confirm the name and optionally
|
|
12
|
+
install dependencies and start `oke dev`. Pass `--yes` for non-interactive use.
|
|
14
13
|
|
|
15
14
|
| Port | What |
|
|
16
15
|
| ------- | ------------------- |
|
|
@@ -18,49 +17,29 @@ optionally install + start `oke dev`. Pass `--yes` or `--template` /
|
|
|
18
17
|
| `:6533` | Console |
|
|
19
18
|
| `:6535` | MCP (live Manifest) |
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
The single bundled template is **standard**: the full recommended file layout
|
|
21
|
+
with empty scaffolding.
|
|
23
22
|
|
|
24
23
|
```bash
|
|
25
|
-
bunx create-oke@latest my-app --template
|
|
26
|
-
bunx create-oke@latest my-app --template minimal
|
|
27
|
-
bunx create-oke@latest my-app --template full
|
|
24
|
+
bunx create-oke@latest my-app --template standard
|
|
28
25
|
bunx create-oke@latest my-app --sql postgres
|
|
29
26
|
bunx create-oke@latest my-app --yes
|
|
30
|
-
bunx create-oke@latest my-notes --from-example notes
|
|
31
27
|
```
|
|
32
28
|
|
|
33
29
|
`--sql sqlite|postgres` is opt-in. The default keeps the dual-mode Store
|
|
34
|
-
config (`local: sqlite` · `docker`/`prod: postgres`) and
|
|
35
|
-
schema.
|
|
36
|
-
`
|
|
37
|
-
`postgres`. The interactive wizard does not ask
|
|
38
|
-
|
|
30
|
+
config (`local: sqlite` · `docker`/`prod: postgres`) and abstract
|
|
31
|
+
`src/schema.decl.ts` (dialect emitted from the active driver). Pass
|
|
32
|
+
`--sql postgres` to pin `oke.config.ts` `store.sql` local/docker/prod to
|
|
33
|
+
`postgres`. The interactive wizard does not ask and keeps the same dual-mode
|
|
34
|
+
default.
|
|
39
35
|
|
|
40
36
|
Store-bearing scaffolds ship `drizzle.config.ts` and `.env.example`. Local
|
|
41
37
|
`oke dev` auto-runs `oke db push` on schema change; use `oke mode docker` /
|
|
42
38
|
`oke dev --docker` for compose infra.
|
|
43
39
|
|
|
44
|
-
| Template | Purpose |
|
|
45
|
-
| ---------- | --------------------------------------------------------- |
|
|
46
|
-
| `hello` | Fastest possible "it works" — one flow, no Store |
|
|
47
|
-
| `minimal` | Smallest shape you'd actually ship — Store + 1–2 flows |
|
|
48
|
-
| `standard` | Full recommended file layout, empty scaffolding (default) |
|
|
49
|
-
| `full` | Every element present and wired, no business logic |
|
|
50
|
-
|
|
51
40
|
Every scaffold writes an `AGENTS.md` so coding agents know the OKE contract
|
|
52
41
|
(`--no-agents-md` to skip).
|
|
53
42
|
|
|
54
|
-
`--from-example` copies a teaching example (including its business logic and
|
|
55
|
-
comments) — most new projects want `--template` instead.
|
|
56
|
-
|
|
57
|
-
| Example | Adds |
|
|
58
|
-
| ------------ | ---------------------------------------------------- |
|
|
59
|
-
| `notes` | `oke` · `flow` · `http` · `store.sql` · typed client |
|
|
60
|
-
| `linkly` | `signal` · `clock` · `gate` · abstract `schema.decl` |
|
|
61
|
-
| `provisions` | durable flows · `vault` · `channel` · plugins |
|
|
62
|
-
| `skyport` | `ai` · multi-tenancy · distributed topology |
|
|
63
|
-
|
|
64
43
|
## Registries
|
|
65
44
|
|
|
66
45
|
| Registry | Package |
|
|
@@ -68,4 +47,4 @@ comments) — most new projects want `--template` instead.
|
|
|
68
47
|
| npm (CLI) | [`create-oke`](https://www.npmjs.com/package/create-oke) — use this with `bunx` |
|
|
69
48
|
| JSR | [`@omqkhafi/create-oke`](https://jsr.io/@omqkhafi/create-oke) — same sources; prefer npm for the binary |
|
|
70
49
|
|
|
71
|
-
Requires **Bun ≥ 1.3**. Docs: [
|
|
50
|
+
Requires **Bun ≥ 1.3**. Docs: [oke.omqkhafi.dev](https://oke.omqkhafi.dev) · [handbook](https://oke.omqkhafi.dev/docs).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-oke",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "Scaffold an okengine app — bunx create-oke@latest <name>",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -13,8 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
15
|
"src",
|
|
16
|
-
"
|
|
17
|
-
"examples"
|
|
16
|
+
"template"
|
|
18
17
|
],
|
|
19
18
|
"type": "module",
|
|
20
19
|
"sideEffects": false,
|
|
@@ -22,7 +21,6 @@
|
|
|
22
21
|
".": "./src/index.ts"
|
|
23
22
|
},
|
|
24
23
|
"scripts": {
|
|
25
|
-
"prepack": "bun ./src/sync-templates.ts",
|
|
26
24
|
"typecheck": "tsc --noEmit",
|
|
27
25
|
"test": "bun test",
|
|
28
26
|
"test:integration": "CREATE_OKE_INTEGRATION=1 bun test tests/scaffold.integration.test.ts tests/dev.integration.test.ts"
|
package/src/cli.test.ts
CHANGED
|
@@ -7,7 +7,6 @@ import { existsSync, mkdtempSync, readFileSync, readdirSync, rmSync } from "node
|
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
import { join } from "node:path";
|
|
9
9
|
import {
|
|
10
|
-
FROM_EXAMPLE_CHOICE,
|
|
11
10
|
formatCdPath,
|
|
12
11
|
nextStepsText,
|
|
13
12
|
parseArgs,
|
|
@@ -19,14 +18,7 @@ import {
|
|
|
19
18
|
type InteractiveAnswers,
|
|
20
19
|
} from "./cli.ts";
|
|
21
20
|
import { listTemplateFiles, scaffold } from "./scaffold.ts";
|
|
22
|
-
import {
|
|
23
|
-
DEFAULT_TEMPLATE,
|
|
24
|
-
EXAMPLES,
|
|
25
|
-
TEMPLATES,
|
|
26
|
-
packageRoot,
|
|
27
|
-
resolveExampleDir,
|
|
28
|
-
resolveTemplateDir,
|
|
29
|
-
} from "./templates.ts";
|
|
21
|
+
import { DEFAULT_TEMPLATE, TEMPLATES, packageRoot, resolveTemplateDir } from "./templates.ts";
|
|
30
22
|
import {
|
|
31
23
|
sanitizeProjectName,
|
|
32
24
|
shouldSkipTemplatePath,
|
|
@@ -41,19 +33,13 @@ describe("parseArgs", () => {
|
|
|
41
33
|
expect(a.name).toBe("my-app");
|
|
42
34
|
expect(a.template).toBe("standard");
|
|
43
35
|
expect(a.templateExplicit).toBe(false);
|
|
44
|
-
expect(a.fromExample).toBeUndefined();
|
|
45
36
|
});
|
|
46
37
|
|
|
47
38
|
test("accepts --template and -t", () => {
|
|
48
|
-
expect(parseArgs(["x", "--template", "
|
|
49
|
-
expect(parseArgs(["x", "-t", "
|
|
50
|
-
expect(parseArgs(["x", "--template=
|
|
51
|
-
expect(parseArgs(["x", "--template", "
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test("accepts --from-example", () => {
|
|
55
|
-
expect(parseArgs(["x", "--from-example", "notes"]).fromExample).toBe("notes");
|
|
56
|
-
expect(parseArgs(["x", "--from-example=skyport"]).fromExample).toBe("skyport");
|
|
39
|
+
expect(parseArgs(["x", "--template", "standard"]).template).toBe("standard");
|
|
40
|
+
expect(parseArgs(["x", "-t", "standard"]).template).toBe("standard");
|
|
41
|
+
expect(parseArgs(["x", "--template=standard"]).template).toBe("standard");
|
|
42
|
+
expect(parseArgs(["x", "--template", "standard"]).templateExplicit).toBe(true);
|
|
57
43
|
});
|
|
58
44
|
|
|
59
45
|
test("accepts --sql sqlite|postgres", () => {
|
|
@@ -64,13 +50,10 @@ describe("parseArgs", () => {
|
|
|
64
50
|
expect(parseArgs(["x", "--sql", "postgres"]).sqlDriverExplicit).toBe(true);
|
|
65
51
|
});
|
|
66
52
|
|
|
67
|
-
test("rejects unknown template
|
|
53
|
+
test("rejects unknown template, option, and sql", () => {
|
|
68
54
|
expect(() => parseArgs(["x", "--template", "nope"])).toThrow(/template/);
|
|
69
|
-
expect(() => parseArgs(["x", "--
|
|
55
|
+
expect(() => parseArgs(["x", "--unknown"])).toThrow(/unknown option/);
|
|
70
56
|
expect(() => parseArgs(["x", "--sql", "mysql"])).toThrow(/sql/);
|
|
71
|
-
expect(() => parseArgs(["x", "--template", "hello", "--from-example", "notes"])).toThrow(
|
|
72
|
-
/either/,
|
|
73
|
-
);
|
|
74
57
|
});
|
|
75
58
|
});
|
|
76
59
|
|
|
@@ -87,9 +70,8 @@ describe("shouldPrompt", () => {
|
|
|
87
70
|
expect(shouldPrompt(parseArgs([]), false)).toBe(false);
|
|
88
71
|
expect(shouldPrompt(parseArgs(["my-app"]), false)).toBe(false);
|
|
89
72
|
expect(shouldPrompt(parseArgs(["my-app", "--yes"]), true)).toBe(false);
|
|
90
|
-
expect(shouldPrompt(parseArgs(["my-app", "--template", "
|
|
91
|
-
expect(shouldPrompt(parseArgs(["--template", "
|
|
92
|
-
expect(shouldPrompt(parseArgs(["--from-example", "notes"]), true)).toBe(false);
|
|
73
|
+
expect(shouldPrompt(parseArgs(["my-app", "--template", "standard"]), true)).toBe(false);
|
|
74
|
+
expect(shouldPrompt(parseArgs(["--template", "standard"]), true)).toBe(false);
|
|
93
75
|
expect(shouldPrompt(parseArgs(["--help"]), true)).toBe(false);
|
|
94
76
|
});
|
|
95
77
|
});
|
|
@@ -110,11 +92,7 @@ describe("parseArgs flags", () => {
|
|
|
110
92
|
});
|
|
111
93
|
|
|
112
94
|
describe("sourceFromArgs", () => {
|
|
113
|
-
test("
|
|
114
|
-
expect(sourceFromArgs(parseArgs(["x", "--from-example", "linkly"]))).toEqual({
|
|
115
|
-
kind: "example",
|
|
116
|
-
id: "linkly",
|
|
117
|
-
});
|
|
95
|
+
test("uses the standard template", () => {
|
|
118
96
|
expect(sourceFromArgs(parseArgs(["x"]))).toEqual({
|
|
119
97
|
kind: "template",
|
|
120
98
|
id: DEFAULT_TEMPLATE,
|
|
@@ -139,23 +117,6 @@ describe("scaffoldArgsFromAnswers ≡ flag-driven", () => {
|
|
|
139
117
|
}
|
|
140
118
|
});
|
|
141
119
|
|
|
142
|
-
test("each --from-example path matches interactive from-example choice", () => {
|
|
143
|
-
for (const id of EXAMPLES) {
|
|
144
|
-
const answers: InteractiveAnswers = {
|
|
145
|
-
name: "x",
|
|
146
|
-
choice: FROM_EXAMPLE_CHOICE,
|
|
147
|
-
example: id,
|
|
148
|
-
installAndRun: false,
|
|
149
|
-
agentsMd: true,
|
|
150
|
-
};
|
|
151
|
-
const fromAnswers = scaffoldArgsFromAnswers(answers);
|
|
152
|
-
const fromFlags = scaffoldArgsFromCli(parseArgs(["x", "--from-example", id]));
|
|
153
|
-
expect(fromAnswers).toEqual(fromFlags);
|
|
154
|
-
expect(fromAnswers.source).toEqual({ kind: "example", id });
|
|
155
|
-
expect(fromAnswers.sqlDriver).toBe("sqlite");
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
|
|
159
120
|
test("interactive never opts into --sql postgres (flag-only)", () => {
|
|
160
121
|
const answers: InteractiveAnswers = {
|
|
161
122
|
name: "x",
|
|
@@ -170,8 +131,8 @@ describe("scaffoldArgsFromAnswers ≡ flag-driven", () => {
|
|
|
170
131
|
).toBe("postgres");
|
|
171
132
|
});
|
|
172
133
|
|
|
173
|
-
test("
|
|
174
|
-
expect(TEMPLATES
|
|
134
|
+
test("only standard is available", () => {
|
|
135
|
+
expect(TEMPLATES).toEqual(["standard"]);
|
|
175
136
|
});
|
|
176
137
|
});
|
|
177
138
|
|
|
@@ -179,7 +140,7 @@ describe("transformPackageJson", () => {
|
|
|
179
140
|
test("rewrites name and okengine file:../.. to installable ref", () => {
|
|
180
141
|
const next = transformPackageJson(
|
|
181
142
|
{
|
|
182
|
-
name: "@oke/template-
|
|
143
|
+
name: "@oke/template-standard",
|
|
183
144
|
private: true,
|
|
184
145
|
dependencies: {
|
|
185
146
|
okengine: "file:../..",
|
|
@@ -298,32 +259,6 @@ describe("scaffold structure", () => {
|
|
|
298
259
|
}
|
|
299
260
|
});
|
|
300
261
|
|
|
301
|
-
test("hello has exactly one flow and no Store", () => {
|
|
302
|
-
const dir = mkdtempSync(join(tmpdir(), "create-oke-hello-assert-"));
|
|
303
|
-
try {
|
|
304
|
-
const result = scaffold({
|
|
305
|
-
targetDir: join(dir, "hello"),
|
|
306
|
-
name: "hello-app",
|
|
307
|
-
source: { kind: "template", id: "hello" },
|
|
308
|
-
});
|
|
309
|
-
const flowFiles = result.files.filter((f) => f.startsWith("src/flows/") && f.endsWith(".ts"));
|
|
310
|
-
expect(flowFiles).toEqual(["src/flows/hello/index.ts"]);
|
|
311
|
-
const flowSrc = readFileSync(join(result.targetDir, "src/flows/hello/index.ts"), "utf8");
|
|
312
|
-
expect(flowSrc).toMatch(/export const hello/);
|
|
313
|
-
expect(flowSrc).not.toMatch(/\bstore\b/);
|
|
314
|
-
expect(result.files.some((f) => f.includes("core.ts"))).toBe(false);
|
|
315
|
-
expect(result.files.some((f) => f.includes("schema.ts"))).toBe(false);
|
|
316
|
-
const all = result.files
|
|
317
|
-
.filter((f) => f.endsWith(".ts"))
|
|
318
|
-
.map((f) => readFileSync(join(result.targetDir, f), "utf8"))
|
|
319
|
-
.join("\n");
|
|
320
|
-
expect(all).not.toMatch(/store\.sql/);
|
|
321
|
-
expect(all).not.toMatch(/\bbookings\b|\borders\b|\blinks\b|\bnotes\b/);
|
|
322
|
-
} finally {
|
|
323
|
-
rmSync(dir, { recursive: true, force: true });
|
|
324
|
-
}
|
|
325
|
-
});
|
|
326
|
-
|
|
327
262
|
test("standard has full four-applications layout and no business domain", () => {
|
|
328
263
|
const dir = mkdtempSync(join(tmpdir(), "create-oke-standard-assert-"));
|
|
329
264
|
try {
|
|
@@ -341,7 +276,7 @@ describe("scaffold structure", () => {
|
|
|
341
276
|
"src/flows/main/shapes.ts",
|
|
342
277
|
"src/flows/main/signals.ts",
|
|
343
278
|
"src/core.ts",
|
|
344
|
-
"src/schema.ts",
|
|
279
|
+
"src/schema.decl.ts",
|
|
345
280
|
"src/app.ts",
|
|
346
281
|
]) {
|
|
347
282
|
expect(result.files).toContain(path);
|
|
@@ -356,26 +291,7 @@ describe("scaffold structure", () => {
|
|
|
356
291
|
}
|
|
357
292
|
});
|
|
358
293
|
|
|
359
|
-
test("--
|
|
360
|
-
const dir = mkdtempSync(join(tmpdir(), "create-oke-from-example-"));
|
|
361
|
-
try {
|
|
362
|
-
const exampleDir = resolveExampleDir("notes");
|
|
363
|
-
const expected = listTemplateFiles(exampleDir);
|
|
364
|
-
const result = scaffold({
|
|
365
|
-
targetDir: join(dir, "notes"),
|
|
366
|
-
name: "from-notes",
|
|
367
|
-
source: { kind: "example", id: "notes" },
|
|
368
|
-
});
|
|
369
|
-
const extras = ["AGENTS.md"];
|
|
370
|
-
if (expected.includes(".env.example")) extras.push(".env.local");
|
|
371
|
-
expect([...result.files].sort()).toEqual([...expected, ...extras].sort());
|
|
372
|
-
expect(result.files).toContain("src/flows/notes/index.ts");
|
|
373
|
-
} finally {
|
|
374
|
-
rmSync(dir, { recursive: true, force: true });
|
|
375
|
-
}
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
test("--sql postgres rewrites schema dialect and pins store.sql", () => {
|
|
294
|
+
test("--sql postgres pins store.sql (abstract schema stays dialect-agnostic)", () => {
|
|
379
295
|
const dir = mkdtempSync(join(tmpdir(), "create-oke-sql-pg-"));
|
|
380
296
|
try {
|
|
381
297
|
const result = scaffold({
|
|
@@ -385,10 +301,14 @@ describe("scaffold structure", () => {
|
|
|
385
301
|
sqlDriver: "postgres",
|
|
386
302
|
});
|
|
387
303
|
expect(result.sqlDriver).toBe("postgres");
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
expect(
|
|
391
|
-
expect(
|
|
304
|
+
// Abstract decl is dialect-agnostic — emit picks pgTable at sync time.
|
|
305
|
+
const decl = readFileSync(join(result.targetDir, "src/schema.decl.ts"), "utf8");
|
|
306
|
+
expect(decl).toContain("store.schema.table(");
|
|
307
|
+
expect(decl).not.toContain("sqliteTable");
|
|
308
|
+
expect(decl).not.toContain("pgTable");
|
|
309
|
+
const drizzle = readFileSync(join(result.targetDir, "drizzle.config.ts"), "utf8");
|
|
310
|
+
expect(drizzle).toContain("OKE_DRIZZLE_DIALECT");
|
|
311
|
+
expect(drizzle).toContain("schema.generated.ts");
|
|
392
312
|
const config = readFileSync(join(result.targetDir, "oke.config.ts"), "utf8");
|
|
393
313
|
expect(config).toMatch(/sql:\s*\{[\s\S]*local:\s*"postgres"/);
|
|
394
314
|
expect(config).toMatch(/sql:\s*\{[\s\S]*docker:\s*"postgres"/);
|
|
@@ -403,9 +323,9 @@ describe("scaffold structure", () => {
|
|
|
403
323
|
const dir = mkdtempSync(join(tmpdir(), "create-oke-no-agents-"));
|
|
404
324
|
try {
|
|
405
325
|
const result = scaffold({
|
|
406
|
-
targetDir: join(dir, "
|
|
326
|
+
targetDir: join(dir, "standard"),
|
|
407
327
|
name: "no-agents",
|
|
408
|
-
source: { kind: "template", id: "
|
|
328
|
+
source: { kind: "template", id: "standard" },
|
|
409
329
|
writeAgentsMd: false,
|
|
410
330
|
});
|
|
411
331
|
expect(result.files).not.toContain("AGENTS.md");
|
|
@@ -443,7 +363,7 @@ describe("non-TTY CLI", () => {
|
|
|
443
363
|
const root = mkdtempSync(join(tmpdir(), "create-oke-flag-"));
|
|
444
364
|
const target = join(root, "flag-app");
|
|
445
365
|
try {
|
|
446
|
-
const code = await run([target, "--template", "
|
|
366
|
+
const code = await run([target, "--template", "standard", "--no-install"], {
|
|
447
367
|
stdinIsTTY: false,
|
|
448
368
|
runPostScaffold: false,
|
|
449
369
|
});
|
|
@@ -453,25 +373,17 @@ describe("non-TTY CLI", () => {
|
|
|
453
373
|
const result = {
|
|
454
374
|
targetDir: target,
|
|
455
375
|
name: "flag-app",
|
|
456
|
-
source: { kind: "template" as const, id: "
|
|
457
|
-
label: "
|
|
376
|
+
source: { kind: "template" as const, id: "standard" as const },
|
|
377
|
+
label: "standard",
|
|
458
378
|
okengineDependency: "x",
|
|
459
379
|
files: [],
|
|
460
380
|
sqlDriver: "sqlite" as const,
|
|
461
381
|
};
|
|
462
382
|
expect(nextStepsText(result)).toContain("oke dev");
|
|
463
383
|
expect(nextStepsText(result)).toContain("bun install");
|
|
464
|
-
expect(nextStepsText(result)).toContain("
|
|
384
|
+
expect(nextStepsText(result)).toContain("oke.omqkhafi.dev");
|
|
465
385
|
} finally {
|
|
466
386
|
rmSync(root, { recursive: true, force: true });
|
|
467
387
|
}
|
|
468
388
|
});
|
|
469
389
|
});
|
|
470
|
-
|
|
471
|
-
describe("examples catalogue", () => {
|
|
472
|
-
test("all four teaching examples resolve", () => {
|
|
473
|
-
for (const id of EXAMPLES) {
|
|
474
|
-
expect(resolveExampleDir(id)).toBeTruthy();
|
|
475
|
-
}
|
|
476
|
-
});
|
|
477
|
-
});
|
package/src/cli.ts
CHANGED
|
@@ -1,25 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `create-oke` CLI —
|
|
2
|
+
* `create-oke` CLI — one standard starter.
|
|
3
3
|
*
|
|
4
4
|
* ```bash
|
|
5
|
-
* bunx create-oke@latest <name> [--template
|
|
6
|
-
* bunx create-oke@latest <name> --from-example notes|linkly|provisions|skyport
|
|
5
|
+
* bunx create-oke@latest <name> [--template standard]
|
|
7
6
|
* bunx create-oke@latest <name> --yes
|
|
8
7
|
* bunx create-oke@latest # interactive when stdin is a TTY
|
|
9
8
|
* ```
|
|
10
9
|
*/
|
|
11
10
|
|
|
12
|
-
import {
|
|
13
|
-
cancel,
|
|
14
|
-
confirm,
|
|
15
|
-
intro,
|
|
16
|
-
isCancel,
|
|
17
|
-
note,
|
|
18
|
-
outro,
|
|
19
|
-
select,
|
|
20
|
-
spinner,
|
|
21
|
-
text,
|
|
22
|
-
} from "@clack/prompts";
|
|
11
|
+
import { cancel, confirm, intro, isCancel, note, outro, spinner, text } from "@clack/prompts";
|
|
23
12
|
import { basename, relative, resolve } from "node:path";
|
|
24
13
|
import { existsSync, rmSync } from "node:fs";
|
|
25
14
|
import { agentsMdContent } from "./agents-md.ts";
|
|
@@ -27,13 +16,9 @@ import { docsUrl } from "./docs-origin.ts";
|
|
|
27
16
|
import { scaffold, type ScaffoldResult, type ScaffoldSource } from "./scaffold.ts";
|
|
28
17
|
import {
|
|
29
18
|
DEFAULT_TEMPLATE,
|
|
30
|
-
EXAMPLE_NEW_IDEAS,
|
|
31
|
-
EXAMPLES,
|
|
32
19
|
TEMPLATE_PURPOSES,
|
|
33
20
|
TEMPLATES,
|
|
34
|
-
isExampleId,
|
|
35
21
|
isTemplateId,
|
|
36
|
-
type ExampleId,
|
|
37
22
|
type TemplateId,
|
|
38
23
|
} from "./templates.ts";
|
|
39
24
|
import { DEFAULT_SQL_DRIVER, SQL_DRIVERS, isSqlDriverId, type SqlDriverId } from "./transform.ts";
|
|
@@ -42,7 +27,6 @@ import { DEFAULT_SQL_DRIVER, SQL_DRIVERS, isSqlDriverId, type SqlDriverId } from
|
|
|
42
27
|
export type CliArgs = {
|
|
43
28
|
readonly name: string | undefined;
|
|
44
29
|
readonly template: TemplateId;
|
|
45
|
-
readonly fromExample: ExampleId | undefined;
|
|
46
30
|
/** True when `--template` / `-t` was present on the argv. */
|
|
47
31
|
readonly templateExplicit: boolean;
|
|
48
32
|
/** Store SQL driver (`sqlite` default). */
|
|
@@ -62,27 +46,13 @@ export type CliArgs = {
|
|
|
62
46
|
readonly targetDir: string | undefined;
|
|
63
47
|
};
|
|
64
48
|
|
|
65
|
-
/**
|
|
66
|
-
* Sentinel choice for "Start from a worked example" in interactive answers.
|
|
67
|
-
* Distinct from clack's internal select value (`__example__`).
|
|
68
|
-
*/
|
|
69
|
-
export const FROM_EXAMPLE_CHOICE = "from-example" as const;
|
|
70
|
-
|
|
71
49
|
/** Answers collected by the interactive ask step (no clack types). */
|
|
72
|
-
export type InteractiveAnswers =
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
| {
|
|
80
|
-
readonly name: string;
|
|
81
|
-
readonly choice: typeof FROM_EXAMPLE_CHOICE;
|
|
82
|
-
readonly example: ExampleId;
|
|
83
|
-
readonly installAndRun: boolean;
|
|
84
|
-
readonly agentsMd: boolean;
|
|
85
|
-
};
|
|
50
|
+
export type InteractiveAnswers = {
|
|
51
|
+
readonly name: string;
|
|
52
|
+
readonly choice: TemplateId;
|
|
53
|
+
readonly installAndRun: boolean;
|
|
54
|
+
readonly agentsMd: boolean;
|
|
55
|
+
};
|
|
86
56
|
|
|
87
57
|
/**
|
|
88
58
|
* Canonical scaffold invocation — shared by interactive and flag-driven paths.
|
|
@@ -103,7 +73,6 @@ export type ScaffoldCallArgs = {
|
|
|
103
73
|
export function parseArgs(argv: readonly string[]): CliArgs {
|
|
104
74
|
let name: string | undefined;
|
|
105
75
|
let template: TemplateId = DEFAULT_TEMPLATE;
|
|
106
|
-
let fromExample: ExampleId | undefined;
|
|
107
76
|
let templateExplicit = false;
|
|
108
77
|
let sqlDriver: SqlDriverId = DEFAULT_SQL_DRIVER;
|
|
109
78
|
let sqlDriverExplicit = false;
|
|
@@ -175,22 +144,6 @@ export function parseArgs(argv: readonly string[]): CliArgs {
|
|
|
175
144
|
templateExplicit = true;
|
|
176
145
|
continue;
|
|
177
146
|
}
|
|
178
|
-
if (a === "--from-example") {
|
|
179
|
-
const next = argv[++i];
|
|
180
|
-
if (!next || !isExampleId(next)) {
|
|
181
|
-
throw new Error(`create-oke: --from-example must be one of ${EXAMPLES.join("|")}`);
|
|
182
|
-
}
|
|
183
|
-
fromExample = next;
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
if (a.startsWith("--from-example=")) {
|
|
187
|
-
const value = a.slice("--from-example=".length);
|
|
188
|
-
if (!isExampleId(value)) {
|
|
189
|
-
throw new Error(`create-oke: --from-example must be one of ${EXAMPLES.join("|")}`);
|
|
190
|
-
}
|
|
191
|
-
fromExample = value;
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
194
147
|
if (a.startsWith("-")) {
|
|
195
148
|
throw new Error(`create-oke: unknown option ${a}`);
|
|
196
149
|
}
|
|
@@ -201,10 +154,6 @@ export function parseArgs(argv: readonly string[]): CliArgs {
|
|
|
201
154
|
throw new Error(`create-oke: unexpected argument ${a}`);
|
|
202
155
|
}
|
|
203
156
|
|
|
204
|
-
if (templateExplicit && fromExample !== undefined) {
|
|
205
|
-
throw new Error("create-oke: use either --template or --from-example, not both");
|
|
206
|
-
}
|
|
207
|
-
|
|
208
157
|
if (argv.includes("--install") && argv.includes("--no-install")) {
|
|
209
158
|
throw new Error("create-oke: use either --install or --no-install, not both");
|
|
210
159
|
}
|
|
@@ -216,7 +165,6 @@ export function parseArgs(argv: readonly string[]): CliArgs {
|
|
|
216
165
|
return {
|
|
217
166
|
name,
|
|
218
167
|
template,
|
|
219
|
-
fromExample,
|
|
220
168
|
templateExplicit,
|
|
221
169
|
sqlDriver,
|
|
222
170
|
sqlDriverExplicit,
|
|
@@ -266,25 +214,18 @@ export function helpText(): string {
|
|
|
266
214
|
(id) =>
|
|
267
215
|
` ${id.padEnd(12)}${TEMPLATE_PURPOSES[id]}${id === DEFAULT_TEMPLATE ? " (default)" : ""}`,
|
|
268
216
|
).join("\n");
|
|
269
|
-
const exampleLines = EXAMPLES.map((id) => ` ${id.padEnd(12)}${EXAMPLE_NEW_IDEAS[id]}`).join(
|
|
270
|
-
"\n",
|
|
271
|
-
);
|
|
272
|
-
|
|
273
217
|
return `create-oke — scaffold an okengine app
|
|
274
218
|
|
|
275
219
|
Usage:
|
|
276
|
-
bunx create-oke@latest <name> [--template
|
|
277
|
-
bunx create-oke@latest <name> --from-example notes|linkly|provisions|skyport
|
|
220
|
+
bunx create-oke@latest <name> [--template standard]
|
|
278
221
|
bunx create-oke@latest <name> --yes
|
|
279
222
|
bunx create-oke@latest # interactive (TTY only)
|
|
280
223
|
|
|
281
224
|
Options:
|
|
282
225
|
-t, --template <id> Clean starter (default: ${DEFAULT_TEMPLATE})
|
|
283
|
-
--from-example <id> Teaching example (non-interactive)
|
|
284
226
|
--sql <id> Opt-in Store SQL dialect: ${SQL_DRIVERS.join("|")}
|
|
285
227
|
Default keeps local sqlite · docker/prod postgres.
|
|
286
|
-
--sql postgres
|
|
287
|
-
pins store.sql local/docker/prod to postgres.
|
|
228
|
+
--sql postgres pins store.sql local/docker/prod to postgres.
|
|
288
229
|
-y, --yes No prompts; defaults + bun install (no oke dev)
|
|
289
230
|
--install Run bun install after scaffold
|
|
290
231
|
--no-install Skip bun install
|
|
@@ -292,16 +233,11 @@ Options:
|
|
|
292
233
|
--no-agents-md Skip AGENTS.md
|
|
293
234
|
-h, --help Show this help
|
|
294
235
|
|
|
295
|
-
|
|
236
|
+
Template:
|
|
296
237
|
${templateLines}
|
|
297
238
|
|
|
298
|
-
--from-example (copies a teaching example, including its business logic and
|
|
299
|
-
comments — most new projects want --template instead):
|
|
300
|
-
${exampleLines}
|
|
301
|
-
|
|
302
239
|
No telemetry. Bun only. On a TTY, a project name alone still opens the wizard
|
|
303
|
-
(confirm name
|
|
304
|
-
--from-example stay fully scriptable.
|
|
240
|
+
(confirm name and install). Non-TTY / --yes / --template stay fully scriptable.
|
|
305
241
|
`;
|
|
306
242
|
}
|
|
307
243
|
|
|
@@ -309,7 +245,7 @@ No telemetry. Bun only. On a TTY, a project name alone still opens the wizard
|
|
|
309
245
|
* Whether the CLI should open the interactive Clack flow.
|
|
310
246
|
*
|
|
311
247
|
* TTY humans get the wizard even when a name is pre-filled. Config flags
|
|
312
|
-
*
|
|
248
|
+
* `--template` or `--yes` skips prompts for CI/agents.
|
|
313
249
|
*
|
|
314
250
|
* @param args - Parsed args
|
|
315
251
|
* @param stdinIsTTY - `process.stdin.isTTY`
|
|
@@ -319,7 +255,6 @@ export function shouldPrompt(args: CliArgs, stdinIsTTY: boolean | undefined): bo
|
|
|
319
255
|
if (args.help) return false;
|
|
320
256
|
if (args.yes) return false;
|
|
321
257
|
if (args.templateExplicit) return false;
|
|
322
|
-
if (args.fromExample !== undefined) return false;
|
|
323
258
|
return true;
|
|
324
259
|
}
|
|
325
260
|
|
|
@@ -329,9 +264,6 @@ export function shouldPrompt(args: CliArgs, stdinIsTTY: boolean | undefined): bo
|
|
|
329
264
|
* @param args - Parsed args
|
|
330
265
|
*/
|
|
331
266
|
export function sourceFromArgs(args: CliArgs): ScaffoldSource {
|
|
332
|
-
if (args.fromExample !== undefined) {
|
|
333
|
-
return { kind: "example", id: args.fromExample };
|
|
334
|
-
}
|
|
335
267
|
return { kind: "template", id: args.template };
|
|
336
268
|
}
|
|
337
269
|
|
|
@@ -359,7 +291,7 @@ export function scaffoldArgsFromCli(args: CliArgs): ScaffoldCallArgs {
|
|
|
359
291
|
* Pure map from interactive answers → scaffold call args.
|
|
360
292
|
*
|
|
361
293
|
* Independent of clack — unit-tested against {@link scaffoldArgsFromCli}
|
|
362
|
-
* for
|
|
294
|
+
* for the standard template.
|
|
363
295
|
*
|
|
364
296
|
* @param answers - Collected interactive answers
|
|
365
297
|
*/
|
|
@@ -368,15 +300,6 @@ export function scaffoldArgsFromAnswers(answers: InteractiveAnswers): ScaffoldCa
|
|
|
368
300
|
const name = basename(targetDir);
|
|
369
301
|
// Interactive always keeps the dual-mode default (local sqlite ·
|
|
370
302
|
// docker/prod postgres). Opt into postgres-everywhere with `--sql postgres`.
|
|
371
|
-
if (answers.choice === FROM_EXAMPLE_CHOICE) {
|
|
372
|
-
return {
|
|
373
|
-
name,
|
|
374
|
-
targetDir,
|
|
375
|
-
source: { kind: "example", id: answers.example },
|
|
376
|
-
agentsMd: answers.agentsMd,
|
|
377
|
-
sqlDriver: DEFAULT_SQL_DRIVER,
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
303
|
return {
|
|
381
304
|
name,
|
|
382
305
|
targetDir,
|
|
@@ -419,54 +342,8 @@ export async function askInteractiveAnswers(
|
|
|
419
342
|
if (isCancel(nameValue)) return null;
|
|
420
343
|
const name = String(nameValue).trim();
|
|
421
344
|
|
|
422
|
-
const templateValue = await select({
|
|
423
|
-
message: "Template",
|
|
424
|
-
options: [
|
|
425
|
-
...TEMPLATES.map((id) => ({
|
|
426
|
-
value: id as string,
|
|
427
|
-
label: id,
|
|
428
|
-
hint: TEMPLATE_PURPOSES[id],
|
|
429
|
-
})),
|
|
430
|
-
{
|
|
431
|
-
value: "__example__",
|
|
432
|
-
label: "Start from a worked example",
|
|
433
|
-
hint: "Teaching apps with business logic — most projects want a template",
|
|
434
|
-
},
|
|
435
|
-
],
|
|
436
|
-
initialValue: DEFAULT_TEMPLATE,
|
|
437
|
-
});
|
|
438
|
-
if (isCancel(templateValue)) return null;
|
|
439
|
-
|
|
440
345
|
const agentsMd = partial.agentsMd ?? true;
|
|
441
346
|
|
|
442
|
-
if (templateValue === "__example__") {
|
|
443
|
-
const exampleValue = await select({
|
|
444
|
-
message: "Example",
|
|
445
|
-
options: EXAMPLES.map((id) => ({
|
|
446
|
-
value: id,
|
|
447
|
-
label: id,
|
|
448
|
-
hint: EXAMPLE_NEW_IDEAS[id],
|
|
449
|
-
})),
|
|
450
|
-
});
|
|
451
|
-
if (isCancel(exampleValue)) return null;
|
|
452
|
-
|
|
453
|
-
const installAndRunValue = await confirm({
|
|
454
|
-
message: "Install dependencies and start oke dev?",
|
|
455
|
-
initialValue: true,
|
|
456
|
-
});
|
|
457
|
-
if (isCancel(installAndRunValue)) return null;
|
|
458
|
-
|
|
459
|
-
return {
|
|
460
|
-
name,
|
|
461
|
-
choice: FROM_EXAMPLE_CHOICE,
|
|
462
|
-
example: exampleValue as ExampleId,
|
|
463
|
-
installAndRun: Boolean(installAndRunValue),
|
|
464
|
-
agentsMd,
|
|
465
|
-
};
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
const templateId = templateValue as TemplateId;
|
|
469
|
-
|
|
470
347
|
const installAndRunValue = await confirm({
|
|
471
348
|
message: "Install dependencies and start oke dev?",
|
|
472
349
|
initialValue: true,
|
|
@@ -475,7 +352,7 @@ export async function askInteractiveAnswers(
|
|
|
475
352
|
|
|
476
353
|
return {
|
|
477
354
|
name,
|
|
478
|
-
choice:
|
|
355
|
+
choice: DEFAULT_TEMPLATE,
|
|
479
356
|
installAndRun: Boolean(installAndRunValue),
|
|
480
357
|
agentsMd,
|
|
481
358
|
};
|
package/src/docs-origin.ts
CHANGED
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
* Canonical public docs origin for scaffolded AGENTS.md and next-steps.
|
|
3
3
|
*
|
|
4
4
|
* Keep in lockstep with `src/docs-origin.ts` in the okengine package.
|
|
5
|
-
* Flip both when the custom domain is attached: `https://oke.omqkhafi.dev`.
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
|
-
/** Live docs site
|
|
9
|
-
export const DOCS_ORIGIN = "https://
|
|
7
|
+
/** Live docs site. */
|
|
8
|
+
export const DOCS_ORIGIN = "https://oke.omqkhafi.dev" as const;
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
11
|
* Absolute docs URL for a path under the handbook / site.
|