@waniwani/kit 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +67 -35
  2. package/dist/cli/codegen.js +1105 -0
  3. package/dist/cli/codegen.js.map +1 -0
  4. package/{cli/env.mjs → dist/cli/env.js} +5 -6
  5. package/dist/cli/env.js.map +1 -0
  6. package/{cli/framework.mjs → dist/cli/framework.js} +112 -115
  7. package/dist/cli/framework.js.map +1 -0
  8. package/dist/cli/index.js +378 -0
  9. package/dist/cli/index.js.map +1 -0
  10. package/dist/cli/init.js +642 -0
  11. package/dist/cli/init.js.map +1 -0
  12. package/dist/cli/log.js +156 -0
  13. package/dist/cli/log.js.map +1 -0
  14. package/dist/cli/manifest.js +57 -0
  15. package/dist/cli/manifest.js.map +1 -0
  16. package/{cli/peers.mjs → dist/cli/peers.js} +77 -88
  17. package/dist/cli/peers.js.map +1 -0
  18. package/dist/cli/scan.js +100 -0
  19. package/dist/cli/scan.js.map +1 -0
  20. package/dist/cli/template.js +173 -0
  21. package/dist/cli/template.js.map +1 -0
  22. package/dist/cli/types.js +14 -0
  23. package/dist/cli/types.js.map +1 -0
  24. package/dist/cli/validate.js +328 -0
  25. package/dist/cli/validate.js.map +1 -0
  26. package/dist/cli/vercel.js +103 -0
  27. package/dist/cli/vercel.js.map +1 -0
  28. package/dist/server.d.ts +1 -1
  29. package/dist/server.d.ts.map +1 -1
  30. package/dist/server.js +0 -1
  31. package/dist/server.js.map +1 -1
  32. package/dist/web.d.ts +8 -7
  33. package/dist/web.d.ts.map +1 -1
  34. package/dist/web.js +7 -6
  35. package/dist/web.js.map +1 -1
  36. package/package.json +13 -9
  37. package/src/server.ts +7 -9
  38. package/src/web.tsx +12 -13
  39. package/cli/codegen.mjs +0 -1267
  40. package/cli/index.mjs +0 -409
  41. package/cli/init.mjs +0 -575
  42. package/cli/log.mjs +0 -178
  43. package/cli/scan.mjs +0 -112
  44. package/cli/template.mjs +0 -190
  45. package/cli/validate.mjs +0 -391
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Resolve the distribution template.
3
+ *
4
+ * The template is a separate public repo, consumed as-is. Nothing is forked
5
+ * into this package: the generator downloads the repo at a pinned commit,
6
+ * caches it, and copies the plumbing out of it. What ships to customers is the
7
+ * same tree anyone can read on GitHub, clone, and deploy by hand.
8
+ *
9
+ * Sources:
10
+ * github:OWNER/REPO#REF a GitHub repo at a branch, tag, or SHA (default)
11
+ * /path/to/checkout a local clone, for working on the template itself
12
+ *
13
+ * Override per command with `--template <source>` or `WANIWANI_TEMPLATE`.
14
+ */
15
+ import { spawnSync } from "node:child_process";
16
+ import { existsSync, mkdirSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
17
+ import { homedir, tmpdir } from "node:os";
18
+ import { join, resolve } from "node:path";
19
+ /**
20
+ * A commit, not a branch.
21
+ *
22
+ * A published version of this package is frozen, and what it generates has to
23
+ * be frozen with it. While the default was `beta`, the ref was re-resolved on
24
+ * the customer's machine at every command, so a push to that branch changed the
25
+ * output of every installed copy, and the assertions that catch a layout move
26
+ * (`REQUIRED` and `assertSeam` in `./codegen.js`) fired in a customer's
27
+ * terminal. Pinning a commit moves that failure into this repo's CI, where
28
+ * `scripts/template-contract.ts` builds a real app against the pin before a
29
+ * release goes out.
30
+ *
31
+ * Bumping it is a one-line diff, and `scripts/bump-deps.ts` proposes it. The
32
+ * commit is on the template's `beta` branch: the generator is written against
33
+ * that branch's layout (`vite.config.ts`, `src/server.ts`, `src/views/`), and
34
+ * `main` is still the older `server/` + `web/` + `api/` split, which it cannot
35
+ * absorb. An annotated tag can replace the SHA here whenever the template grows
36
+ * one, with no change to the resolver.
37
+ *
38
+ * This commit is `beta`'s head, and it reads `search` and `tracking` off
39
+ * `src/waniwani.ts` — the two fields `generateServerApp` emits from the app's
40
+ * `defineApp({ ... })`. That pairing is the reason to bump the two together:
41
+ * moving the pin here without the generator emitting those fields compiles to
42
+ * TS2339, and the contract is what catches it.
43
+ *
44
+ * Working on the template itself does not need a release: pass `--template` or
45
+ * set `WANIWANI_TEMPLATE` to a branch ref or a local checkout.
46
+ */
47
+ export const DEFAULT_TEMPLATE = "github:WaniWani-AI/mcp-distribution-template#c0d00e72a3733a5f42389731fe6bbaf7e0e07863";
48
+ const CACHE_ROOT = join(homedir(), ".cache", "waniwani", "templates");
49
+ /** `github:owner/repo#ref` -> its parts. */
50
+ function parseGithub(source) {
51
+ const match = /^github:([^/]+)\/([^#]+)(?:#(.+))?$/.exec(source);
52
+ if (!match)
53
+ return null;
54
+ return { owner: match[1], repo: match[2], ref: match[3] ?? "main" };
55
+ }
56
+ /** A full commit SHA, which is already the thing a ref has to be resolved to. */
57
+ function isSha(ref) {
58
+ return /^[0-9a-f]{40}$/i.test(ref);
59
+ }
60
+ /**
61
+ * Resolve a ref to a commit SHA, so a cache entry is content-addressed and two
62
+ * builds of the same ref cannot silently differ.
63
+ */
64
+ async function resolveSha({ owner, repo, ref }) {
65
+ // The pinned default is a commit, and asking the API to resolve a commit to
66
+ // itself is a round trip that can rate-limit, fail, or go down. A cached
67
+ // pin then needs no network at all, which is the point of pinning.
68
+ if (isSha(ref))
69
+ return ref.toLowerCase();
70
+ let response;
71
+ try {
72
+ response = await fetch(`https://api.github.com/repos/${owner}/${repo}/commits/${ref}`, {
73
+ headers: { Accept: "application/vnd.github.sha" },
74
+ });
75
+ }
76
+ catch (cause) {
77
+ // Unreachable network. A cached template is a reasonable answer.
78
+ const reason = cause instanceof Error ? cause.message : String(cause);
79
+ throw Object.assign(new Error(`cannot reach GitHub: ${reason}`), { offline: true });
80
+ }
81
+ // A bad ref is the caller's mistake, not a network problem — falling back
82
+ // to a cached template here would hide the typo.
83
+ if (!response.ok) {
84
+ throw new Error(`GitHub returned ${response.status} for ${owner}/${repo}@${ref}` +
85
+ (response.status === 404 ? " — check the repo name and ref, or that it is public" : ""));
86
+ }
87
+ return (await response.text()).trim();
88
+ }
89
+ async function download({ owner, repo, sha }, destination) {
90
+ const response = await fetch(`https://codeload.github.com/${owner}/${repo}/tar.gz/${sha}`);
91
+ if (!response.ok) {
92
+ throw new Error(`could not download ${owner}/${repo}@${sha}: ${response.status}`);
93
+ }
94
+ const archive = join(tmpdir(), `waniwani-template-${sha}.tar.gz`);
95
+ writeFileSync(archive, Buffer.from(await response.arrayBuffer()));
96
+ // Extract into a staging directory first, so an interrupted run cannot
97
+ // leave a half-populated cache entry that later builds would trust.
98
+ const staging = `${destination}.partial`;
99
+ rmSync(staging, { recursive: true, force: true });
100
+ mkdirSync(staging, { recursive: true });
101
+ const result = spawnSync("tar", ["-xzf", archive, "-C", staging, "--strip-components=1"]);
102
+ rmSync(archive, { force: true });
103
+ if (result.status !== 0) {
104
+ rmSync(staging, { recursive: true, force: true });
105
+ throw new Error(`could not extract the template archive: ${result.stderr?.toString().trim()}`);
106
+ }
107
+ rmSync(destination, { recursive: true, force: true });
108
+ spawnSync("mv", [staging, destination]);
109
+ }
110
+ /** The newest cache entry for a repo, used when the network is unavailable. */
111
+ function newestCached(owner, repo) {
112
+ if (!existsSync(CACHE_ROOT))
113
+ return null;
114
+ const prefix = `${owner}-${repo}-`;
115
+ const entries = readdirSync(CACHE_ROOT)
116
+ .filter((name) => name.startsWith(prefix))
117
+ .map((name) => ({ name, mtime: statSync(join(CACHE_ROOT, name)).mtimeMs }))
118
+ .sort((a, b) => b.mtime - a.mtime);
119
+ const newest = entries[0];
120
+ return newest ? join(CACHE_ROOT, newest.name) : null;
121
+ }
122
+ /**
123
+ * @param source `github:owner/repo#ref` or a local path
124
+ */
125
+ export async function resolveTemplate(source = DEFAULT_TEMPLATE) {
126
+ const github = parseGithub(source);
127
+ if (!github) {
128
+ const dir = resolve(source);
129
+ if (!existsSync(dir)) {
130
+ throw new Error(`template not found: ${dir}`);
131
+ }
132
+ return { dir, source, local: true };
133
+ }
134
+ const { owner, repo, ref } = github;
135
+ mkdirSync(CACHE_ROOT, { recursive: true });
136
+ let sha;
137
+ try {
138
+ sha = await resolveSha(github);
139
+ }
140
+ catch (error) {
141
+ const fallback = error.offline ? newestCached(owner, repo) : null;
142
+ if (!fallback)
143
+ throw error;
144
+ return {
145
+ dir: fallback,
146
+ source,
147
+ ref,
148
+ sha: fallback.split("-").pop(),
149
+ cached: true,
150
+ offline: true,
151
+ };
152
+ }
153
+ const dir = join(CACHE_ROOT, `${owner}-${repo}-${sha}`);
154
+ const cached = existsSync(dir);
155
+ if (!cached) {
156
+ await download({ owner, repo, sha }, dir);
157
+ }
158
+ return { dir, source, ref, sha, cached };
159
+ }
160
+ /** One-line description of what a build used, for logs and provenance files. */
161
+ export function describeTemplate(template) {
162
+ if (template.local)
163
+ return `${template.dir} (local)`;
164
+ const state = template.offline ? "offline, cached" : template.cached ? "cached" : "downloaded";
165
+ // A pinned source already carries the commit, so printing the source verbatim
166
+ // would repeat all 40 characters of it next to the short form. Collapse to
167
+ // the repo, and say that the commit came from a pin rather than a branch.
168
+ const github = parseGithub(template.source);
169
+ const pinned = github && isSha(github.ref);
170
+ const origin = pinned ? `github:${github.owner}/${github.repo}` : template.source;
171
+ return `${origin} @ ${template.sha?.slice(0, 7)} (${pinned ? `pinned, ${state}` : state})`;
172
+ }
173
+ //# sourceMappingURL=template.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template.js","sourceRoot":"","sources":["../../cli/template.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The shapes that cross module boundaries in this CLI.
3
+ *
4
+ * Four of them carry the bugs this package has actually shipped, which is why
5
+ * they are written down rather than inferred: `App` flows from the scanner into
6
+ * both the validator and the generator, `Override` is the fleet-wide dependency
7
+ * mechanism, `PackageManifest` is the object `codegen` performs surgery on, and
8
+ * `Report` is the verdict every command exits on.
9
+ *
10
+ * Everything else in here is a small record that happens to be shared by two
11
+ * modules. Types local to one module stay in that module.
12
+ */
13
+ export {};
14
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../cli/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG"}
@@ -0,0 +1,328 @@
1
+ /**
2
+ * The build check.
3
+ *
4
+ * Structural rules first (cheap, from the filesystem), then the modules are
5
+ * actually imported so a broken export or a flow that fails to compile is
6
+ * reported here rather than as a stack trace at request time.
7
+ */
8
+ import { existsSync, readFileSync } from "node:fs";
9
+ import { join, relative } from "node:path";
10
+ import { loadAppEnv } from "./env.js";
11
+ import { compare, floorOf, installable } from "./peers.js";
12
+ const NAME_RE = /^[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?$/;
13
+ /**
14
+ * What may appear in an endpoint path segment. Deliberately narrower than what
15
+ * a filesystem allows: the segment becomes a URL path, and a file called
16
+ * `Book Call.ts` would be served at a URL nobody would guess.
17
+ */
18
+ const SEGMENT_RE = /^[a-zA-Z0-9._-]+$/;
19
+ const HTTP_METHODS = new Set(["get", "post", "put", "patch", "delete", "head", "options"]);
20
+ class Report {
21
+ root;
22
+ errors = [];
23
+ warnings = [];
24
+ constructor(root) {
25
+ this.root = root;
26
+ }
27
+ error(where, message, hint) {
28
+ this.errors.push({ where, message, hint });
29
+ }
30
+ warn(where, message, hint) {
31
+ this.warnings.push({ where, message, hint });
32
+ }
33
+ get ok() {
34
+ return this.errors.length === 0;
35
+ }
36
+ }
37
+ function rel(root, file) {
38
+ return relative(root, file) || ".";
39
+ }
40
+ /** Everything the filesystem alone can tell us. */
41
+ function checkStructure(app, report) {
42
+ const { root } = app;
43
+ if (!app.configFile) {
44
+ report.error("waniwani.config.ts", "missing app config", "create waniwani.config.ts with `export default defineApp({ name: '...' })`");
45
+ }
46
+ if (app.tools.length + app.widgets.length + app.flows.length === 0) {
47
+ report.error(".", "this app exposes nothing", "add a tool (tools/<name>.ts), a widget (widgets/<name>/), or a flow (flows/<name>.ts)");
48
+ }
49
+ for (const widget of app.widgets) {
50
+ const where = rel(root, widget.dir);
51
+ if (!widget.contract) {
52
+ report.error(where, "missing widget.ts", "every widget folder needs a widget.ts with `export default defineWidget({ ... })`");
53
+ }
54
+ if (!widget.ui) {
55
+ report.error(where, "missing ui.tsx", "every widget folder needs a ui.tsx with a default-exported React component");
56
+ }
57
+ }
58
+ // Styling is Tailwind, out of the template's `src/index.css`. Nothing imports
59
+ // an app's own CSS, so a styles.css is a file whose rules never load — and it
60
+ // fails in the worst way, by rendering an unstyled widget rather than an
61
+ // error. Naming it here costs one deletion; missing it costs a debugging
62
+ // session against a bundle that never mentions the file.
63
+ for (const file of app.strayStyles) {
64
+ report.error(rel(root, file), "app CSS is not bundled — nothing imports this file", "style with Tailwind utility classes in ui.tsx; the template's src/index.css carries the @theme tokens and the `dark` variant");
65
+ }
66
+ // A widget's folder name is its MCP tool name and its bundle entry name, so
67
+ // it has to survive both.
68
+ const named = [
69
+ ...app.tools.map((t) => ({ kind: "tool", name: t.name, where: rel(root, t.file) })),
70
+ ...app.widgets.map((w) => ({ kind: "widget", name: w.name, where: rel(root, w.dir) })),
71
+ ];
72
+ const seen = new Map();
73
+ for (const entry of named) {
74
+ if (!NAME_RE.test(entry.name)) {
75
+ report.error(entry.where, `"${entry.name}" is not a valid MCP tool name`, "use lowercase letters, digits, dashes and underscores");
76
+ }
77
+ const previous = seen.get(entry.name);
78
+ if (previous) {
79
+ report.error(entry.where, `name "${entry.name}" is already taken by ${previous.kind} ${previous.where}`, "tool and widget names share one namespace — rename one of them");
80
+ }
81
+ seen.set(entry.name, entry);
82
+ }
83
+ // An endpoint's file position is its URL, so a segment that cannot appear in
84
+ // a URL is a file served somewhere unguessable, and two files resolving to
85
+ // one path means the second mount is dead — Express answers from the first.
86
+ const paths = new Map();
87
+ // The generator names one import per endpoint, camel-cased from the path, so
88
+ // two paths that camel-case alike (`api/cal-slots.ts`, `api/cal/slots.ts`)
89
+ // would emit the same identifier twice and fail in generated code the author
90
+ // cannot open.
91
+ const identifiers = new Map();
92
+ for (const endpoint of app.endpoints) {
93
+ const where = rel(root, endpoint.file);
94
+ const identifier = endpoint.segments
95
+ .join("-")
96
+ .replace(/[^a-zA-Z0-9]/g, "")
97
+ .toLowerCase();
98
+ const clash = identifiers.get(identifier);
99
+ if (clash) {
100
+ report.error(where, `this path generates the same import name as ${clash}`, "rename one of the two — the generator derives an identifier from the path");
101
+ }
102
+ identifiers.set(identifier, where);
103
+ for (const segment of endpoint.segments) {
104
+ if (SEGMENT_RE.test(segment))
105
+ continue;
106
+ report.error(where, `"${segment}" cannot be part of a URL path`, "use letters, digits, dashes, dots and underscores — the file's position is the endpoint's path");
107
+ }
108
+ const previous = paths.get(endpoint.path);
109
+ if (previous) {
110
+ report.error(where, `${endpoint.path} is already served by ${previous}`, "two files resolve to one path — Express answers from the first, so this one never runs");
111
+ }
112
+ paths.set(endpoint.path, where);
113
+ }
114
+ // Flows point at widgets by name. Catching a typo here beats catching it
115
+ // when a user is halfway through a conversation.
116
+ const widgetNames = new Set(app.widgets.map((w) => w.name));
117
+ for (const flow of app.flows) {
118
+ const source = readFileSync(flow.file, "utf-8");
119
+ for (const match of source.matchAll(/showWidget\(\s*\{[^}]*?tool:\s*["'`]([^"'`]+)["'`]/gs)) {
120
+ const target = match[1];
121
+ if (!widgetNames.has(target)) {
122
+ report.error(rel(root, flow.file), `showWidget references the widget "${target}", which does not exist`, widgetNames.size > 0
123
+ ? `known widgets: ${[...widgetNames].join(", ")}`
124
+ : "this app has no widgets/ folder");
125
+ }
126
+ }
127
+ }
128
+ }
129
+ /** Import each module and check the shape of what it exports. */
130
+ async function checkModules(app, report) {
131
+ const { root } = app;
132
+ if (app.configFile) {
133
+ const config = await load(app.configFile, rel(root, app.configFile), report);
134
+ if (config && !config.name) {
135
+ report.error(rel(root, app.configFile), "defineApp() is missing `name`", "the MCP server name, e.g. name: 'oney-split-payment'");
136
+ }
137
+ }
138
+ for (const tool of app.tools) {
139
+ const where = rel(root, tool.file);
140
+ const def = await load(tool.file, where, report);
141
+ if (!def)
142
+ continue;
143
+ if (typeof def.run !== "function") {
144
+ report.error(where, "tool is missing run()", "export default defineTool({ ..., run })");
145
+ }
146
+ if (!def.description) {
147
+ report.error(where, "tool is missing a description", "the description is how the model decides to call it — say when to use it");
148
+ }
149
+ if (!def.title) {
150
+ report.warn(where, "tool is missing a title", "titles show up in connector UIs");
151
+ }
152
+ }
153
+ for (const widget of app.widgets) {
154
+ if (!widget.contract)
155
+ continue;
156
+ const where = rel(root, widget.contract);
157
+ const def = await load(widget.contract, where, report);
158
+ if (!def)
159
+ continue;
160
+ if (!def.data || typeof def.data !== "object") {
161
+ report.error(where, "widget is missing a `data` schema", "data is the single schema for input, output, and the component's props");
162
+ }
163
+ if (!def.description) {
164
+ report.error(where, "widget is missing a description", "the description is how the model decides to show it");
165
+ }
166
+ }
167
+ for (const endpoint of app.endpoints) {
168
+ const where = rel(root, endpoint.file);
169
+ const def = await load(endpoint.file, where, report);
170
+ if (!def)
171
+ continue;
172
+ if (typeof def.handler !== "function") {
173
+ report.error(where, "endpoint is missing handler()", "export default defineEndpoint({ handler: (req, res) => { ... } })");
174
+ }
175
+ for (const method of (def.method ? [def.method].flat() : [])) {
176
+ if (HTTP_METHODS.has(method))
177
+ continue;
178
+ report.error(where, `"${method}" is not an HTTP method`, `one of: ${[...HTTP_METHODS].join(", ")}`);
179
+ }
180
+ }
181
+ for (const flow of app.flows) {
182
+ const where = rel(root, flow.file);
183
+ const def = await load(flow.file, where, report);
184
+ if (!def)
185
+ continue;
186
+ if (!def.name || !def.config || typeof def.handler !== "function") {
187
+ report.error(where, "this is not a compiled flow", "export default createFlow({ ... }).addEdge(...).compile()");
188
+ }
189
+ }
190
+ }
191
+ /**
192
+ * App modules are TypeScript, and they import each other with the `.js`
193
+ * specifiers TypeScript's ESM output requires — `../lib/plans.js` for a file on
194
+ * disk called `plans.ts`. Node's built-in type stripping does not remap those,
195
+ * so validation registers tsx's resolver before importing anything out of the
196
+ * app folder. Bun does the remapping on its own, which is what hid this while
197
+ * the CLI still ran under bun.
198
+ *
199
+ * Registration is global to the process and idempotent here, so it happens once
200
+ * on the first load rather than at startup — `waniwani start` never validates.
201
+ */
202
+ let resolverRegistered = false;
203
+ async function registerTypeScriptResolver() {
204
+ if (resolverRegistered)
205
+ return;
206
+ resolverRegistered = true;
207
+ const { register } = await import("tsx/esm/api");
208
+ register();
209
+ }
210
+ async function load(file, where, report) {
211
+ try {
212
+ await registerTypeScriptResolver();
213
+ const module = (await import(`${file}?t=${Date.now()}`));
214
+ const def = module.default;
215
+ if (!def) {
216
+ report.error(where, "no default export", "the runtime loads this module's default export");
217
+ return null;
218
+ }
219
+ return def;
220
+ }
221
+ catch (error) {
222
+ report.error(where, "failed to load", error instanceof Error ? error.message : String(error));
223
+ return null;
224
+ }
225
+ }
226
+ /**
227
+ * The SDK version an app asked for, against the floor this package declares.
228
+ *
229
+ * `@waniwani/sdk` is a required peer (see the manifest's `//sdk` note), so the
230
+ * app owns the version and this is the one place that says what the runtime and
231
+ * the pinned template need underneath it. It reads two manifests off disk and
232
+ * fetches nothing, which is why it runs in `check` rather than waiting for the
233
+ * dependency merge in `codegen.ts` — a version that cannot work should not
234
+ * need a template download to be told so.
235
+ *
236
+ * Undeclared is not an error. npm and bun both install a required peer, and
237
+ * `codegen.ts` writes one into the generated project, so an app that never
238
+ * mentions the SDK still gets a working copy.
239
+ */
240
+ function checkPeers(app, report) {
241
+ let manifest;
242
+ try {
243
+ manifest = JSON.parse(readFileSync(join(app.root, "package.json"), "utf-8"));
244
+ }
245
+ catch {
246
+ // No manifest, or an unparseable one. Both are `init`'s business, and
247
+ // neither is improved by a second error about a dependency inside it.
248
+ return;
249
+ }
250
+ const name = "@waniwani/sdk";
251
+ const spec = manifest.dependencies?.[name] ?? manifest.devDependencies?.[name];
252
+ if (spec == null) {
253
+ return;
254
+ }
255
+ const floor = floorOf(name);
256
+ const suggestion = installable(name);
257
+ switch (compare(spec, name)) {
258
+ case "below":
259
+ report.error("package.json", `${name} ${spec} cannot reach ${floor}, which this kit needs`, `no version that range allows will work: below the floor the SDK declares a @modelcontextprotocol/ext-apps peer that conflicts with the framework's, and npm refuses the tree. Set ${name} to ${suggestion}.`);
260
+ break;
261
+ case "prerelease":
262
+ report.warn("package.json", `${name} ${spec} is a prerelease, and ${floor} does not accept one`, `npm and bun both exclude a prerelease from a range that names none, so the install warns and the tree may not be what this spec says. Deliberate is fine; ${suggestion} is the released floor.`);
263
+ break;
264
+ case "reachable":
265
+ report.warn("package.json", `${name} ${spec} also allows versions below ${floor}`, `a fresh install resolves above the floor, and a lockfile written before it moved can hold this tree below it. ${suggestion} says the floor out loud.`);
266
+ break;
267
+ default:
268
+ // "ok", and "unknown" for an expression this cannot parse — a
269
+ // workspace protocol or a git URL, where the version is not in the
270
+ // string and guessing at it would be a false alarm either way.
271
+ break;
272
+ }
273
+ }
274
+ /**
275
+ * A `vercel.json` at the app root, when the kit's build output needs none.
276
+ *
277
+ * A deploy config that names a build command is the dangerous half: it overrides
278
+ * the one Vercel would pick, and the command it names is a build this kit no
279
+ * longer performs. The staging command is called out by name because it is what
280
+ * a build wrote into every app repo before the tree moved to the app root, and
281
+ * it now deletes the tree it used to place: `waniwani build` stages
282
+ * `.vercel/output`, then the `rm -rf` in that command removes it and the `cp`
283
+ * fails on a source that is gone.
284
+ *
285
+ * Anything else in the file is the app's own business — a `maxDuration`, a
286
+ * region, a cron — so this warns and never fails.
287
+ */
288
+ function checkDeployConfig(app, report) {
289
+ const file = join(app.root, "vercel.json");
290
+ if (!existsSync(file))
291
+ return;
292
+ let config;
293
+ try {
294
+ config = JSON.parse(readFileSync(file, "utf-8"));
295
+ }
296
+ catch {
297
+ report.warn("vercel.json", "is not valid JSON", "Vercel fails the build before it starts.");
298
+ return;
299
+ }
300
+ const command = typeof config.buildCommand === "string" ? config.buildCommand : "";
301
+ if (command.includes(".waniwani/.vercel/output")) {
302
+ report.warn("vercel.json", "stages the build output itself, and the build already does", "`waniwani build` leaves the tree at `.vercel/output`, so this command's `rm -rf` deletes it and its `cp` fails on a source that no longer exists. Drop the field: `framework: null` is the only key this file needs.");
303
+ return;
304
+ }
305
+ if (command) {
306
+ report.warn("vercel.json", `overrides the build command with ${JSON.stringify(command)}`, "Vercel runs this instead of the `build` script in package.json, which is what runs `waniwani build`. Drop the field unless the app genuinely builds differently.");
307
+ }
308
+ if (Array.isArray(config.routes)) {
309
+ report.warn("vercel.json", "carries a `routes` table", "the build's own routing config already sends `/api/*` at the server, and a top-level `routes` entry replaces Vercel's whole routing phase rather than adding to it.");
310
+ }
311
+ }
312
+ export async function validateApp(app) {
313
+ // The check imports every server-safe module for real, and a module that
314
+ // builds a client at import time reads the environment while doing it. An app
315
+ // that runs fine would otherwise fail its own build check over a variable
316
+ // sitting in the file next to it.
317
+ loadAppEnv(app.root);
318
+ const report = new Report(app.root);
319
+ checkStructure(app, report);
320
+ checkPeers(app, report);
321
+ checkDeployConfig(app, report);
322
+ // Importing broken modules produces noise on top of structural errors.
323
+ if (report.ok) {
324
+ await checkModules(app, report);
325
+ }
326
+ return report;
327
+ }
328
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../cli/validate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * The Vercel boundary.
3
+ *
4
+ * Vercel adopts a build's output when it finds a Build Output API tree at
5
+ * `.vercel/output` in the project's root directory, and nowhere else. The
6
+ * framework emits that tree inside the generated project, which is gitignored
7
+ * and absent from a clone, so the last thing a build does is move it up to the
8
+ * app root — the one path Vercel reads.
9
+ *
10
+ * That move is what removes the need for a `vercel.json` in an app repo. With
11
+ * the tree where Vercel looks for it, a git-connected project needs no deploy
12
+ * config at all: the `Other` preset runs the `build` script from `package.json`,
13
+ * which is `waniwani build`, and the output is adopted as built. Every decision
14
+ * a deploy config would carry is taken here instead, once, for every app on
15
+ * this kit.
16
+ */
17
+ import { cpSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
18
+ import { dirname, join } from "node:path";
19
+ /** Where the framework writes its tree, and where Vercel reads one. */
20
+ const OUTPUT = join(".vercel", "output");
21
+ /**
22
+ * Every request under `/api` reaches the server this kit built.
23
+ *
24
+ * Vercel reserves a root `api/` directory and compiles every file under one
25
+ * into a serverless function of its own, which for an app folder means one
26
+ * broken function per endpoint — `defineEndpoint({ ... })` is an object, not a
27
+ * Vercel handler — sitting in the filesystem layer ahead of the server that
28
+ * actually serves them. The reservation cannot be waived: the file list is read
29
+ * before the build command runs, so a build that deletes the directory fails
30
+ * with `File not found`, and `outputDirectory` does not suppress the builder.
31
+ *
32
+ * What it can be beaten by is precedence. This route goes in ahead of the
33
+ * `filesystem` handler, which is where those functions sit, so `/api/*` never
34
+ * reaches them.
35
+ */
36
+ const API_ROUTE = "/api(/.*)?";
37
+ /**
38
+ * Move the framework's Build Output tree from the generated project up to the
39
+ * app root, and route `/api/*` at the server on the way.
40
+ *
41
+ * @returns the directory Vercel will read, or null when the build emitted no tree
42
+ */
43
+ export function stageBuildOutput(generatedDir, appRoot) {
44
+ const from = join(generatedDir, OUTPUT);
45
+ if (!existsSync(from))
46
+ return null;
47
+ const to = join(appRoot, OUTPUT);
48
+ if (from !== to) {
49
+ // `.vercel/` also holds the CLI's project link, so only the output half is
50
+ // cleared. Rename over a copy: same filesystem, and it leaves nothing
51
+ // behind to fall out of date.
52
+ rmSync(to, { recursive: true, force: true });
53
+ mkdirSync(dirname(to), { recursive: true });
54
+ try {
55
+ renameSync(from, to);
56
+ }
57
+ catch {
58
+ cpSync(from, to, { recursive: true });
59
+ rmSync(from, { recursive: true, force: true });
60
+ }
61
+ // The framework recreates its own `.vercel/` on every build, so what the
62
+ // move leaves behind is an empty directory that means nothing.
63
+ rmSync(dirname(from), { recursive: true, force: true });
64
+ }
65
+ routeApiAtTheServer(to);
66
+ return to;
67
+ }
68
+ /**
69
+ * Insert the `/api` route ahead of the filesystem handler in the tree's own
70
+ * routing table.
71
+ *
72
+ * The destination is read from the config rather than named here: the framework
73
+ * decides what its function is called, and a route pointing at a name it has
74
+ * since changed would black-hole every request.
75
+ */
76
+ function routeApiAtTheServer(outputDir) {
77
+ const file = join(outputDir, "config.json");
78
+ if (!existsSync(file))
79
+ return;
80
+ let config;
81
+ try {
82
+ config = JSON.parse(readFileSync(file, "utf-8"));
83
+ }
84
+ catch {
85
+ return;
86
+ }
87
+ const routes = config.routes;
88
+ if (!Array.isArray(routes))
89
+ return;
90
+ if (routes.some((route) => route.src === API_ROUTE))
91
+ return;
92
+ const filesystem = routes.findIndex((route) => route.handle === "filesystem");
93
+ if (filesystem === -1)
94
+ return;
95
+ // The catch-all below the filesystem handler is the server. Anything under
96
+ // `/api` that the app did not build is already going there.
97
+ const server = routes.slice(filesystem).find((route) => route.dest && !route.handle);
98
+ if (!server?.dest)
99
+ return;
100
+ routes.splice(filesystem, 0, { src: API_ROUTE, dest: server.dest });
101
+ writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`);
102
+ }
103
+ //# sourceMappingURL=vercel.js.map