@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.
- package/README.md +67 -35
- package/dist/cli/codegen.js +1105 -0
- package/dist/cli/codegen.js.map +1 -0
- package/{cli/env.mjs → dist/cli/env.js} +5 -6
- package/dist/cli/env.js.map +1 -0
- package/{cli/framework.mjs → dist/cli/framework.js} +112 -115
- package/dist/cli/framework.js.map +1 -0
- package/dist/cli/index.js +378 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.js +642 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/log.js +156 -0
- package/dist/cli/log.js.map +1 -0
- package/dist/cli/manifest.js +57 -0
- package/dist/cli/manifest.js.map +1 -0
- package/{cli/peers.mjs → dist/cli/peers.js} +77 -88
- package/dist/cli/peers.js.map +1 -0
- package/dist/cli/scan.js +100 -0
- package/dist/cli/scan.js.map +1 -0
- package/dist/cli/template.js +173 -0
- package/dist/cli/template.js.map +1 -0
- package/dist/cli/types.js +14 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/cli/validate.js +328 -0
- package/dist/cli/validate.js.map +1 -0
- package/dist/cli/vercel.js +103 -0
- package/dist/cli/vercel.js.map +1 -0
- package/dist/server.d.ts +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +0 -1
- package/dist/server.js.map +1 -1
- package/dist/web.d.ts +8 -7
- package/dist/web.d.ts.map +1 -1
- package/dist/web.js +7 -6
- package/dist/web.js.map +1 -1
- package/package.json +13 -9
- package/src/server.ts +7 -9
- package/src/web.tsx +12 -13
- package/cli/codegen.mjs +0 -1267
- package/cli/index.mjs +0 -409
- package/cli/init.mjs +0 -575
- package/cli/log.mjs +0 -178
- package/cli/scan.mjs +0 -112
- package/cli/template.mjs +0 -190
- package/cli/validate.mjs +0 -391
package/dist/cli/init.js
ADDED
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `waniwani init`, the first command anyone runs.
|
|
3
|
+
*
|
|
4
|
+
* What it writes is an app folder that already passes `waniwani check` and
|
|
5
|
+
* already renders something: a config, a tool, and the widget that displays what
|
|
6
|
+
* the tool returned. The point of scaffolding a working pair instead of an empty
|
|
7
|
+
* folder is that the tool-to-widget hand-off is the one piece of this framework
|
|
8
|
+
* nobody guesses correctly from the type signatures.
|
|
9
|
+
*
|
|
10
|
+
* waniwani init my-app create my-app/ and scaffold in it
|
|
11
|
+
* waniwani init . scaffold in the current directory
|
|
12
|
+
* waniwani init ask for a name, and put the app where the
|
|
13
|
+
* answer says: a name of its own creates
|
|
14
|
+
* ./<name>/, the offered default (the current
|
|
15
|
+
* folder's name) scaffolds in place
|
|
16
|
+
*
|
|
17
|
+
* --name <name> the MCP server name, default the directory name
|
|
18
|
+
* --host <host> where it deploys: vercel, alpic, container, none
|
|
19
|
+
* --minimal config and one tool, no widget
|
|
20
|
+
* --yes take every default, ask nothing
|
|
21
|
+
* --no-install skip the dependency install
|
|
22
|
+
* --force overwrite app files that are already there
|
|
23
|
+
*
|
|
24
|
+
* Running it inside a repo that already has files is expected and supported.
|
|
25
|
+
* A `package.json` is merged rather than replaced, a `.gitignore` gains the
|
|
26
|
+
* lines it lacks, and a `README.md` or `.env.example` that exists is left
|
|
27
|
+
* alone. Only the app's own source files count as a collision, and those stop
|
|
28
|
+
* the command until `--force` says otherwise.
|
|
29
|
+
*/
|
|
30
|
+
import { spawn } from "node:child_process";
|
|
31
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
32
|
+
import { basename, dirname, join, relative } from "node:path";
|
|
33
|
+
import { createInterface } from "node:readline/promises";
|
|
34
|
+
import { bold, dim, green, red, yellow } from "./log.js";
|
|
35
|
+
import { PACKAGE_VERSION } from "./manifest.js";
|
|
36
|
+
import { installable } from "./peers.js";
|
|
37
|
+
/** A directory name as an MCP server name: `My App` becomes `my-app`. */
|
|
38
|
+
function slugify(input) {
|
|
39
|
+
const slug = input
|
|
40
|
+
.trim()
|
|
41
|
+
.toLowerCase()
|
|
42
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
43
|
+
.replace(/^-+|-+$/g, "");
|
|
44
|
+
return slug || "my-app";
|
|
45
|
+
}
|
|
46
|
+
/** `my-app` becomes `My app`, for the human-facing title. */
|
|
47
|
+
function titleize(slug) {
|
|
48
|
+
const words = slug.replace(/[-_]+/g, " ");
|
|
49
|
+
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A title, made safe to drop into the generated TypeScript and Markdown.
|
|
53
|
+
* Backticks, quotes, backslashes and `$` are the characters that would end a
|
|
54
|
+
* string or a template literal early, and a product name needs none of them.
|
|
55
|
+
*/
|
|
56
|
+
function cleanTitle(input) {
|
|
57
|
+
return input
|
|
58
|
+
.replace(/[`"'\\$]/g, "")
|
|
59
|
+
.replace(/\s+/g, " ")
|
|
60
|
+
.trim();
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* What a new app depends on.
|
|
64
|
+
*
|
|
65
|
+
* Every version is read off this package's own manifest: `@waniwani/kit` at the
|
|
66
|
+
* version of the CLI doing the scaffolding, and the four peers at the floors
|
|
67
|
+
* this package declares, capped by `installable` so a floor does not install
|
|
68
|
+
* the next major on the day it lands. A scaffold that wrote its own numbers
|
|
69
|
+
* here would be the one file in the folder that can be wrong the day it is
|
|
70
|
+
* created.
|
|
71
|
+
*
|
|
72
|
+
* `@waniwani/sdk` is written out even though a required peer is auto-installed
|
|
73
|
+
* without it, because the app imports it directly — `flows/*.ts` calls
|
|
74
|
+
* `createFlow` — and a package you import belongs in your own manifest rather
|
|
75
|
+
* than arriving because something else asked for it.
|
|
76
|
+
*/
|
|
77
|
+
function dependencies() {
|
|
78
|
+
return {
|
|
79
|
+
"@waniwani/kit": `^${PACKAGE_VERSION}`,
|
|
80
|
+
"@waniwani/sdk": installable("@waniwani/sdk"),
|
|
81
|
+
react: installable("react"),
|
|
82
|
+
"react-dom": installable("react-dom"),
|
|
83
|
+
zod: installable("zod"),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
// ------------------------------------------------------------ scaffold content
|
|
87
|
+
/**
|
|
88
|
+
* The tool name and the widget name the scaffold uses. They appear in five
|
|
89
|
+
* files, including inside prose the model reads, so they are named once.
|
|
90
|
+
*/
|
|
91
|
+
const TOOL = "search-products";
|
|
92
|
+
const WIDGET = "product-list";
|
|
93
|
+
function packageJson(app) {
|
|
94
|
+
return `${JSON.stringify({
|
|
95
|
+
name: app.name,
|
|
96
|
+
private: true,
|
|
97
|
+
type: "module",
|
|
98
|
+
scripts: {
|
|
99
|
+
check: "waniwani check",
|
|
100
|
+
dev: "waniwani dev",
|
|
101
|
+
build: "waniwani build",
|
|
102
|
+
start: "waniwani start",
|
|
103
|
+
},
|
|
104
|
+
dependencies: dependencies(),
|
|
105
|
+
}, null, 2)}\n`;
|
|
106
|
+
}
|
|
107
|
+
function appConfig(app) {
|
|
108
|
+
return `import { defineApp } from "@waniwani/kit";
|
|
109
|
+
|
|
110
|
+
export default defineApp({
|
|
111
|
+
// The MCP server name. Hosts show \`title\` to humans and use this one as the id.
|
|
112
|
+
name: ${JSON.stringify(app.name)},
|
|
113
|
+
title: ${JSON.stringify(app.title)},
|
|
114
|
+
});
|
|
115
|
+
`;
|
|
116
|
+
}
|
|
117
|
+
function tool() {
|
|
118
|
+
return `import { defineTool } from "@waniwani/kit";
|
|
119
|
+
import { z } from "zod";
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The filename is the tool name, so this file is \`${TOOL}\`. Rename the
|
|
123
|
+
* file and the tool renames with it.
|
|
124
|
+
*
|
|
125
|
+
* Swap CATALOGUE for whatever answers the question for real: a fetch, a
|
|
126
|
+
* database, an internal API. \`run\` may be async.
|
|
127
|
+
*/
|
|
128
|
+
const CATALOGUE = [
|
|
129
|
+
{ id: "aeron", name: "Aeron chair", price: 1290, blurb: "Mesh task chair, twelve-year warranty." },
|
|
130
|
+
{ id: "sayl", name: "Sayl chair", price: 545, blurb: "Suspension back, the light one." },
|
|
131
|
+
{ id: "nevi", name: "Nevi sit-stand desk", price: 890, blurb: "Electric, 70 to 120 cm." },
|
|
132
|
+
{ id: "ollin", name: "Ollin monitor arm", price: 235, blurb: "Single arm, holds up to 9 kg." },
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
export default defineTool({
|
|
136
|
+
// Shown to humans in connector UIs.
|
|
137
|
+
title: "Search the catalogue",
|
|
138
|
+
// The only thing the model reads before deciding to call this, so it says
|
|
139
|
+
// when to call it and what not to do instead.
|
|
140
|
+
description:
|
|
141
|
+
"Find products matching what the shopper asked for. Call this before naming any product or quoting any price, and never answer either from memory. Pass the shopper's own words as the query.",
|
|
142
|
+
// Zod shapes, written as plain objects instead of z.object({ ... }).
|
|
143
|
+
input: {
|
|
144
|
+
query: z.string().describe("What the shopper asked for, in their words, e.g. 'a chair under 600'."),
|
|
145
|
+
},
|
|
146
|
+
output: {
|
|
147
|
+
products: z.array(
|
|
148
|
+
z.object({
|
|
149
|
+
id: z.string(),
|
|
150
|
+
name: z.string(),
|
|
151
|
+
price: z.number().describe("Price in euros."),
|
|
152
|
+
blurb: z.string(),
|
|
153
|
+
}),
|
|
154
|
+
),
|
|
155
|
+
},
|
|
156
|
+
// Becomes MCP annotations. This tool reads and does nothing else.
|
|
157
|
+
hints: { readOnly: true },
|
|
158
|
+
run: ({ query }) => {
|
|
159
|
+
const terms = query.toLowerCase().split(/\\s+/).filter(Boolean);
|
|
160
|
+
const matched = CATALOGUE.filter((product) =>
|
|
161
|
+
terms.some((term) => \`\${product.name} \${product.blurb}\`.toLowerCase().includes(term)),
|
|
162
|
+
);
|
|
163
|
+
// The whole catalogue when nothing matched, so an early conversation has
|
|
164
|
+
// something on screen while you are still wiring this up.
|
|
165
|
+
return { products: matched.length > 0 ? matched : CATALOGUE };
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
`;
|
|
169
|
+
}
|
|
170
|
+
function widgetContract() {
|
|
171
|
+
return `import { defineWidget } from "@waniwani/kit";
|
|
172
|
+
import { z } from "zod";
|
|
173
|
+
|
|
174
|
+
const product = z.object({
|
|
175
|
+
id: z.string(),
|
|
176
|
+
name: z.string(),
|
|
177
|
+
price: z.number().describe("Price in euros."),
|
|
178
|
+
blurb: z.string().describe("One line about the product."),
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* The folder name is the tool name, so this widget is \`${WIDGET}\`.
|
|
183
|
+
*
|
|
184
|
+
* \`data\` is one schema doing three jobs: the tool's input, its structured
|
|
185
|
+
* output, and the props \`useWidget()\` hands ui.tsx. Server and UI cannot drift.
|
|
186
|
+
*
|
|
187
|
+
* This file is imported by the server and by the browser bundle, so it stays
|
|
188
|
+
* free of React and CSS. The component sits next to it in ui.tsx.
|
|
189
|
+
*/
|
|
190
|
+
export default defineWidget({
|
|
191
|
+
title: "Product list",
|
|
192
|
+
description:
|
|
193
|
+
"Show the product cards. Call this once ${TOOL} has returned products, passing them through unmodified. Frame it in one short sentence before calling, e.g. \\"Here's what fits.\\" The widget renders every name and price itself, so do NOT list them in text.",
|
|
194
|
+
data: {
|
|
195
|
+
query: z.string().describe("What the shopper asked for. Shown as the heading."),
|
|
196
|
+
products: z.array(product).describe("Products returned by ${TOOL}, unmodified."),
|
|
197
|
+
},
|
|
198
|
+
hints: { readOnly: true },
|
|
199
|
+
// Text handed to the model alongside the rendered widget. Use it to say what
|
|
200
|
+
// the model should not repeat, and what it should wait for.
|
|
201
|
+
llmText: (data) =>
|
|
202
|
+
\`The product list is on screen with \${data.products.length} products. It renders every name and price itself, so do NOT repeat them in text.
|
|
203
|
+
|
|
204
|
+
Wait for the shopper to pick one, then answer about that product.\`,
|
|
205
|
+
});
|
|
206
|
+
`;
|
|
207
|
+
}
|
|
208
|
+
function widgetUi() {
|
|
209
|
+
return `import { useLayout, useSendFollowUpMessage, useWidget } from "@waniwani/kit/web";
|
|
210
|
+
import widget from "./widget.js";
|
|
211
|
+
|
|
212
|
+
const euros = (value: number) =>
|
|
213
|
+
new Intl.NumberFormat("en-IE", { style: "currency", currency: "EUR" }).format(value);
|
|
214
|
+
|
|
215
|
+
export default function ProductList() {
|
|
216
|
+
// Typed off the widget's own \`data\` schema. No generated helpers, no server
|
|
217
|
+
// type import.
|
|
218
|
+
const { data } = useWidget(widget);
|
|
219
|
+
const sendFollowUp = useSendFollowUpMessage();
|
|
220
|
+
|
|
221
|
+
// The host hands the colour scheme to the view instead of to the browser, so
|
|
222
|
+
// \`prefers-color-scheme\` is the wrong signal and Tailwind's \`dark:\` variant is
|
|
223
|
+
// wired to a \`dark\` class (see the template's src/index.css). Every widget puts
|
|
224
|
+
// that class on its own root: a view is its own bundle in its own iframe, so
|
|
225
|
+
// there is no shared ancestor to hang it off.
|
|
226
|
+
const { theme } = useLayout();
|
|
227
|
+
const root = theme === "dark" ? "dark" : "";
|
|
228
|
+
|
|
229
|
+
// \`data\` arrives as soon as the host has the tool input, which on most hosts is
|
|
230
|
+
// before the server has responded. Render optimistically.
|
|
231
|
+
if (!data) {
|
|
232
|
+
return <div className={\`\${root} font-sans text-sm text-slate-500\`}>Loading…</div>;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return (
|
|
236
|
+
<div className={\`\${root} font-sans text-slate-900 dark:text-slate-100\`}>
|
|
237
|
+
<h1 className="mb-3 text-lg font-semibold tracking-tight">{data.query}</h1>
|
|
238
|
+
|
|
239
|
+
<div className="grid grid-cols-[repeat(auto-fit,minmax(180px,1fr))] gap-2.5">
|
|
240
|
+
{data.products.map((product) => (
|
|
241
|
+
<button
|
|
242
|
+
type="button"
|
|
243
|
+
key={product.id}
|
|
244
|
+
// A click becomes a message from the shopper, which is what moves
|
|
245
|
+
// the conversation on.
|
|
246
|
+
onClick={() => sendFollowUp(\`Tell me more about the \${product.name}.\`)}
|
|
247
|
+
className="flex cursor-pointer flex-col items-start gap-1 rounded-2xl border-[1.5px] border-slate-200 bg-white p-3.5 text-left transition duration-150 hover:-translate-y-px hover:border-slate-400 hover:shadow-lg hover:shadow-slate-900/10 dark:border-slate-700 dark:bg-slate-900 dark:hover:border-slate-500"
|
|
248
|
+
// What the model reads in place of the pixels.
|
|
249
|
+
data-llm={\`\${product.name}, \${euros(product.price)}: \${product.blurb}\`}
|
|
250
|
+
>
|
|
251
|
+
<span className="text-[22px] font-bold tracking-tight">{euros(product.price)}</span>
|
|
252
|
+
<span className="font-semibold">{product.name}</span>
|
|
253
|
+
<span className="text-[13px] text-slate-500 dark:text-slate-400">{product.blurb}</span>
|
|
254
|
+
</button>
|
|
255
|
+
))}
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
`;
|
|
261
|
+
}
|
|
262
|
+
function envExample() {
|
|
263
|
+
return `# Optional. Without it the app still runs: flows use MemoryKvStore and
|
|
264
|
+
# withWaniwani degrades to a no-op. With it, flow state is hosted and tracking
|
|
265
|
+
# reaches app.waniwani.ai.
|
|
266
|
+
WANIWANI_API_KEY=
|
|
267
|
+
WANIWANI_PUBLIC_KEY=
|
|
268
|
+
`;
|
|
269
|
+
}
|
|
270
|
+
function gitignore() {
|
|
271
|
+
return `node_modules/
|
|
272
|
+
.waniwani/
|
|
273
|
+
.env
|
|
274
|
+
.env.local
|
|
275
|
+
`;
|
|
276
|
+
}
|
|
277
|
+
function readme(app) {
|
|
278
|
+
return `# ${app.title}
|
|
279
|
+
|
|
280
|
+
An MCP app built with [@waniwani/kit](https://www.npmjs.com/package/@waniwani/kit).
|
|
281
|
+
You own the folders below. The server, the transport, the bundling and the deploy
|
|
282
|
+
files are the kit's.
|
|
283
|
+
|
|
284
|
+
\`\`\`
|
|
285
|
+
waniwani.config.ts the app's name and title, plus optional instructions
|
|
286
|
+
tools/*.ts one file per tool; the filename is the tool name
|
|
287
|
+
widgets/<name>/ widget.ts for the contract, ui.tsx for the component
|
|
288
|
+
flows/*.ts multi-step conversations, from @waniwani/sdk
|
|
289
|
+
\`\`\`
|
|
290
|
+
|
|
291
|
+
## Commands
|
|
292
|
+
|
|
293
|
+
\`\`\`bash
|
|
294
|
+
npm run dev # dev server, regenerating on every change
|
|
295
|
+
npm run check # validate the folder without building
|
|
296
|
+
npm run build # production build
|
|
297
|
+
npm run start # run the production build
|
|
298
|
+
\`\`\`
|
|
299
|
+
|
|
300
|
+
\`.waniwani/\` is build output, the way \`.next/\` is. Every command regenerates it
|
|
301
|
+
and it stays out of git.
|
|
302
|
+
`;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* The deploy targets `init` offers.
|
|
306
|
+
*
|
|
307
|
+
* Only Vercel needs a file in the app repo, and only for one reason: the
|
|
308
|
+
* framework preset is a project setting that Vercel resolves before the build
|
|
309
|
+
* command runs, so nothing the build emits can correct a project whose preset
|
|
310
|
+
* says Next.js or Express. `framework: null` selects `Other`, which is the
|
|
311
|
+
* preset that runs the `build` script and adopts the Build Output tree the kit
|
|
312
|
+
* leaves at `.vercel/output`.
|
|
313
|
+
*
|
|
314
|
+
* Nothing else belongs in that file. A `buildCommand` would restate the `build`
|
|
315
|
+
* script, and a `routes` table would duplicate the routing config the build
|
|
316
|
+
* writes — both would then go stale against a kit that moved on. This one key is
|
|
317
|
+
* a fact about the project rather than about the build, so it never changes.
|
|
318
|
+
*
|
|
319
|
+
* Alpic and a container image both read their config from the generated project,
|
|
320
|
+
* which the build regenerates, so neither leaves anything tracked behind.
|
|
321
|
+
*/
|
|
322
|
+
const HOSTS = [
|
|
323
|
+
{
|
|
324
|
+
id: "vercel",
|
|
325
|
+
label: "Vercel",
|
|
326
|
+
note: "git push, or `vercel deploy --prebuilt`",
|
|
327
|
+
deploy: ["git push", "vercel deploy --prebuilt # or upload a local build"],
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
id: "alpic",
|
|
331
|
+
label: "Alpic",
|
|
332
|
+
note: "alpic.json comes from the build",
|
|
333
|
+
deploy: ["waniwani build", "cd .waniwani && alpic deploy"],
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
id: "container",
|
|
337
|
+
label: "Docker or self-hosted",
|
|
338
|
+
note: "Dockerfile comes from the build",
|
|
339
|
+
deploy: ["waniwani build", "docker build .waniwani"],
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
id: "none",
|
|
343
|
+
label: "Not yet",
|
|
344
|
+
note: "nothing written, add it later",
|
|
345
|
+
deploy: [],
|
|
346
|
+
},
|
|
347
|
+
];
|
|
348
|
+
export function hostById(id) {
|
|
349
|
+
return HOSTS.find((host) => host.id === id) ?? HOSTS[0];
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* What a git-connected Vercel project needs from the repo, and nothing more.
|
|
353
|
+
* See HOSTS for why this is one key.
|
|
354
|
+
*/
|
|
355
|
+
function vercelJson() {
|
|
356
|
+
return `${JSON.stringify({
|
|
357
|
+
$schema: "https://openapi.vercel.sh/vercel.json",
|
|
358
|
+
framework: null,
|
|
359
|
+
}, null, 2)}\n`;
|
|
360
|
+
}
|
|
361
|
+
function scaffold(app, { minimal, host }) {
|
|
362
|
+
const files = [
|
|
363
|
+
{
|
|
364
|
+
path: "package.json",
|
|
365
|
+
contents: packageJson(app),
|
|
366
|
+
whenPresent: "merge",
|
|
367
|
+
merge: mergePackageJson,
|
|
368
|
+
},
|
|
369
|
+
{ path: ".gitignore", contents: gitignore(), whenPresent: "merge", merge: mergeGitignore },
|
|
370
|
+
{ path: ".env.example", contents: envExample(), whenPresent: "keep" },
|
|
371
|
+
{ path: "README.md", contents: readme(app), whenPresent: "keep" },
|
|
372
|
+
{ path: "waniwani.config.ts", contents: appConfig(app) },
|
|
373
|
+
{ path: `tools/${TOOL}.ts`, contents: tool() },
|
|
374
|
+
];
|
|
375
|
+
if (!minimal) {
|
|
376
|
+
files.push({ path: `widgets/${WIDGET}/widget.ts`, contents: widgetContract() }, { path: `widgets/${WIDGET}/ui.tsx`, contents: widgetUi() });
|
|
377
|
+
}
|
|
378
|
+
// A repo that already answers Vercel its own way keeps that answer: the file
|
|
379
|
+
// may carry a region, a cron, or a `maxDuration` this has no business
|
|
380
|
+
// replacing. `waniwani check` reads it and names the keys that fight the build.
|
|
381
|
+
if (host === "vercel") {
|
|
382
|
+
files.push({ path: "vercel.json", contents: vercelJson(), whenPresent: "keep" });
|
|
383
|
+
}
|
|
384
|
+
return files;
|
|
385
|
+
}
|
|
386
|
+
// -------------------------------------------------------------------- merging
|
|
387
|
+
/**
|
|
388
|
+
* Add the scripts and dependencies an app needs to a manifest that is already
|
|
389
|
+
* there, and touch nothing else. A key the repo already declares is the repo's
|
|
390
|
+
* decision, including a `dev` script that runs something other than this CLI.
|
|
391
|
+
*
|
|
392
|
+
* @returns the keys added, for the CLI to report
|
|
393
|
+
*/
|
|
394
|
+
function mergePackageJson(file, contents) {
|
|
395
|
+
const existing = JSON.parse(readFileSync(file, "utf-8"));
|
|
396
|
+
const generated = JSON.parse(contents);
|
|
397
|
+
const added = [];
|
|
398
|
+
const fold = (section) => {
|
|
399
|
+
const merged = { ...existing[section] };
|
|
400
|
+
for (const [key, value] of Object.entries(generated[section] ?? {})) {
|
|
401
|
+
if (merged[key])
|
|
402
|
+
continue;
|
|
403
|
+
merged[key] = value;
|
|
404
|
+
added.push(`${section}.${key}`);
|
|
405
|
+
}
|
|
406
|
+
return merged;
|
|
407
|
+
};
|
|
408
|
+
const next = {
|
|
409
|
+
...existing,
|
|
410
|
+
// App modules are ESM. A repo that says commonjs is warned about instead of
|
|
411
|
+
// rewritten, since flipping it changes how the rest of that repo loads.
|
|
412
|
+
type: existing.type ?? "module",
|
|
413
|
+
scripts: fold("scripts"),
|
|
414
|
+
dependencies: fold("dependencies"),
|
|
415
|
+
};
|
|
416
|
+
if (!existing.type)
|
|
417
|
+
added.push("type");
|
|
418
|
+
if (added.length > 0) {
|
|
419
|
+
writeFileSync(file, `${JSON.stringify(next, null, 2)}\n`);
|
|
420
|
+
}
|
|
421
|
+
return added;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Add the lines the app does not ignore yet, leaving every line it wrote alone.
|
|
425
|
+
* A trailing slash is not part of the comparison, so a repo ignoring
|
|
426
|
+
* `node_modules` does not gain `node_modules/` next to it.
|
|
427
|
+
*
|
|
428
|
+
* @returns the lines added, for the CLI to report
|
|
429
|
+
*/
|
|
430
|
+
function mergeGitignore(file, contents) {
|
|
431
|
+
const existing = readFileSync(file, "utf-8");
|
|
432
|
+
const bare = (line) => line.trim().replace(/\/$/, "");
|
|
433
|
+
const known = new Set(existing.split("\n").map(bare));
|
|
434
|
+
const additions = contents
|
|
435
|
+
.split("\n")
|
|
436
|
+
.filter((line) => line.trim() && !line.trim().startsWith("#") && !known.has(bare(line)));
|
|
437
|
+
if (additions.length === 0)
|
|
438
|
+
return [];
|
|
439
|
+
const prefix = !existing || existing.endsWith("\n") ? "" : "\n";
|
|
440
|
+
writeFileSync(file, `${existing}${prefix}${additions.join("\n")}\n`);
|
|
441
|
+
return additions;
|
|
442
|
+
}
|
|
443
|
+
// ------------------------------------------------------------------- the shell
|
|
444
|
+
function write(file, contents) {
|
|
445
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
446
|
+
writeFileSync(file, contents);
|
|
447
|
+
}
|
|
448
|
+
/** One question, with the default in parentheses and Enter taking it. */
|
|
449
|
+
async function ask(question, fallback) {
|
|
450
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
451
|
+
try {
|
|
452
|
+
const answer = await rl.question(`${question} ${dim(`(${fallback})`)} `);
|
|
453
|
+
return answer.trim() || fallback;
|
|
454
|
+
}
|
|
455
|
+
finally {
|
|
456
|
+
rl.close();
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* One question with a numbered list, and Enter taking the first option.
|
|
461
|
+
*
|
|
462
|
+
* Numbered rather than arrow-driven: this CLI writes plain lines everywhere else,
|
|
463
|
+
* and a raw-mode menu is the one piece of terminal state a `waniwani init` piped
|
|
464
|
+
* into something would leave behind.
|
|
465
|
+
*/
|
|
466
|
+
async function choose(question, options) {
|
|
467
|
+
const first = options[0];
|
|
468
|
+
console.log(`\n${bold(question)}`);
|
|
469
|
+
for (const [index, option] of options.entries()) {
|
|
470
|
+
console.log(` ${dim(`${index + 1}`)} ${option.label} ${dim(`— ${option.note}`)}`);
|
|
471
|
+
}
|
|
472
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
473
|
+
try {
|
|
474
|
+
const answer = (await rl.question(`\nPick one ${dim(`(1, ${first.label})`)} `)).trim();
|
|
475
|
+
if (!answer)
|
|
476
|
+
return first.id;
|
|
477
|
+
// A number picks by position; anything else is matched against the labels,
|
|
478
|
+
// so typing `vercel` works as well as typing `1`.
|
|
479
|
+
const picked = Number(answer);
|
|
480
|
+
if (Number.isInteger(picked) && picked >= 1 && picked <= options.length) {
|
|
481
|
+
return options[picked - 1].id;
|
|
482
|
+
}
|
|
483
|
+
const lowered = answer.toLowerCase();
|
|
484
|
+
const named = options.find((option) => option.id === lowered || option.label.toLowerCase().startsWith(lowered));
|
|
485
|
+
return named?.id ?? first.id;
|
|
486
|
+
}
|
|
487
|
+
finally {
|
|
488
|
+
rl.close();
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* The package manager that invoked this command, which npm, pnpm, yarn and bun
|
|
493
|
+
* all announce in `npm_config_user_agent`. Nothing to detect from lockfiles: a
|
|
494
|
+
* new folder has none, and a `waniwani init` inside an existing repo was still
|
|
495
|
+
* typed with one of the four.
|
|
496
|
+
*/
|
|
497
|
+
function packageManager() {
|
|
498
|
+
const agent = process.env.npm_config_user_agent ?? "";
|
|
499
|
+
const names = ["bun", "pnpm", "yarn", "npm"];
|
|
500
|
+
return names.find((name) => agent.startsWith(name)) ?? "npm";
|
|
501
|
+
}
|
|
502
|
+
/** How each manager runs a binary out of node_modules, for the closing lines. */
|
|
503
|
+
const RUNNERS = {
|
|
504
|
+
npm: "npx",
|
|
505
|
+
pnpm: "pnpm",
|
|
506
|
+
yarn: "yarn",
|
|
507
|
+
bun: "bunx",
|
|
508
|
+
};
|
|
509
|
+
function install(root, manager) {
|
|
510
|
+
console.log(`\n${dim(`installing with ${manager}…`)}`);
|
|
511
|
+
return new Promise((resolvePromise) => {
|
|
512
|
+
const child = spawn(manager, ["install"], {
|
|
513
|
+
cwd: root,
|
|
514
|
+
stdio: "inherit",
|
|
515
|
+
// npm and yarn are .cmd shims on Windows, which execvp cannot run.
|
|
516
|
+
shell: process.platform === "win32",
|
|
517
|
+
});
|
|
518
|
+
child.on("close", (code) => resolvePromise(code === 0));
|
|
519
|
+
child.on("error", () => resolvePromise(false));
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Scaffold an app folder, install its dependencies, and say what to run.
|
|
524
|
+
*
|
|
525
|
+
* @param appRoot the directory to scaffold, created if it does not exist
|
|
526
|
+
* @param flags parsed CLI flags
|
|
527
|
+
* @param options.targeted a directory was named on the command line
|
|
528
|
+
* @returns a process exit code
|
|
529
|
+
*/
|
|
530
|
+
export async function init(appRoot, flags, { targeted = true } = {}) {
|
|
531
|
+
const interactive = process.stdin.isTTY && !flags.yes && !flags.name;
|
|
532
|
+
const suggested = slugify(basename(appRoot));
|
|
533
|
+
// One question, and both fields come out of the answer: `Acme Shop` gives the
|
|
534
|
+
// server the name `acme-shop` and keeps `Acme Shop` as the title. An answer
|
|
535
|
+
// that is already a slug gets a title with a capital on the front.
|
|
536
|
+
const answer = flags.name ?? (interactive ? await ask("App name", suggested) : suggested);
|
|
537
|
+
const name = slugify(answer);
|
|
538
|
+
const typed = cleanTitle(answer);
|
|
539
|
+
const app = { name, title: typed && typed !== name ? typed : titleize(name) };
|
|
540
|
+
// A name typed at the prompt with no directory to put it in names the
|
|
541
|
+
// directory as well: `oney` in ~/Projects means ~/Projects/oney, which is how
|
|
542
|
+
// create-next-app's one question reads. Taking the offered default leaves
|
|
543
|
+
// everything where it is, since that default is the current folder's own name,
|
|
544
|
+
// and `waniwani init .` names the current folder outright.
|
|
545
|
+
const root = !targeted && interactive && name !== suggested ? join(appRoot, name) : appRoot;
|
|
546
|
+
// Asked rather than assumed, because the answer decides whether the repo
|
|
547
|
+
// carries a deploy file at all, and because seeing the four options is how
|
|
548
|
+
// someone learns the app is not tied to one host.
|
|
549
|
+
const host = typeof flags.host === "string"
|
|
550
|
+
? hostById(flags.host).id
|
|
551
|
+
: interactive
|
|
552
|
+
? await choose("Where will this deploy?", HOSTS)
|
|
553
|
+
: hostById(undefined).id;
|
|
554
|
+
const files = scaffold(app, { minimal: Boolean(flags.minimal), host });
|
|
555
|
+
// Nothing is written until every collision is known, so a refusal leaves the
|
|
556
|
+
// directory exactly as it was.
|
|
557
|
+
const clashes = files.filter((file) => !file.whenPresent && existsSync(join(root, file.path)));
|
|
558
|
+
if (clashes.length > 0 && !flags.force) {
|
|
559
|
+
console.error(`\n${red("✗")} ${bold("already an app folder here")}\n`);
|
|
560
|
+
for (const file of clashes) {
|
|
561
|
+
console.error(` ${file.path}`);
|
|
562
|
+
}
|
|
563
|
+
console.error(`\n${dim("pass --force to overwrite, or init into a new directory")}`);
|
|
564
|
+
return 1;
|
|
565
|
+
}
|
|
566
|
+
mkdirSync(root, { recursive: true });
|
|
567
|
+
const actions = [];
|
|
568
|
+
for (const file of files) {
|
|
569
|
+
const target = join(root, file.path);
|
|
570
|
+
if (!existsSync(target)) {
|
|
571
|
+
write(target, file.contents);
|
|
572
|
+
actions.push([green("+"), file.path, null]);
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
if (file.whenPresent === "keep") {
|
|
576
|
+
actions.push([dim("·"), file.path, "yours, left alone"]);
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
if (file.whenPresent === "merge" && file.merge) {
|
|
580
|
+
const changed = file.merge(target, file.contents);
|
|
581
|
+
actions.push([
|
|
582
|
+
changed.length > 0 ? yellow("~") : dim("·"),
|
|
583
|
+
file.path,
|
|
584
|
+
changed.length > 0 ? `+ ${changed.join(", ")}` : "nothing to add",
|
|
585
|
+
]);
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
// A collision the caller chose to overwrite.
|
|
589
|
+
write(target, file.contents);
|
|
590
|
+
actions.push([yellow("~"), file.path, "overwritten"]);
|
|
591
|
+
}
|
|
592
|
+
console.log(`\n${green("✓")} ${bold(app.name)} ${dim(`→ ${root}`)}\n`);
|
|
593
|
+
for (const [marker, path, note] of actions) {
|
|
594
|
+
console.log(` ${marker} ${path}${note ? ` ${dim(note)}` : ""}`);
|
|
595
|
+
}
|
|
596
|
+
// The merge leaves a script the repo already had alone, so the way into the dev
|
|
597
|
+
// loop is whatever survived that: `npm run dev` when it is ours, the CLI by
|
|
598
|
+
// name when the repo's own `dev` runs something else.
|
|
599
|
+
const manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf-8"));
|
|
600
|
+
const ours = manifest.scripts?.dev === "waniwani dev";
|
|
601
|
+
if (manifest.type !== "module") {
|
|
602
|
+
console.log(`\n${yellow("!")} ${bold("package.json")} says ${bold(`"type": "${manifest.type}"`)}`);
|
|
603
|
+
console.log(` ${dim('app modules are ESM: set it to "module" or the build cannot load them')}`);
|
|
604
|
+
}
|
|
605
|
+
const manager = packageManager();
|
|
606
|
+
// A scaffold with no node_modules still checks out and still reads, so a
|
|
607
|
+
// failed install is reported and the folder is kept.
|
|
608
|
+
const installed = flags.install === false ? null : await install(root, manager);
|
|
609
|
+
console.log(`\n${bold("From here")}`);
|
|
610
|
+
// `init apps/store` is scaffolded two levels down, so the path is the one to
|
|
611
|
+
// print rather than the directory's own name.
|
|
612
|
+
const from = relative(process.cwd(), root);
|
|
613
|
+
if (from) {
|
|
614
|
+
console.log(` cd ${from}`);
|
|
615
|
+
}
|
|
616
|
+
if (installed === false) {
|
|
617
|
+
console.log(` ${yellow(`${manager} install`)} ${dim("(the first attempt failed)")}`);
|
|
618
|
+
}
|
|
619
|
+
else if (installed === null) {
|
|
620
|
+
console.log(` ${manager} install`);
|
|
621
|
+
}
|
|
622
|
+
console.log(` ${ours ? `${manager} run dev` : `${RUNNERS[manager]} waniwani dev`}`);
|
|
623
|
+
console.log(`\n${bold("Then")}`);
|
|
624
|
+
console.log(` ${dim("·")} edit ${bold(`tools/${TOOL}.ts`)} to answer with your own data`);
|
|
625
|
+
if (!flags.minimal) {
|
|
626
|
+
console.log(` ${dim("·")} edit ${bold(`widgets/${WIDGET}/ui.tsx`)} for how it looks on screen`);
|
|
627
|
+
}
|
|
628
|
+
console.log(` ${dim("·")} add ${bold("flows/<name>.ts")} for a multi-step conversation`);
|
|
629
|
+
const target = hostById(host);
|
|
630
|
+
if (target.deploy.length > 0) {
|
|
631
|
+
console.log(`\n${bold(`Deploying to ${target.label}`)}`);
|
|
632
|
+
for (const line of target.deploy) {
|
|
633
|
+
console.log(` ${line}`);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
console.log(`\n${bold("Deploying")}`);
|
|
638
|
+
console.log(` ${dim("·")} rerun with ${bold("--host vercel")} for the one file Vercel needs`);
|
|
639
|
+
}
|
|
640
|
+
return 0;
|
|
641
|
+
}
|
|
642
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../cli/init.ts"],"names":[],"mappings":"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"}
|