create-oke 0.1.6 → 0.2.2
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 +26 -6
- 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 +6 -2
- package/src/agents-md.ts +99 -0
- package/src/cli.test.ts +318 -19
- package/src/cli.ts +561 -29
- package/src/docs-origin.ts +19 -0
- package/src/index.ts +3 -1
- package/src/scaffold.ts +59 -32
- package/src/sync-templates.ts +28 -7
- package/src/templates.ts +85 -12
- package/src/transform.ts +5 -2
- package/templates/full/README.md +49 -0
- 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 +45 -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 +38 -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 +27 -0
- package/templates/hello/tests/hello.test.ts +10 -0
- package/templates/minimal/README.md +41 -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 +49 -0
- package/templates/minimal/src/schema.ts +8 -0
- package/templates/minimal/tests/minimal.test.ts +13 -0
- package/templates/standard/README.md +47 -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 +31 -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/src/index.ts
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* ```bash
|
|
6
6
|
* bunx create-oke@latest my-app
|
|
7
|
-
* bunx create-oke@latest my-app --
|
|
7
|
+
* bunx create-oke@latest my-app --yes
|
|
8
|
+
* bunx create-oke@latest my-app --template hello
|
|
9
|
+
* bunx create-oke@latest my-app --from-example notes
|
|
8
10
|
* ```
|
|
9
11
|
*
|
|
10
12
|
* @module
|
package/src/scaffold.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copy
|
|
2
|
+
* Copy a template or example tree and apply the package.json transform.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import {
|
|
@@ -8,15 +8,19 @@ import {
|
|
|
8
8
|
mkdirSync,
|
|
9
9
|
readdirSync,
|
|
10
10
|
readFileSync,
|
|
11
|
+
rmSync,
|
|
11
12
|
statSync,
|
|
12
13
|
writeFileSync,
|
|
13
14
|
} from "node:fs";
|
|
14
15
|
import { join, relative, resolve } from "node:path";
|
|
15
16
|
import {
|
|
17
|
+
resolveExampleDir,
|
|
16
18
|
resolveLocalOkengineRoot,
|
|
17
19
|
resolveTemplateDir,
|
|
20
|
+
type ExampleId,
|
|
18
21
|
type TemplateId,
|
|
19
22
|
} from "./templates.ts";
|
|
23
|
+
import { agentsMdContent } from "./agents-md.ts";
|
|
20
24
|
import {
|
|
21
25
|
sanitizeProjectName,
|
|
22
26
|
shouldSkipTemplatePath,
|
|
@@ -25,35 +29,48 @@ import {
|
|
|
25
29
|
type ScaffoldPackageJson,
|
|
26
30
|
} from "./transform.ts";
|
|
27
31
|
|
|
32
|
+
/** Where the scaffold copies from. */
|
|
33
|
+
export type ScaffoldSource =
|
|
34
|
+
| { readonly kind: "template"; readonly id: TemplateId }
|
|
35
|
+
| { readonly kind: "example"; readonly id: ExampleId };
|
|
36
|
+
|
|
28
37
|
/** Options for {@link scaffold}. */
|
|
29
38
|
export type ScaffoldOptions = {
|
|
30
39
|
/** Destination directory (created). Absolute or cwd-relative. */
|
|
31
40
|
readonly targetDir: string;
|
|
32
41
|
/** npm package / folder name. */
|
|
33
42
|
readonly name: string;
|
|
34
|
-
/** Template
|
|
35
|
-
readonly
|
|
43
|
+
/** Template or teaching-example source. */
|
|
44
|
+
readonly source: ScaffoldSource;
|
|
45
|
+
/** Write root `AGENTS.md` (default true). */
|
|
46
|
+
readonly writeAgentsMd?: boolean;
|
|
36
47
|
};
|
|
37
48
|
|
|
38
49
|
/** Result of a successful scaffold. */
|
|
39
50
|
export type ScaffoldResult = {
|
|
40
51
|
readonly targetDir: string;
|
|
41
52
|
readonly name: string;
|
|
42
|
-
readonly
|
|
53
|
+
readonly source: ScaffoldSource;
|
|
54
|
+
/** Display label (`standard`, `notes`, …). */
|
|
55
|
+
readonly label: string;
|
|
43
56
|
readonly okengineDependency: string;
|
|
44
57
|
/** Relative paths written (POSIX), sorted. */
|
|
45
58
|
readonly files: readonly string[];
|
|
46
59
|
};
|
|
47
60
|
|
|
48
61
|
/**
|
|
49
|
-
* Scaffold a new okengine project from
|
|
62
|
+
* Scaffold a new okengine project from a clean template or teaching example.
|
|
50
63
|
*
|
|
51
|
-
* @param options - Name,
|
|
64
|
+
* @param options - Name, source, destination
|
|
52
65
|
*/
|
|
53
66
|
export function scaffold(options: ScaffoldOptions): ScaffoldResult {
|
|
54
67
|
const name = sanitizeProjectName(options.name);
|
|
55
68
|
const targetDir = resolve(options.targetDir);
|
|
56
|
-
const
|
|
69
|
+
const sourceDir =
|
|
70
|
+
options.source.kind === "template"
|
|
71
|
+
? resolveTemplateDir(options.source.id)
|
|
72
|
+
: resolveExampleDir(options.source.id);
|
|
73
|
+
const label = options.source.id;
|
|
57
74
|
|
|
58
75
|
if (existsSync(targetDir)) {
|
|
59
76
|
const entries = readdirSync(targetDir);
|
|
@@ -66,33 +83,43 @@ export function scaffold(options: ScaffoldOptions): ScaffoldResult {
|
|
|
66
83
|
mkdirSync(targetDir, { recursive: true });
|
|
67
84
|
}
|
|
68
85
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
86
|
+
try {
|
|
87
|
+
const okengineDependency = resolveOkengineDependency(
|
|
88
|
+
resolveLocalOkengineRoot(),
|
|
89
|
+
);
|
|
90
|
+
const written: string[] = [];
|
|
73
91
|
|
|
74
|
-
|
|
92
|
+
copyTree(sourceDir, targetDir, "", written);
|
|
75
93
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
94
|
+
const pkgPath = join(targetDir, "package.json");
|
|
95
|
+
if (!existsSync(pkgPath)) {
|
|
96
|
+
throw new Error(`create-oke: source "${label}" has no package.json`);
|
|
97
|
+
}
|
|
98
|
+
const sourcePkg = JSON.parse(
|
|
99
|
+
readFileSync(pkgPath, "utf8"),
|
|
100
|
+
) as ScaffoldPackageJson;
|
|
101
|
+
const nextPkg = transformPackageJson(sourcePkg, name, okengineDependency);
|
|
102
|
+
writeFileSync(pkgPath, `${JSON.stringify(nextPkg, null, 2)}\n`, "utf8");
|
|
103
|
+
|
|
104
|
+
if (options.writeAgentsMd !== false) {
|
|
105
|
+
const agentsPath = join(targetDir, "AGENTS.md");
|
|
106
|
+
writeFileSync(agentsPath, agentsMdContent(name), "utf8");
|
|
107
|
+
if (!written.includes("AGENTS.md")) written.push("AGENTS.md");
|
|
108
|
+
}
|
|
87
109
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
110
|
+
written.sort();
|
|
111
|
+
return {
|
|
112
|
+
targetDir,
|
|
113
|
+
name,
|
|
114
|
+
source: options.source,
|
|
115
|
+
label,
|
|
116
|
+
okengineDependency,
|
|
117
|
+
files: written,
|
|
118
|
+
};
|
|
119
|
+
} catch (e) {
|
|
120
|
+
rmSync(targetDir, { recursive: true, force: true });
|
|
121
|
+
throw e;
|
|
122
|
+
}
|
|
96
123
|
}
|
|
97
124
|
|
|
98
125
|
/**
|
|
@@ -129,7 +156,7 @@ function copyTree(
|
|
|
129
156
|
}
|
|
130
157
|
|
|
131
158
|
/**
|
|
132
|
-
* List relative file paths in
|
|
159
|
+
* List relative file paths in a source tree using the same skip rules as scaffold.
|
|
133
160
|
*
|
|
134
161
|
* @param templateDir - Absolute template directory
|
|
135
162
|
*/
|
package/src/sync-templates.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
/**
|
|
3
|
-
* prepack: copy `examples
|
|
3
|
+
* prepack: copy repo-root `templates/` and `examples/` into this package so the
|
|
4
4
|
* published npm package can scaffold without the monorepo checkout.
|
|
5
5
|
*
|
|
6
6
|
* Applies the same skip rules as runtime scaffold (no node_modules, no
|
|
@@ -16,30 +16,51 @@ import {
|
|
|
16
16
|
statSync,
|
|
17
17
|
} from "node:fs";
|
|
18
18
|
import { join, relative, resolve } from "node:path";
|
|
19
|
-
import { packageRoot, TEMPLATES } from "./templates.ts";
|
|
19
|
+
import { EXAMPLES, packageRoot, TEMPLATES } from "./templates.ts";
|
|
20
20
|
import { shouldSkipTemplatePath } from "./transform.ts";
|
|
21
21
|
|
|
22
22
|
const pkg = packageRoot();
|
|
23
|
+
const templatesRoot = resolve(pkg, "../../templates");
|
|
23
24
|
const examplesRoot = resolve(pkg, "../../examples");
|
|
24
|
-
const
|
|
25
|
+
const outTemplates = join(pkg, "templates");
|
|
26
|
+
const outExamples = join(pkg, "examples");
|
|
25
27
|
|
|
28
|
+
if (!existsSync(templatesRoot)) {
|
|
29
|
+
console.error(
|
|
30
|
+
`create-oke prepack: templates/ not found at ${templatesRoot}`,
|
|
31
|
+
);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
26
34
|
if (!existsSync(examplesRoot)) {
|
|
27
35
|
console.error(`create-oke prepack: examples/ not found at ${examplesRoot}`);
|
|
28
36
|
process.exit(1);
|
|
29
37
|
}
|
|
30
38
|
|
|
31
|
-
rmSync(
|
|
32
|
-
|
|
39
|
+
rmSync(outTemplates, { recursive: true, force: true });
|
|
40
|
+
rmSync(outExamples, { recursive: true, force: true });
|
|
41
|
+
mkdirSync(outTemplates, { recursive: true });
|
|
42
|
+
mkdirSync(outExamples, { recursive: true });
|
|
33
43
|
|
|
34
44
|
for (const id of TEMPLATES) {
|
|
45
|
+
const src = join(templatesRoot, id);
|
|
46
|
+
const dst = join(outTemplates, id);
|
|
47
|
+
if (!existsSync(src)) {
|
|
48
|
+
console.error(`create-oke prepack: missing template ${src}`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
copyFiltered(src, dst, src);
|
|
52
|
+
console.log(`create-oke prepack: template ${id}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (const id of EXAMPLES) {
|
|
35
56
|
const src = join(examplesRoot, id);
|
|
36
|
-
const dst = join(
|
|
57
|
+
const dst = join(outExamples, id);
|
|
37
58
|
if (!existsSync(src)) {
|
|
38
59
|
console.error(`create-oke prepack: missing example ${src}`);
|
|
39
60
|
process.exit(1);
|
|
40
61
|
}
|
|
41
62
|
copyFiltered(src, dst, src);
|
|
42
|
-
console.log(`create-oke prepack: ${id}`);
|
|
63
|
+
console.log(`create-oke prepack: example ${id}`);
|
|
43
64
|
}
|
|
44
65
|
|
|
45
66
|
/**
|
package/src/templates.ts
CHANGED
|
@@ -1,20 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Template ids and resolution
|
|
3
|
-
*
|
|
2
|
+
* Template / example ids and resolution.
|
|
3
|
+
*
|
|
4
|
+
* Clean starters live in repo-root `templates/<id>`.
|
|
5
|
+
* Teaching apps live in repo-root `examples/<id>` (`--from-example`).
|
|
6
|
+
* Published installs use the prepack copies under this package.
|
|
4
7
|
*/
|
|
5
8
|
|
|
6
9
|
import { existsSync, readFileSync } from "node:fs";
|
|
7
10
|
import { dirname, join, resolve } from "node:path";
|
|
8
11
|
import { fileURLToPath } from "node:url";
|
|
9
12
|
|
|
10
|
-
/**
|
|
11
|
-
export const TEMPLATES = ["
|
|
13
|
+
/** Clean starter template ids — match repo-root `templates/*`. */
|
|
14
|
+
export const TEMPLATES = ["hello", "minimal", "standard", "full"] as const;
|
|
12
15
|
|
|
13
|
-
/** A known
|
|
16
|
+
/** A known clean starter template. */
|
|
14
17
|
export type TemplateId = (typeof TEMPLATES)[number];
|
|
15
18
|
|
|
16
|
-
/** Default when `--template` is omitted (
|
|
17
|
-
export const DEFAULT_TEMPLATE: TemplateId = "
|
|
19
|
+
/** Default when `--template` is omitted (recommended project layout). */
|
|
20
|
+
export const DEFAULT_TEMPLATE: TemplateId = "standard";
|
|
21
|
+
|
|
22
|
+
/** One-line purpose for each clean template (interactive select + help). */
|
|
23
|
+
export const TEMPLATE_PURPOSES: Readonly<Record<TemplateId, string>> = {
|
|
24
|
+
hello: 'Fastest possible "it works" — one flow, no Store',
|
|
25
|
+
minimal: "Smallest shape you'd actually ship — Store + 1–2 flows",
|
|
26
|
+
standard: "Full recommended file layout, empty scaffolding",
|
|
27
|
+
full: "Every element present and wired, no business logic",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Teaching example ids — match `examples/*` and four-applications.md. */
|
|
31
|
+
export const EXAMPLES = ["notes", "linkly", "provisions", "skyport"] as const;
|
|
32
|
+
|
|
33
|
+
/** A known teaching example. */
|
|
34
|
+
export type ExampleId = (typeof EXAMPLES)[number];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* "New ideas" one-liners from four-applications.md — reused for
|
|
38
|
+
* `--from-example` help and the interactive example select.
|
|
39
|
+
*/
|
|
40
|
+
export const EXAMPLE_NEW_IDEAS: Readonly<Record<ExampleId, string>> = {
|
|
41
|
+
notes:
|
|
42
|
+
"`oke`, `on`, `flow`, `http`, `store.sql`, `fx`, typed errors, the typed client.",
|
|
43
|
+
linkly:
|
|
44
|
+
"`signal` and its three delivery physics · `clock` · `gate` · triggers beyond HTTP · transactional emit · cross-unit decoupling.",
|
|
45
|
+
provisions:
|
|
46
|
+
"`durable` flows and the journal · `vault` · `channel` with fallback chains and i18n · live queries · plugins · a CDC trigger · the three cache tiers.",
|
|
47
|
+
skyport:
|
|
48
|
+
"the `ai` element (models, prompts, RAG, agents) · multi-tenancy · SLOs and journeys · distributed topology · the three scaling axes.",
|
|
49
|
+
};
|
|
18
50
|
|
|
19
51
|
/**
|
|
20
52
|
* Whether `value` is a known {@link TemplateId}.
|
|
@@ -25,6 +57,15 @@ export function isTemplateId(value: string): value is TemplateId {
|
|
|
25
57
|
return (TEMPLATES as readonly string[]).includes(value);
|
|
26
58
|
}
|
|
27
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Whether `value` is a known {@link ExampleId}.
|
|
62
|
+
*
|
|
63
|
+
* @param value - Candidate string
|
|
64
|
+
*/
|
|
65
|
+
export function isExampleId(value: string): value is ExampleId {
|
|
66
|
+
return (EXAMPLES as readonly string[]).includes(value);
|
|
67
|
+
}
|
|
68
|
+
|
|
28
69
|
/**
|
|
29
70
|
* Directory that contains this package (`packages/create-oke`).
|
|
30
71
|
*/
|
|
@@ -33,25 +74,25 @@ export function packageRoot(): string {
|
|
|
33
74
|
}
|
|
34
75
|
|
|
35
76
|
/**
|
|
36
|
-
* Resolve the directory to copy for
|
|
77
|
+
* Resolve the directory to copy for a clean `--template`.
|
|
37
78
|
*
|
|
38
79
|
* Order:
|
|
39
|
-
* 1. `
|
|
40
|
-
* 2. Monorepo `
|
|
80
|
+
* 1. `CREATE_OKE_TEMPLATES` env (absolute path to a `templates/` tree)
|
|
81
|
+
* 2. Monorepo `templates/<template>` (two levels up from this package)
|
|
41
82
|
* 3. Bundled `templates/<template>` (npm prepack copy)
|
|
42
83
|
*
|
|
43
84
|
* @param template - Template id
|
|
44
85
|
* @returns Absolute path to the template source tree
|
|
45
86
|
*/
|
|
46
87
|
export function resolveTemplateDir(template: TemplateId): string {
|
|
47
|
-
const envRoot = process.env["
|
|
88
|
+
const envRoot = process.env["CREATE_OKE_TEMPLATES"];
|
|
48
89
|
if (envRoot) {
|
|
49
90
|
const fromEnv = resolve(envRoot, template);
|
|
50
91
|
if (existsSync(fromEnv)) return fromEnv;
|
|
51
92
|
}
|
|
52
93
|
|
|
53
94
|
const pkg = packageRoot();
|
|
54
|
-
const monorepo = resolve(pkg, "../../
|
|
95
|
+
const monorepo = resolve(pkg, "../../templates", template);
|
|
55
96
|
if (existsSync(monorepo)) return monorepo;
|
|
56
97
|
|
|
57
98
|
const bundled = resolve(pkg, "templates", template);
|
|
@@ -64,6 +105,38 @@ export function resolveTemplateDir(template: TemplateId): string {
|
|
|
64
105
|
);
|
|
65
106
|
}
|
|
66
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Resolve the directory to copy for `--from-example`.
|
|
110
|
+
*
|
|
111
|
+
* Order:
|
|
112
|
+
* 1. `CREATE_OKE_EXAMPLES` env (absolute path to an `examples/` tree)
|
|
113
|
+
* 2. Monorepo `examples/<example>` (two levels up from this package)
|
|
114
|
+
* 3. Bundled `examples/<example>` (npm prepack copy)
|
|
115
|
+
*
|
|
116
|
+
* @param example - Example id
|
|
117
|
+
* @returns Absolute path to the example source tree
|
|
118
|
+
*/
|
|
119
|
+
export function resolveExampleDir(example: ExampleId): string {
|
|
120
|
+
const envRoot = process.env["CREATE_OKE_EXAMPLES"];
|
|
121
|
+
if (envRoot) {
|
|
122
|
+
const fromEnv = resolve(envRoot, example);
|
|
123
|
+
if (existsSync(fromEnv)) return fromEnv;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const pkg = packageRoot();
|
|
127
|
+
const monorepo = resolve(pkg, "../../examples", example);
|
|
128
|
+
if (existsSync(monorepo)) return monorepo;
|
|
129
|
+
|
|
130
|
+
const bundled = resolve(pkg, "examples", example);
|
|
131
|
+
if (existsSync(bundled)) return bundled;
|
|
132
|
+
|
|
133
|
+
throw new Error(
|
|
134
|
+
`create-oke: example "${example}" not found.\n` +
|
|
135
|
+
` looked at: ${monorepo}\n` +
|
|
136
|
+
` ${bundled}`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
67
140
|
/**
|
|
68
141
|
* Absolute path to the okengine package root when running inside this monorepo.
|
|
69
142
|
*
|
package/src/transform.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Rewrites applied when copying
|
|
2
|
+
* Rewrites applied when copying a `templates/<id>` or `examples/<id>` tree
|
|
3
|
+
* into a new project.
|
|
3
4
|
*
|
|
4
5
|
* Exactly:
|
|
5
6
|
* 1. `package.json` `"name"` → the user-provided project name
|
|
6
7
|
* 2. `package.json` `"okengine": "file:../.."` → an installable reference
|
|
7
8
|
* (absolute `file:<okengine-root>` in the monorepo; registry version otherwise)
|
|
8
|
-
* 3. Drop monorepo-only files that import paths outside the
|
|
9
|
+
* 3. Drop monorepo-only files that import paths outside the source tree
|
|
9
10
|
* (today: `tests/docker.test.ts`)
|
|
10
11
|
*/
|
|
11
12
|
|
|
@@ -61,6 +62,8 @@ export function transformPackageJson(
|
|
|
61
62
|
return {
|
|
62
63
|
...source,
|
|
63
64
|
name: projectName,
|
|
65
|
+
// Starter apps always begin at 0.0.1 — never the framework lockstep version.
|
|
66
|
+
version: "0.0.1",
|
|
64
67
|
dependencies,
|
|
65
68
|
};
|
|
66
69
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# full
|
|
2
|
+
|
|
3
|
+
**Every element present and wired** — no business logic. Use when you want the
|
|
4
|
+
complete surface ready to fill in.
|
|
5
|
+
|
|
6
|
+
## Run
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
bun install
|
|
10
|
+
oke dev # app :6530 · Console :6533 · MCP :6535
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
| Port | Try |
|
|
14
|
+
|---|---|
|
|
15
|
+
| `:6530` | `GET /` · `GET /health` |
|
|
16
|
+
| `:6533` | Console |
|
|
17
|
+
| `:6535` | MCP |
|
|
18
|
+
|
|
19
|
+
## What’s in this template
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
full/
|
|
23
|
+
├── oke.config.ts
|
|
24
|
+
├── src/
|
|
25
|
+
│ ├── app.ts # gates, secrets, signals, stores, channel, ai
|
|
26
|
+
│ ├── core.ts / schema.ts # Store
|
|
27
|
+
│ ├── gates.ts # open gate (used on HTTP)
|
|
28
|
+
│ ├── vault.ts # appSecret
|
|
29
|
+
│ ├── channels.ts # welcome template
|
|
30
|
+
│ ├── ai.ts # stub model + echo prompt
|
|
31
|
+
│ ├── locales/en.ts · ar.ts
|
|
32
|
+
│ └── flows/main/
|
|
33
|
+
│ ├── index.ts # GET / · GET /health · every("1d")
|
|
34
|
+
│ ├── shapes.ts
|
|
35
|
+
│ └── signals.ts # pinged
|
|
36
|
+
└── tests/full.test.ts
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- **Elements wired:** Flow · Store · Signal · Clock (`every`) · Gate · Vault · Channel · AI
|
|
40
|
+
- **Unit:** `main`
|
|
41
|
+
- **App `$options`:** `gates`, `secrets`, `signals`, `stores`, `channel`, `ai`
|
|
42
|
+
- **Flows:** `root`, `health` (emits `pinged`), plus a trivial daily cron
|
|
43
|
+
|
|
44
|
+
Replace stubs with real models, prompts, channels, and domain flows. Prefer
|
|
45
|
+
`standard` if you do not need AI / clock / vault on day one.
|
|
46
|
+
|
|
47
|
+
## Agent contract
|
|
48
|
+
|
|
49
|
+
See [`AGENTS.md`](./AGENTS.md). Handbook: [okengine.vercel.app/docs](https://okengine.vercel.app/docs)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineConfig } from "okengine/config";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
drivers: {
|
|
5
|
+
store: {
|
|
6
|
+
sql: { dev: "sqlite", test: "memory", prod: "postgres" },
|
|
7
|
+
kv: { dev: "memory", test: "memory", prod: "redis" },
|
|
8
|
+
},
|
|
9
|
+
signal: { dev: "memory", test: "memory", prod: "postgres" },
|
|
10
|
+
clock: { dev: "memory", test: "frozen", prod: "postgres" },
|
|
11
|
+
vault: { dev: "dotenv", test: "memory", prod: "sops" },
|
|
12
|
+
channel: {
|
|
13
|
+
email: { dev: "console", test: "console", prod: "smtp" },
|
|
14
|
+
sms: { dev: "console", test: "console", prod: "unifonic" },
|
|
15
|
+
whatsapp: { dev: "console", test: "console", prod: "wa-cloud" },
|
|
16
|
+
},
|
|
17
|
+
ai: {
|
|
18
|
+
dev: "mock",
|
|
19
|
+
test: "mock",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
i18n: { locales: ["en", "ar"], default: "en", dir: { ar: "rtl" } },
|
|
23
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@oke/template-full",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"okengine": "file:../..",
|
|
8
|
+
"drizzle-orm": "^1.0.0-rc.4",
|
|
9
|
+
"zod": "^4.4.3"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "bun test",
|
|
13
|
+
"dev": "oke dev"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ai } from "okengine";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export const stub = ai.model("stub", { provider: "anthropic", tier: "haiku" });
|
|
5
|
+
|
|
6
|
+
/** Stub prompt — replace with real versioned prompts. */
|
|
7
|
+
export const echo = stub.prompt("echo", {
|
|
8
|
+
in: z.object({ text: z.string() }),
|
|
9
|
+
out: z.object({ text: z.string() }),
|
|
10
|
+
version: 1,
|
|
11
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { db } from "./core";
|
|
2
|
+
import { open } from "./gates";
|
|
3
|
+
import { appSecret } from "./vault";
|
|
4
|
+
import { welcome } from "./channels";
|
|
5
|
+
import { stub, echo } from "./ai";
|
|
6
|
+
import { pinged } from "./flows/main/signals";
|
|
7
|
+
import "./channels";
|
|
8
|
+
import "./gates";
|
|
9
|
+
import "./ai";
|
|
10
|
+
import "./locales/en";
|
|
11
|
+
import "./locales/ar";
|
|
12
|
+
|
|
13
|
+
import { oke } from "okengine";
|
|
14
|
+
import * as main from "./flows/main";
|
|
15
|
+
|
|
16
|
+
export const app = oke({ name: "full" }).adopt({ main });
|
|
17
|
+
|
|
18
|
+
export type App = typeof app;
|
|
19
|
+
|
|
20
|
+
Object.assign(app.$options, {
|
|
21
|
+
env: "test",
|
|
22
|
+
gates: [open],
|
|
23
|
+
secrets: [appSecret],
|
|
24
|
+
signals: [pinged],
|
|
25
|
+
stores: [db],
|
|
26
|
+
channel: {
|
|
27
|
+
templates: [welcome],
|
|
28
|
+
defaultLocale: "en",
|
|
29
|
+
},
|
|
30
|
+
ai: {
|
|
31
|
+
models: [stub],
|
|
32
|
+
prompts: [echo],
|
|
33
|
+
},
|
|
34
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { channel } from "okengine";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export const mail = channel.email({ from: "App <no-reply@example.com>" });
|
|
5
|
+
|
|
6
|
+
/** Stub channel template — replace with real copy. */
|
|
7
|
+
export const welcome = mail.template("welcome", {
|
|
8
|
+
schema: z.object({ name: z.string() }),
|
|
9
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { on, flow, http, every } from "okengine";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { open } from "../../gates";
|
|
4
|
+
import { Health } from "./shapes";
|
|
5
|
+
import { pinged } from "./signals";
|
|
6
|
+
|
|
7
|
+
/** First-run welcome — visit :6530/ after `oke dev`. */
|
|
8
|
+
export const root = on(
|
|
9
|
+
http.get("/").gate(open),
|
|
10
|
+
flow({
|
|
11
|
+
out: z.object({
|
|
12
|
+
ok: z.literal(true),
|
|
13
|
+
console: z.string(),
|
|
14
|
+
try: z.string(),
|
|
15
|
+
}),
|
|
16
|
+
do: () => ({
|
|
17
|
+
ok: true as const,
|
|
18
|
+
console: "http://127.0.0.1:6533",
|
|
19
|
+
try: "/health",
|
|
20
|
+
}),
|
|
21
|
+
}),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
export const health = on(
|
|
25
|
+
http.get("/health").gate(open),
|
|
26
|
+
flow({
|
|
27
|
+
out: Health,
|
|
28
|
+
do: async (_input, fx) => {
|
|
29
|
+
await fx.emit(pinged, { at: Date.now() });
|
|
30
|
+
return { ok: true as const };
|
|
31
|
+
},
|
|
32
|
+
}),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
on(pinged, flow({
|
|
36
|
+
do: async () => {},
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
/** Trivial cron — replace with real schedules. */
|
|
40
|
+
on(
|
|
41
|
+
every("1d"),
|
|
42
|
+
flow({
|
|
43
|
+
do: async () => {},
|
|
44
|
+
}),
|
|
45
|
+
);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
|
|
2
|
+
import { id, now } from "okengine/store";
|
|
3
|
+
|
|
4
|
+
export const entries = sqliteTable("entries", {
|
|
5
|
+
id: text("id").primaryKey().$defaultFn(id),
|
|
6
|
+
body: text("body").notNull(),
|
|
7
|
+
createdAt: integer("created_at").notNull().$defaultFn(now),
|
|
8
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { vault } from "okengine";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
/** Stub secret contract — replace with your secrets. */
|
|
5
|
+
export const appSecret = vault.secret("APP_SECRET", {
|
|
6
|
+
schema: z.string().min(1),
|
|
7
|
+
description: "Application secret",
|
|
8
|
+
dev: "dev-only-secret",
|
|
9
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { test, expect } from "bun:test";
|
|
2
|
+
import { createTestApp } from "okengine/test";
|
|
3
|
+
import { app } from "../src/app";
|
|
4
|
+
|
|
5
|
+
test("boots — health flow with wired elements", async () => {
|
|
6
|
+
const t = await createTestApp(app);
|
|
7
|
+
const { data, error } = await t.api.main.health({});
|
|
8
|
+
expect(error).toBeNull();
|
|
9
|
+
expect(data).toEqual({ ok: true });
|
|
10
|
+
await t.signals.drain();
|
|
11
|
+
});
|