create-kywi-app 0.19.0 → 0.20.0
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 +156 -12
- package/bin/create-kywi-app.mjs +128 -6
- package/lib/config-scan.mjs +385 -0
- package/lib/eject.mjs +424 -0
- package/lib/templates.mjs +458 -187
- package/lib/upgrade.mjs +490 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -83,28 +83,50 @@ CLAUDE.md thin pointer that sends a Claude Code session
|
|
|
83
83
|
.claude/skills/kywi-personalization/SKILL.md Claude Code project skill: confirm the use case, then
|
|
84
84
|
build audiences, page variants, or A/B experiments — loaded
|
|
85
85
|
automatically when the owner wants personalization or A/B testing
|
|
86
|
-
lib/kywi.ts the ONE wiring point: kywi.config.ts → core's render path
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
lib/kywi.ts the ONE wiring point: kywi.config.ts → core's render path (stamped)
|
|
87
|
+
kywi.layers.ts GENERATED layer registry: imports every site/theme layer so core can
|
|
88
|
+
resolve overrides theme → site → core at build time (never edit; upgrade regenerates) (stamped)
|
|
89
|
+
sites/default/index.ts this site's layer: re-exports lib/modules.tsx as `modules` (add `templates` to override)
|
|
90
|
+
sites/default/themes/default/index.tsx the default theme's layer: `templates.nav` → components/site-nav.tsx (coupled only)
|
|
91
|
+
middleware.ts re-exports core's middleware (auth gate, session refresh, cookie→bearer bridge) (stamped)
|
|
92
|
+
app/api/v1/[...kywi]/route.ts the versioned API — a thin delegate to core's `createApiRoute` (cookie lifting lives in core) (stamped)
|
|
93
|
+
app/llms.txt/route.ts etc. the root AX routes — thin delegates to core's `createAxRoute` (stamped: all 4 of
|
|
94
|
+
llms.txt, llms-full.txt, sitemap.xml, robots.txt)
|
|
89
95
|
app/layout.tsx root <html>/<body>
|
|
90
96
|
app/icon.svg favicon
|
|
91
|
-
app/admin/[[...admin]]/page.tsx mounts the FULL core admin (KywiAdminApp, every surface) at /admin
|
|
97
|
+
app/admin/[[...admin]]/page.tsx mounts the FULL core admin (KywiAdminApp, every surface) at /admin (stamped)
|
|
92
98
|
```
|
|
93
99
|
|
|
94
100
|
**coupled** additionally gets `app/(site)/layout.tsx` + `app/(site)/[[...slug]]/page.tsx`
|
|
95
|
-
(the public site — renders "/" and every published page at its slug),
|
|
96
|
-
`app/(site)/site.css` and `components/site-nav.tsx
|
|
97
|
-
instead get `app/page.tsx` (returns 404 — no public
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
+
(the public site — renders "/" and every published page at its slug, both **stamped**),
|
|
102
|
+
plus `app/(site)/site.css` and `components/site-nav.tsx` (project-owned, unstamped).
|
|
103
|
+
**headless** and **decoupled** instead get `app/page.tsx` (returns 404 — no public
|
|
104
|
+
rendering, unstamped).
|
|
105
|
+
|
|
106
|
+
All eleven of the files marked **(stamped)** above are **thin, version-stamped
|
|
107
|
+
delegates**: the two `app/(site)` route files, `middleware.ts`, `lib/kywi.ts`,
|
|
108
|
+
`kywi.layers.ts`, the API route, the four AX root routes and the admin catch-all
|
|
109
|
+
page. Each opens with a `// kywi-render v2 (create-kywi-app <version>)
|
|
110
|
+
sha256:<12 hex>` comment — the hash is over the file's own body, so
|
|
111
|
+
`create-kywi-app upgrade` (see below) can tell an untouched generated file
|
|
112
|
+
(safe to replace) from one you've edited (refused) without a diff against
|
|
113
|
+
every historical emission. The
|
|
101
114
|
public render path itself — content and locale resolution, SEO metadata, JSON-LD,
|
|
102
115
|
feed and nav hydration, personalization, A/B assignment and the front-of-site
|
|
103
116
|
editor — is shipped as `@kywi-software/core/next`, so `pnpm up
|
|
104
117
|
@kywi-software/core` upgrades it. Before this, ~1,370 lines of it were copied
|
|
105
118
|
into every project, where no upgrade could ever reach them again. What a project
|
|
106
|
-
owns: `kywi.config.ts`, `app/(site)/site.css`, `lib/modules.tsx
|
|
107
|
-
`components/site-nav.tsx
|
|
119
|
+
owns: `kywi.config.ts`, `app/(site)/site.css`, `lib/modules.tsx`,
|
|
120
|
+
`components/site-nav.tsx` and the layer files under `sites/**`.
|
|
121
|
+
|
|
122
|
+
**The layer chain.** Every named artifact core renders — a module renderer, the nav,
|
|
123
|
+
the front-edit overlay, the personalization runtime, the `/kywi.js` loader — is resolved
|
|
124
|
+
per request as **theme → site → core**, first hit wins, from the registry `kywi.layers.ts`
|
|
125
|
+
imports. To override one, export it by name from `sites/<site>/index.ts` (`modules` or
|
|
126
|
+
`templates`) or from `sites/<site>/themes/<theme>/index.tsx`; never edit core or the
|
|
127
|
+
generated files. Core validates the registry at startup and fails loudly when a configured
|
|
128
|
+
site or theme is missing from it, when two themes of one site disagree on region names, or
|
|
129
|
+
when a layer overrides a module name neither core nor `kywi.config.ts` defines.
|
|
108
130
|
|
|
109
131
|
Note what's *not* in this list: there is no `components/admin-shell.tsx`, no
|
|
110
132
|
per-route `app/admin/content/**`, no `lib/admin-auth.ts`. Since 0.2.0 the whole
|
|
@@ -159,6 +181,128 @@ It writes the same five files (`AGENTS.md`, `CLAUDE.md`, and the three
|
|
|
159
181
|
Use `@latest` so an existing project picks up the newest guidance regardless of
|
|
160
182
|
which `create-kywi-app` version originally scaffolded it.
|
|
161
183
|
|
|
184
|
+
## Upgrading a project
|
|
185
|
+
|
|
186
|
+
Every generated render-path file is version-stamped (see the eleven **(stamped)**
|
|
187
|
+
files above), so `create-kywi-app upgrade` can prove which ones are still exactly
|
|
188
|
+
what it emitted and safe to replace, versus which ones carry your own edits and
|
|
189
|
+
must not be touched. Run it **from the project's root**:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
npx create-kywi-app@latest upgrade --dry-run # see the full report, write nothing
|
|
193
|
+
npx create-kywi-app@latest upgrade # apply it
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
What it does, one line per file in this order — landmarks, then layer files,
|
|
197
|
+
then retired pre-0.19 glue:
|
|
198
|
+
|
|
199
|
+
- **`created`** — a landmark file the project is missing, or a layer file
|
|
200
|
+
(`sites/<id>/index.ts`, `sites/<id>/themes/<theme>/index.tsx`) a site declared
|
|
201
|
+
in `kywi.config.ts` has none of yet.
|
|
202
|
+
- **`replaced`** — a stamped landmark whose hash proves it is still untouched;
|
|
203
|
+
swapped for the current emission.
|
|
204
|
+
- **`current`** — a stamped landmark that already matches the current emission
|
|
205
|
+
byte for byte.
|
|
206
|
+
- **`kept`** — a layer file under `sites/**` that already exists. Layer files are
|
|
207
|
+
created once and never compared or overwritten again — from the moment they
|
|
208
|
+
exist they are the project's.
|
|
209
|
+
- **`REFUSED (edited)`** / **`REFUSED (unstamped)`** — a landmark whose hash
|
|
210
|
+
doesn't match (someone's work is in it) or that carries no stamp at all
|
|
211
|
+
(predates the layered render path, or was hand-written). The exception: a
|
|
212
|
+
project scaffolded by 0.19.0 has six landmarks (the API route, the four AX
|
|
213
|
+
root routes, the admin catch-all page) that 0.19.0 emitted with no stamp at
|
|
214
|
+
all — `upgrade` recognises those six by their exact 0.19.0 bytes and
|
|
215
|
+
`replace`s them like any other untouched file, so a 0.19.0 project upgrades
|
|
216
|
+
with a plain `upgrade`, no `--force` needed.
|
|
217
|
+
- **`REFUSED (legacy)`** — one of the six retired pre-0.19 files
|
|
218
|
+
(`lib/site.ts`, `lib/config.ts`, `lib/kywi-js-loader.ts`,
|
|
219
|
+
`components/kywi-js-loader.tsx`, `components/personalization-runtime.tsx`,
|
|
220
|
+
`app/(site)/kywi-front-edit.tsx`) is still present; their contents now live
|
|
221
|
+
in `@kywi-software/core/next`, and leaving a stale copy shadows the real
|
|
222
|
+
thing. `--force` turns this into **`removed`** instead.
|
|
223
|
+
- **`backed up: <path> → .kywi-upgrade/<timestamp>/<path>`** — printed above any
|
|
224
|
+
`replaced`/`removed` line that `--force` produced from a refusal: the original
|
|
225
|
+
is copied there before it's touched.
|
|
226
|
+
- **`warn: <path> was ejected from @kywi-software/core <v>; installed is <v>`** —
|
|
227
|
+
advisory only, for a file `create-kywi-app eject` (below) put under `sites/**`
|
|
228
|
+
whose stamped core version no longer matches the installed one.
|
|
229
|
+
- **`note: …`** — advisory hints: stale `AGENTS.md` guidance (`create-kywi-app
|
|
230
|
+
agents --force` refreshes it), or an installed `@kywi-software/core` older
|
|
231
|
+
than the generated files target.
|
|
232
|
+
|
|
233
|
+
Without `--force`, an edited or unstamped file (and the six retired files) is
|
|
234
|
+
**refused, not touched** — the command reports it and leaves it exactly as it
|
|
235
|
+
is. Re-run with `--force` to replace/remove refused files too; each one is
|
|
236
|
+
backed up under `.kywi-upgrade/<timestamp>/` first, so nothing is discarded.
|
|
237
|
+
`--dry-run` runs the identical plan and prints the identical report, but writes
|
|
238
|
+
nothing.
|
|
239
|
+
|
|
240
|
+
**Exit codes:** `0` — nothing was refused (with `--force`, every refusable file
|
|
241
|
+
was replaced/removed instead). `2` — one or more files are still refused
|
|
242
|
+
because `--force` wasn't passed — a CI job should treat this as "the project
|
|
243
|
+
isn't caught up yet." `1` — no `kywi.config.ts` here, or it couldn't be scanned
|
|
244
|
+
(the layer registry is generated from it, so the command would rather refuse to
|
|
245
|
+
write anything than emit one that fails `assertLayerContracts` at boot).
|
|
246
|
+
|
|
247
|
+
**What it never touches:** `kywi.config.ts`, `package.json`, `lib/modules.tsx`,
|
|
248
|
+
`components/site-nav.tsx`, `app/(site)/site.css`, `app/layout.tsx`, and any
|
|
249
|
+
`sites/**` file that already exists.
|
|
250
|
+
|
|
251
|
+
**Where your edits belong now**, once a file is refused or you're migrating off
|
|
252
|
+
a pre-0.19 project:
|
|
253
|
+
|
|
254
|
+
- module renderers → `sites/<site>/index.ts`
|
|
255
|
+
- nav / front-edit / personalization runtime / JS loader → `create-kywi-app
|
|
256
|
+
eject <artifact>` (below)
|
|
257
|
+
- styles → `app/(site)/site.css`
|
|
258
|
+
|
|
259
|
+
## Ejecting a core artifact
|
|
260
|
+
|
|
261
|
+
Four of core's render-path client components ship as readable `.tsx` sources
|
|
262
|
+
inside `@kywi-software/core`'s `eject/` folder, one per name:
|
|
263
|
+
|
|
264
|
+
| artifact | source file |
|
|
265
|
+
| -------------------------- | ------------------------------- |
|
|
266
|
+
| `nav` | `site-nav.tsx` |
|
|
267
|
+
| `frontEdit` | `front-edit.tsx` |
|
|
268
|
+
| `personalizationRuntime` | `personalization-runtime.tsx` |
|
|
269
|
+
| `jsLoader` | `kywi-js-loader.tsx` |
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
npx create-kywi-app@latest eject <artifact> [--site <id>] [--theme <name>] [--layer site|theme] [--force]
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
It copies the named artifact into the project as a layer override you own —
|
|
276
|
+
`sites/<site>/themes/<theme>/<file>.tsx` by default (`--layer theme`; `--site`
|
|
277
|
+
defaults to the first site `kywi.config.ts` declares, `--theme` to that site's
|
|
278
|
+
own configured theme), or `sites/<site>/<file>.tsx` with `--layer site`
|
|
279
|
+
(`--theme` is meaningless there — a site layer isn't per-theme). The copy opens with two stamp
|
|
280
|
+
lines: `'use client'` first (a directive after any other line is inert, so this
|
|
281
|
+
has to stay first), then `// kywi-eject <artifact> (@kywi-software/core
|
|
282
|
+
<version>)` — the provenance `create-kywi-app upgrade` reads to warn when
|
|
283
|
+
core's copy has since moved on.
|
|
284
|
+
|
|
285
|
+
When the target layer's index file still has the shape `create-kywi-app`
|
|
286
|
+
generates, `eject` also **wires** the copy into that layer's `templates`
|
|
287
|
+
export automatically (dropping any import that pointed the same entry at its
|
|
288
|
+
previous component, so a re-eject with `--force` doesn't stack imports). When
|
|
289
|
+
the index has been hand-edited into some other shape, it refuses to touch that
|
|
290
|
+
file and prints the two lines to add by hand instead — an import line and a
|
|
291
|
+
`<artifact>: <ExportName>,` entry for inside `templates`.
|
|
292
|
+
|
|
293
|
+
`--force` here means something different than in `upgrade`: it overwrites an
|
|
294
|
+
**existing ejected file** at the target path (without it, `eject` refuses to
|
|
295
|
+
clobber one). Needs `node_modules` installed, and an installed
|
|
296
|
+
`@kywi-software/core >= 0.20.0` (the first release that ships `eject/`).
|
|
297
|
+
|
|
298
|
+
Ejecting is a deliberate, one-artifact opt-out of core upgrades: from that
|
|
299
|
+
moment the file is the project's own, `pnpm up @kywi-software/core` can no
|
|
300
|
+
longer reach it, and `create-kywi-app upgrade` only warns — via the `warn:`
|
|
301
|
+
line above — when the installed core has moved past the version it was copied
|
|
302
|
+
from. Reach for it when you need to change the behaviour of one of these four
|
|
303
|
+
components; everything else (module renderers, styles) has a lighter-weight
|
|
304
|
+
home — see "Where your edits belong now" above.
|
|
305
|
+
|
|
162
306
|
## Installing the generated packages
|
|
163
307
|
|
|
164
308
|
All `@kywi-software/*` packages (`core`, `cli`, `sdk`, `mcp`, `js`) are
|
package/bin/create-kywi-app.mjs
CHANGED
|
@@ -18,6 +18,8 @@ import { stdin, stdout, argv, exit } from 'node:process'
|
|
|
18
18
|
import { fileURLToPath } from 'node:url'
|
|
19
19
|
import { readFileSync } from 'node:fs'
|
|
20
20
|
import { buildFileSet, guidanceFileSet, detectAgentsLandmarks } from '../lib/templates.mjs'
|
|
21
|
+
import { planUpgrade, applyUpgrade, formatReport, exitCodeFor } from '../lib/upgrade.mjs'
|
|
22
|
+
import { planEject, applyEject, wiringHints } from '../lib/eject.mjs'
|
|
21
23
|
|
|
22
24
|
const MODES = ['coupled', 'headless', 'decoupled']
|
|
23
25
|
const DB_PROVIDERS = ['postgresql', 'mysql']
|
|
@@ -38,19 +40,41 @@ function readKywiVersion() {
|
|
|
38
40
|
// ── Arg parsing ───────────────────────────────────────────────────────────────
|
|
39
41
|
|
|
40
42
|
function parseArgs(args) {
|
|
41
|
-
const opts = {
|
|
43
|
+
const opts = {
|
|
44
|
+
name: undefined,
|
|
45
|
+
yes: false,
|
|
46
|
+
mode: undefined,
|
|
47
|
+
db: undefined,
|
|
48
|
+
auth: undefined,
|
|
49
|
+
help: false,
|
|
50
|
+
force: false,
|
|
51
|
+
// Parsed for the subcommands that offer a preview of the edits they would
|
|
52
|
+
// make; `eject` always writes, so it ignores this one.
|
|
53
|
+
dryRun: false,
|
|
54
|
+
site: undefined,
|
|
55
|
+
theme: undefined,
|
|
56
|
+
layer: undefined,
|
|
57
|
+
positional: [],
|
|
58
|
+
}
|
|
42
59
|
const positional = opts.positional
|
|
43
60
|
for (let i = 0; i < args.length; i++) {
|
|
44
61
|
const arg = args[i]
|
|
45
62
|
if (arg === '--yes' || arg === '-y') opts.yes = true
|
|
46
63
|
else if (arg === '--help' || arg === '-h') opts.help = true
|
|
47
64
|
else if (arg === '--force' || arg === '-f') opts.force = true
|
|
65
|
+
else if (arg === '--dry-run') opts.dryRun = true
|
|
48
66
|
else if (arg === '--mode') opts.mode = args[++i]
|
|
49
67
|
else if (arg.startsWith('--mode=')) opts.mode = arg.slice(7)
|
|
50
68
|
else if (arg === '--db') opts.db = args[++i]
|
|
51
69
|
else if (arg.startsWith('--db=')) opts.db = arg.slice(5)
|
|
52
70
|
else if (arg === '--auth') opts.auth = args[++i]
|
|
53
71
|
else if (arg.startsWith('--auth=')) opts.auth = arg.slice(7)
|
|
72
|
+
else if (arg === '--site') opts.site = args[++i]
|
|
73
|
+
else if (arg.startsWith('--site=')) opts.site = arg.slice(7)
|
|
74
|
+
else if (arg === '--theme') opts.theme = args[++i]
|
|
75
|
+
else if (arg.startsWith('--theme=')) opts.theme = arg.slice(8)
|
|
76
|
+
else if (arg === '--layer') opts.layer = args[++i]
|
|
77
|
+
else if (arg.startsWith('--layer=')) opts.layer = arg.slice(8)
|
|
54
78
|
else if (!arg.startsWith('-')) positional.push(arg)
|
|
55
79
|
}
|
|
56
80
|
if (positional.length > 0) opts.name = positional[0]
|
|
@@ -68,13 +92,30 @@ Usage:
|
|
|
68
92
|
.claude/skills/) in the CURRENT
|
|
69
93
|
Kywi project — run it from the
|
|
70
94
|
project root
|
|
95
|
+
create-kywi-app upgrade [--dry-run] [--force]
|
|
96
|
+
Bring the CURRENT project's
|
|
97
|
+
generated render path up to this
|
|
98
|
+
create-kywi-app: replace stamped,
|
|
99
|
+
unedited files, create what is
|
|
100
|
+
missing, and report anything it
|
|
101
|
+
refuses to touch
|
|
102
|
+
create-kywi-app eject <artifact> Copy one of core's render
|
|
103
|
+
artifacts into this project as a
|
|
104
|
+
layer override you own
|
|
71
105
|
|
|
72
106
|
Options:
|
|
73
107
|
--yes, -y Use defaults, skip prompts
|
|
74
108
|
--mode <mode> coupled | headless | decoupled (default: coupled)
|
|
75
109
|
--db <provider> postgresql | mysql (default: postgresql)
|
|
76
110
|
--auth <list> comma-separated: credentials,google,github (default: credentials)
|
|
111
|
+
--site <id> eject: which site's layer (default: the first site in kywi.config.ts)
|
|
112
|
+
--theme <name> eject: which theme's layer (default: that site's theme)
|
|
113
|
+
--layer <layer> eject: site | theme (default: theme)
|
|
77
114
|
--force, -f agents: overwrite existing guidance files (default: skip them)
|
|
115
|
+
upgrade: replace edited/unstamped/legacy files too — each
|
|
116
|
+
original is copied to .kywi-upgrade/<timestamp>/ first
|
|
117
|
+
--dry-run upgrade: print the full report, write nothing
|
|
118
|
+
eject: overwrite an existing override
|
|
78
119
|
--help, -h Show this help
|
|
79
120
|
|
|
80
121
|
Examples:
|
|
@@ -83,6 +124,13 @@ Examples:
|
|
|
83
124
|
npx create-kywi-app blog --mode headless --auth credentials,google
|
|
84
125
|
npx create-kywi-app@latest agents # add guidance to an existing project
|
|
85
126
|
npx create-kywi-app@latest agents --force # refresh it to the latest version
|
|
127
|
+
npx create-kywi-app@latest upgrade --dry-run # see what an upgrade would change
|
|
128
|
+
npx create-kywi-app@latest upgrade # apply it
|
|
129
|
+
|
|
130
|
+
Exit codes (upgrade): 0 nothing refused · 2 something was refused · 1 the project
|
|
131
|
+
or its kywi.config.ts could not be read.
|
|
132
|
+
npx create-kywi-app@latest eject nav # own the site nav in this theme
|
|
133
|
+
npx create-kywi-app@latest eject frontEdit --layer site
|
|
86
134
|
`)
|
|
87
135
|
}
|
|
88
136
|
|
|
@@ -256,8 +304,83 @@ async function runAgentsCommand(opts) {
|
|
|
256
304
|
return 0
|
|
257
305
|
}
|
|
258
306
|
|
|
307
|
+
// ── `upgrade` subcommand ──────────────────────────────────────────────────────
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* `create-kywi-app upgrade [--dry-run] [--force]` — bring an existing project's
|
|
311
|
+
* generated render path up to this create-kywi-app's emission. The whole
|
|
312
|
+
* decision lives in ../lib/upgrade.mjs (planned, applied, formatted and scored
|
|
313
|
+
* separately so it is unit-testable and so `--dry-run` cannot drift from a real
|
|
314
|
+
* run); this wrapper is just cwd + stdout + exit code.
|
|
315
|
+
*/
|
|
316
|
+
async function runUpgradeCommand(opts) {
|
|
317
|
+
const plan = planUpgrade(process.cwd(), { force: opts.force, dryRun: opts.dryRun })
|
|
318
|
+
const applied = applyUpgrade(plan, { dryRun: opts.dryRun })
|
|
319
|
+
stdout.write(formatReport(plan, applied))
|
|
320
|
+
return exitCodeFor(plan)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ── `eject` subcommand ────────────────────────────────────────────────────────
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* `create-kywi-app eject <artifact> [--site <id>] [--theme <name>] [--layer site|theme] [--force]`
|
|
327
|
+
*
|
|
328
|
+
* Copies one of core's render artifacts (`nav`, `frontEdit`,
|
|
329
|
+
* `personalizationRuntime`, `jsLoader`) into this project's layer chain as an
|
|
330
|
+
* override the project owns, and wires it into that layer's `templates` export.
|
|
331
|
+
* The mechanics live in ../lib/eject.mjs; this is argument handling and the
|
|
332
|
+
* report — including the cost, stated plainly: the copy stops following core.
|
|
333
|
+
*/
|
|
334
|
+
async function runEjectCommand(opts) {
|
|
335
|
+
const cwd = process.cwd()
|
|
336
|
+
// positional[0] is the subcommand name itself.
|
|
337
|
+
const artifact = opts.positional[1]
|
|
338
|
+
|
|
339
|
+
let plan
|
|
340
|
+
let result
|
|
341
|
+
try {
|
|
342
|
+
plan = planEject(cwd, { artifact, site: opts.site, theme: opts.theme, layer: opts.layer })
|
|
343
|
+
result = applyEject(plan, { force: opts.force })
|
|
344
|
+
} catch (err) {
|
|
345
|
+
if (!err?.userFacing) throw err
|
|
346
|
+
stdout.write(`\n✖ ${err.message}\n`)
|
|
347
|
+
return err.exitCode ?? 1
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
stdout.write(`\n${result.overwrote ? 'overwrote' : 'ejected'}: ${plan.targetRel}\n`)
|
|
351
|
+
if (result.wired) {
|
|
352
|
+
stdout.write(`wired: ${plan.indexRel}\n`)
|
|
353
|
+
} else {
|
|
354
|
+
const [importLine, entryLine] = wiringHints(plan)
|
|
355
|
+
stdout.write(`not wired (edit by hand): ${plan.indexRel}\n`)
|
|
356
|
+
stdout.write(` ${importLine}\n`)
|
|
357
|
+
stdout.write(` ${entryLine} ← inside its \`templates\` object\n`)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const where = plan.theme ? `site "${plan.site}", theme "${plan.theme}"` : `site "${plan.site}"`
|
|
361
|
+
stdout.write(
|
|
362
|
+
`\n✔ ${plan.artifact} is now a ${plan.layer}-layer override for ${where}. ` +
|
|
363
|
+
`It no longer follows core upgrades; \`create-kywi-app upgrade\` will warn when core ${plan.coreVersion} moves.\n`,
|
|
364
|
+
)
|
|
365
|
+
return 0
|
|
366
|
+
}
|
|
367
|
+
|
|
259
368
|
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
260
369
|
|
|
370
|
+
/**
|
|
371
|
+
* Subcommands that operate on the CURRENT project rather than scaffolding a new
|
|
372
|
+
* one. A bare first positional that is not a key here is a project name, which
|
|
373
|
+
* is why the table is consulted before anything else runs. Null-prototyped so a
|
|
374
|
+
* project literally named `constructor` or `toString` scaffolds instead of
|
|
375
|
+
* dispatching to something off Object.prototype.
|
|
376
|
+
*/
|
|
377
|
+
const COMMANDS = {
|
|
378
|
+
__proto__: null,
|
|
379
|
+
agents: runAgentsCommand,
|
|
380
|
+
upgrade: runUpgradeCommand,
|
|
381
|
+
eject: runEjectCommand,
|
|
382
|
+
}
|
|
383
|
+
|
|
261
384
|
async function main() {
|
|
262
385
|
const opts = parseArgs(argv.slice(2))
|
|
263
386
|
if (opts.help) {
|
|
@@ -265,11 +388,10 @@ async function main() {
|
|
|
265
388
|
return 0
|
|
266
389
|
}
|
|
267
390
|
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
391
|
+
// A subcommand (`agents`, `upgrade`, `eject`) acts on the CURRENT project instead of
|
|
392
|
+
// scaffolding a new one.
|
|
393
|
+
const cmd = COMMANDS[opts.positional[0]]
|
|
394
|
+
if (cmd) return cmd(opts)
|
|
273
395
|
|
|
274
396
|
let answers
|
|
275
397
|
try {
|