@cosmicdrift/kumiko-dev-server 0.329.0 → 0.330.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 +17 -0
- package/bin/kumiko-build.ts +3 -2
- package/bin/kumiko-dev.ts +1 -1
- package/bin/kumiko-init-deploy.ts +1 -1
- package/bin/kumiko-schema-check.ts +4 -1
- package/dist/build-server-bundle.d.ts +31 -0
- package/dist/build-server-bundle.js +282 -0
- package/dist/build.d.ts +2 -0
- package/dist/build.js +5 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +9 -0
- package/dist/codegen/index.d.ts +4 -0
- package/dist/codegen/index.js +4 -0
- package/dist/codegen/render.d.ts +55 -0
- package/dist/codegen/render.js +273 -0
- package/dist/codegen/run-codegen.d.ts +20 -0
- package/dist/codegen/run-codegen.js +219 -0
- package/dist/codegen/scan-events.d.ts +76 -0
- package/dist/codegen/scan-events.js +447 -0
- package/dist/codegen/watch.d.ts +33 -0
- package/dist/codegen/watch.js +112 -0
- package/dist/compose-stacks.d.ts +60 -0
- package/dist/compose-stacks.js +127 -0
- package/dist/crash-tracker.d.ts +17 -0
- package/dist/crash-tracker.js +37 -0
- package/dist/create-kumiko-server.d.ts +195 -0
- package/dist/create-kumiko-server.js +1040 -0
- package/dist/env-schema.d.ts +15 -0
- package/dist/env-schema.js +60 -0
- package/dist/few-shot-corpus.d.ts +72 -0
- package/dist/few-shot-corpus.js +262 -0
- package/dist/framework-version.d.ts +1 -0
- package/dist/framework-version.js +31 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +33 -0
- package/dist/init-deploy-cli.d.ts +10 -0
- package/dist/init-deploy-cli.js +111 -0
- package/dist/kebab.d.ts +1 -0
- package/{src/kebab.ts → dist/kebab.js} +2 -3
- package/dist/run-dev-app.d.ts +174 -0
- package/dist/run-dev-app.js +403 -0
- package/dist/scaffold-app-feature.d.ts +16 -0
- package/dist/scaffold-app-feature.js +240 -0
- package/dist/scaffold-app.d.ts +45 -0
- package/dist/scaffold-app.js +852 -0
- package/dist/scaffold-demo-tasks.d.ts +8 -0
- package/{src/scaffold-demo-tasks.ts → dist/scaffold-demo-tasks.js} +69 -98
- package/dist/scaffold-deploy.d.ts +92 -0
- package/dist/scaffold-deploy.js +254 -0
- package/dist/scaffold-feature.d.ts +26 -0
- package/dist/scaffold-feature.js +163 -0
- package/dist/schema-apply.d.ts +11 -0
- package/dist/schema-apply.js +95 -0
- package/dist/schema-check-core.d.ts +2 -0
- package/{src/schema-check-core.ts → dist/schema-check-core.js} +8 -10
- package/dist/schema-tables-auth-mode.d.ts +2 -0
- package/{src/schema-tables-auth-mode.ts → dist/schema-tables-auth-mode.js} +2 -10
- package/dist/schema-tables-minimal.d.ts +2 -0
- package/{src/schema-tables-minimal.ts → dist/schema-tables-minimal.js} +2 -11
- package/dist/setup-test-stack-from-features.d.ts +11 -0
- package/dist/setup-test-stack-from-features.js +42 -0
- package/dist/welcome-banner.d.ts +15 -0
- package/dist/welcome-banner.js +27 -0
- package/package.json +71 -24
- package/src/changes.json +6 -0
- package/src/__tests__/build-prod-bundle.integration.test.ts +0 -282
- package/src/__tests__/build-server-bundle.test.ts +0 -228
- package/src/__tests__/classify-change.test.ts +0 -87
- package/src/__tests__/compose-stacks.test.ts +0 -273
- package/src/__tests__/config-resolver-default.integration.test.ts +0 -165
- package/src/__tests__/crash-tracker.test.ts +0 -89
- package/src/__tests__/create-kumiko-server-errors.test.ts +0 -54
- package/src/__tests__/create-kumiko-server-options.test.ts +0 -33
- package/src/__tests__/create-kumiko-server.integration.test.ts +0 -1007
- package/src/__tests__/discover-format.test.ts +0 -32
- package/src/__tests__/env-schema.integration.test.ts +0 -248
- package/src/__tests__/env-schema.test.ts +0 -132
- package/src/__tests__/few-shot-corpus.test.ts +0 -316
- package/src/__tests__/framework-version.test.ts +0 -45
- package/src/__tests__/init-deploy-cli.test.ts +0 -113
- package/src/__tests__/kumiko-schema-check.test.ts +0 -56
- package/src/__tests__/merge-extra-context.test.ts +0 -67
- package/src/__tests__/resolve-stylesheet.test.ts +0 -132
- package/src/__tests__/run-dev-app-boot-validation.test.ts +0 -38
- package/src/__tests__/run-dev-app.integration.test.ts +0 -290
- package/src/__tests__/runtime-classification.test.ts +0 -26
- package/src/__tests__/saas-identity-wire.integration.test.ts +0 -427
- package/src/__tests__/scaffold-app-feature.test.ts +0 -216
- package/src/__tests__/scaffold-app.test.ts +0 -630
- package/src/__tests__/scaffold-demo-tasks.test.ts +0 -47
- package/src/__tests__/scaffold-deploy.test.ts +0 -464
- package/src/__tests__/scaffold-dev-cycle.integration.test.ts +0 -109
- package/src/__tests__/scaffold-feature.test.ts +0 -143
- package/src/__tests__/schema-apply.integration.test.ts +0 -191
- package/src/__tests__/setup-test-stack-from-features.integration.test.ts +0 -30
- package/src/__tests__/setup-test-stack-job-run-logger.integration.test.ts +0 -72
- package/src/__tests__/walkthrough.integration.test.ts +0 -140
- package/src/__tests__/welcome-banner.test.ts +0 -42
- package/src/build-server-bundle.ts +0 -346
- package/src/build.ts +0 -28
- package/src/codegen/__tests__/render-codegen.test.ts +0 -100
- package/src/codegen/__tests__/run-codegen.test.ts +0 -665
- package/src/codegen/__tests__/strict-mode-diagnostics.test.ts +0 -347
- package/src/codegen/__tests__/watch.test.ts +0 -222
- package/src/codegen/index.ts +0 -19
- package/src/codegen/render.ts +0 -305
- package/src/codegen/run-codegen.ts +0 -278
- package/src/codegen/scan-events.ts +0 -565
- package/src/codegen/watch.ts +0 -140
- package/src/compose-stacks.ts +0 -193
- package/src/crash-tracker.ts +0 -56
- package/src/create-kumiko-server.ts +0 -1397
- package/src/env-schema.ts +0 -72
- package/src/few-shot-corpus.ts +0 -370
- package/src/framework-version.ts +0 -39
- package/src/index.ts +0 -105
- package/src/init-deploy-cli.ts +0 -148
- package/src/run-dev-app.ts +0 -694
- package/src/scaffold-app-feature.ts +0 -283
- package/src/scaffold-app.ts +0 -1033
- package/src/scaffold-deploy.ts +0 -393
- package/src/scaffold-feature.ts +0 -226
- package/src/schema-apply.ts +0 -123
- package/src/setup-test-stack-from-features.ts +0 -80
- package/src/welcome-banner.ts +0 -43
|
@@ -1,316 +0,0 @@
|
|
|
1
|
-
// Few-Shot-Corpus tests:
|
|
2
|
-
// 1. Smoke — buildFewShotCorpus produces a structurally valid corpus
|
|
3
|
-
// against a tmp-dir mini-repo (canonical multi-kind feature, broken
|
|
4
|
-
// legacy feature, and a non-feature file).
|
|
5
|
-
// 2. Output shape — id stable, paths repo-relative, authoring-style
|
|
6
|
-
// flags match the parse-error count.
|
|
7
|
-
// 3. Warnings — duplicate-id collisions and parser-throw don't get
|
|
8
|
-
// swallowed; they show up in the corpus.warnings array.
|
|
9
|
-
// 4. pathToId — pure-function unit tests for the id derivation.
|
|
10
|
-
//
|
|
11
|
-
// Note: The drift-check against the checked-in `docs/few-shot-corpus.json`
|
|
12
|
-
// has been moved to `kumiko-enterprise/packages/ai-foundation` (Phase O.6)
|
|
13
|
-
// because the corpus JSON now lives there — framework is public and must
|
|
14
|
-
// not carry the eval baseline.
|
|
15
|
-
|
|
16
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
17
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
18
|
-
import { tmpdir } from "node:os";
|
|
19
|
-
import { join } from "node:path";
|
|
20
|
-
import { buildFewShotCorpus, pathToId } from "@cosmicdrift/kumiko-dev-server";
|
|
21
|
-
|
|
22
|
-
let workdir: string;
|
|
23
|
-
|
|
24
|
-
beforeEach(() => {
|
|
25
|
-
workdir = mkdtempSync(join(tmpdir(), "kumiko-corpus-"));
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
afterEach(() => {
|
|
29
|
-
rmSync(workdir, { recursive: true, force: true });
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
// =============================================================================
|
|
33
|
-
// Smoke test on a tmp-dir mini-repo
|
|
34
|
-
// =============================================================================
|
|
35
|
-
|
|
36
|
-
describe("buildFewShotCorpus — smoke", () => {
|
|
37
|
-
test("returns one entry per discovered feature-file with parsed shape", () => {
|
|
38
|
-
// Plant two feature-files inside `samples/recipes/` so the default
|
|
39
|
-
// scan-roots pick them up.
|
|
40
|
-
plantPackage(workdir, "samples/recipes/canonical-demo", {
|
|
41
|
-
pkgName: "@cosmicdrift/kumiko-sample-canonical-demo",
|
|
42
|
-
pkgDescription: "Canonical-form demo feature.",
|
|
43
|
-
featureSource: buildCanonicalMultiKindFeature("canonicalDemo"),
|
|
44
|
-
});
|
|
45
|
-
plantPackage(workdir, "samples/recipes/legacy-demo", {
|
|
46
|
-
pkgName: "@cosmicdrift/kumiko-sample-legacy-demo",
|
|
47
|
-
pkgDescription: "Handler-object-reference-style legacy feature.",
|
|
48
|
-
featureSource: buildLegacyFeature("legacyDemo"),
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
const corpus = buildFewShotCorpus({ repoRoot: workdir });
|
|
52
|
-
|
|
53
|
-
expect(corpus.entries).toHaveLength(2);
|
|
54
|
-
expect(corpus.totals.all).toBe(2);
|
|
55
|
-
expect(corpus.totals.canonical).toBe(1);
|
|
56
|
-
expect(corpus.totals.legacy).toBe(1);
|
|
57
|
-
expect(corpus.warnings).toEqual([]);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test("multi-kind canonical entry exposes tags and kind-counts across all kinds", () => {
|
|
61
|
-
plantPackage(workdir, "samples/recipes/multi-kind", {
|
|
62
|
-
pkgName: "@cosmicdrift/kumiko-sample-multi-kind",
|
|
63
|
-
pkgDescription: "Demo feature spanning entity, writeHandler, nav.",
|
|
64
|
-
featureSource: buildCanonicalMultiKindFeature("multiKind"),
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
const corpus = buildFewShotCorpus({ repoRoot: workdir });
|
|
68
|
-
const entry = corpus.entries[0];
|
|
69
|
-
if (!entry) throw new Error("expected one entry");
|
|
70
|
-
|
|
71
|
-
// Tags collected from PATTERN_LIBRARY.category for each kind. The
|
|
72
|
-
// exact category names live in the library — assert the union has
|
|
73
|
-
// at least the categories we expect to see for these kinds, instead
|
|
74
|
-
// of pinning the full set (so adding categories doesn't break this
|
|
75
|
-
// test). Why this matters: `tags` is the retrieval key for L2.
|
|
76
|
-
expect(entry.tags.length).toBeGreaterThanOrEqual(2);
|
|
77
|
-
|
|
78
|
-
expect(entry.patternsByKind).toMatchObject({
|
|
79
|
-
entity: 1,
|
|
80
|
-
writeHandler: 1,
|
|
81
|
-
nav: 1,
|
|
82
|
-
});
|
|
83
|
-
expect(entry.authoringStyle).toBe("canonical");
|
|
84
|
-
expect(entry.parseErrors).toEqual([]);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test("entries carry id, repo-relative paths, description, tags", () => {
|
|
88
|
-
plantPackage(workdir, "samples/recipes/demo", {
|
|
89
|
-
pkgName: "@cosmicdrift/kumiko-sample-demo",
|
|
90
|
-
pkgDescription: "Demo.",
|
|
91
|
-
featureSource: buildCanonicalMultiKindFeature("demo"),
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
const corpus = buildFewShotCorpus({ repoRoot: workdir });
|
|
95
|
-
const entry = corpus.entries[0];
|
|
96
|
-
if (!entry) throw new Error("expected one entry");
|
|
97
|
-
|
|
98
|
-
expect(entry.id).toBe("recipes/demo");
|
|
99
|
-
expect(entry.sourcePath).toBe("samples/recipes/demo/src/feature.ts");
|
|
100
|
-
expect(entry.packageJsonPath).toBe("samples/recipes/demo/package.json");
|
|
101
|
-
expect(entry.packageName).toBe("@cosmicdrift/kumiko-sample-demo");
|
|
102
|
-
expect(entry.description).toBe("Demo.");
|
|
103
|
-
expect(entry.featureName).toBe("demo");
|
|
104
|
-
expect(entry.authoringStyle).toBe("canonical");
|
|
105
|
-
expect(entry.parseErrors).toEqual([]);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test("source paths inside parsed patterns are repo-relative (no absolute paths)", () => {
|
|
109
|
-
plantPackage(workdir, "samples/recipes/demo", {
|
|
110
|
-
pkgName: "x",
|
|
111
|
-
pkgDescription: "x",
|
|
112
|
-
featureSource: buildCanonicalMultiKindFeature("demo"),
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
const corpus = buildFewShotCorpus({ repoRoot: workdir });
|
|
116
|
-
const entry = corpus.entries[0];
|
|
117
|
-
if (!entry) throw new Error("expected one entry");
|
|
118
|
-
|
|
119
|
-
// Walk the patterns recursively and assert every `file` field is
|
|
120
|
-
// relative — no absolute paths leaking into the JSON.
|
|
121
|
-
const json = JSON.stringify(entry.patterns);
|
|
122
|
-
expect(json).not.toContain(workdir);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
test("non-feature .ts files are ignored", () => {
|
|
126
|
-
const recipesDir = join(workdir, "samples", "recipes", "demo", "src");
|
|
127
|
-
mkdirSync(recipesDir, { recursive: true });
|
|
128
|
-
writeFileSync(
|
|
129
|
-
join(recipesDir, "..", "package.json"),
|
|
130
|
-
JSON.stringify({ name: "x", description: "x" }),
|
|
131
|
-
);
|
|
132
|
-
writeFileSync(join(recipesDir, "feature.ts"), buildCanonicalMultiKindFeature("demo"));
|
|
133
|
-
writeFileSync(join(recipesDir, "helpers.ts"), "export const x = 1;\n");
|
|
134
|
-
writeFileSync(join(recipesDir, "feature.test.ts"), "// not a feature\n");
|
|
135
|
-
|
|
136
|
-
const corpus = buildFewShotCorpus({ repoRoot: workdir });
|
|
137
|
-
expect(corpus.entries).toHaveLength(1);
|
|
138
|
-
expect(corpus.entries[0]?.sourcePath.endsWith("feature.ts")).toBe(true);
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
// =============================================================================
|
|
143
|
-
// Warnings — silent skips are gone; parser-throw / duplicate-id surface
|
|
144
|
-
// =============================================================================
|
|
145
|
-
|
|
146
|
-
describe("buildFewShotCorpus — warnings", () => {
|
|
147
|
-
test("duplicate id between two scan roots produces a warning, not an overwrite", () => {
|
|
148
|
-
// Both paths collapse to the same id under pathToId:
|
|
149
|
-
// samples/foo/src/feature.ts → strip "samples/" + "/src/feature.ts" → "foo"
|
|
150
|
-
// packages/foo/src/feature.ts → strip "packages/" + "/src/feature.ts" → "foo"
|
|
151
|
-
// Without the duplicate-id check the second entry would silently
|
|
152
|
-
// overwrite the first in any consumer that built `Map<id, entry>`.
|
|
153
|
-
plantPackage(workdir, "samples/foo", {
|
|
154
|
-
pkgName: "@cosmicdrift/kumiko-sample-foo-a",
|
|
155
|
-
pkgDescription: "First foo.",
|
|
156
|
-
featureSource: buildCanonicalMultiKindFeature("fooA"),
|
|
157
|
-
});
|
|
158
|
-
plantPackage(workdir, "packages/foo", {
|
|
159
|
-
pkgName: "@cosmicdrift/kumiko-sample-foo-b",
|
|
160
|
-
pkgDescription: "Second foo.",
|
|
161
|
-
featureSource: buildCanonicalMultiKindFeature("fooB"),
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
// Scan both roots so both feature-files get picked up.
|
|
165
|
-
const corpus = buildFewShotCorpus({
|
|
166
|
-
repoRoot: workdir,
|
|
167
|
-
scanRoots: ["samples", "packages"],
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
expect(corpus.entries).toHaveLength(1);
|
|
171
|
-
expect(corpus.warnings).toHaveLength(1);
|
|
172
|
-
expect(corpus.warnings[0]?.message).toMatch(/^duplicate-id:/);
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
test("parser-throw surfaces as a warning instead of silent skip", () => {
|
|
176
|
-
// A file that ts-morph can read syntactically but parseFeatureFile's
|
|
177
|
-
// top-level invariants reject. Easiest: a file that matches the
|
|
178
|
-
// FEATURE_FILE_PATTERN regex but contains code that crashes the
|
|
179
|
-
// parser (e.g. raw `import` from a non-resolvable module isn't
|
|
180
|
-
// enough — the parser falls back to ParseError, not a throw). Use
|
|
181
|
-
// outright invalid TypeScript.
|
|
182
|
-
const recipesDir = join(workdir, "samples", "recipes", "broken", "src");
|
|
183
|
-
mkdirSync(recipesDir, { recursive: true });
|
|
184
|
-
writeFileSync(
|
|
185
|
-
join(recipesDir, "..", "package.json"),
|
|
186
|
-
JSON.stringify({ name: "x", description: "x" }),
|
|
187
|
-
);
|
|
188
|
-
// Garbage that ts-morph still parses but as a script with errors.
|
|
189
|
-
// The parser may either return a corpus with errors (legacy entry)
|
|
190
|
-
// or throw — both branches are valid. We assert: if it *throws*,
|
|
191
|
-
// the warning is present; if it doesn't, no warning is needed.
|
|
192
|
-
writeFileSync(join(recipesDir, "feature.ts"), "this is not typescript {{{");
|
|
193
|
-
|
|
194
|
-
const corpus = buildFewShotCorpus({ repoRoot: workdir });
|
|
195
|
-
// Either path is acceptable, but the corpus must not silently
|
|
196
|
-
// disappear the file:
|
|
197
|
-
const handled =
|
|
198
|
-
corpus.entries.length === 1 ||
|
|
199
|
-
corpus.warnings.some((w) => w.sourcePath.endsWith("feature.ts"));
|
|
200
|
-
expect(handled).toBe(true);
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
// =============================================================================
|
|
205
|
-
// pathToId — pure function unit tests
|
|
206
|
-
// =============================================================================
|
|
207
|
-
|
|
208
|
-
describe("pathToId", () => {
|
|
209
|
-
test("strips samples/ prefix and /src/feature.ts suffix", () => {
|
|
210
|
-
expect(pathToId("samples/recipes/basic-entity/src/feature.ts")).toBe("recipes/basic-entity");
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
test("strips packages/ prefix", () => {
|
|
214
|
-
expect(pathToId("packages/bundled-features/src/auth-email-password/feature.ts")).toBe(
|
|
215
|
-
"bundled-features/src/auth-email-password",
|
|
216
|
-
);
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
test("strips plain /feature.ts suffix when no /src/ in the path", () => {
|
|
220
|
-
expect(pathToId("samples/apps/showcase/src/features/demos/feature.ts")).toBe(
|
|
221
|
-
"apps/showcase/src/features/demos",
|
|
222
|
-
);
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
test("returns the path unchanged when no prefix or suffix matches", () => {
|
|
226
|
-
expect(pathToId("misc/random.ts")).toBe("misc/random.ts");
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
test("only strips the leading samples|packages prefix once (no greedy)", () => {
|
|
230
|
-
// packages/samples/foo/feature.ts: only the first prefix gets dropped.
|
|
231
|
-
expect(pathToId("packages/samples/foo/feature.ts")).toBe("samples/foo");
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
// =============================================================================
|
|
236
|
-
// Helpers — feature-file content + workspace planting
|
|
237
|
-
// =============================================================================
|
|
238
|
-
|
|
239
|
-
function plantPackage(
|
|
240
|
-
root: string,
|
|
241
|
-
relPath: string,
|
|
242
|
-
opts: {
|
|
243
|
-
pkgName: string;
|
|
244
|
-
pkgDescription: string;
|
|
245
|
-
featureSource: string;
|
|
246
|
-
},
|
|
247
|
-
): void {
|
|
248
|
-
const dir = join(root, relPath, "src");
|
|
249
|
-
mkdirSync(dir, { recursive: true });
|
|
250
|
-
writeFileSync(
|
|
251
|
-
join(root, relPath, "package.json"),
|
|
252
|
-
JSON.stringify({ name: opts.pkgName, description: opts.pkgDescription }),
|
|
253
|
-
);
|
|
254
|
-
writeFileSync(join(dir, "feature.ts"), opts.featureSource);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Hand-rolled canonical-form feature-file with three kinds (entity,
|
|
259
|
-
* writeHandler, nav) so the smoke-test exercises tags + counts across
|
|
260
|
-
* categories. Mirrors `samples/recipes/designer-demo/src/feature.ts`
|
|
261
|
-
* but inlined here so the test stays self-contained.
|
|
262
|
-
*/
|
|
263
|
-
function buildCanonicalMultiKindFeature(featureName: string): string {
|
|
264
|
-
return `// kumiko-feature-version: 1
|
|
265
|
-
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
266
|
-
import { z } from "zod";
|
|
267
|
-
|
|
268
|
-
defineFeature("${featureName}", (r) => {
|
|
269
|
-
r.entity({
|
|
270
|
-
name: "task",
|
|
271
|
-
fields: {
|
|
272
|
-
title: { type: "text", required: true },
|
|
273
|
-
done: { type: "boolean", default: false },
|
|
274
|
-
},
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
r.writeHandler({
|
|
278
|
-
name: "task:create",
|
|
279
|
-
schema: z.object({ title: z.string() }),
|
|
280
|
-
handler: async (_event, _ctx) => {
|
|
281
|
-
return { isSuccess: true, data: { id: "x" } };
|
|
282
|
-
},
|
|
283
|
-
access: { roles: ["user"] },
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
r.nav({
|
|
287
|
-
id: "tasks",
|
|
288
|
-
label: "Tasks",
|
|
289
|
-
screen: "${featureName}:screen:task-list",
|
|
290
|
-
});
|
|
291
|
-
});
|
|
292
|
-
`;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function buildLegacyFeature(featureName: string): string {
|
|
296
|
-
// Handler-object-reference style — writeHandler receives a single
|
|
297
|
-
// identifier referencing a pre-built handler object rather than an
|
|
298
|
-
// inline string name + schema + handler. The parser has no extractor
|
|
299
|
-
// shape for that (it only reads a literal name as the first arg, or
|
|
300
|
-
// an inline object-literal form), so it produces a ParseError and the
|
|
301
|
-
// corpus marks the entry as authoringStyle: "legacy".
|
|
302
|
-
//
|
|
303
|
-
// Note: a captured-const *entity definition* (`r.entity("x", xEntity)`)
|
|
304
|
-
// used to be the legacy example here, but #998 taught the parser to
|
|
305
|
-
// resolve such references via a raw-ref sentinel — that idiom parses
|
|
306
|
-
// cleanly now and no longer exercises the legacy path.
|
|
307
|
-
return `
|
|
308
|
-
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
309
|
-
|
|
310
|
-
const itemCreateHandler = { name: "item:create" };
|
|
311
|
-
|
|
312
|
-
defineFeature("${featureName}", (r) => {
|
|
313
|
-
r.writeHandler(itemCreateHandler);
|
|
314
|
-
});
|
|
315
|
-
`;
|
|
316
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { resolveFrameworkVersion } from "../framework-version";
|
|
6
|
-
|
|
7
|
-
const tmpRoots: string[] = [];
|
|
8
|
-
|
|
9
|
-
function makeTree(): { root: string; nested: string } {
|
|
10
|
-
const root = mkdtempSync(join(tmpdir(), "framework-version-"));
|
|
11
|
-
tmpRoots.push(root);
|
|
12
|
-
const nested = join(root, "a", "b");
|
|
13
|
-
mkdirSync(nested, { recursive: true });
|
|
14
|
-
return { root, nested };
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
afterEach(() => {
|
|
18
|
-
for (const root of tmpRoots.splice(0)) rmSync(root, { recursive: true, force: true });
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
describe("resolveFrameworkVersion", () => {
|
|
22
|
-
test("resolves the real dev-server version with a caret range", async () => {
|
|
23
|
-
const own: { version: string } = await Bun.file(
|
|
24
|
-
join(import.meta.dir, "../../package.json"),
|
|
25
|
-
).json();
|
|
26
|
-
expect(resolveFrameworkVersion()).toBe(`^${own.version}`);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test("skips corrupt and foreign package.json files on the way up", () => {
|
|
30
|
-
const { root, nested } = makeTree();
|
|
31
|
-
writeFileSync(
|
|
32
|
-
join(root, "package.json"),
|
|
33
|
-
'{"name":"@cosmicdrift/kumiko-dev-server","version":"1.2.3"}',
|
|
34
|
-
);
|
|
35
|
-
writeFileSync(join(root, "a", "package.json"), '{"name":"other","version":"9.9.9"}');
|
|
36
|
-
writeFileSync(join(nested, "package.json"), "{ not json");
|
|
37
|
-
expect(resolveFrameworkVersion(nested)).toBe("^1.2.3");
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test("ignores a dev-server manifest without a version", () => {
|
|
41
|
-
const { root, nested } = makeTree();
|
|
42
|
-
writeFileSync(join(root, "package.json"), '{"name":"@cosmicdrift/kumiko-dev-server"}');
|
|
43
|
-
expect(resolveFrameworkVersion(nested)).toBeUndefined();
|
|
44
|
-
});
|
|
45
|
-
});
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
2
|
-
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { runInitDeployCli } from "../init-deploy-cli";
|
|
6
|
-
|
|
7
|
-
function makeOut() {
|
|
8
|
-
const logs: string[] = [];
|
|
9
|
-
const errs: string[] = [];
|
|
10
|
-
return {
|
|
11
|
-
out: { log: (m: string) => logs.push(m), err: (m: string) => errs.push(m) },
|
|
12
|
-
logs,
|
|
13
|
-
errs,
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
describe("runInitDeployCli", () => {
|
|
18
|
-
let tmp: string;
|
|
19
|
-
|
|
20
|
-
beforeEach(() => {
|
|
21
|
-
tmp = mkdtempSync(join(tmpdir(), "kumiko-init-deploy-cli-"));
|
|
22
|
-
});
|
|
23
|
-
afterEach(() => {
|
|
24
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("scaffolds with an explicit --app and reports exit 0", async () => {
|
|
28
|
-
const { out, logs } = makeOut();
|
|
29
|
-
const code = await runInitDeployCli({ argv: ["--app", "myapp"], cwd: tmp, out });
|
|
30
|
-
expect(code).toBe(0);
|
|
31
|
-
expect(logs.some((l) => l.includes("Deploy scaffolding generated"))).toBe(true);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it("defaults --app from package.json name, stripping the scope", async () => {
|
|
35
|
-
writeFileSync(join(tmp, "package.json"), JSON.stringify({ name: "@cosmicdrift/some-app" }));
|
|
36
|
-
const { out, logs } = makeOut();
|
|
37
|
-
const code = await runInitDeployCli({ argv: [], cwd: tmp, out });
|
|
38
|
-
expect(code).toBe(0);
|
|
39
|
-
expect(logs.some((l) => l.includes("some-app"))).toBe(true);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it("fails with a usage error when --app is missing and package.json has no name", async () => {
|
|
43
|
-
const { out, errs } = makeOut();
|
|
44
|
-
const code = await runInitDeployCli({ argv: [], cwd: tmp, out });
|
|
45
|
-
expect(code).toBe(1);
|
|
46
|
-
expect(errs.some((e) => e.includes("--app <name> is required"))).toBe(true);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("--check and --force are mutually exclusive (exit 2)", async () => {
|
|
50
|
-
const { out, errs } = makeOut();
|
|
51
|
-
const code = await runInitDeployCli({
|
|
52
|
-
argv: ["--app", "myapp", "--check", "--force"],
|
|
53
|
-
cwd: tmp,
|
|
54
|
-
out,
|
|
55
|
-
});
|
|
56
|
-
expect(code).toBe(2);
|
|
57
|
-
expect(errs.some((e) => e.includes("mutually exclusive"))).toBe(true);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it("--check exits 0 when deploy/ is already in sync", async () => {
|
|
61
|
-
const first = await runInitDeployCli({
|
|
62
|
-
argv: ["--app", "myapp"],
|
|
63
|
-
cwd: tmp,
|
|
64
|
-
out: makeOut().out,
|
|
65
|
-
});
|
|
66
|
-
expect(first).toBe(0);
|
|
67
|
-
const { out, logs } = makeOut();
|
|
68
|
-
const code = await runInitDeployCli({ argv: ["--app", "myapp", "--check"], cwd: tmp, out });
|
|
69
|
-
expect(code).toBe(0);
|
|
70
|
-
expect(logs.some((l) => l.includes("in sync"))).toBe(true);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it("--check exits 1 and lists drifted files when deploy/ is missing or stale", async () => {
|
|
74
|
-
const { out: outMissing, errs: errsMissing } = makeOut();
|
|
75
|
-
const missingCode = await runInitDeployCli({
|
|
76
|
-
argv: ["--app", "myapp", "--check"],
|
|
77
|
-
cwd: tmp,
|
|
78
|
-
out: outMissing,
|
|
79
|
-
});
|
|
80
|
-
expect(missingCode).toBe(1);
|
|
81
|
-
expect(errsMissing.some((e) => e.includes("missing"))).toBe(true);
|
|
82
|
-
|
|
83
|
-
await runInitDeployCli({ argv: ["--app", "myapp"], cwd: tmp, out: makeOut().out });
|
|
84
|
-
writeFileSync(join(tmp, "deploy", "Dockerfile"), "# stale");
|
|
85
|
-
const { out: outStale, errs: errsStale } = makeOut();
|
|
86
|
-
const staleCode = await runInitDeployCli({
|
|
87
|
-
argv: ["--app", "myapp", "--check"],
|
|
88
|
-
cwd: tmp,
|
|
89
|
-
out: outStale,
|
|
90
|
-
});
|
|
91
|
-
expect(staleCode).toBe(1);
|
|
92
|
-
expect(errsStale.some((e) => e.includes("differs"))).toBe(true);
|
|
93
|
-
expect(errsStale.some((e) => e.includes("--force to regenerate"))).toBe(true);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it("rejects a non-kebab appName via the underlying scaffold validation", async () => {
|
|
97
|
-
const { out, errs } = makeOut();
|
|
98
|
-
const code = await runInitDeployCli({ argv: ["--app", "MyApp"], cwd: tmp, out });
|
|
99
|
-
expect(code).toBe(1);
|
|
100
|
-
expect(errs.some((e) => e.includes("kebab-case"))).toBe(true);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it("rejects an invalid package.json#kumiko.deploy config with exit 1", async () => {
|
|
104
|
-
writeFileSync(
|
|
105
|
-
join(tmp, "package.json"),
|
|
106
|
-
JSON.stringify({ name: "myapp", kumiko: { deploy: { stackNetwork: "foo" } } }),
|
|
107
|
-
);
|
|
108
|
-
const { out, errs } = makeOut();
|
|
109
|
-
const code = await runInitDeployCli({ argv: ["--app", "myapp"], cwd: tmp, out });
|
|
110
|
-
expect(code).toBe(1);
|
|
111
|
-
expect(errs.some((e) => e.includes("stackNetwork"))).toBe(true);
|
|
112
|
-
});
|
|
113
|
-
});
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
// Unit-tests for the pure helpers behind the kumiko-schema-check bin.
|
|
2
|
-
|
|
3
|
-
import { describe, expect, test } from "bun:test";
|
|
4
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
-
import { tmpdir } from "node:os";
|
|
6
|
-
import { join } from "node:path";
|
|
7
|
-
import { implicitAuthModeFeatureNames, resolveGeneratePath } from "../schema-check-core";
|
|
8
|
-
|
|
9
|
-
describe("resolveGeneratePath", () => {
|
|
10
|
-
test("defaults to drizzle/generate.ts when neither candidate exists", () => {
|
|
11
|
-
const cwd = mkdtempSync(join(tmpdir(), "schema-check-"));
|
|
12
|
-
try {
|
|
13
|
-
expect(resolveGeneratePath(cwd)).toBe(join(cwd, "drizzle/generate.ts"));
|
|
14
|
-
} finally {
|
|
15
|
-
rmSync(cwd, { recursive: true, force: true });
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
test("falls back to schema/generate.ts when drizzle/ is absent but schema/ exists", () => {
|
|
20
|
-
const cwd = mkdtempSync(join(tmpdir(), "schema-check-"));
|
|
21
|
-
try {
|
|
22
|
-
mkdirSync(join(cwd, "schema"), { recursive: true });
|
|
23
|
-
writeFileSync(join(cwd, "schema/generate.ts"), "// stub", "utf-8");
|
|
24
|
-
expect(resolveGeneratePath(cwd)).toBe(join(cwd, "schema/generate.ts"));
|
|
25
|
-
} finally {
|
|
26
|
-
rmSync(cwd, { recursive: true, force: true });
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test("prefers drizzle/generate.ts when both exist", () => {
|
|
31
|
-
const cwd = mkdtempSync(join(tmpdir(), "schema-check-"));
|
|
32
|
-
try {
|
|
33
|
-
mkdirSync(join(cwd, "drizzle"), { recursive: true });
|
|
34
|
-
mkdirSync(join(cwd, "schema"), { recursive: true });
|
|
35
|
-
writeFileSync(join(cwd, "drizzle/generate.ts"), "// stub", "utf-8");
|
|
36
|
-
writeFileSync(join(cwd, "schema/generate.ts"), "// stub", "utf-8");
|
|
37
|
-
expect(resolveGeneratePath(cwd)).toBe(join(cwd, "drizzle/generate.ts"));
|
|
38
|
-
} finally {
|
|
39
|
-
rmSync(cwd, { recursive: true, force: true });
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe("implicitAuthModeFeatureNames", () => {
|
|
45
|
-
test("liefert exakt das bundled-foundation-Set", () => {
|
|
46
|
-
// Bewusst hartkodierte Erwartung: ein Vergleich gegen composeFeatures
|
|
47
|
-
// wäre tautologisch (die Funktion IST dieser Ausdruck). Ändert sich das
|
|
48
|
-
// Foundation-Set, muss dieser Test bewusst angefasst werden.
|
|
49
|
-
expect([...implicitAuthModeFeatureNames()].sort()).toEqual([
|
|
50
|
-
"auth-email-password",
|
|
51
|
-
"config",
|
|
52
|
-
"tenant",
|
|
53
|
-
"user",
|
|
54
|
-
]);
|
|
55
|
-
});
|
|
56
|
-
});
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { mergeExtraContext } from "../setup-test-stack-from-features";
|
|
3
|
-
|
|
4
|
-
// #882/4: the integration test only asserted getFeature("config") — which
|
|
5
|
-
// includeBundled already provides — never that presets:["config"] actually
|
|
6
|
-
// merges configResolver/templateResolver into extraContext. Unit-test the merge
|
|
7
|
-
// directly instead.
|
|
8
|
-
|
|
9
|
-
const fakeDeps = {
|
|
10
|
-
registry: {} as never,
|
|
11
|
-
db: { marker: "fake-db" } as never,
|
|
12
|
-
sseBroker: {} as never,
|
|
13
|
-
redis: {} as never,
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
describe("mergeExtraContext", () => {
|
|
17
|
-
test("no presets → returns base untouched (function passthrough)", () => {
|
|
18
|
-
const base = () => ({ foo: "bar" });
|
|
19
|
-
const merged = mergeExtraContext(base, []);
|
|
20
|
-
expect(merged).toBe(base);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
test("no presets → returns base untouched (object passthrough)", () => {
|
|
24
|
-
const base = { foo: "bar" };
|
|
25
|
-
const merged = mergeExtraContext(base, []);
|
|
26
|
-
expect(merged).toBe(base);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test("config preset merges configResolver + _configAccessorFactory, keeping base-object fields", () => {
|
|
30
|
-
const merged = mergeExtraContext({ foo: "bar" }, ["config"]);
|
|
31
|
-
expect(typeof merged).toBe("function");
|
|
32
|
-
const result = (merged as (deps: typeof fakeDeps) => Record<string, unknown>)(fakeDeps);
|
|
33
|
-
expect(result["foo"]).toBe("bar");
|
|
34
|
-
expect(result["configResolver"]).toBeDefined();
|
|
35
|
-
// buildHandlerContext derives ctx.config from _configAccessorFactory, not configResolver (fw#3313).
|
|
36
|
-
expect(result["_configAccessorFactory"]).toBeDefined();
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test("config preset merges configResolver + _configAccessorFactory, keeping base-fn fields", () => {
|
|
40
|
-
const merged = mergeExtraContext(() => ({ fromFn: 1 }), ["config"]);
|
|
41
|
-
const result = (merged as (deps: typeof fakeDeps) => Record<string, unknown>)(fakeDeps);
|
|
42
|
-
expect(result["fromFn"]).toBe(1);
|
|
43
|
-
expect(result["configResolver"]).toBeDefined();
|
|
44
|
-
expect(result["_configAccessorFactory"]).toBeDefined();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test("config preset keeps a base-supplied configResolver and derives the factory from it", () => {
|
|
48
|
-
const ownResolver = { get: async () => undefined } as never;
|
|
49
|
-
const merged = mergeExtraContext({ configResolver: ownResolver }, ["config"]);
|
|
50
|
-
const result = (merged as (deps: typeof fakeDeps) => Record<string, unknown>)(fakeDeps);
|
|
51
|
-
expect(result["configResolver"]).toBe(ownResolver);
|
|
52
|
-
expect(result["_configAccessorFactory"]).toBeDefined();
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test("template-resolver preset merges templateResolver, built from deps.db", () => {
|
|
56
|
-
const merged = mergeExtraContext(undefined, ["template-resolver"]);
|
|
57
|
-
const result = (merged as (deps: typeof fakeDeps) => Record<string, unknown>)(fakeDeps);
|
|
58
|
-
expect(result["templateResolver"]).toBeDefined();
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test("both presets merge both fields", () => {
|
|
62
|
-
const merged = mergeExtraContext(undefined, ["config", "template-resolver"]);
|
|
63
|
-
const result = (merged as (deps: typeof fakeDeps) => Record<string, unknown>)(fakeDeps);
|
|
64
|
-
expect(result["configResolver"]).toBeDefined();
|
|
65
|
-
expect(result["templateResolver"]).toBeDefined();
|
|
66
|
-
});
|
|
67
|
-
});
|