create-oke 0.1.4 → 0.2.1
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 +55 -0
- package/{templates → examples}/linkly/package.json +1 -1
- package/{templates → examples}/notes/package.json +1 -1
- package/{templates → examples}/provisions/package.json +1 -1
- package/{templates → examples}/skyport/package.json +1 -1
- package/package.json +7 -3
- package/src/cli.test.ts +246 -18
- package/src/cli.ts +358 -32
- package/src/index.ts +9 -1
- package/src/scaffold.ts +50 -32
- package/src/sync-templates.ts +28 -7
- package/src/templates.ts +85 -12
- package/src/transform.ts +3 -2
- package/templates/full/oke.config.ts +23 -0
- package/templates/full/package.json +15 -0
- package/templates/full/src/ai.ts +11 -0
- package/templates/full/src/app.ts +34 -0
- package/templates/full/src/channels.ts +9 -0
- package/templates/full/src/core.ts +4 -0
- package/templates/full/src/flows/main/index.ts +27 -0
- package/templates/full/src/flows/main/shapes.ts +3 -0
- package/templates/full/src/flows/main/signals.ts +8 -0
- package/templates/full/src/gates.ts +4 -0
- package/templates/full/src/locales/ar.ts +3 -0
- package/templates/full/src/locales/en.ts +3 -0
- package/templates/full/src/schema.ts +8 -0
- package/templates/full/src/vault.ts +9 -0
- package/templates/full/tests/full.test.ts +11 -0
- package/templates/hello/README.md +13 -0
- package/templates/hello/oke.config.ts +3 -0
- package/templates/hello/package.json +14 -0
- package/templates/hello/src/app.ts +10 -0
- package/templates/hello/src/flows/hello/index.ts +10 -0
- package/templates/hello/tests/hello.test.ts +10 -0
- package/templates/minimal/oke.config.ts +7 -0
- package/templates/minimal/package.json +15 -0
- package/templates/minimal/src/app.ts +13 -0
- package/templates/minimal/src/core.ts +4 -0
- package/templates/minimal/src/flows/main/index.ts +32 -0
- package/templates/minimal/src/schema.ts +8 -0
- package/templates/minimal/tests/minimal.test.ts +13 -0
- package/templates/standard/oke.config.ts +19 -0
- package/templates/standard/package.json +15 -0
- package/templates/standard/src/app.ts +18 -0
- package/templates/standard/src/channels.ts +4 -0
- package/templates/standard/src/core.ts +4 -0
- package/templates/standard/src/flows/main/index.ts +14 -0
- package/templates/standard/src/flows/main/shapes.ts +2 -0
- package/templates/standard/src/flows/main/signals.ts +4 -0
- package/templates/standard/src/gates.ts +4 -0
- package/templates/standard/src/locales/ar.ts +2 -0
- package/templates/standard/src/locales/en.ts +2 -0
- package/templates/standard/src/schema.ts +9 -0
- package/templates/standard/src/vault.ts +4 -0
- package/templates/standard/tests/standard.test.ts +10 -0
- /package/{templates → examples}/linkly/oke.config.ts +0 -0
- /package/{templates → examples}/linkly/src/app.ts +0 -0
- /package/{templates → examples}/linkly/src/core.ts +0 -0
- /package/{templates → examples}/linkly/src/flows/analytics/index.ts +0 -0
- /package/{templates → examples}/linkly/src/flows/links/index.ts +0 -0
- /package/{templates → examples}/linkly/src/flows/links/shapes.ts +0 -0
- /package/{templates → examples}/linkly/src/flows/links/signals.ts +0 -0
- /package/{templates → examples}/linkly/src/gates.ts +0 -0
- /package/{templates → examples}/linkly/src/schema.ts +0 -0
- /package/{templates → examples}/linkly/tests/linkly.test.ts +0 -0
- /package/{templates → examples}/notes/oke.config.ts +0 -0
- /package/{templates → examples}/notes/src/app.ts +0 -0
- /package/{templates → examples}/notes/src/core.ts +0 -0
- /package/{templates → examples}/notes/src/flows/notes/index.ts +0 -0
- /package/{templates → examples}/notes/src/schema.ts +0 -0
- /package/{templates → examples}/notes/tests/notes.test.ts +0 -0
- /package/{templates → examples}/provisions/oke.config.ts +0 -0
- /package/{templates → examples}/provisions/src/app.ts +0 -0
- /package/{templates → examples}/provisions/src/channels.ts +0 -0
- /package/{templates → examples}/provisions/src/core.ts +0 -0
- /package/{templates → examples}/provisions/src/flows/notifications/index.ts +0 -0
- /package/{templates → examples}/provisions/src/flows/orders/index.ts +0 -0
- /package/{templates → examples}/provisions/src/flows/orders/shapes.ts +0 -0
- /package/{templates → examples}/provisions/src/flows/orders/signals.ts +0 -0
- /package/{templates → examples}/provisions/src/flows/payments/index.ts +0 -0
- /package/{templates → examples}/provisions/src/flows/payments/shapes.ts +0 -0
- /package/{templates → examples}/provisions/src/flows/payments/stripe.ts +0 -0
- /package/{templates → examples}/provisions/src/gates.ts +0 -0
- /package/{templates → examples}/provisions/src/locales/ar.ts +0 -0
- /package/{templates → examples}/provisions/src/locales/en.ts +0 -0
- /package/{templates → examples}/provisions/src/plugins/audit-schema.ts +0 -0
- /package/{templates → examples}/provisions/src/plugins/audit.ts +0 -0
- /package/{templates → examples}/provisions/src/schema.ts +0 -0
- /package/{templates → examples}/provisions/src/vault.ts +0 -0
- /package/{templates → examples}/provisions/tests/orders.test.ts +0 -0
- /package/{templates → examples}/skyport/evals/triage.jsonl +0 -0
- /package/{templates → examples}/skyport/oke.config.ts +0 -0
- /package/{templates → examples}/skyport/oke.images.lock +0 -0
- /package/{templates → examples}/skyport/src/ai.ts +0 -0
- /package/{templates → examples}/skyport/src/app.ts +0 -0
- /package/{templates → examples}/skyport/src/channels.ts +0 -0
- /package/{templates → examples}/skyport/src/core.ts +0 -0
- /package/{templates → examples}/skyport/src/flows/bookings/index.ts +0 -0
- /package/{templates → examples}/skyport/src/flows/bookings/shapes.ts +0 -0
- /package/{templates → examples}/skyport/src/flows/bookings/signals.ts +0 -0
- /package/{templates → examples}/skyport/src/flows/notifications/index.ts +0 -0
- /package/{templates → examples}/skyport/src/flows/payments/index.ts +0 -0
- /package/{templates → examples}/skyport/src/flows/payments/shapes.ts +0 -0
- /package/{templates → examples}/skyport/src/flows/support/index.ts +0 -0
- /package/{templates → examples}/skyport/src/flows/users/elements.ts +0 -0
- /package/{templates → examples}/skyport/src/flows/users/index.ts +0 -0
- /package/{templates → examples}/skyport/src/flows/users/shapes.ts +0 -0
- /package/{templates → examples}/skyport/src/gates.ts +0 -0
- /package/{templates → examples}/skyport/src/journeys.ts +0 -0
- /package/{templates → examples}/skyport/src/locales/ar.ts +0 -0
- /package/{templates → examples}/skyport/src/locales/en.ts +0 -0
- /package/{templates → examples}/skyport/src/plugins/audit-schema.ts +0 -0
- /package/{templates → examples}/skyport/src/plugins/audit.ts +0 -0
- /package/{templates → examples}/skyport/src/plugins/panel.tsx +0 -0
- /package/{templates → examples}/skyport/src/schema.ts +0 -0
- /package/{templates → examples}/skyport/src/vault.ts +0 -0
- /package/{templates → examples}/skyport/tests/support.test.ts +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# create-oke
|
|
2
|
+
|
|
3
|
+
Scaffold an [okengine](https://www.npmjs.com/package/okengine) app.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
bunx create-oke@latest my-app --template hello
|
|
7
|
+
cd my-app
|
|
8
|
+
oke dev
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
| Port | What |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `:6530` | your app |
|
|
14
|
+
| `:6533` | Console |
|
|
15
|
+
| `:6535` | MCP |
|
|
16
|
+
|
|
17
|
+
Default template is **standard** (full recommended file layout, empty scaffolding).
|
|
18
|
+
Pick another with `--template`, or start from a teaching example with `--from-example`:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
bunx create-oke@latest my-app --template hello
|
|
22
|
+
bunx create-oke@latest my-app --template minimal
|
|
23
|
+
bunx create-oke@latest my-app --template full
|
|
24
|
+
bunx create-oke@latest my-notes --from-example notes
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
| Template | Purpose |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `hello` | Fastest possible "it works" — one flow, no Store |
|
|
30
|
+
| `minimal` | Smallest shape you'd actually ship — Store + 1–2 flows |
|
|
31
|
+
| `standard` | Full recommended file layout, empty scaffolding (default) |
|
|
32
|
+
| `full` | Every element present and wired, no business logic |
|
|
33
|
+
|
|
34
|
+
`--from-example` copies a teaching example (including its business logic and
|
|
35
|
+
comments) — most new projects want `--template` instead.
|
|
36
|
+
|
|
37
|
+
| Example | Adds |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `notes` | `oke` · `flow` · `http` · `store.sql` · typed client |
|
|
40
|
+
| `linkly` | `signal` · `clock` · `gate` |
|
|
41
|
+
| `provisions` | durable flows · `vault` · `channel` · plugins |
|
|
42
|
+
| `skyport` | `ai` · multi-tenancy · distributed topology |
|
|
43
|
+
|
|
44
|
+
With no arguments in a real terminal, create-oke asks for a project name and
|
|
45
|
+
template interactively. Non-TTY / CI / any explicit flag stays fully scriptable
|
|
46
|
+
— zero prompts.
|
|
47
|
+
|
|
48
|
+
## Registries
|
|
49
|
+
|
|
50
|
+
| Registry | Package |
|
|
51
|
+
|---|---|
|
|
52
|
+
| npm (CLI) | [`create-oke`](https://www.npmjs.com/package/create-oke) — use this with `bunx` |
|
|
53
|
+
| JSR | [`@omqkhafi/create-oke`](https://jsr.io/@omqkhafi/create-oke) — same sources; prefer npm for the binary |
|
|
54
|
+
|
|
55
|
+
Requires **Bun ≥ 1.3**. Framework docs: [okengine README](https://github.com/omqkhafi/okengine#readme) · [unified theory](https://github.com/omqkhafi/okengine/blob/main/docs/spec/unified-theory.md).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-oke",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Scaffold an okengine app — bunx create-oke@latest <name>",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -11,14 +11,15 @@
|
|
|
11
11
|
"directory": "packages/create-oke"
|
|
12
12
|
},
|
|
13
13
|
"bin": {
|
|
14
|
-
"create-oke": "
|
|
14
|
+
"create-oke": "src/index.ts"
|
|
15
15
|
},
|
|
16
16
|
"exports": {
|
|
17
17
|
".": "./src/index.ts"
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
20
|
"src",
|
|
21
|
-
"templates"
|
|
21
|
+
"templates",
|
|
22
|
+
"examples"
|
|
22
23
|
],
|
|
23
24
|
"scripts": {
|
|
24
25
|
"prepack": "bun ./src/sync-templates.ts",
|
|
@@ -29,6 +30,9 @@
|
|
|
29
30
|
"engines": {
|
|
30
31
|
"bun": ">=1.3"
|
|
31
32
|
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@clack/prompts": "^1.7.0"
|
|
35
|
+
},
|
|
32
36
|
"devDependencies": {
|
|
33
37
|
"@types/bun": "latest",
|
|
34
38
|
"typescript": "^7.0.2"
|
package/src/cli.test.ts
CHANGED
|
@@ -1,14 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Unit tests for create-oke argument parsing and
|
|
2
|
+
* Unit tests for create-oke argument parsing, transforms, and non-TTY behavior.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { describe, expect, test } from "bun:test";
|
|
6
|
-
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
6
|
+
import { mkdtempSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
import { join } from "node:path";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
FROM_EXAMPLE_CHOICE,
|
|
11
|
+
formatCdPath,
|
|
12
|
+
nextStepsText,
|
|
13
|
+
parseArgs,
|
|
14
|
+
run,
|
|
15
|
+
scaffoldArgsFromAnswers,
|
|
16
|
+
scaffoldArgsFromCli,
|
|
17
|
+
shouldPrompt,
|
|
18
|
+
sourceFromArgs,
|
|
19
|
+
type InteractiveAnswers,
|
|
20
|
+
} from "./cli.ts";
|
|
10
21
|
import { listTemplateFiles, scaffold } from "./scaffold.ts";
|
|
11
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
DEFAULT_TEMPLATE,
|
|
24
|
+
EXAMPLES,
|
|
25
|
+
TEMPLATES,
|
|
26
|
+
packageRoot,
|
|
27
|
+
resolveExampleDir,
|
|
28
|
+
resolveTemplateDir,
|
|
29
|
+
} from "./templates.ts";
|
|
12
30
|
import {
|
|
13
31
|
sanitizeProjectName,
|
|
14
32
|
shouldSkipTemplatePath,
|
|
@@ -16,22 +34,98 @@ import {
|
|
|
16
34
|
} from "./transform.ts";
|
|
17
35
|
|
|
18
36
|
describe("parseArgs", () => {
|
|
19
|
-
test("defaults template to
|
|
37
|
+
test("defaults template to standard", () => {
|
|
20
38
|
const a = parseArgs(["my-app"]);
|
|
21
39
|
expect(a.name).toBe("my-app");
|
|
22
|
-
expect(a.template).toBe("
|
|
40
|
+
expect(a.template).toBe("standard");
|
|
41
|
+
expect(a.templateExplicit).toBe(false);
|
|
42
|
+
expect(a.fromExample).toBeUndefined();
|
|
23
43
|
});
|
|
24
44
|
|
|
25
45
|
test("accepts --template and -t", () => {
|
|
26
|
-
expect(parseArgs(["x", "--template", "
|
|
27
|
-
expect(parseArgs(["x", "-t", "
|
|
28
|
-
expect(parseArgs(["x", "--template=
|
|
29
|
-
|
|
46
|
+
expect(parseArgs(["x", "--template", "hello"]).template).toBe("hello");
|
|
47
|
+
expect(parseArgs(["x", "-t", "full"]).template).toBe("full");
|
|
48
|
+
expect(parseArgs(["x", "--template=minimal"]).template).toBe("minimal");
|
|
49
|
+
expect(parseArgs(["x", "--template", "hello"]).templateExplicit).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("accepts --from-example", () => {
|
|
53
|
+
expect(parseArgs(["x", "--from-example", "notes"]).fromExample).toBe(
|
|
54
|
+
"notes",
|
|
55
|
+
);
|
|
56
|
+
expect(parseArgs(["x", "--from-example=skyport"]).fromExample).toBe(
|
|
57
|
+
"skyport",
|
|
30
58
|
);
|
|
31
59
|
});
|
|
32
60
|
|
|
33
|
-
test("rejects unknown template", () => {
|
|
61
|
+
test("rejects unknown template / example / both", () => {
|
|
34
62
|
expect(() => parseArgs(["x", "--template", "nope"])).toThrow(/template/);
|
|
63
|
+
expect(() => parseArgs(["x", "--from-example", "nope"])).toThrow(/example/);
|
|
64
|
+
expect(() =>
|
|
65
|
+
parseArgs(["x", "--template", "hello", "--from-example", "notes"]),
|
|
66
|
+
).toThrow(/either/);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("shouldPrompt", () => {
|
|
71
|
+
test("TTY + no args → interactive", () => {
|
|
72
|
+
expect(shouldPrompt(parseArgs([]), true)).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("non-TTY or any explicit input → no prompts", () => {
|
|
76
|
+
expect(shouldPrompt(parseArgs([]), false)).toBe(false);
|
|
77
|
+
expect(shouldPrompt(parseArgs(["my-app"]), true)).toBe(false);
|
|
78
|
+
expect(shouldPrompt(parseArgs(["--template", "hello"]), true)).toBe(false);
|
|
79
|
+
expect(shouldPrompt(parseArgs(["--from-example", "notes"]), true)).toBe(
|
|
80
|
+
false,
|
|
81
|
+
);
|
|
82
|
+
expect(shouldPrompt(parseArgs(["--help"]), true)).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe("sourceFromArgs", () => {
|
|
87
|
+
test("prefers example when set", () => {
|
|
88
|
+
expect(sourceFromArgs(parseArgs(["x", "--from-example", "linkly"]))).toEqual(
|
|
89
|
+
{ kind: "example", id: "linkly" },
|
|
90
|
+
);
|
|
91
|
+
expect(sourceFromArgs(parseArgs(["x"]))).toEqual({
|
|
92
|
+
kind: "template",
|
|
93
|
+
id: DEFAULT_TEMPLATE,
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("scaffoldArgsFromAnswers ≡ flag-driven", () => {
|
|
99
|
+
test("each --template path matches interactive choice", () => {
|
|
100
|
+
for (const id of TEMPLATES) {
|
|
101
|
+
const answers: InteractiveAnswers = { name: "x", choice: id };
|
|
102
|
+
const fromAnswers = scaffoldArgsFromAnswers(answers);
|
|
103
|
+
const fromFlags = scaffoldArgsFromCli(
|
|
104
|
+
parseArgs(["x", "--template", id]),
|
|
105
|
+
);
|
|
106
|
+
expect(fromAnswers).toEqual(fromFlags);
|
|
107
|
+
expect(fromAnswers.source).toEqual({ kind: "template", id });
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("each --from-example path matches interactive from-example choice", () => {
|
|
112
|
+
for (const id of EXAMPLES) {
|
|
113
|
+
const answers: InteractiveAnswers = {
|
|
114
|
+
name: "x",
|
|
115
|
+
choice: FROM_EXAMPLE_CHOICE,
|
|
116
|
+
example: id,
|
|
117
|
+
};
|
|
118
|
+
const fromAnswers = scaffoldArgsFromAnswers(answers);
|
|
119
|
+
const fromFlags = scaffoldArgsFromCli(
|
|
120
|
+
parseArgs(["x", "--from-example", id]),
|
|
121
|
+
);
|
|
122
|
+
expect(fromAnswers).toEqual(fromFlags);
|
|
123
|
+
expect(fromAnswers.source).toEqual({ kind: "example", id });
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("8 paths cover every template and every example", () => {
|
|
128
|
+
expect(TEMPLATES.length + EXAMPLES.length).toBe(8);
|
|
35
129
|
});
|
|
36
130
|
});
|
|
37
131
|
|
|
@@ -39,7 +133,7 @@ describe("transformPackageJson", () => {
|
|
|
39
133
|
test("rewrites name and okengine file:../.. to installable ref", () => {
|
|
40
134
|
const next = transformPackageJson(
|
|
41
135
|
{
|
|
42
|
-
name: "@oke/
|
|
136
|
+
name: "@oke/template-hello",
|
|
43
137
|
private: true,
|
|
44
138
|
dependencies: {
|
|
45
139
|
okengine: "file:../..",
|
|
@@ -81,7 +175,7 @@ describe("formatCdPath", () => {
|
|
|
81
175
|
});
|
|
82
176
|
|
|
83
177
|
describe("scaffold structure", () => {
|
|
84
|
-
test("each template produces
|
|
178
|
+
test("each clean template produces its source tree (minus skips)", () => {
|
|
85
179
|
for (const id of TEMPLATES) {
|
|
86
180
|
const dir = mkdtempSync(join(tmpdir(), `create-oke-${id}-`));
|
|
87
181
|
try {
|
|
@@ -90,7 +184,7 @@ describe("scaffold structure", () => {
|
|
|
90
184
|
const result = scaffold({
|
|
91
185
|
targetDir: join(dir, id),
|
|
92
186
|
name: `app-${id}`,
|
|
93
|
-
|
|
187
|
+
source: { kind: "template", id },
|
|
94
188
|
});
|
|
95
189
|
expect([...result.files].sort()).toEqual(expected);
|
|
96
190
|
const pkg = JSON.parse(
|
|
@@ -98,13 +192,147 @@ describe("scaffold structure", () => {
|
|
|
98
192
|
) as { name: string; dependencies: { okengine: string } };
|
|
99
193
|
expect(pkg.name).toBe(`app-${id}`);
|
|
100
194
|
expect(pkg.dependencies.okengine).not.toBe("file:../..");
|
|
101
|
-
expect(
|
|
102
|
-
pkg.dependencies.okengine.startsWith("file:") ||
|
|
103
|
-
/^\d+\.\d+\.\d+/.test(pkg.dependencies.okengine),
|
|
104
|
-
).toBe(true);
|
|
105
195
|
} finally {
|
|
106
196
|
rmSync(dir, { recursive: true, force: true });
|
|
107
197
|
}
|
|
108
198
|
}
|
|
109
199
|
});
|
|
200
|
+
|
|
201
|
+
test("hello has exactly one flow and no Store", () => {
|
|
202
|
+
const dir = mkdtempSync(join(tmpdir(), "create-oke-hello-assert-"));
|
|
203
|
+
try {
|
|
204
|
+
const result = scaffold({
|
|
205
|
+
targetDir: join(dir, "hello"),
|
|
206
|
+
name: "hello-app",
|
|
207
|
+
source: { kind: "template", id: "hello" },
|
|
208
|
+
});
|
|
209
|
+
const flowFiles = result.files.filter(
|
|
210
|
+
(f) => f.startsWith("src/flows/") && f.endsWith(".ts"),
|
|
211
|
+
);
|
|
212
|
+
expect(flowFiles).toEqual(["src/flows/hello/index.ts"]);
|
|
213
|
+
const flowSrc = readFileSync(
|
|
214
|
+
join(result.targetDir, "src/flows/hello/index.ts"),
|
|
215
|
+
"utf8",
|
|
216
|
+
);
|
|
217
|
+
expect(flowSrc).toMatch(/export const hello/);
|
|
218
|
+
expect(flowSrc).not.toMatch(/\bstore\b/);
|
|
219
|
+
expect(result.files.some((f) => f.includes("core.ts"))).toBe(false);
|
|
220
|
+
expect(result.files.some((f) => f.includes("schema.ts"))).toBe(false);
|
|
221
|
+
const all = result.files
|
|
222
|
+
.filter((f) => f.endsWith(".ts"))
|
|
223
|
+
.map((f) => readFileSync(join(result.targetDir, f), "utf8"))
|
|
224
|
+
.join("\n");
|
|
225
|
+
expect(all).not.toMatch(/store\.sql/);
|
|
226
|
+
expect(all).not.toMatch(/\bbookings\b|\borders\b|\blinks\b|\bnotes\b/);
|
|
227
|
+
} finally {
|
|
228
|
+
rmSync(dir, { recursive: true, force: true });
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("standard has full four-applications layout and no business domain", () => {
|
|
233
|
+
const dir = mkdtempSync(join(tmpdir(), "create-oke-standard-assert-"));
|
|
234
|
+
try {
|
|
235
|
+
const result = scaffold({
|
|
236
|
+
targetDir: join(dir, "standard"),
|
|
237
|
+
name: "standard-app",
|
|
238
|
+
source: { kind: "template", id: "standard" },
|
|
239
|
+
});
|
|
240
|
+
for (const path of [
|
|
241
|
+
"src/gates.ts",
|
|
242
|
+
"src/vault.ts",
|
|
243
|
+
"src/channels.ts",
|
|
244
|
+
"src/locales/en.ts",
|
|
245
|
+
"src/locales/ar.ts",
|
|
246
|
+
"src/flows/main/shapes.ts",
|
|
247
|
+
"src/flows/main/signals.ts",
|
|
248
|
+
"src/core.ts",
|
|
249
|
+
"src/schema.ts",
|
|
250
|
+
"src/app.ts",
|
|
251
|
+
]) {
|
|
252
|
+
expect(result.files).toContain(path);
|
|
253
|
+
}
|
|
254
|
+
const all = result.files
|
|
255
|
+
.filter((f) => f.endsWith(".ts") || f.endsWith(".md"))
|
|
256
|
+
.map((f) => readFileSync(join(result.targetDir, f), "utf8"))
|
|
257
|
+
.join("\n");
|
|
258
|
+
expect(all).not.toMatch(/\bbookings\b|\borders\b|\blinks\b|\bstripe\b/i);
|
|
259
|
+
} finally {
|
|
260
|
+
rmSync(dir, { recursive: true, force: true });
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("--from-example notes matches today's example tree", () => {
|
|
265
|
+
const dir = mkdtempSync(join(tmpdir(), "create-oke-from-example-"));
|
|
266
|
+
try {
|
|
267
|
+
const exampleDir = resolveExampleDir("notes");
|
|
268
|
+
const expected = listTemplateFiles(exampleDir);
|
|
269
|
+
const result = scaffold({
|
|
270
|
+
targetDir: join(dir, "notes"),
|
|
271
|
+
name: "from-notes",
|
|
272
|
+
source: { kind: "example", id: "notes" },
|
|
273
|
+
});
|
|
274
|
+
expect([...result.files].sort()).toEqual(expected);
|
|
275
|
+
expect(result.files).toContain("src/flows/notes/index.ts");
|
|
276
|
+
} finally {
|
|
277
|
+
rmSync(dir, { recursive: true, force: true });
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
describe("non-TTY CLI", () => {
|
|
283
|
+
test("no args + non-TTY → zero prompts, exit 1", async () => {
|
|
284
|
+
const code = await run([], { stdinIsTTY: false });
|
|
285
|
+
expect(code).toBe(1);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test("spawned with piped stdin shows no clack prompts", async () => {
|
|
289
|
+
const proc = Bun.spawn(["bun", "run", join(packageRoot(), "src/index.ts")], {
|
|
290
|
+
cwd: packageRoot(),
|
|
291
|
+
stdin: "pipe",
|
|
292
|
+
stdout: "pipe",
|
|
293
|
+
stderr: "pipe",
|
|
294
|
+
});
|
|
295
|
+
proc.stdin.end();
|
|
296
|
+
const code = await proc.exited;
|
|
297
|
+
const out = await new Response(proc.stdout).text();
|
|
298
|
+
const err = await new Response(proc.stderr).text();
|
|
299
|
+
expect(code).toBe(1);
|
|
300
|
+
expect(err).toMatch(/missing <name>/);
|
|
301
|
+
// Clack intro / select chrome must not appear.
|
|
302
|
+
expect(out + err).not.toMatch(/Project name|Start from a worked example/);
|
|
303
|
+
expect(out + err).not.toMatch(/│/);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test("flag-driven scaffold prints the shared next-steps text", async () => {
|
|
307
|
+
const root = mkdtempSync(join(tmpdir(), "create-oke-flag-"));
|
|
308
|
+
const target = join(root, "flag-app");
|
|
309
|
+
try {
|
|
310
|
+
const code = await run(
|
|
311
|
+
[target, "--template", "hello"],
|
|
312
|
+
{ stdinIsTTY: false },
|
|
313
|
+
);
|
|
314
|
+
expect(code).toBe(0);
|
|
315
|
+
expect(readdirSync(target).length).toBeGreaterThan(0);
|
|
316
|
+
const result = {
|
|
317
|
+
targetDir: target,
|
|
318
|
+
name: "flag-app",
|
|
319
|
+
source: { kind: "template" as const, id: "hello" as const },
|
|
320
|
+
label: "hello",
|
|
321
|
+
okengineDependency: "x",
|
|
322
|
+
files: [],
|
|
323
|
+
};
|
|
324
|
+
expect(nextStepsText(result)).toContain("oke dev");
|
|
325
|
+
expect(nextStepsText(result)).toContain("bun install");
|
|
326
|
+
} finally {
|
|
327
|
+
rmSync(root, { recursive: true, force: true });
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
describe("examples catalogue", () => {
|
|
333
|
+
test("all four teaching examples resolve", () => {
|
|
334
|
+
for (const id of EXAMPLES) {
|
|
335
|
+
expect(resolveExampleDir(id)).toBeTruthy();
|
|
336
|
+
}
|
|
337
|
+
});
|
|
110
338
|
});
|