okengine 0.14.1 → 0.15.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/package.json +3 -4
- package/site/content/docs/elements/ai.mdx +2 -1
- package/site/content/docs/elements/channel.mdx +1 -0
- package/site/content/docs/elements/clock.mdx +3 -0
- package/site/content/docs/elements/flow.mdx +3 -0
- package/site/content/docs/elements/gate.mdx +25 -22
- package/site/content/docs/elements/signal.mdx +4 -4
- package/site/content/docs/elements/store.mdx +86 -27
- package/site/content/docs/elements/vault.mdx +1 -0
- package/site/content/docs/get-started/basic-usage.mdx +22 -13
- package/site/content/docs/get-started/index.mdx +5 -0
- package/site/content/docs/get-started/introduction.mdx +11 -3
- package/site/content/docs/get-started/meta.json +1 -1
- package/site/content/docs/get-started/project-structure.mdx +932 -0
- package/site/content/docs/reference/cli.md +13 -10
- package/site/content/docs/reference/client.mdx +25 -21
- package/site/content/docs/reference/security.md +1 -1
- package/src/auth/bindings.ts +14 -5
- package/src/auth/cross-plane.ts +10 -3
- package/src/cli/ai-setup/ai-setup.test.ts +62 -0
- package/src/cli/ai-setup/apply.ts +32 -1
- package/src/cli/attach-host-console.test.ts +3 -2
- package/src/cli/attach-host-console.ts +3 -0
- package/src/cli/build.test.ts +4 -2
- package/src/cli/db-seed.ts +48 -15
- package/src/cli/db.test.ts +104 -0
- package/src/cli/db.ts +122 -10
- package/src/cli/dev-controls.test.ts +20 -0
- package/src/cli/dev-controls.ts +13 -0
- package/src/cli/dev.test.ts +15 -4
- package/src/cli/dev.ts +102 -29
- package/src/cli/ensure-drizzle-config.ts +2 -1
- package/src/cli/tui/DevLive.tsx +12 -2
- package/src/cli/tui/keys.test.ts +2 -1
- package/src/cli/tui/keys.ts +3 -2
- package/src/compiler/aot.test.ts +1 -1
- package/src/compiler/extract.test.ts +409 -33
- package/src/compiler/extract.ts +318 -67
- package/src/compiler/flow-path.test.ts +96 -0
- package/src/compiler/flow-path.ts +173 -0
- package/src/compiler/generate-adopt.test.ts +112 -3
- package/src/compiler/generate-adopt.ts +280 -26
- package/src/compiler/schema-from-ast.ts +609 -0
- package/src/console/server/bind.ts +2 -2
- package/src/console/server/console.test.ts +3 -0
- package/src/console/server/flows.ts +120 -40
- package/src/console/server/invoke-user-flow.test.ts +3 -1
- package/src/console/server/invoke-user-flow.ts +17 -2
- package/src/console/server/iso-at.test.ts +9 -0
- package/src/console/server/iso-at.ts +11 -0
- package/src/console/server/security-headers.ts +6 -3
- package/src/console/server/spa-proxy.test.ts +1 -0
- package/src/console/server/sql-catalog.ts +147 -7
- package/src/console/server/sql-rls.test.ts +16 -0
- package/src/console/server/state.ts +8 -0
- package/src/console/server/store.test.ts +7 -4
- package/src/console/server/store.ts +142 -58
- package/src/console/ui-next/dist/assets/agent-disclosure-C0X1fbWF.js +1 -0
- package/src/console/ui-next/dist/assets/{cache-glyph-BanhLsEY.js → cache-glyph-F1FI122b.js} +1 -1
- package/src/console/ui-next/dist/assets/{call-pii-button-bqkxMrJH.js → call-pii-button-DEDCl_j3.js} +1 -1
- package/src/console/ui-next/dist/assets/collapsible-LPqGvfoz.js +1 -0
- package/src/console/ui-next/dist/assets/{confirm-sheet-DDCRmG62.js → confirm-sheet-3ptDzXbz.js} +1 -1
- package/src/console/ui-next/dist/assets/{duration-tone-oiRxPVsZ.js → duration-tone-sC3lGABz.js} +1 -1
- package/src/console/ui-next/dist/assets/{explorer-empty-HjTnVQoR.js → explorer-empty-BvYrygyO.js} +1 -1
- package/src/console/ui-next/dist/assets/flows-page-Dluo1Bd7.js +1 -0
- package/src/console/ui-next/dist/assets/{highlighted-json-CvDPvveV.js → highlighted-json-CS_O8L-r.js} +1 -1
- package/src/console/ui-next/dist/assets/http-method--sWDdXSB.js +1 -0
- package/src/console/ui-next/dist/assets/index-Ca3HZMVq.js +66 -0
- package/src/console/ui-next/dist/assets/index-UtTDRQpS.css +2 -0
- package/src/console/ui-next/dist/assets/{link-DF7SZ9Ek.js → link-COeyggt-.js} +1 -1
- package/src/console/ui-next/dist/assets/{observability-page-BEZDzyYh.js → observability-page-HK9-BO8z.js} +2 -2
- package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +1 -0
- package/src/console/ui-next/dist/assets/replica-lag-DBIFFf7d.js +18 -0
- package/src/console/ui-next/dist/assets/sheet-form-Dnwa5oOR.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-keys-CoqwEIi0.js +1 -0
- package/src/console/ui-next/dist/assets/skeleton-BHcfDYcb.js +1 -0
- package/src/console/ui-next/dist/assets/store-page-BSohH3wM.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-CFIGRnnA.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-BgLl34w7.js +54 -0
- package/src/console/ui-next/dist/assets/units-page-B0cFE76A.js +1 -0
- package/src/console/ui-next/dist/assets/{use-vault-list-Cl79j_ku.js → use-vault-list-BLbZhzyF.js} +1 -1
- package/src/console/ui-next/dist/assets/vault-page-B1SbYe10.js +2 -0
- package/src/console/ui-next/dist/index.html +6 -6
- package/src/console/ui-next/src/client.ts +35 -7
- package/src/console/ui-next/src/components/motion/table/index.tsx +16 -0
- package/src/console/ui-next/src/components/motion/table/types.ts +4 -0
- package/src/console/ui-next/src/components/ui/kbd.tsx +2 -2
- package/src/console/ui-next/src/features/flows/traces/http-method.ts +19 -0
- package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +19 -1
- package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +10 -3
- package/src/console/ui-next/src/features/store/detail/browse-section.tsx +45 -1
- package/src/console/ui-next/src/features/store/detail/resource-panel.tsx +78 -16
- package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +155 -14
- package/src/console/ui-next/src/features/store/explorer/store-tree.tsx +33 -133
- package/src/console/ui-next/src/features/store/files/file-preview.tsx +4 -1
- package/src/console/ui-next/src/features/store/grid/rls-policy-sheet.tsx +326 -165
- package/src/console/ui-next/src/features/store/grid/sql-insert-sheet.tsx +6 -0
- package/src/console/ui-next/src/features/store/grid/store-data-grid.tsx +50 -3
- package/src/console/ui-next/src/features/store/lib/files-meta.test.ts +3 -0
- package/src/console/ui-next/src/features/store/lib/files-meta.ts +12 -0
- package/src/console/ui-next/src/features/store/lib/query-defaults.test.ts +25 -0
- package/src/console/ui-next/src/features/store/lib/query-defaults.ts +85 -3
- package/src/console/ui-next/src/features/store/lib/query-gate.ts +30 -4
- package/src/console/ui-next/src/features/store/lib/rls-policy.test.ts +139 -0
- package/src/console/ui-next/src/features/store/lib/rls-policy.ts +223 -21
- package/src/console/ui-next/src/features/store/lib/schema-graph.ts +4 -0
- package/src/console/ui-next/src/features/store/lib/sql-catalog.test.ts +24 -0
- package/src/console/ui-next/src/features/store/lib/sql-catalog.ts +19 -0
- package/src/console/ui-next/src/features/store/lib/store-tree.test.ts +6 -0
- package/src/console/ui-next/src/features/store/lib/store-tree.ts +13 -0
- package/src/console/ui-next/src/features/store/query/query-console.tsx +48 -14
- package/src/console/ui-next/src/features/store/query/query-gate-menu.tsx +19 -20
- package/src/console/ui-next/src/features/store/query/query-gate-parts.tsx +2 -1
- package/src/console/ui-next/src/features/store/query/query-results.tsx +110 -83
- package/src/console/ui-next/src/features/store/schema/schema-table-node.tsx +17 -0
- package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +112 -72
- package/src/console/ui-next/src/features/units/lib/contract-input.test.ts +206 -1
- package/src/console/ui-next/src/features/units/lib/contract-input.ts +55 -0
- package/src/console/ui-next/src/features/vault/lib/types.ts +1 -1
- package/src/docker/compose-health.test.ts +27 -0
- package/src/docker/compose-health.ts +19 -3
- package/src/docker/docker.test.ts +3 -0
- package/src/docker/recipes/pgdog.ts +2 -0
- package/src/drivers/clock-postgres.ts +13 -9
- package/src/drivers/instances-postgres.ts +4 -6
- package/src/drivers/journal-postgres.ts +13 -9
- package/src/drivers/pg-rls.test.ts +87 -0
- package/src/drivers/pg-rls.ts +233 -7
- package/src/drivers/pglite.ts +26 -3
- package/src/drivers/postgres.test.ts +23 -0
- package/src/drivers/postgres.ts +133 -9
- package/src/drivers/types.ts +10 -0
- package/src/elements/clock/declare.ts +26 -1
- package/src/elements/gate/boot.ts +5 -5
- package/src/elements/gate/declare.ts +36 -4
- package/src/elements/gate/flatten.ts +1 -1
- package/src/elements/gate/runtime.ts +1 -1
- package/src/elements/store/declare.ts +9 -6
- package/src/elements/store/emit-drizzle.ts +44 -8
- package/src/elements/store/resource.test.ts +1 -1
- package/src/elements/store/rls-identity.test.ts +95 -0
- package/src/elements/store/rls-identity.ts +116 -0
- package/src/elements/store/runtime.ts +4 -0
- package/src/elements/store/schema-decl.test.ts +37 -0
- package/src/elements/store/schema-decl.ts +161 -0
- package/src/elements/store/seed.ts +1 -1
- package/src/elements/store/sql-condition.test.ts +12 -0
- package/src/elements/store/sql-condition.ts +26 -2
- package/src/elements/store/sql-rls-isolation.test.ts +165 -0
- package/src/elements/store/sql-rls-stamp.test.ts +117 -0
- package/src/elements/store/sql-session.ts +81 -3
- package/src/elements/store/upsert-app.test.ts +1 -1
- package/src/elements/store.ts +14 -0
- package/src/http.ts +20 -3
- package/src/index.ts +10 -1
- package/src/kernel/adopt-barrel-fresh.test.ts +1 -1
- package/src/kernel/adopt-routes.ts +30 -2
- package/src/kernel/app.ts +131 -14
- package/src/kernel/auto-cache.test.ts +8 -8
- package/src/kernel/auto-registry.test.ts +49 -9
- package/src/kernel/boot.test.ts +4 -4
- package/src/kernel/boot.ts +2 -2
- package/src/kernel/effects-stamping.test.ts +3 -3
- package/src/kernel/element-registries.ts +12 -5
- package/src/kernel/errors.ts +24 -0
- package/src/kernel/flow-units.ts +64 -0
- package/src/kernel/flow.ts +29 -17
- package/src/kernel/fx.ts +23 -0
- package/src/kernel/horizontal-child.ts +4 -4
- package/src/kernel/http-path-pending.ts +20 -0
- package/src/kernel/http-resource.ts +4 -0
- package/src/kernel/http-stream.test.ts +4 -4
- package/src/kernel/pipeline.test.ts +2 -2
- package/src/kernel/plugin-elements.test.ts +1 -1
- package/src/kernel/plugin-needs.test.ts +1 -1
- package/src/kernel/ready.test.ts +2 -2
- package/src/kernel/resource-mount.test.ts +14 -0
- package/src/kernel/run-duration.test.ts +2 -2
- package/src/kernel/stamp-http.test.ts +79 -0
- package/src/kernel/stamp-http.ts +75 -0
- package/src/kernel/triggers.ts +52 -27
- package/src/manifest/types.ts +13 -0
- package/src/release/measure.ts +2 -2
- package/src/runtime/json-code-block.test.ts +2 -2
- package/src/runtime/serve.test.ts +4 -4
- package/src/term.test.ts +52 -4
- package/src/term.ts +125 -17
- package/src/test/reset-element-registries.ts +8 -3
- package/src/cli/ask-seed.test.ts +0 -96
- package/src/cli/ask-seed.ts +0 -82
- package/src/console/ui-next/dist/assets/collapsible-DYb0xU8C.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-DxDsOd4f.js +0 -1
- package/src/console/ui-next/dist/assets/http-method-CJCBYL2j.js +0 -1
- package/src/console/ui-next/dist/assets/index-Ce6WKWKM.js +0 -66
- package/src/console/ui-next/dist/assets/index-D4Ldtj79.css +0 -2
- package/src/console/ui-next/dist/assets/query-gate-parts-1m8m1iNp.js +0 -2
- package/src/console/ui-next/dist/assets/react-B1ML8gxg.js +0 -1
- package/src/console/ui-next/dist/assets/replica-lag-C8_BRt2x.js +0 -18
- package/src/console/ui-next/dist/assets/sheet-form-D-ata7jy.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-DO4IsVqv.js +0 -1
- package/src/console/ui-next/dist/assets/skeleton-CL_X0GCj.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-v3LXdYpr.js +0 -45
- package/src/console/ui-next/dist/assets/trace-detail-sheet-DFLFfUUX.js +0 -2
- package/src/console/ui-next/dist/assets/units-page-C0gW6Kdo.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-DuKzqwzW.js +0 -2
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `.adopt()`
|
|
3
|
-
* virtual-module Bun plugin (investigated and rejected: `Bun.plugin()`'s
|
|
2
|
+
* `.adopt()` / auto-register barrel generator — the disk-file alternative
|
|
3
|
+
* to a virtual-module Bun plugin (investigated and rejected: `Bun.plugin()`'s
|
|
4
4
|
* `onResolve` is never invoked for an unresolvable specifier during a plain
|
|
5
5
|
* runtime `import()`, so `oke dev` and `oke build` would resolve a virtual
|
|
6
6
|
* module differently — measured, not assumed).
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* (
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* Two unit shapes:
|
|
9
|
+
* - **Barrel** — only `index.ts` (+ skip-list) → `import * as notes from "./notes/index.ts"; export { notes }`
|
|
10
|
+
* - **Tree** — `[param]` / `(group)` / extra route files → synthesized namespace
|
|
11
|
+
* with `stampHttpPath` / `stampFlowName` after import
|
|
12
|
+
*
|
|
13
|
+
* Mixed barrel `index.ts` + tree files is a generate error (ambiguous:
|
|
14
|
+
* `index` is also a reserved leaf).
|
|
13
15
|
*/
|
|
14
16
|
|
|
15
|
-
import { mkdir, readdir, rename, writeFile } from "node:fs/promises";
|
|
17
|
+
import { mkdir, readdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
16
18
|
import { join } from "node:path";
|
|
17
19
|
|
|
20
|
+
import {
|
|
21
|
+
importSpecifierFromWalked,
|
|
22
|
+
isSkipFlowFile,
|
|
23
|
+
nameFromFlowFile,
|
|
24
|
+
pathFromFlowFile,
|
|
25
|
+
toPosixPath,
|
|
26
|
+
} from "./flow-path.ts";
|
|
27
|
+
|
|
18
28
|
/** Options for {@link generateAdoptBarrel}. */
|
|
19
29
|
export interface GenerateAdoptBarrelOptions {
|
|
20
30
|
/** Project root (contains `src/flows`). */
|
|
@@ -29,13 +39,16 @@ export interface AdoptBarrelResult {
|
|
|
29
39
|
readonly source: string;
|
|
30
40
|
/** Unit (folder) names included, sorted. */
|
|
31
41
|
readonly units: readonly string[];
|
|
32
|
-
/** Folders under `flowsDir` skipped
|
|
42
|
+
/** Folders under `flowsDir` skipped (empty / invalid name / no routes). */
|
|
33
43
|
readonly skipped: readonly string[];
|
|
34
44
|
}
|
|
35
45
|
|
|
36
46
|
const GENERATED_HEADER =
|
|
37
47
|
"// AUTO-GENERATED by `oke dev` / `oke build` — do not edit by hand.\n" +
|
|
38
|
-
"// Regenerated from every `src/flows/<unit
|
|
48
|
+
"// Regenerated from every `src/flows/<unit>/` folder (barrel or file tree).\n";
|
|
49
|
+
|
|
50
|
+
const CATCHALL_COMMENT =
|
|
51
|
+
'// Catch-all param is "*" — call api.docs.get({ "*": "a/b/c" }), not { slug }.\n';
|
|
39
52
|
|
|
40
53
|
/** Filename of the on-disk `.adopt()` barrel under the flows directory. */
|
|
41
54
|
export const ADOPT_BARREL_FILE = "generated.ts";
|
|
@@ -43,11 +56,39 @@ export const ADOPT_BARREL_FILE = "generated.ts";
|
|
|
43
56
|
/** A folder name usable as an `export * as <name>` binding. */
|
|
44
57
|
const VALID_UNIT_NAME = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
45
58
|
|
|
59
|
+
const EXPORT_CONST = /^export const ([A-Za-z_$][A-Za-z0-9_$]*)\b/gm;
|
|
60
|
+
|
|
61
|
+
const EXPLICIT_FLOW_UNIT = /flow\(\s*["']([A-Za-z_$][A-Za-z0-9_$]*)\./g;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Generate-time failure (mixed barrel + tree, export collision, unit drift).
|
|
65
|
+
*/
|
|
66
|
+
export class GenerateAdoptError extends Error {
|
|
67
|
+
/** Stable error name. */
|
|
68
|
+
override readonly name = "GenerateAdoptError";
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param message - Why generation failed
|
|
72
|
+
*/
|
|
73
|
+
constructor(message: string) {
|
|
74
|
+
super(message);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** One route file discovered under a unit folder. */
|
|
79
|
+
interface WalkedFlowFile {
|
|
80
|
+
/** POSIX path relative to the unit folder (`[id]/get.ts`). */
|
|
81
|
+
readonly relFromUnit: string;
|
|
82
|
+
/** File source (for export + drift scans). */
|
|
83
|
+
readonly source: string;
|
|
84
|
+
/** `export const` names. */
|
|
85
|
+
readonly exports: readonly string[];
|
|
86
|
+
}
|
|
87
|
+
|
|
46
88
|
/**
|
|
47
89
|
* Write the adopt barrel atomically: `generated.ts.tmp` → rename to
|
|
48
|
-
* `generated.ts`.
|
|
49
|
-
*
|
|
50
|
-
* truncate first.
|
|
90
|
+
* `generated.ts`. Skips the write when bytes are unchanged so the `src/`
|
|
91
|
+
* watcher + bun `--hot` do not loop.
|
|
51
92
|
*
|
|
52
93
|
* @param rootDir - Project root
|
|
53
94
|
* @param source - Full barrel source from {@link generateAdoptBarrel}
|
|
@@ -57,24 +98,28 @@ export async function writeAdoptBarrel(
|
|
|
57
98
|
rootDir: string,
|
|
58
99
|
source: string,
|
|
59
100
|
flowsDir = "src/flows",
|
|
60
|
-
): Promise<
|
|
101
|
+
): Promise<{ written: boolean }> {
|
|
61
102
|
const dir = join(rootDir, flowsDir);
|
|
62
103
|
const target = join(dir, ADOPT_BARREL_FILE);
|
|
63
104
|
const tmp = `${target}.tmp`;
|
|
64
105
|
await mkdir(dir, { recursive: true });
|
|
106
|
+
try {
|
|
107
|
+
const existing = await readFile(target, "utf8");
|
|
108
|
+
if (existing === source) return { written: false };
|
|
109
|
+
} catch {
|
|
110
|
+
// missing — write
|
|
111
|
+
}
|
|
65
112
|
await writeFile(tmp, source);
|
|
66
113
|
await rename(tmp, target);
|
|
114
|
+
return { written: true };
|
|
67
115
|
}
|
|
68
116
|
|
|
69
117
|
/**
|
|
70
|
-
* Scan `<rootDir>/<flowsDir>/*` and emit a barrel
|
|
71
|
-
* folder's `index.ts` under its folder name — the same shape
|
|
72
|
-
* `.adopt({ main, notes })` already takes by hand.
|
|
118
|
+
* Scan `<rootDir>/<flowsDir>/*` and emit a barrel that registers every unit.
|
|
73
119
|
*
|
|
74
|
-
* Never throws
|
|
75
|
-
* barrel
|
|
76
|
-
*
|
|
77
|
-
* `assertAdoptBarrelFresh` in `kernel/boot.ts`).
|
|
120
|
+
* Never throws for a missing/unreadable flows directory (empty barrel).
|
|
121
|
+
* Mixed barrel + tree, export collisions, and unit-prefix drift throw
|
|
122
|
+
* {@link GenerateAdoptError}.
|
|
78
123
|
*
|
|
79
124
|
* @param options - Root dir / flows dir override
|
|
80
125
|
*/
|
|
@@ -86,12 +131,17 @@ export async function generateAdoptBarrel(
|
|
|
86
131
|
|
|
87
132
|
const units: string[] = [];
|
|
88
133
|
const skipped: string[] = [];
|
|
134
|
+
const unitBlocks: string[] = [];
|
|
135
|
+
const registerNames: string[] = [];
|
|
136
|
+
const declareLines: string[] = [];
|
|
137
|
+
let hasCatchAll = false;
|
|
138
|
+
let needsStampImport = false;
|
|
139
|
+
|
|
89
140
|
try {
|
|
90
141
|
const dirents = await readdir(abs, { withFileTypes: true });
|
|
91
142
|
for (const d of dirents.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
92
143
|
if (!d.isDirectory()) continue;
|
|
93
|
-
|
|
94
|
-
if (!hasBarrel) {
|
|
144
|
+
if (d.name.startsWith("_")) {
|
|
95
145
|
skipped.push(d.name);
|
|
96
146
|
continue;
|
|
97
147
|
}
|
|
@@ -99,13 +149,217 @@ export async function generateAdoptBarrel(
|
|
|
99
149
|
skipped.push(d.name);
|
|
100
150
|
continue;
|
|
101
151
|
}
|
|
152
|
+
|
|
153
|
+
const unitAbs = join(abs, d.name);
|
|
154
|
+
const files = await listFlowFiles(unitAbs);
|
|
155
|
+
const hasIndex = files.some((f) => f.relFromUnit === "index.ts");
|
|
156
|
+
const hasOther = files.some((f) => f.relFromUnit !== "index.ts");
|
|
157
|
+
|
|
158
|
+
if (hasIndex && hasOther) {
|
|
159
|
+
throw new GenerateAdoptError(
|
|
160
|
+
`Unit "${d.name}" mixes a barrel index.ts with tree route files. ` +
|
|
161
|
+
`Use only index.ts (barrel), or move the collection path to route.ts ` +
|
|
162
|
+
`and keep [id]/ beside it.`,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (files.length === 0) {
|
|
167
|
+
skipped.push(d.name);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
102
171
|
units.push(d.name);
|
|
172
|
+
registerNames.push(d.name);
|
|
173
|
+
declareLines.push(` readonly ${d.name}: typeof ${d.name};`);
|
|
174
|
+
|
|
175
|
+
if (unitBlocks.length > 0) unitBlocks.push("");
|
|
176
|
+
|
|
177
|
+
if (hasIndex && !hasOther) {
|
|
178
|
+
const spec = JSON.stringify(importSpecifierFromWalked(`${d.name}/index.ts`));
|
|
179
|
+
unitBlocks.push(`import * as ${d.name} from ${spec};`);
|
|
180
|
+
unitBlocks.push(`export { ${d.name} };`);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
needsStampImport = true;
|
|
185
|
+
const { lines, catchAll } = emitTreeUnit(d.name, files);
|
|
186
|
+
if (catchAll) hasCatchAll = true;
|
|
187
|
+
unitBlocks.push(...lines);
|
|
103
188
|
}
|
|
104
|
-
} catch {
|
|
189
|
+
} catch (err) {
|
|
190
|
+
if (err instanceof GenerateAdoptError) throw err;
|
|
105
191
|
// Missing flows dir — empty barrel, not a throw (mirrors extractManifest).
|
|
106
192
|
}
|
|
107
193
|
|
|
108
|
-
const
|
|
109
|
-
|
|
194
|
+
const parts: string[] = [GENERATED_HEADER.trimEnd()];
|
|
195
|
+
if (hasCatchAll) parts.push(CATCHALL_COMMENT.trimEnd());
|
|
196
|
+
if (registerNames.length > 0) {
|
|
197
|
+
const stamp = needsStampImport ? ", stampFlowName, stampHttpPath" : "";
|
|
198
|
+
parts.push(`import { registerFlowUnits${stamp} } from "okengine";`);
|
|
199
|
+
}
|
|
200
|
+
if (unitBlocks.length > 0) {
|
|
201
|
+
parts.push(unitBlocks.join("\n"));
|
|
202
|
+
}
|
|
203
|
+
if (registerNames.length > 0) {
|
|
204
|
+
parts.push(`registerFlowUnits({ ${registerNames.join(", ")} });`);
|
|
205
|
+
parts.push('declare module "okengine" {');
|
|
206
|
+
parts.push(" interface RegisteredFlowUnits {");
|
|
207
|
+
parts.push(...declareLines);
|
|
208
|
+
parts.push(" }");
|
|
209
|
+
parts.push("}");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const source = `${parts.join("\n")}\n`;
|
|
110
213
|
return { source, units, skipped };
|
|
111
214
|
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Walk a unit folder for route files (skip-list applied).
|
|
218
|
+
*
|
|
219
|
+
* @param unitAbs - Absolute unit directory
|
|
220
|
+
*/
|
|
221
|
+
async function listFlowFiles(unitAbs: string): Promise<WalkedFlowFile[]> {
|
|
222
|
+
const out: WalkedFlowFile[] = [];
|
|
223
|
+
|
|
224
|
+
const walk = async (dir: string, rel: string): Promise<void> => {
|
|
225
|
+
const dirents = await readdir(dir, { withFileTypes: true });
|
|
226
|
+
for (const d of dirents) {
|
|
227
|
+
if (d.name.startsWith("_")) continue;
|
|
228
|
+
const posixName = toPosixPath(d.name);
|
|
229
|
+
if (d.isDirectory()) {
|
|
230
|
+
if (posixName.startsWith("[[") && posixName.endsWith("]]")) {
|
|
231
|
+
throw new GenerateAdoptError(
|
|
232
|
+
`Optional catch-all "${posixName}" is not supported — use [...slug] (param is "*").`,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
await walk(join(dir, d.name), rel ? `${rel}/${posixName}` : posixName);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (!posixName.endsWith(".ts") && !posixName.endsWith(".tsx")) continue;
|
|
239
|
+
if (isSkipFlowFile(posixName)) continue;
|
|
240
|
+
const relFile = rel ? `${rel}/${posixName}` : posixName;
|
|
241
|
+
const source = await readFile(join(dir, d.name), "utf8");
|
|
242
|
+
const exports = listExportedConsts(source);
|
|
243
|
+
const relPosix = toPosixPath(relFile);
|
|
244
|
+
// Barrel `index.ts` counts even with no `export const` (`export {}` / re-exports).
|
|
245
|
+
if (exports.length === 0 && relPosix !== "index.ts") continue;
|
|
246
|
+
out.push({ relFromUnit: relPosix, source, exports });
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
await walk(unitAbs, "");
|
|
252
|
+
} catch (err) {
|
|
253
|
+
if (err instanceof GenerateAdoptError) throw err;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return out.sort((a, b) => a.relFromUnit.localeCompare(b.relFromUnit));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Collect `export const` binding names.
|
|
261
|
+
*
|
|
262
|
+
* @param source - TypeScript source
|
|
263
|
+
*/
|
|
264
|
+
export function listExportedConsts(source: string): string[] {
|
|
265
|
+
const names: string[] = [];
|
|
266
|
+
EXPORT_CONST.lastIndex = 0;
|
|
267
|
+
let match: RegExpExecArray | null;
|
|
268
|
+
while ((match = EXPORT_CONST.exec(source))) {
|
|
269
|
+
const name = match[1];
|
|
270
|
+
if (name) names.push(name);
|
|
271
|
+
}
|
|
272
|
+
return names;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Emit import + stamped namespace for a tree unit.
|
|
277
|
+
*
|
|
278
|
+
* @param unit - Folder name
|
|
279
|
+
* @param files - Walked route files
|
|
280
|
+
*/
|
|
281
|
+
function emitTreeUnit(
|
|
282
|
+
unit: string,
|
|
283
|
+
files: WalkedFlowFile[],
|
|
284
|
+
): { lines: string[]; catchAll: boolean } {
|
|
285
|
+
const used = new Map<string, string>();
|
|
286
|
+
let catchAll = false;
|
|
287
|
+
const importLines: string[] = [];
|
|
288
|
+
const fieldLines: string[] = [];
|
|
289
|
+
|
|
290
|
+
for (const file of files) {
|
|
291
|
+
if (file.relFromUnit.includes("[...")) catchAll = true;
|
|
292
|
+
assertUnitPrefix(unit, file);
|
|
293
|
+
const spec = importSpecifierFromWalked(`${unit}/${file.relFromUnit}`);
|
|
294
|
+
if (spec.includes("\\")) {
|
|
295
|
+
throw new GenerateAdoptError(
|
|
296
|
+
`Import specifier for ${unit}/${file.relFromUnit} is not POSIX.`,
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
const alias = importAlias(unit, file.relFromUnit);
|
|
300
|
+
importLines.push(`import * as ${alias} from ${JSON.stringify(spec)};`);
|
|
301
|
+
|
|
302
|
+
const inferred =
|
|
303
|
+
pathFromFlowFile(`${unit}/${file.relFromUnit}`) ??
|
|
304
|
+
pathFromFlowFile(`src/flows/${unit}/${file.relFromUnit}`);
|
|
305
|
+
|
|
306
|
+
for (const exp of file.exports) {
|
|
307
|
+
const prev = used.get(exp);
|
|
308
|
+
if (prev !== undefined) {
|
|
309
|
+
throw new GenerateAdoptError(
|
|
310
|
+
`Unit "${unit}" exports "${exp}" from both ${prev} and ${file.relFromUnit}.`,
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
used.set(exp, file.relFromUnit);
|
|
314
|
+
const flowName = nameFromFlowFile(`${unit}/${file.relFromUnit}`, exp) ?? `${unit}.${exp}`;
|
|
315
|
+
const pathLit = JSON.stringify(inferred ?? "/");
|
|
316
|
+
fieldLines.push(
|
|
317
|
+
` ${exp}: stampHttpPath(stampFlowName(${alias}.${exp}, ${JSON.stringify(flowName)}), ${pathLit}),`,
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const lines = [
|
|
323
|
+
...importLines,
|
|
324
|
+
"",
|
|
325
|
+
`const ${unit} = {`,
|
|
326
|
+
...fieldLines,
|
|
327
|
+
`};`,
|
|
328
|
+
`export { ${unit} };`,
|
|
329
|
+
];
|
|
330
|
+
return { lines, catchAll };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Fail when `flow("other.x")` lives under a different unit folder.
|
|
335
|
+
*
|
|
336
|
+
* @param unit - Folder name
|
|
337
|
+
* @param file - Walked file
|
|
338
|
+
*/
|
|
339
|
+
function assertUnitPrefix(unit: string, file: WalkedFlowFile): void {
|
|
340
|
+
EXPLICIT_FLOW_UNIT.lastIndex = 0;
|
|
341
|
+
let match: RegExpExecArray | null;
|
|
342
|
+
while ((match = EXPLICIT_FLOW_UNIT.exec(file.source))) {
|
|
343
|
+
const prefix = match[1];
|
|
344
|
+
if (prefix && prefix !== unit) {
|
|
345
|
+
throw new GenerateAdoptError(
|
|
346
|
+
`flow("${prefix}.…") in ${unit}/${file.relFromUnit} does not match the folder "${unit}".`,
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Stable import alias for a walked file (`notes/[id]/get.ts` → `notes_$id$_get`).
|
|
354
|
+
*
|
|
355
|
+
* @param unit - Unit folder
|
|
356
|
+
* @param relFromUnit - POSIX relative path
|
|
357
|
+
*/
|
|
358
|
+
export function importAlias(unit: string, relFromUnit: string): string {
|
|
359
|
+
const posix = toPosixPath(relFromUnit).replace(/\.[^.]+$/, "");
|
|
360
|
+
const body = posix
|
|
361
|
+
.replace(/\[/g, "$")
|
|
362
|
+
.replace(/\]/g, "$")
|
|
363
|
+
.replace(/[^\w$]/g, "_");
|
|
364
|
+
return `${unit}_${body}`;
|
|
365
|
+
}
|