uniweb 1.0.0 → 1.0.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.
Files changed (129) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +532 -3
  3. package/package.json +60 -11
  4. package/partials/adding-pages.hbs +77 -0
  5. package/partials/agents.md +3000 -0
  6. package/partials/ai-assistance.hbs +28 -0
  7. package/partials/components-docs.hbs +13 -0
  8. package/partials/config-reference.hbs +66 -0
  9. package/partials/deployment.hbs +13 -0
  10. package/partials/learn-more.hbs +6 -0
  11. package/partials/quick-start.hbs +8 -0
  12. package/partials/search-docs.hbs +110 -0
  13. package/src/backend/asset-download.js +222 -0
  14. package/src/backend/client.js +986 -0
  15. package/src/backend/foundation-bring-along.js +692 -0
  16. package/src/backend/payment-handoff.js +362 -0
  17. package/src/backend/service-request.js +210 -0
  18. package/src/backend/site-media.js +255 -0
  19. package/src/backend/site-preview.js +77 -0
  20. package/src/backend/site-sync.js +2515 -0
  21. package/src/backend/workspace.js +234 -0
  22. package/src/commands/add.js +1900 -0
  23. package/src/commands/build.js +1214 -0
  24. package/src/commands/clone.js +466 -0
  25. package/src/commands/content.js +202 -0
  26. package/src/commands/deploy.js +537 -0
  27. package/src/commands/dev.js +202 -0
  28. package/src/commands/docs.js +530 -0
  29. package/src/commands/doctor.js +1982 -0
  30. package/src/commands/export.js +116 -0
  31. package/src/commands/families.js +82 -0
  32. package/src/commands/forget.js +197 -0
  33. package/src/commands/handoff.js +21 -0
  34. package/src/commands/i18n.js +1707 -0
  35. package/src/commands/inspect.js +375 -0
  36. package/src/commands/invite.js +24 -0
  37. package/src/commands/org.js +96 -0
  38. package/src/commands/publish.js +1087 -0
  39. package/src/commands/pull.js +1257 -0
  40. package/src/commands/push.js +663 -0
  41. package/src/commands/refresh.js +212 -0
  42. package/src/commands/register.js +1092 -0
  43. package/src/commands/rename.js +647 -0
  44. package/src/commands/site.js +358 -0
  45. package/src/commands/snapshot.js +461 -0
  46. package/src/commands/starter.js +206 -0
  47. package/src/commands/status.js +302 -0
  48. package/src/commands/sync.js +93 -0
  49. package/src/commands/template.js +107 -0
  50. package/src/commands/update.js +887 -0
  51. package/src/commands/validate.js +376 -0
  52. package/src/framework-index.json +248 -0
  53. package/src/index.js +2241 -0
  54. package/src/templates/fetchers/github.js +113 -0
  55. package/src/templates/fetchers/npm.js +85 -0
  56. package/src/templates/fetchers/release.js +92 -0
  57. package/src/templates/index.js +190 -0
  58. package/src/templates/processor.js +333 -0
  59. package/src/templates/resolver.js +232 -0
  60. package/src/templates/validator.js +155 -0
  61. package/src/utils/agents-stamp.js +110 -0
  62. package/src/utils/args.js +126 -0
  63. package/src/utils/asset-upload.js +270 -0
  64. package/src/utils/bytes.js +30 -0
  65. package/src/utils/code-upload.js +394 -0
  66. package/src/utils/config.js +417 -0
  67. package/src/utils/conformance.js +274 -0
  68. package/src/utils/dep-survey.js +176 -0
  69. package/src/utils/destination-prompt.js +146 -0
  70. package/src/utils/discover.js +75 -0
  71. package/src/utils/env.js +22 -0
  72. package/src/utils/exit.js +30 -0
  73. package/src/utils/fetch-retry.js +109 -0
  74. package/src/utils/flag-guard.js +246 -0
  75. package/src/utils/foundation-name.js +179 -0
  76. package/src/utils/git.js +342 -0
  77. package/src/utils/host-prompt.js +51 -0
  78. package/src/utils/install-integrity.js +210 -0
  79. package/src/utils/interactive.js +118 -0
  80. package/src/utils/json-file.js +72 -0
  81. package/src/utils/names.js +91 -0
  82. package/src/utils/placement.js +151 -0
  83. package/src/utils/pm.js +165 -0
  84. package/src/utils/preview.js +20 -0
  85. package/src/utils/pull-written.js +132 -0
  86. package/src/utils/records-guard.js +88 -0
  87. package/src/utils/registry-auth.js +994 -0
  88. package/src/utils/registry-orgs.js +290 -0
  89. package/src/utils/scaffold.js +483 -0
  90. package/src/utils/schemaless-report.js +30 -0
  91. package/src/utils/semver-precedence.js +80 -0
  92. package/src/utils/session-file.js +95 -0
  93. package/src/utils/site-content-refs.js +23 -0
  94. package/src/utils/site-copies.js +123 -0
  95. package/src/utils/site-data-upload.js +302 -0
  96. package/src/utils/site-identity.js +137 -0
  97. package/src/utils/snapshot-settings.js +112 -0
  98. package/src/utils/update-check.js +302 -0
  99. package/src/utils/uwx-read.js +157 -0
  100. package/src/utils/workspace.js +223 -0
  101. package/src/versions.js +467 -0
  102. package/starter/foundation/main.js +50 -0
  103. package/starter/foundation/sections/Section/index.jsx +95 -0
  104. package/starter/foundation/sections/Section/meta.js +44 -0
  105. package/starter/site/pages/about/1-about.md.hbs +13 -0
  106. package/starter/site/pages/about/page.yml +3 -0
  107. package/starter/site/pages/home/1-welcome.md.hbs +14 -0
  108. package/starter/site/pages/home/page.yml +3 -0
  109. package/templates/foundation/_gitignore +5 -0
  110. package/templates/foundation/components/.gitkeep +0 -0
  111. package/templates/foundation/main.js.hbs +33 -0
  112. package/templates/foundation/package.json.hbs +51 -0
  113. package/templates/foundation/sections/.gitkeep +0 -0
  114. package/templates/foundation/styles.css +6 -0
  115. package/templates/foundation/vite.config.js +3 -0
  116. package/templates/site/_gitignore +12 -0
  117. package/templates/site/entry.js +7 -0
  118. package/templates/site/index.html.hbs +13 -0
  119. package/templates/site/package.json.hbs +27 -0
  120. package/templates/site/public/favicon.svg +7 -0
  121. package/templates/site/site.yml.hbs +91 -0
  122. package/templates/site/theme.yml +99 -0
  123. package/templates/site/vite.config.js +7 -0
  124. package/templates/workspace/AGENTS.md.hbs +2 -0
  125. package/templates/workspace/README.md.hbs +87 -0
  126. package/templates/workspace/_gitignore +29 -0
  127. package/templates/workspace/_vscode/settings.json +6 -0
  128. package/templates/workspace/package.json.hbs +20 -0
  129. package/templates/workspace/pnpm-workspace.yaml.hbs +26 -0
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Export Command
3
+ *
4
+ * Produces a self-contained, vite-built site artifact in `dist/` for
5
+ * hosting on a third-party CDN (Netlify, Vercel, GitHub Pages, S3 +
6
+ * CloudFront, etc.). Does NOT upload anywhere — `uniweb deploy --host`
7
+ * uploads to a third-party host; `uniweb publish` ships to Uniweb hosting.
8
+ *
9
+ * The `dist/` output bundles the runtime + foundation + content into
10
+ * concatenated packaging, with a vite-built `index.html` + `entry.js` +
11
+ * `assets/`. The user copies it to whatever host they like.
12
+ *
13
+ * Internally this is `uniweb build --bundle` plus user guidance for the
14
+ * upload step. The `--link` / `--bundle` flag pair is internal-only
15
+ * vocabulary now (Phase 2 of the CLI ergonomics overhaul); users see
16
+ * `uniweb publish` (Uniweb hosting), `uniweb deploy --host` (third-party),
17
+ * and `uniweb export` (self-contained artifact).
18
+ *
19
+ * Usage:
20
+ * uniweb export Produce dist/ for static hosting
21
+ * uniweb export --no-prerender Skip per-page prerendered HTML
22
+ * uniweb export --host <name> Pick a host adapter for postBuild
23
+ * (e.g. cloudflare-pages, s3-cloudfront,
24
+ * github-pages, generic-static).
25
+ * Default: cloudflare-pages.
26
+ */
27
+
28
+ import { execSync } from 'node:child_process'
29
+ import { existsSync } from 'node:fs'
30
+ import { join } from 'node:path'
31
+
32
+ import { resolveSiteDir } from './deploy.js'
33
+
34
+ const c = {
35
+ reset: '\x1b[0m',
36
+ dim: '\x1b[2m',
37
+ cyan: '\x1b[36m',
38
+ green: '\x1b[32m',
39
+ red: '\x1b[31m'
40
+ }
41
+ const say = {
42
+ ok: (m) => console.log(`${c.green}✓${c.reset} ${m}`),
43
+ info: (m) => console.log(`${c.cyan}→${c.reset} ${m}`),
44
+ err: (m) => console.error(`${c.red}✗${c.reset} ${m}`),
45
+ dim: (m) => console.log(` ${c.dim}${m}${c.reset}`)
46
+ }
47
+
48
+ export async function exportSite(args = []) {
49
+ const siteDir = await resolveSiteDir(args, 'export')
50
+
51
+ // Pass through --no-prerender and --host. Everything else is ignored.
52
+ // `uniweb export` stays low-flag: the user picks the destination host
53
+ // themselves outside the CLI, so there's nothing to configure beyond
54
+ // what `uniweb build --bundle` already exposes.
55
+ const noPrerender = args.includes('--no-prerender')
56
+ const buildArgs = ['build', '--bundle']
57
+ if (noPrerender) buildArgs.push('--no-prerender')
58
+
59
+ const { readFlagValue } = await import('../utils/args.js')
60
+ const hostFlag = readFlagValue(args, '--host')
61
+ if (hostFlag === null) {
62
+ // --host with no value → prompt here so the build subprocess gets
63
+ // a concrete value (and doesn't re-prompt against its own argv).
64
+ const { promptForHost } = await import('../utils/host-prompt.js')
65
+ try {
66
+ const chosen = await promptForHost({ args })
67
+ buildArgs.push('--host', chosen)
68
+ } catch (err) {
69
+ say.err(err.message)
70
+ process.exit(1)
71
+ }
72
+ } else if (typeof hostFlag === 'string') {
73
+ buildArgs.push('--host', hostFlag)
74
+ }
75
+
76
+ say.info('Exporting site (vite build → dist/)…')
77
+ console.log('')
78
+
79
+ // Spawn the SAME CLI binary (process.argv[1]) — same reason as deploy.js:
80
+ // npx walks node_modules and could resolve to a different version.
81
+ try {
82
+ execSync(`node ${JSON.stringify(process.argv[1])} ${buildArgs.join(' ')}`, {
83
+ cwd: siteDir,
84
+ stdio: 'inherit'
85
+ })
86
+ } catch {
87
+ say.err('Build failed. See output above.')
88
+ process.exit(1)
89
+ }
90
+
91
+ const distDir = join(siteDir, 'dist')
92
+ if (!existsSync(distDir)) {
93
+ say.err('Build did not produce dist/.')
94
+ process.exit(1)
95
+ }
96
+
97
+ console.log('')
98
+ say.ok('Export complete.')
99
+ console.log('')
100
+ console.log(` ${c.dim}Artifact:${c.reset} ${c.cyan}${distDir}${c.reset}`)
101
+ console.log('')
102
+ console.log(
103
+ ` ${c.dim}Upload the contents of ${c.reset}${c.cyan}dist/${c.reset}${c.dim} to your static host. Examples:${c.reset}`
104
+ )
105
+ console.log(
106
+ ` ${c.dim}Netlify:${c.reset} ${c.cyan}netlify deploy --prod --dir=dist${c.reset}`
107
+ )
108
+ console.log(` ${c.dim}Vercel:${c.reset} ${c.cyan}vercel --prod${c.reset}`)
109
+ console.log(
110
+ ` ${c.dim}S3:${c.reset} ${c.cyan}aws s3 sync dist/ s3://your-bucket/${c.reset}`
111
+ )
112
+ console.log('')
113
+ console.log(
114
+ ` ${c.dim}For Uniweb hosting instead, use ${c.reset}${c.cyan}uniweb publish${c.reset}${c.dim}.${c.reset}`
115
+ )
116
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * `uniweb families` — the standard section families this CLI knows.
3
+ *
4
+ * ⭐ NAMED AFTER THE FIELD, not after the things it lists. A developer's question
5
+ * is "what can I put in `family:`?", and they will type the word they just read
6
+ * in a `meta.js` or a doctor warning.
7
+ *
8
+ * ⚠️ IT REPORTS THE ROSTER THIS CLI SHIPS WITH, not a live fetch — the list comes
9
+ * from the `@uniweb/schemas` version this CLI resolved. That is the right answer,
10
+ * because it is also the list `uniweb doctor` matched against. A newer family
11
+ * than your CLI will not appear, and declaring one is not fatal either way: an
12
+ * unrecognized value falls back to a generic illustration.
13
+ */
14
+
15
+ import { FAMILIES, GROUPS } from '@uniweb/schemas/families'
16
+ import { getCliVersion } from '../versions.js'
17
+
18
+ const colors = {
19
+ reset: '\x1b[0m',
20
+ bright: '\x1b[1m',
21
+ dim: '\x1b[2m',
22
+ cyan: '\x1b[36m'
23
+ }
24
+
25
+ export async function families(args = []) {
26
+ if (args.includes('--json')) {
27
+ // ⛔ stdout carries JSON and nothing else, so it pipes. Every human-facing
28
+ // line in this command goes to stdout only in the non-JSON branch.
29
+ process.stdout.write(
30
+ JSON.stringify(
31
+ { cliVersion: getCliVersion(), count: FAMILIES.length, groups: GROUPS, families: FAMILIES },
32
+ null,
33
+ 2
34
+ ) + '\n'
35
+ )
36
+ return
37
+ }
38
+
39
+ const filter = args.find((a) => !a.startsWith('-'))
40
+ const shown = filter
41
+ ? FAMILIES.filter(
42
+ (f) =>
43
+ f.id.includes(filter.toLowerCase()) ||
44
+ f.label.toLowerCase().includes(filter.toLowerCase()) ||
45
+ f.group === filter.toLowerCase()
46
+ )
47
+ : FAMILIES
48
+
49
+ console.log('')
50
+ console.log(
51
+ `${colors.bright}Section families${colors.reset} ${colors.dim}(uniweb ${getCliVersion()})${colors.reset}`
52
+ )
53
+
54
+ if (shown.length === 0) {
55
+ console.log('')
56
+ console.log(` ${colors.dim}Nothing matches "${filter}".${colors.reset}`)
57
+ console.log(
58
+ ` ${colors.dim}A name of your own is fine — it falls back to a generic illustration.${colors.reset}`
59
+ )
60
+ console.log('')
61
+ return
62
+ }
63
+
64
+ const width = Math.max(...shown.map((f) => f.id.length))
65
+ for (const group of GROUPS) {
66
+ const rows = shown.filter((f) => f.group === group.id)
67
+ if (rows.length === 0) continue
68
+ console.log('')
69
+ console.log(` ${colors.bright}${group.label}${colors.reset} ${colors.dim}— ${group.description}${colors.reset}`)
70
+ for (const f of rows) {
71
+ console.log(` ${colors.cyan}${f.id.padEnd(width)}${colors.reset} ${colors.dim}${f.label}${colors.reset}`)
72
+ }
73
+ }
74
+
75
+ console.log('')
76
+ console.log(` ${colors.dim}Declare one in a section's meta.js: family: 'hero'${colors.reset}`)
77
+ console.log(
78
+ ` ${colors.dim}A component already named for its family needs no declaration.${colors.reset}`
79
+ )
80
+ console.log(` ${colors.dim}--json for scripts. uniweb doctor reports what your sections resolved to.${colors.reset}`)
81
+ console.log('')
82
+ }
@@ -0,0 +1,197 @@
1
+ /**
2
+ * `uniweb forget` — remove what this project recorded about where it has synced and
3
+ * deployed. Local files only; nothing on any backend changes.
4
+ *
5
+ * uniweb forget --server <url> one backend
6
+ * uniweb forget --all everything — for a COPY that is to become a new project
7
+ *
8
+ * ## `--server <url>`
9
+ *
10
+ * - that backend's section of `sync.json` — the site uuid, the record map, asset
11
+ * ids, provisioned services and the rest of what it minted;
12
+ * - that backend's section of `.uniweb/backend-cache.json`;
13
+ * - the `deploys:` records in `deploy.yml` that name it. ⭐ Its TARGETS stay: a
14
+ * target is where you chose to ship, not something the backend told us, and the
15
+ * next publish there simply creates a new site.
16
+ *
17
+ * Every other backend is untouched, which is only possible because all three are
18
+ * keyed by backend.
19
+ *
20
+ * ## `--all`
21
+ *
22
+ * A copied project carries the original's `sync.json`, so its first push updates the
23
+ * ORIGINAL's site — the copy sends uuids that are the original's. `--all` deletes
24
+ * `sync.json`, `.uniweb/backend-cache.json` and `deploy.yml`, after which the copy's
25
+ * next push creates a new site.
26
+ *
27
+ * ⭐ **`deploy.yml` goes whole, targets included** *[Diego, 2026-09-21]*. Its targets
28
+ * name the original's destinations — its Pages project, its bucket, its domain — so a
29
+ * deploy from the copy would overwrite the original there. The copy's first deploy
30
+ * writes a fresh one.
31
+ *
32
+ * ## What neither form touches
33
+ *
34
+ * ⛔ **Record files.** A record's `$uuid` is its OWN id, not any backend's. It is
35
+ * written once, on the record's first push anywhere, and never changes after;
36
+ * removing it would delete the record's identity, which is not what forgetting a
37
+ * backend means. A copy keeps them too: they never reach a backend again once the
38
+ * map that pointed them at the original's entities is gone. *[Diego, 2026-09-20.]*
39
+ *
40
+ * ⛔ **Any backend.** Every site still exists where it was. Deleting one is a
41
+ * different operation, on the backend's side.
42
+ *
43
+ * ## Why it exists
44
+ *
45
+ * Two jobs. A script pushes a template site to a short-lived dev server and then
46
+ * removes the traces of it without discarding the rest of the project (`--server`)
47
+ * — which is also why traceless publish was dropped: push and pull leave traces too.
48
+ * And someone duplicates a project to start a new site from it (`--all`).
49
+ *
50
+ * ⚠️ **No default target.** Forgetting a backend you still use means its next push
51
+ * creates a second site there, so the verb makes you name it — `--server` is
52
+ * required even when the project has synced with only one.
53
+ */
54
+
55
+ import { readFlagValue } from '../utils/args.js'
56
+ import { checkFlags } from '../utils/flag-guard.js'
57
+ import { resolveSiteDir } from './deploy.js'
58
+ import { forgetBackendCache, forgetAllBackendCaches } from '../backend/site-sync.js'
59
+ import { clearBackend, forgetSyncStore, readBackendState } from '@uniweb/build/uwx'
60
+ import { forgetDeploys, forgetDeployYml } from '@uniweb/build/site'
61
+ import { syncedBackends, normalizeOrigin } from '../utils/site-identity.js'
62
+
63
+ const c = {
64
+ reset: '\x1b[0m',
65
+ bold: '\x1b[1m',
66
+ dim: '\x1b[2m',
67
+ cyan: '\x1b[36m',
68
+ green: '\x1b[32m',
69
+ yellow: '\x1b[33m',
70
+ red: '\x1b[31m'
71
+ }
72
+ const say = {
73
+ ok: (m) => console.log(`${c.green}✓${c.reset} ${m}`),
74
+ info: (m) => console.log(`${c.cyan}→${c.reset} ${m}`),
75
+ warn: (m) => console.log(`${c.yellow}⚠${c.reset} ${m}`),
76
+ err: (m) => console.error(`${c.red}✗${c.reset} ${m}`),
77
+ dim: (m) => console.log(` ${c.dim}${m}${c.reset}`)
78
+ }
79
+
80
+ /** "a", "a and b", "a, b and c" */
81
+ function listed(items) {
82
+ return items.length < 2
83
+ ? items.join('')
84
+ : `${items.slice(0, -1).join(', ')} and ${items[items.length - 1]}`
85
+ }
86
+
87
+ /**
88
+ * @param {string[]} args
89
+ * @returns {Promise<{ exitCode: number, forgot?: string[], removed?: string[] }>}
90
+ */
91
+ export async function forget(args = []) {
92
+ const badFlag = checkFlags('forget', args)
93
+ if (badFlag) {
94
+ say.err(badFlag.message)
95
+ return { exitCode: 2 }
96
+ }
97
+
98
+ const all = args.includes('--all')
99
+ const flag = readFlagValue(args, '--server')
100
+ if (all && flag) {
101
+ say.err('Use --server <url> or --all, not both.')
102
+ return { exitCode: 2 }
103
+ }
104
+
105
+ const siteDir = await resolveSiteDir(args, 'forget')
106
+ const known = syncedBackends(siteDir)
107
+
108
+ if (all) return forgetAll(siteDir)
109
+
110
+ if (!flag) {
111
+ say.err('Name what to forget: uniweb forget --server <url>')
112
+ if (known.length) {
113
+ say.dim(`This project has synced with: ${known.join(', ')}`)
114
+ } else {
115
+ say.dim('This project has not synced with any backend.')
116
+ }
117
+ say.dim('For a copied project that should become a new one: uniweb forget --all')
118
+ return { exitCode: 2 }
119
+ }
120
+
121
+ const origin = normalizeOrigin(flag)
122
+ if (!origin) {
123
+ say.err(`Not a URL: ${flag}`)
124
+ return { exitCode: 2 }
125
+ }
126
+
127
+ // Read before removing, so the report can say what the section held.
128
+ const state = readBackendState(siteDir, origin)
129
+ const removed = []
130
+ if (clearBackend(siteDir, origin)) removed.push('sync.json')
131
+ if (forgetBackendCache(siteDir, origin)) removed.push('.uniweb/backend-cache.json')
132
+ let deploys = []
133
+ try {
134
+ deploys = await forgetDeploys(siteDir, origin)
135
+ if (deploys.length) removed.push('deploy.yml')
136
+ } catch (err) {
137
+ // Not a stop: the identity above is already forgotten, and a deploy.yml that does
138
+ // not parse fails every deploy on its own. Rewriting a file we could not read
139
+ // would be the one thing worse than leaving it.
140
+ say.warn(`${err.message} — its deploy records were left as they are.`)
141
+ }
142
+
143
+ // Idempotent on purpose: a script calls this after every run, and a second call
144
+ // (or a call for a backend the push never reached) is not an error.
145
+ if (!removed.length) {
146
+ say.info(`Nothing recorded for ${origin} — nothing to forget.`)
147
+ if (known.length) say.dim(`This project has synced with: ${known.join(', ')}`)
148
+ return { exitCode: 0, forgot: [origin], removed }
149
+ }
150
+
151
+ say.ok(`Forgot ${c.bold}${origin}${c.reset}`)
152
+ if (state.site?.uuid) say.dim(`site ${state.site.uuid}`)
153
+ const counts = [
154
+ ['records', state.records],
155
+ ['assets', state.assets]
156
+ ]
157
+ .map(([name, map]) => [name, map ? Object.keys(map).length : 0])
158
+ .filter(([, n]) => n > 0)
159
+ .map(([name, n]) => `${n} ${name}`)
160
+ if (counts.length) say.dim(counts.join(' · '))
161
+ if (deploys.length) {
162
+ say.dim(`deploy records: ${deploys.join(', ')} (the targets stay)`)
163
+ }
164
+ say.dim(`removed from ${listed(removed)}`)
165
+ say.dim('The site still exists on that backend; this removed only what this project recorded.')
166
+
167
+ return { exitCode: 0, forgot: [origin], removed }
168
+ }
169
+
170
+ /** `--all`: the three files, whole. */
171
+ async function forgetAll(siteDir) {
172
+ const origins = forgetSyncStore(siteDir)
173
+ const cache = forgetAllBackendCaches(siteDir)
174
+ const targets = await forgetDeployYml(siteDir)
175
+
176
+ const removed = []
177
+ if (origins) removed.push('sync.json')
178
+ if (targets) removed.push('deploy.yml')
179
+ if (cache) removed.push('.uniweb/backend-cache.json')
180
+
181
+ if (!removed.length) {
182
+ say.info('Nothing recorded — this project has not synced or deployed anywhere.')
183
+ return { exitCode: 0, forgot: [], removed }
184
+ }
185
+
186
+ say.ok('Forgot every backend and deploy target')
187
+ if (origins?.length) say.dim(`backends: ${origins.join(', ')}`)
188
+ if (targets?.length) say.dim(`deploy targets: ${targets.join(', ')}`)
189
+ say.dim(`removed ${listed(removed)}`)
190
+ say.dim('Its next push creates a new site. Every existing site is untouched.')
191
+ // ⛔ The mistake this cannot prevent: running it in the ORIGINAL. That project's
192
+ // next push would then mint a second site on every backend. Both files are
193
+ // committed, so the way back is one command — say it now, while it is cheap.
194
+ say.dim('If this was the original rather than a copy, restore sync.json and deploy.yml from git.')
195
+
196
+ return { exitCode: 0, forgot: origins || [], removed }
197
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * handoff — RESERVED (not available on the new backend yet).
3
+ *
4
+ * The "create a site and hand it off to a client" flow ran against the legacy
5
+ * platform (the PHP backend + Cloudflare Worker) the CLI no longer talks to. A
6
+ * new-backend CLI equivalent isn't built; the command name is kept reserved so a
7
+ * future rebuild can claim it without a breaking change.
8
+ */
9
+
10
+ export async function handoff() {
11
+ console.error(
12
+ "\x1b[31m✗\x1b[0m `uniweb handoff` isn't available on the new backend yet."
13
+ )
14
+ console.error(
15
+ ' The legacy site-handoff flow was retired with the PHP backend.'
16
+ )
17
+ console.error(' Manage client sites from the Uniweb app for now.')
18
+ process.exit(1)
19
+ }
20
+
21
+ export default handoff