create-oke 0.2.3 → 0.2.8
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 +32 -20
- package/examples/linkly/.env.example +15 -0
- package/examples/linkly/drizzle.config.ts +11 -0
- package/examples/linkly/oke.config.ts +6 -4
- package/examples/linkly/package.json +7 -4
- package/examples/linkly/src/flows/analytics/index.ts +29 -14
- package/examples/linkly/src/flows/links/index.ts +58 -35
- package/examples/linkly/src/flows/links/signals.ts +4 -3
- package/examples/linkly/src/gates.ts +4 -2
- package/examples/linkly/src/schema.decl.ts +39 -0
- package/examples/linkly/src/schema.generated.ts +30 -0
- package/examples/linkly/src/schema.ts +2 -17
- package/examples/linkly/tests/linkly.test.ts +9 -9
- package/examples/notes/.env.example +12 -0
- package/examples/notes/drizzle.config.ts +11 -0
- package/examples/notes/oke.config.ts +8 -1
- package/examples/notes/package.json +7 -4
- package/examples/notes/src/app.ts +1 -1
- package/examples/notes/src/flows/notes/index.ts +44 -30
- package/examples/notes/src/schema.ts +3 -3
- package/examples/notes/tests/notes.test.ts +1 -1
- package/examples/provisions/.env.example +19 -0
- package/examples/provisions/drizzle.config.ts +11 -0
- package/examples/provisions/oke.config.ts +10 -8
- package/examples/provisions/package.json +7 -4
- package/examples/provisions/src/app.ts +3 -3
- package/examples/provisions/src/channels.ts +4 -3
- package/examples/provisions/src/flows/notifications/index.ts +13 -7
- package/examples/provisions/src/flows/orders/index.ts +58 -31
- package/examples/provisions/src/flows/payments/index.ts +12 -6
- package/examples/provisions/src/schema.ts +7 -7
- package/examples/provisions/src/vault.ts +2 -2
- package/examples/provisions/tests/orders.test.ts +9 -8
- package/examples/skyport/.env.example +34 -0
- package/examples/skyport/drizzle.config.ts +11 -0
- package/examples/skyport/oke.config.ts +25 -22
- package/examples/skyport/package.json +8 -5
- package/examples/skyport/src/ai.ts +4 -4
- package/examples/skyport/src/app.ts +2 -13
- package/examples/skyport/src/flows/bookings/index.ts +45 -22
- package/examples/skyport/src/flows/bookings/shapes.ts +4 -4
- package/examples/skyport/src/flows/bookings/signals.ts +6 -2
- package/examples/skyport/src/flows/support/index.ts +37 -25
- package/examples/skyport/src/schema.ts +11 -8
- package/examples/skyport/src/vault.ts +1 -1
- package/examples/skyport/tests/support.test.ts +6 -6
- package/package.json +9 -9
- package/src/cli.test.ts +132 -64
- package/src/cli.ts +58 -33
- package/src/index.ts +0 -0
- package/src/scaffold.ts +53 -15
- package/src/sync-templates.ts +2 -11
- package/src/templates.ts +1 -2
- package/src/transform.ts +59 -9
- package/templates/full/.env.example +24 -0
- package/templates/full/README.md +22 -6
- package/templates/full/drizzle.config.ts +14 -0
- package/templates/full/oke.config.ts +38 -9
- package/templates/full/package.json +3 -0
- package/templates/hello/.env.example +9 -0
- package/templates/hello/README.md +6 -4
- package/templates/minimal/.env.example +13 -0
- package/templates/minimal/README.md +15 -7
- package/templates/minimal/drizzle.config.ts +14 -0
- package/templates/minimal/oke.config.ts +8 -1
- package/templates/minimal/package.json +3 -0
- package/templates/standard/.env.example +21 -0
- package/templates/standard/README.md +25 -8
- package/templates/standard/drizzle.config.ts +14 -0
- package/templates/standard/oke.config.ts +36 -8
- package/templates/standard/package.json +3 -0
|
@@ -2,8 +2,12 @@ import { signal } from "okengine";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
4
|
export const orderPlaced = signal("order-placed", {
|
|
5
|
-
schema: z.object({ orderId: z.string() }),
|
|
5
|
+
schema: z.object({ orderId: z.string() }),
|
|
6
|
+
delivery: "once",
|
|
7
|
+
retries: 5,
|
|
8
|
+
deadLetter: true,
|
|
6
9
|
});
|
|
7
10
|
export const seatFeed = signal("seat-feed", {
|
|
8
|
-
schema: z.object({ flightId: z.string(), left: z.number() }),
|
|
11
|
+
schema: z.object({ flightId: z.string(), left: z.number() }),
|
|
12
|
+
delivery: "live",
|
|
9
13
|
});
|
|
@@ -6,33 +6,45 @@ import { db } from "../../core";
|
|
|
6
6
|
import { tickets } from "../../schema";
|
|
7
7
|
|
|
8
8
|
// ① A prompt call with a provider fallback chain and a validated result
|
|
9
|
-
export const createTicket = on(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
export const createTicket = on(
|
|
10
|
+
http.post("/tickets").gate(member),
|
|
11
|
+
flow({
|
|
12
|
+
in: z.object({ subject: z.string(), body: z.string() }),
|
|
13
|
+
out: z.object({ id: z.string(), urgency: z.string() }),
|
|
14
|
+
do: async (input, fx) => {
|
|
15
|
+
const t = await fx.ask(triage, input, { via: [smart, fast] });
|
|
16
|
+
const id = fx.id();
|
|
17
|
+
await fx
|
|
18
|
+
.store(db)
|
|
19
|
+
.insert(tickets)
|
|
20
|
+
.values({ id, ...input, ...t });
|
|
21
|
+
return { id, urgency: t.urgency };
|
|
22
|
+
},
|
|
23
|
+
}),
|
|
24
|
+
);
|
|
19
25
|
// effects → writes[sql:tickets] asks[ticket-triage v3] cost[~$0.01] nondeterministic
|
|
20
26
|
|
|
21
27
|
// ② RAG — retrieve, then answer with streaming tokens
|
|
22
|
-
export const askDocs = on(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
export const askDocs = on(
|
|
29
|
+
http.post("/ask").gate(member).live(),
|
|
30
|
+
flow({
|
|
31
|
+
in: z.object({ question: z.string() }),
|
|
32
|
+
do: async ({ question }, fx) => {
|
|
33
|
+
const context = await fx.search(embed, question, { topK: 5 });
|
|
34
|
+
return fx.stream(smart, { prompt: "answer-with-context", data: { question, context } });
|
|
35
|
+
// streaming reaches the client through the Signal element — no separate socket layer
|
|
36
|
+
},
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
30
39
|
|
|
31
40
|
// ③ A durable, bounded agent
|
|
32
|
-
export const supportAgent = on(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
export const supportAgent = on(
|
|
42
|
+
http.post("/support").gate(member),
|
|
43
|
+
flow({
|
|
44
|
+
durable: true, // nondeterministic calls are ALWAYS journaled
|
|
45
|
+
in: z.object({ message: z.string() }),
|
|
46
|
+
do: ({ message }, fx) => fx.run(support, { message }),
|
|
47
|
+
// the agent can only call getBooking and refundBooking, and only within THIS user's
|
|
48
|
+
// gates and tenant scope — it cannot exceed what the code declares
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
@@ -2,21 +2,24 @@ import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
|
|
|
2
2
|
import { id } from "okengine/store";
|
|
3
3
|
|
|
4
4
|
export const bookings = sqliteTable("bookings", {
|
|
5
|
-
id:
|
|
6
|
-
userId:
|
|
7
|
-
flightId:
|
|
8
|
-
seats:
|
|
9
|
-
status:
|
|
5
|
+
id: text("id").primaryKey().$defaultFn(id),
|
|
6
|
+
userId: text("user_id").notNull(),
|
|
7
|
+
flightId: text("flight_id").notNull(),
|
|
8
|
+
seats: integer("seats").notNull(),
|
|
9
|
+
status: text("status").notNull().default("pending"),
|
|
10
10
|
createdAt: integer("created_at").notNull(),
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
export const flights = sqliteTable("flights", {
|
|
14
|
-
id:
|
|
14
|
+
id: text("id").primaryKey(),
|
|
15
15
|
seatsAvailable: integer("seats_available").notNull(),
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
export const tickets = sqliteTable("tickets", {
|
|
19
|
-
id: text("id").primaryKey(),
|
|
20
|
-
|
|
19
|
+
id: text("id").primaryKey(),
|
|
20
|
+
subject: text("subject").notNull(),
|
|
21
|
+
body: text("body").notNull(),
|
|
22
|
+
urgency: text("urgency"),
|
|
23
|
+
team: text("team"),
|
|
21
24
|
summary: text("summary"),
|
|
22
25
|
});
|
|
@@ -4,13 +4,13 @@ import { app } from "../src/app";
|
|
|
4
4
|
import { triage } from "../src/ai";
|
|
5
5
|
|
|
6
6
|
test("AI triage is mockable and cost-bounded", async () => {
|
|
7
|
-
const t = await createTestApp(app);
|
|
8
|
-
t.ai.mock(triage, { urgency: "high", team: "ops", summary: "seat dispute" });
|
|
7
|
+
const t = await createTestApp(app);
|
|
8
|
+
t.ai.mock(triage, { urgency: "high", team: "ops", summary: "seat dispute" });
|
|
9
9
|
|
|
10
|
-
const u = await t.auth.loginAs({});
|
|
11
|
-
const { data } = await t.api.support.createTicket({ subject: "…", body: "…" }, { as: u });
|
|
10
|
+
const u = await t.auth.loginAs({});
|
|
11
|
+
const { data } = await t.api.support.createTicket({ subject: "…", body: "…" }, { as: u });
|
|
12
12
|
|
|
13
|
-
expect(data!.urgency).toBe("high");
|
|
14
|
-
expect(t.ai.cost()).toBeLessThan(0.02);
|
|
13
|
+
expect(data!.urgency).toBe("high");
|
|
14
|
+
expect(t.ai.cost()).toBeLessThan(0.02); // budgets are assertable
|
|
15
15
|
await t.close();
|
|
16
16
|
});
|
package/package.json
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-oke",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "Scaffold an okengine app — bunx create-oke@latest <name>",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"sideEffects": false,
|
|
8
6
|
"repository": {
|
|
9
7
|
"type": "git",
|
|
10
8
|
"url": "git+https://github.com/omqkhafi/okengine.git",
|
|
@@ -13,28 +11,30 @@
|
|
|
13
11
|
"bin": {
|
|
14
12
|
"create-oke": "src/index.ts"
|
|
15
13
|
},
|
|
16
|
-
"exports": {
|
|
17
|
-
".": "./src/index.ts"
|
|
18
|
-
},
|
|
19
14
|
"files": [
|
|
20
15
|
"src",
|
|
21
16
|
"templates",
|
|
22
17
|
"examples"
|
|
23
18
|
],
|
|
19
|
+
"type": "module",
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"exports": {
|
|
22
|
+
".": "./src/index.ts"
|
|
23
|
+
},
|
|
24
24
|
"scripts": {
|
|
25
25
|
"prepack": "bun ./src/sync-templates.ts",
|
|
26
26
|
"typecheck": "tsc --noEmit",
|
|
27
27
|
"test": "bun test",
|
|
28
28
|
"test:integration": "CREATE_OKE_INTEGRATION=1 bun test tests/scaffold.integration.test.ts tests/dev.integration.test.ts"
|
|
29
29
|
},
|
|
30
|
-
"engines": {
|
|
31
|
-
"bun": ">=1.3"
|
|
32
|
-
},
|
|
33
30
|
"dependencies": {
|
|
34
31
|
"@clack/prompts": "^1.7.0"
|
|
35
32
|
},
|
|
36
33
|
"devDependencies": {
|
|
37
34
|
"@types/bun": "latest",
|
|
38
35
|
"typescript": "^7.0.2"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"bun": ">=1.3"
|
|
39
39
|
}
|
|
40
40
|
}
|
package/src/cli.test.ts
CHANGED
|
@@ -3,13 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { describe, expect, test } from "bun:test";
|
|
6
|
-
import {
|
|
7
|
-
existsSync,
|
|
8
|
-
mkdtempSync,
|
|
9
|
-
readFileSync,
|
|
10
|
-
readdirSync,
|
|
11
|
-
rmSync,
|
|
12
|
-
} from "node:fs";
|
|
6
|
+
import { existsSync, mkdtempSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
|
13
7
|
import { tmpdir } from "node:os";
|
|
14
8
|
import { join } from "node:path";
|
|
15
9
|
import {
|
|
@@ -36,7 +30,9 @@ import {
|
|
|
36
30
|
import {
|
|
37
31
|
sanitizeProjectName,
|
|
38
32
|
shouldSkipTemplatePath,
|
|
33
|
+
transformConfigForSqlDriver,
|
|
39
34
|
transformPackageJson,
|
|
35
|
+
transformSchemaForSqlDriver,
|
|
40
36
|
} from "./transform.ts";
|
|
41
37
|
|
|
42
38
|
describe("parseArgs", () => {
|
|
@@ -56,20 +52,25 @@ describe("parseArgs", () => {
|
|
|
56
52
|
});
|
|
57
53
|
|
|
58
54
|
test("accepts --from-example", () => {
|
|
59
|
-
expect(parseArgs(["x", "--from-example", "notes"]).fromExample).toBe(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
);
|
|
55
|
+
expect(parseArgs(["x", "--from-example", "notes"]).fromExample).toBe("notes");
|
|
56
|
+
expect(parseArgs(["x", "--from-example=skyport"]).fromExample).toBe("skyport");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("accepts --sql sqlite|postgres", () => {
|
|
60
|
+
expect(parseArgs(["x"]).sqlDriver).toBe("sqlite");
|
|
61
|
+
expect(parseArgs(["x"]).sqlDriverExplicit).toBe(false);
|
|
62
|
+
expect(parseArgs(["x", "--sql", "postgres"]).sqlDriver).toBe("postgres");
|
|
63
|
+
expect(parseArgs(["x", "--sql=sqlite"]).sqlDriver).toBe("sqlite");
|
|
64
|
+
expect(parseArgs(["x", "--sql", "postgres"]).sqlDriverExplicit).toBe(true);
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
-
test("rejects unknown template / example / both", () => {
|
|
67
|
+
test("rejects unknown template / example / sql / both", () => {
|
|
68
68
|
expect(() => parseArgs(["x", "--template", "nope"])).toThrow(/template/);
|
|
69
69
|
expect(() => parseArgs(["x", "--from-example", "nope"])).toThrow(/example/);
|
|
70
|
-
expect(() =>
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
expect(() => parseArgs(["x", "--sql", "mysql"])).toThrow(/sql/);
|
|
71
|
+
expect(() => parseArgs(["x", "--template", "hello", "--from-example", "notes"])).toThrow(
|
|
72
|
+
/either/,
|
|
73
|
+
);
|
|
73
74
|
});
|
|
74
75
|
});
|
|
75
76
|
|
|
@@ -86,13 +87,9 @@ describe("shouldPrompt", () => {
|
|
|
86
87
|
expect(shouldPrompt(parseArgs([]), false)).toBe(false);
|
|
87
88
|
expect(shouldPrompt(parseArgs(["my-app"]), false)).toBe(false);
|
|
88
89
|
expect(shouldPrompt(parseArgs(["my-app", "--yes"]), true)).toBe(false);
|
|
89
|
-
expect(shouldPrompt(parseArgs(["my-app", "--template", "hello"]), true)).toBe(
|
|
90
|
-
false,
|
|
91
|
-
);
|
|
90
|
+
expect(shouldPrompt(parseArgs(["my-app", "--template", "hello"]), true)).toBe(false);
|
|
92
91
|
expect(shouldPrompt(parseArgs(["--template", "hello"]), true)).toBe(false);
|
|
93
|
-
expect(shouldPrompt(parseArgs(["--from-example", "notes"]), true)).toBe(
|
|
94
|
-
false,
|
|
95
|
-
);
|
|
92
|
+
expect(shouldPrompt(parseArgs(["--from-example", "notes"]), true)).toBe(false);
|
|
96
93
|
expect(shouldPrompt(parseArgs(["--help"]), true)).toBe(false);
|
|
97
94
|
});
|
|
98
95
|
});
|
|
@@ -108,17 +105,16 @@ describe("parseArgs flags", () => {
|
|
|
108
105
|
});
|
|
109
106
|
|
|
110
107
|
test("rejects --install with --no-install", () => {
|
|
111
|
-
expect(() => parseArgs(["x", "--install", "--no-install"])).toThrow(
|
|
112
|
-
/install/,
|
|
113
|
-
);
|
|
108
|
+
expect(() => parseArgs(["x", "--install", "--no-install"])).toThrow(/install/);
|
|
114
109
|
});
|
|
115
110
|
});
|
|
116
111
|
|
|
117
112
|
describe("sourceFromArgs", () => {
|
|
118
113
|
test("prefers example when set", () => {
|
|
119
|
-
expect(sourceFromArgs(parseArgs(["x", "--from-example", "linkly"]))).toEqual(
|
|
120
|
-
|
|
121
|
-
|
|
114
|
+
expect(sourceFromArgs(parseArgs(["x", "--from-example", "linkly"]))).toEqual({
|
|
115
|
+
kind: "example",
|
|
116
|
+
id: "linkly",
|
|
117
|
+
});
|
|
122
118
|
expect(sourceFromArgs(parseArgs(["x"]))).toEqual({
|
|
123
119
|
kind: "template",
|
|
124
120
|
id: DEFAULT_TEMPLATE,
|
|
@@ -136,11 +132,10 @@ describe("scaffoldArgsFromAnswers ≡ flag-driven", () => {
|
|
|
136
132
|
agentsMd: true,
|
|
137
133
|
};
|
|
138
134
|
const fromAnswers = scaffoldArgsFromAnswers(answers);
|
|
139
|
-
const fromFlags = scaffoldArgsFromCli(
|
|
140
|
-
parseArgs(["x", "--template", id]),
|
|
141
|
-
);
|
|
135
|
+
const fromFlags = scaffoldArgsFromCli(parseArgs(["x", "--template", id]));
|
|
142
136
|
expect(fromAnswers).toEqual(fromFlags);
|
|
143
137
|
expect(fromAnswers.source).toEqual({ kind: "template", id });
|
|
138
|
+
expect(fromAnswers.sqlDriver).toBe("sqlite");
|
|
144
139
|
}
|
|
145
140
|
});
|
|
146
141
|
|
|
@@ -154,14 +149,27 @@ describe("scaffoldArgsFromAnswers ≡ flag-driven", () => {
|
|
|
154
149
|
agentsMd: true,
|
|
155
150
|
};
|
|
156
151
|
const fromAnswers = scaffoldArgsFromAnswers(answers);
|
|
157
|
-
const fromFlags = scaffoldArgsFromCli(
|
|
158
|
-
parseArgs(["x", "--from-example", id]),
|
|
159
|
-
);
|
|
152
|
+
const fromFlags = scaffoldArgsFromCli(parseArgs(["x", "--from-example", id]));
|
|
160
153
|
expect(fromAnswers).toEqual(fromFlags);
|
|
161
154
|
expect(fromAnswers.source).toEqual({ kind: "example", id });
|
|
155
|
+
expect(fromAnswers.sqlDriver).toBe("sqlite");
|
|
162
156
|
}
|
|
163
157
|
});
|
|
164
158
|
|
|
159
|
+
test("interactive never opts into --sql postgres (flag-only)", () => {
|
|
160
|
+
const answers: InteractiveAnswers = {
|
|
161
|
+
name: "x",
|
|
162
|
+
choice: "standard",
|
|
163
|
+
installAndRun: false,
|
|
164
|
+
agentsMd: true,
|
|
165
|
+
};
|
|
166
|
+
expect(scaffoldArgsFromAnswers(answers).sqlDriver).toBe("sqlite");
|
|
167
|
+
expect(
|
|
168
|
+
scaffoldArgsFromCli(parseArgs(["x", "--template", "standard", "--sql", "postgres"]))
|
|
169
|
+
.sqlDriver,
|
|
170
|
+
).toBe("postgres");
|
|
171
|
+
});
|
|
172
|
+
|
|
165
173
|
test("8 paths cover every template and every example", () => {
|
|
166
174
|
expect(TEMPLATES.length + EXAMPLES.length).toBe(8);
|
|
167
175
|
});
|
|
@@ -189,11 +197,51 @@ describe("transformPackageJson", () => {
|
|
|
189
197
|
});
|
|
190
198
|
});
|
|
191
199
|
|
|
200
|
+
describe("transformSchemaForSqlDriver", () => {
|
|
201
|
+
const sqliteSchema = `import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
|
|
202
|
+
export const pings = sqliteTable("pings", {});
|
|
203
|
+
`;
|
|
204
|
+
|
|
205
|
+
test("postgres swaps dialect imports and table helper", () => {
|
|
206
|
+
const next = transformSchemaForSqlDriver(sqliteSchema, "postgres");
|
|
207
|
+
expect(next).toContain('from "drizzle-orm/pg-core"');
|
|
208
|
+
expect(next).toContain("pgTable(");
|
|
209
|
+
expect(next).not.toContain("sqliteTable");
|
|
210
|
+
expect(next).not.toContain("sqlite-core");
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("sqlite is idempotent on template sources", () => {
|
|
214
|
+
expect(transformSchemaForSqlDriver(sqliteSchema, "sqlite")).toBe(sqliteSchema);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe("transformConfigForSqlDriver", () => {
|
|
219
|
+
const config = `export default defineConfig({
|
|
220
|
+
drivers: {
|
|
221
|
+
store: {
|
|
222
|
+
sql: {
|
|
223
|
+
local: "sqlite",
|
|
224
|
+
docker: "postgres",
|
|
225
|
+
test: "memory",
|
|
226
|
+
prod: "postgres",
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
`;
|
|
232
|
+
|
|
233
|
+
test("postgres pins local/docker/prod and leaves test memory", () => {
|
|
234
|
+
const next = transformConfigForSqlDriver(config, "postgres");
|
|
235
|
+
expect(next).toContain('local: "postgres"');
|
|
236
|
+
expect(next).toContain('docker: "postgres"');
|
|
237
|
+
expect(next).toContain('prod: "postgres"');
|
|
238
|
+
expect(next).toContain('test: "memory"');
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
192
242
|
describe("shouldSkipTemplatePath", () => {
|
|
193
243
|
test("skips node_modules, locks, and monorepo docker test", () => {
|
|
194
|
-
expect(shouldSkipTemplatePath("node_modules/okengine/package.json")).toBe(
|
|
195
|
-
true,
|
|
196
|
-
);
|
|
244
|
+
expect(shouldSkipTemplatePath("node_modules/okengine/package.json")).toBe(true);
|
|
197
245
|
expect(shouldSkipTemplatePath("bun.lock")).toBe(true);
|
|
198
246
|
expect(shouldSkipTemplatePath("tests/docker.test.ts")).toBe(true);
|
|
199
247
|
expect(shouldSkipTemplatePath("tests/support.test.ts")).toBe(false);
|
|
@@ -225,23 +273,23 @@ describe("scaffold structure", () => {
|
|
|
225
273
|
name: `app-${id}`,
|
|
226
274
|
source: { kind: "template", id },
|
|
227
275
|
});
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
);
|
|
276
|
+
const extras = ["AGENTS.md"];
|
|
277
|
+
if (expected.includes(".env.example")) extras.push(".env.local");
|
|
278
|
+
expect([...result.files].sort()).toEqual([...expected, ...extras].sort());
|
|
231
279
|
expect(result.files).toContain(".gitignore");
|
|
232
280
|
expect(result.files).toContain("README.md");
|
|
233
|
-
expect(
|
|
234
|
-
|
|
235
|
-
|
|
281
|
+
expect(result.sqlDriver).toBe("sqlite");
|
|
282
|
+
expect(readFileSync(join(result.targetDir, "AGENTS.md"), "utf8")).toMatch(
|
|
283
|
+
/one law|on\(Trigger\)/i,
|
|
284
|
+
);
|
|
236
285
|
const readme = readFileSync(join(result.targetDir, "README.md"), "utf8");
|
|
237
286
|
expect(readme).toMatch(/oke dev/);
|
|
238
287
|
expect(readme).toMatch(new RegExp(`^# ${id}`, "m"));
|
|
239
|
-
expect(
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
) as { name: string; dependencies: { okengine: string } };
|
|
288
|
+
expect(readFileSync(join(result.targetDir, ".gitignore"), "utf8")).toMatch(/node_modules/);
|
|
289
|
+
const pkg = JSON.parse(readFileSync(join(result.targetDir, "package.json"), "utf8")) as {
|
|
290
|
+
name: string;
|
|
291
|
+
dependencies: { okengine: string };
|
|
292
|
+
};
|
|
245
293
|
expect(pkg.name).toBe(`app-${id}`);
|
|
246
294
|
expect(pkg.dependencies.okengine).not.toBe("file:../..");
|
|
247
295
|
} finally {
|
|
@@ -258,14 +306,9 @@ describe("scaffold structure", () => {
|
|
|
258
306
|
name: "hello-app",
|
|
259
307
|
source: { kind: "template", id: "hello" },
|
|
260
308
|
});
|
|
261
|
-
const flowFiles = result.files.filter(
|
|
262
|
-
(f) => f.startsWith("src/flows/") && f.endsWith(".ts"),
|
|
263
|
-
);
|
|
309
|
+
const flowFiles = result.files.filter((f) => f.startsWith("src/flows/") && f.endsWith(".ts"));
|
|
264
310
|
expect(flowFiles).toEqual(["src/flows/hello/index.ts"]);
|
|
265
|
-
const flowSrc = readFileSync(
|
|
266
|
-
join(result.targetDir, "src/flows/hello/index.ts"),
|
|
267
|
-
"utf8",
|
|
268
|
-
);
|
|
311
|
+
const flowSrc = readFileSync(join(result.targetDir, "src/flows/hello/index.ts"), "utf8");
|
|
269
312
|
expect(flowSrc).toMatch(/export const hello/);
|
|
270
313
|
expect(flowSrc).not.toMatch(/\bstore\b/);
|
|
271
314
|
expect(result.files.some((f) => f.includes("core.ts"))).toBe(false);
|
|
@@ -323,15 +366,39 @@ describe("scaffold structure", () => {
|
|
|
323
366
|
name: "from-notes",
|
|
324
367
|
source: { kind: "example", id: "notes" },
|
|
325
368
|
});
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
);
|
|
369
|
+
const extras = ["AGENTS.md"];
|
|
370
|
+
if (expected.includes(".env.example")) extras.push(".env.local");
|
|
371
|
+
expect([...result.files].sort()).toEqual([...expected, ...extras].sort());
|
|
329
372
|
expect(result.files).toContain("src/flows/notes/index.ts");
|
|
330
373
|
} finally {
|
|
331
374
|
rmSync(dir, { recursive: true, force: true });
|
|
332
375
|
}
|
|
333
376
|
});
|
|
334
377
|
|
|
378
|
+
test("--sql postgres rewrites schema dialect and pins store.sql", () => {
|
|
379
|
+
const dir = mkdtempSync(join(tmpdir(), "create-oke-sql-pg-"));
|
|
380
|
+
try {
|
|
381
|
+
const result = scaffold({
|
|
382
|
+
targetDir: join(dir, "pg-app"),
|
|
383
|
+
name: "pg-app",
|
|
384
|
+
source: { kind: "template", id: "standard" },
|
|
385
|
+
sqlDriver: "postgres",
|
|
386
|
+
});
|
|
387
|
+
expect(result.sqlDriver).toBe("postgres");
|
|
388
|
+
const schema = readFileSync(join(result.targetDir, "src/schema.ts"), "utf8");
|
|
389
|
+
expect(schema).toContain('from "drizzle-orm/pg-core"');
|
|
390
|
+
expect(schema).toContain("pgTable(");
|
|
391
|
+
expect(schema).not.toContain("sqliteTable");
|
|
392
|
+
const config = readFileSync(join(result.targetDir, "oke.config.ts"), "utf8");
|
|
393
|
+
expect(config).toMatch(/sql:\s*\{[\s\S]*local:\s*"postgres"/);
|
|
394
|
+
expect(config).toMatch(/sql:\s*\{[\s\S]*docker:\s*"postgres"/);
|
|
395
|
+
expect(config).toMatch(/sql:\s*\{[\s\S]*prod:\s*"postgres"/);
|
|
396
|
+
expect(config).toMatch(/sql:\s*\{[\s\S]*test:\s*"memory"/);
|
|
397
|
+
} finally {
|
|
398
|
+
rmSync(dir, { recursive: true, force: true });
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
|
|
335
402
|
test("--no-agents-md skips AGENTS.md", () => {
|
|
336
403
|
const dir = mkdtempSync(join(tmpdir(), "create-oke-no-agents-"));
|
|
337
404
|
try {
|
|
@@ -376,10 +443,10 @@ describe("non-TTY CLI", () => {
|
|
|
376
443
|
const root = mkdtempSync(join(tmpdir(), "create-oke-flag-"));
|
|
377
444
|
const target = join(root, "flag-app");
|
|
378
445
|
try {
|
|
379
|
-
const code = await run(
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
);
|
|
446
|
+
const code = await run([target, "--template", "hello", "--no-install"], {
|
|
447
|
+
stdinIsTTY: false,
|
|
448
|
+
runPostScaffold: false,
|
|
449
|
+
});
|
|
383
450
|
expect(code).toBe(0);
|
|
384
451
|
expect(readdirSync(target).length).toBeGreaterThan(0);
|
|
385
452
|
expect(existsSync(join(target, "AGENTS.md"))).toBe(true);
|
|
@@ -390,6 +457,7 @@ describe("non-TTY CLI", () => {
|
|
|
390
457
|
label: "hello",
|
|
391
458
|
okengineDependency: "x",
|
|
392
459
|
files: [],
|
|
460
|
+
sqlDriver: "sqlite" as const,
|
|
393
461
|
};
|
|
394
462
|
expect(nextStepsText(result)).toContain("oke dev");
|
|
395
463
|
expect(nextStepsText(result)).toContain("bun install");
|