uniweb 0.13.17 → 0.14.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 +1 -1
- package/package.json +7 -7
- package/partials/agents.md +51 -0
- package/src/backend/client.js +173 -40
- package/src/backend/data-bundle.js +15 -3
- package/src/backend/foundation-bring-along.js +76 -21
- package/src/backend/payment-handoff.js +28 -9
- package/src/backend/site-media.js +147 -19
- package/src/backend/site-sync.js +362 -40
- package/src/commands/add.js +575 -273
- package/src/commands/build.js +160 -57
- package/src/commands/clone.js +79 -26
- package/src/commands/content.js +11 -7
- package/src/commands/deploy.js +80 -32
- package/src/commands/dev.js +39 -17
- package/src/commands/docs.js +44 -16
- package/src/commands/doctor.js +338 -82
- package/src/commands/export.js +15 -7
- package/src/commands/handoff.js +6 -2
- package/src/commands/i18n.js +248 -99
- package/src/commands/inspect.js +48 -19
- package/src/commands/invite.js +9 -3
- package/src/commands/org.js +19 -5
- package/src/commands/publish.js +229 -60
- package/src/commands/pull.js +157 -48
- package/src/commands/push.js +144 -42
- package/src/commands/refresh.js +37 -10
- package/src/commands/register.js +235 -64
- package/src/commands/rename.js +126 -46
- package/src/commands/runtime.js +74 -24
- package/src/commands/status.js +48 -15
- package/src/commands/sync.js +7 -2
- package/src/commands/template.js +12 -4
- package/src/commands/update.js +263 -87
- package/src/commands/validate.js +115 -32
- package/src/framework-index.json +15 -13
- package/src/index.js +298 -145
- package/src/templates/fetchers/github.js +7 -7
- package/src/templates/fetchers/npm.js +3 -3
- package/src/templates/fetchers/release.js +21 -18
- package/src/templates/index.js +34 -12
- package/src/templates/processor.js +44 -12
- package/src/templates/resolver.js +42 -15
- package/src/templates/validator.js +14 -7
- package/src/utils/asset-upload.js +90 -22
- package/src/utils/code-upload.js +62 -37
- package/src/utils/config.js +31 -10
- package/src/utils/dep-survey.js +33 -7
- package/src/utils/destination-prompt.js +27 -17
- package/src/utils/git.js +66 -22
- package/src/utils/host-prompt.js +4 -3
- package/src/utils/install-integrity.js +8 -4
- package/src/utils/interactive.js +3 -3
- package/src/utils/json-file.js +6 -2
- package/src/utils/names.js +15 -6
- package/src/utils/placement.js +16 -8
- package/src/utils/registry-auth.js +185 -57
- package/src/utils/registry-orgs.js +142 -53
- package/src/utils/runtime-upload.js +52 -14
- package/src/utils/scaffold.js +55 -14
- package/src/utils/site-content-refs.js +3 -1
- package/src/utils/update-check.js +43 -17
- package/src/utils/workspace.js +13 -7
- package/src/versions.js +18 -7
- package/templates/site/_gitignore +5 -0
package/src/commands/clone.js
CHANGED
|
@@ -58,8 +58,14 @@ import { isNonInteractive, getCliPrefix } from '../utils/interactive.js'
|
|
|
58
58
|
import { extractFoundationRef } from '../utils/site-content-refs.js'
|
|
59
59
|
|
|
60
60
|
const colors = {
|
|
61
|
-
reset: '\x1b[0m',
|
|
62
|
-
|
|
61
|
+
reset: '\x1b[0m',
|
|
62
|
+
bright: '\x1b[1m',
|
|
63
|
+
dim: '\x1b[2m',
|
|
64
|
+
red: '\x1b[31m',
|
|
65
|
+
green: '\x1b[32m',
|
|
66
|
+
yellow: '\x1b[33m',
|
|
67
|
+
blue: '\x1b[36m',
|
|
68
|
+
cyan: '\x1b[36m'
|
|
63
69
|
}
|
|
64
70
|
const log = console.log
|
|
65
71
|
const success = (m) => log(`${colors.green}✓${colors.reset} ${m}`)
|
|
@@ -71,12 +77,16 @@ function flagValue(args, name) {
|
|
|
71
77
|
const eq = args.find((a) => a.startsWith(`${name}=`))
|
|
72
78
|
if (eq) return eq.slice(name.length + 1)
|
|
73
79
|
const i = args.indexOf(name)
|
|
74
|
-
if (i !== -1 && args[i + 1] && !args[i + 1].startsWith('-'))
|
|
80
|
+
if (i !== -1 && args[i + 1] && !args[i + 1].startsWith('-'))
|
|
81
|
+
return args[i + 1]
|
|
75
82
|
return null
|
|
76
83
|
}
|
|
77
84
|
|
|
78
85
|
function slugify(s) {
|
|
79
|
-
return String(s || '')
|
|
86
|
+
return String(s || '')
|
|
87
|
+
.toLowerCase()
|
|
88
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
89
|
+
.replace(/^-+|-+$/g, '')
|
|
80
90
|
}
|
|
81
91
|
|
|
82
92
|
// Tolerant single-entity document extraction (mirrors pull.js; duplicated rather
|
|
@@ -110,7 +120,7 @@ export function extractCloneSeeds(document) {
|
|
|
110
120
|
const info = document?.info || {}
|
|
111
121
|
return {
|
|
112
122
|
foundationRef: extractFoundationRef(info, document),
|
|
113
|
-
name: unwrapScalar(info.name) ?? unwrapScalar(document?.name) ?? null
|
|
123
|
+
name: unwrapScalar(info.name) ?? unwrapScalar(document?.name) ?? null
|
|
114
124
|
}
|
|
115
125
|
}
|
|
116
126
|
|
|
@@ -154,27 +164,35 @@ export async function clone(args = [], deps = {}) {
|
|
|
154
164
|
|
|
155
165
|
if (!siteUuid) {
|
|
156
166
|
error('Missing site uuid.')
|
|
157
|
-
log(
|
|
158
|
-
|
|
167
|
+
log(
|
|
168
|
+
`\nUsage: ${getCliPrefix()} clone <site-uuid> [name|.] [--path <dir>] [--project <name>] [--no-collections]`
|
|
169
|
+
)
|
|
170
|
+
log(
|
|
171
|
+
`${colors.dim}Sites are private — run \`uniweb login\` first.${colors.reset}`
|
|
172
|
+
)
|
|
159
173
|
return { exitCode: 2 }
|
|
160
174
|
}
|
|
161
175
|
|
|
162
|
-
const noCollections =
|
|
176
|
+
const noCollections =
|
|
177
|
+
args.includes('--no-collections') || args.includes('--content-only')
|
|
163
178
|
const pathFlag = flagValue(args, '--path')
|
|
164
179
|
const projectFlag = flagValue(args, '--project')
|
|
165
180
|
const tokenFlag = flagValue(args, '--token')
|
|
166
|
-
const explicitBackend =
|
|
181
|
+
const explicitBackend =
|
|
182
|
+
flagValue(args, '--backend') || flagValue(args, '--registry')
|
|
167
183
|
const client = new BackendClient({
|
|
168
184
|
originFlag: explicitBackend,
|
|
169
185
|
token: tokenFlag,
|
|
170
186
|
getToken: deps.getToken,
|
|
171
187
|
fetchImpl: deps.fetch,
|
|
172
188
|
args,
|
|
173
|
-
command: 'Cloning'
|
|
189
|
+
command: 'Cloning'
|
|
174
190
|
})
|
|
175
191
|
|
|
176
192
|
// 1. GET the site-content document (no @uniweb/build needed for a read).
|
|
177
|
-
info(
|
|
193
|
+
info(
|
|
194
|
+
`Reading site ${colors.bright}${siteUuid}${colors.reset} from ${colors.dim}${client.origin}${colors.reset} …`
|
|
195
|
+
)
|
|
178
196
|
let payload
|
|
179
197
|
try {
|
|
180
198
|
const res = await client.pullSiteContent(siteUuid)
|
|
@@ -184,7 +202,8 @@ export async function clone(args = [], deps = {}) {
|
|
|
184
202
|
}
|
|
185
203
|
if (!res.ok) {
|
|
186
204
|
error(`Could not read the site: HTTP ${res.status} ${res.statusText}`)
|
|
187
|
-
if (res.status === 401 || res.status === 403)
|
|
205
|
+
if (res.status === 401 || res.status === 403)
|
|
206
|
+
note('Run `uniweb login` first (or pass --token <bearer>).')
|
|
188
207
|
return { exitCode: 1 }
|
|
189
208
|
}
|
|
190
209
|
payload = await res.json()
|
|
@@ -200,7 +219,9 @@ export async function clone(args = [], deps = {}) {
|
|
|
200
219
|
}
|
|
201
220
|
const { foundationRef, name: siteDisplayName } = extractCloneSeeds(document)
|
|
202
221
|
if (!foundationRef) {
|
|
203
|
-
note(
|
|
222
|
+
note(
|
|
223
|
+
'! The pulled site declares no foundation ref — set `foundation:` in site.yml after clone.'
|
|
224
|
+
)
|
|
204
225
|
}
|
|
205
226
|
|
|
206
227
|
// 2. Resolve placement (one verb, context-aware).
|
|
@@ -224,7 +245,12 @@ export async function clone(args = [], deps = {}) {
|
|
|
224
245
|
} else if (existingRoot) {
|
|
225
246
|
isNewWorkspace = false
|
|
226
247
|
projectDir = existingRoot
|
|
227
|
-
placement = resolvePlacement(
|
|
248
|
+
placement = resolvePlacement(
|
|
249
|
+
existingRoot,
|
|
250
|
+
target,
|
|
251
|
+
{ path: pathFlag, project: projectFlag },
|
|
252
|
+
SITE_KIND
|
|
253
|
+
)
|
|
228
254
|
siteDir = join(existingRoot, placement.relativePath)
|
|
229
255
|
sitePkgName = placement.packageName
|
|
230
256
|
workspaceName = sitePkgName
|
|
@@ -232,11 +258,15 @@ export async function clone(args = [], deps = {}) {
|
|
|
232
258
|
isNewWorkspace = true
|
|
233
259
|
workspaceName = target || slugify(siteDisplayName) || null
|
|
234
260
|
if (!workspaceName) {
|
|
235
|
-
error(
|
|
261
|
+
error(
|
|
262
|
+
'Could not derive a project name from the site. Pass one: `uniweb clone <uuid> <name>`.'
|
|
263
|
+
)
|
|
236
264
|
return { exitCode: 2 }
|
|
237
265
|
}
|
|
238
266
|
if (!/^[a-z0-9-]+$/.test(workspaceName)) {
|
|
239
|
-
error(
|
|
267
|
+
error(
|
|
268
|
+
`Invalid project name "${workspaceName}" — use lowercase letters, numbers, and hyphens.`
|
|
269
|
+
)
|
|
240
270
|
return { exitCode: 2 }
|
|
241
271
|
}
|
|
242
272
|
projectDir = resolve(cwd, workspaceName)
|
|
@@ -256,18 +286,28 @@ export async function clone(args = [], deps = {}) {
|
|
|
256
286
|
|
|
257
287
|
// 3. Scaffold the harness (ref-only site: foundationRef, no foundationPath).
|
|
258
288
|
const onProgress = (m) => note(m)
|
|
259
|
-
const siteContext = {
|
|
289
|
+
const siteContext = {
|
|
290
|
+
name: sitePkgName,
|
|
291
|
+
projectName: workspaceName,
|
|
292
|
+
...(foundationRef ? { foundationRef } : {})
|
|
293
|
+
}
|
|
260
294
|
|
|
261
295
|
if (isNewWorkspace) {
|
|
262
296
|
info(`Scaffolding ${colors.bright}${workspaceName}${colors.reset} …`)
|
|
263
297
|
await scaffoldWorkspace(
|
|
264
298
|
projectDir,
|
|
265
|
-
{
|
|
266
|
-
|
|
299
|
+
{
|
|
300
|
+
projectName: workspaceName,
|
|
301
|
+
workspaceGlobs: ['site'],
|
|
302
|
+
scripts: { dev: 'uniweb dev', build: 'uniweb build' }
|
|
303
|
+
},
|
|
304
|
+
{ onProgress }
|
|
267
305
|
)
|
|
268
306
|
await scaffoldSite(siteDir, siteContext, { onProgress })
|
|
269
307
|
} else {
|
|
270
|
-
info(
|
|
308
|
+
info(
|
|
309
|
+
`Adding site ${colors.bright}${sitePkgName}${colors.reset} to the workspace at ${colors.dim}${placement.relativePath}/${colors.reset} …`
|
|
310
|
+
)
|
|
271
311
|
await scaffoldSite(siteDir, siteContext, { onProgress })
|
|
272
312
|
await addWorkspaceGlob(existingRoot, placement.relativePath)
|
|
273
313
|
}
|
|
@@ -276,7 +316,9 @@ export async function clone(args = [], deps = {}) {
|
|
|
276
316
|
// same uuid (the backend resolves the site's @uniweb/folder from it), so there is no
|
|
277
317
|
// separate folder uuid to seed.
|
|
278
318
|
seedYamlUuid(join(siteDir, 'site.yml'), siteUuid)
|
|
279
|
-
success(
|
|
319
|
+
success(
|
|
320
|
+
`Scaffolded the site harness${foundationRef ? ` (foundation: ${foundationRef})` : ''}.`
|
|
321
|
+
)
|
|
280
322
|
|
|
281
323
|
// 5. Install, then delegate the projection to the project-local `uniweb pull`.
|
|
282
324
|
const pm = detectWorkspacePm(projectDir) || 'pnpm'
|
|
@@ -289,7 +331,9 @@ export async function clone(args = [], deps = {}) {
|
|
|
289
331
|
info(`Installing dependencies (${installCmd(pm)}) …`)
|
|
290
332
|
const r = spawnSync(pm, ['install'], { cwd: projectDir, stdio: 'inherit' })
|
|
291
333
|
if (r.status !== 0) {
|
|
292
|
-
error(
|
|
334
|
+
error(
|
|
335
|
+
`Install failed. Once it succeeds, run \`uniweb pull\` from ${siteDir} to fetch the content.`
|
|
336
|
+
)
|
|
293
337
|
return { exitCode: 1 }
|
|
294
338
|
}
|
|
295
339
|
}
|
|
@@ -305,17 +349,26 @@ export async function clone(args = [], deps = {}) {
|
|
|
305
349
|
await deps.runPull(siteDir, pm, pullExtra)
|
|
306
350
|
} else {
|
|
307
351
|
info('Pulling content …')
|
|
308
|
-
const r = spawnSync(pm, pullExecArgv(pm, pullExtra), {
|
|
352
|
+
const r = spawnSync(pm, pullExecArgv(pm, pullExtra), {
|
|
353
|
+
cwd: siteDir,
|
|
354
|
+
stdio: 'inherit'
|
|
355
|
+
})
|
|
309
356
|
if (r.status !== 0) {
|
|
310
|
-
error(
|
|
357
|
+
error(
|
|
358
|
+
`Content pull failed. Fix the issue, then run \`uniweb pull\` from ${siteDir}.`
|
|
359
|
+
)
|
|
311
360
|
return { exitCode: 1 }
|
|
312
361
|
}
|
|
313
362
|
}
|
|
314
363
|
|
|
315
364
|
log('')
|
|
316
|
-
success(
|
|
365
|
+
success(
|
|
366
|
+
`Cloned site into ${colors.bright}${isNewWorkspace && !inPlace ? workspaceName : siteDir}${colors.reset}`
|
|
367
|
+
)
|
|
317
368
|
if (isNewWorkspace && !inPlace) {
|
|
318
|
-
log(
|
|
369
|
+
log(
|
|
370
|
+
`\nNext: ${colors.cyan}cd ${workspaceName} && uniweb dev${colors.reset}`
|
|
371
|
+
)
|
|
319
372
|
} else {
|
|
320
373
|
log(`\nNext: ${colors.cyan}uniweb dev${colors.reset}`)
|
|
321
374
|
}
|
package/src/commands/content.js
CHANGED
|
@@ -28,14 +28,14 @@ const c = {
|
|
|
28
28
|
cyan: '\x1b[36m',
|
|
29
29
|
green: '\x1b[32m',
|
|
30
30
|
red: '\x1b[31m',
|
|
31
|
-
yellow: '\x1b[33m'
|
|
31
|
+
yellow: '\x1b[33m'
|
|
32
32
|
}
|
|
33
33
|
const say = {
|
|
34
34
|
ok: (m) => console.log(`${c.green}✓${c.reset} ${m}`),
|
|
35
35
|
info: (m) => console.log(`${c.cyan}→${c.reset} ${m}`),
|
|
36
36
|
err: (m) => console.error(`${c.red}✗${c.reset} ${m}`),
|
|
37
37
|
warn: (m) => console.log(`${c.yellow}!${c.reset} ${m}`),
|
|
38
|
-
dim: (m) => console.log(` ${c.dim}${m}${c.reset}`)
|
|
38
|
+
dim: (m) => console.log(` ${c.dim}${m}${c.reset}`)
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
function usage() {
|
|
@@ -103,16 +103,18 @@ async function contentExport(args) {
|
|
|
103
103
|
// export), so a fresh project exports a uuid-less document.
|
|
104
104
|
buf = await uwx.emitSiteSyncPackage(dir, {
|
|
105
105
|
sidecar: useSidecar, // read <dir>/.uniweb/uwx-ids.json if present
|
|
106
|
-
sourceLocale
|
|
106
|
+
sourceLocale
|
|
107
107
|
})
|
|
108
108
|
defaultName = basename(dir)
|
|
109
109
|
} else {
|
|
110
110
|
const schema = JSON.parse(readFileSync(schemaPath, 'utf8'))
|
|
111
|
-
say.info(
|
|
111
|
+
say.info(
|
|
112
|
+
`Packaging foundation schema → @uniweb/foundation-schema (.uwx)…`
|
|
113
|
+
)
|
|
112
114
|
buf = uwx.emitFoundationSchemaPackage(schema, {
|
|
113
115
|
sidecar: useSidecar ? join(dir, uwx.SIDECAR_RELPATH) : undefined,
|
|
114
116
|
foundationDir: dir,
|
|
115
|
-
sourceLocale
|
|
117
|
+
sourceLocale
|
|
116
118
|
})
|
|
117
119
|
defaultName = (schema?._self?.name || basename(dir)).replace(
|
|
118
120
|
/[^a-z0-9._-]+/gi,
|
|
@@ -157,9 +159,11 @@ async function contentExport(args) {
|
|
|
157
159
|
const pg = countPages(entity.pages)
|
|
158
160
|
if (pg.p) counts.pages = pg.p
|
|
159
161
|
if (pg.s) counts.page_sections = pg.s
|
|
160
|
-
if (entity.layout_sections?.length)
|
|
162
|
+
if (entity.layout_sections?.length)
|
|
163
|
+
counts.layout_sections = entity.layout_sections.length
|
|
161
164
|
if (entity.extensions?.length) counts.extensions = entity.extensions.length
|
|
162
|
-
if (entity.collections?.length)
|
|
165
|
+
if (entity.collections?.length)
|
|
166
|
+
counts.collections = entity.collections.length
|
|
163
167
|
}
|
|
164
168
|
|
|
165
169
|
console.log('')
|
package/src/commands/deploy.js
CHANGED
|
@@ -49,7 +49,11 @@ import { existsSync, readFileSync } from 'node:fs'
|
|
|
49
49
|
import { resolve, join } from 'node:path'
|
|
50
50
|
import { execSync } from 'node:child_process'
|
|
51
51
|
|
|
52
|
-
import {
|
|
52
|
+
import {
|
|
53
|
+
loadDeployYml,
|
|
54
|
+
resolveTarget,
|
|
55
|
+
recordLastDeploy
|
|
56
|
+
} from '@uniweb/build/site'
|
|
53
57
|
import { promptForDestination } from '../utils/destination-prompt.js'
|
|
54
58
|
import { readFlagValue } from '../utils/args.js'
|
|
55
59
|
import { parseBoolEnv } from '../utils/env.js'
|
|
@@ -59,7 +63,7 @@ import {
|
|
|
59
63
|
findWorkspaceRoot,
|
|
60
64
|
findSites,
|
|
61
65
|
classifyPackage,
|
|
62
|
-
promptSelect
|
|
66
|
+
promptSelect
|
|
63
67
|
} from '../utils/workspace.js'
|
|
64
68
|
import { isNonInteractive, getCliPrefix } from '../utils/interactive.js'
|
|
65
69
|
|
|
@@ -70,14 +74,14 @@ const c = {
|
|
|
70
74
|
cyan: '\x1b[36m',
|
|
71
75
|
green: '\x1b[32m',
|
|
72
76
|
yellow: '\x1b[33m',
|
|
73
|
-
red: '\x1b[31m'
|
|
77
|
+
red: '\x1b[31m'
|
|
74
78
|
}
|
|
75
79
|
const say = {
|
|
76
80
|
ok: (m) => console.log(`${c.green}✓${c.reset} ${m}`),
|
|
77
81
|
info: (m) => console.log(`${c.cyan}→${c.reset} ${m}`),
|
|
78
82
|
warn: (m) => console.log(`${c.yellow}⚠${c.reset} ${m}`),
|
|
79
83
|
err: (m) => console.error(`${c.red}✗${c.reset} ${m}`),
|
|
80
|
-
dim: (m) => console.log(` ${c.dim}${m}${c.reset}`)
|
|
84
|
+
dim: (m) => console.log(` ${c.dim}${m}${c.reset}`)
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
// ─── Main ───────────────────────────────────────────────────
|
|
@@ -126,7 +130,7 @@ export async function deploy(args = []) {
|
|
|
126
130
|
const wantsWizard = hostFromFlag === null
|
|
127
131
|
const knownHost = wantsWizard
|
|
128
132
|
? null
|
|
129
|
-
:
|
|
133
|
+
: hostFromFlag || (resolved.fromFile ? resolved.host : null)
|
|
130
134
|
|
|
131
135
|
if (knownHost === 'uniweb') {
|
|
132
136
|
plan = { kind: 'uniweb' }
|
|
@@ -146,14 +150,23 @@ export async function deploy(args = []) {
|
|
|
146
150
|
}
|
|
147
151
|
say.err('`uniweb deploy` needs a destination.')
|
|
148
152
|
console.log('')
|
|
149
|
-
say.dim(
|
|
150
|
-
|
|
153
|
+
say.dim(
|
|
154
|
+
'`uniweb publish` Uniweb Cloud (sync + dynamic hosting; brings the foundation along)'
|
|
155
|
+
)
|
|
156
|
+
say.dim(
|
|
157
|
+
'`uniweb deploy --host=…` A third-party host — run `uniweb deploy --help` for the list'
|
|
158
|
+
)
|
|
151
159
|
say.dim('`uniweb add ci --host=…` Set up CI so every push deploys')
|
|
152
|
-
say.dim(
|
|
160
|
+
say.dim(
|
|
161
|
+
'`uniweb export` Self-contained dist/ artifact you upload anywhere'
|
|
162
|
+
)
|
|
153
163
|
process.exit(1)
|
|
154
164
|
}
|
|
155
165
|
try {
|
|
156
|
-
plan = await promptForDestination({
|
|
166
|
+
plan = await promptForDestination({
|
|
167
|
+
args,
|
|
168
|
+
preselect: resolved.fromFile ? resolved.host : null
|
|
169
|
+
})
|
|
157
170
|
} catch (err) {
|
|
158
171
|
say.err(err.message)
|
|
159
172
|
process.exit(1)
|
|
@@ -181,7 +194,9 @@ export async function deploy(args = []) {
|
|
|
181
194
|
// passing a flag it would ignore — a dry run that actually ran a full
|
|
182
195
|
// build would be a lie.
|
|
183
196
|
if (dryRun) {
|
|
184
|
-
say.info(
|
|
197
|
+
say.info(
|
|
198
|
+
'Dry run — would run `uniweb export` to build a self-contained dist/.'
|
|
199
|
+
)
|
|
185
200
|
return
|
|
186
201
|
}
|
|
187
202
|
say.info('Building a self-contained artifact → running `uniweb export`.')
|
|
@@ -208,7 +223,7 @@ export async function deploy(args = []) {
|
|
|
208
223
|
await deployStaticHost(siteDir, plan.host, resolved, {
|
|
209
224
|
dryRun,
|
|
210
225
|
autoSave,
|
|
211
|
-
hostOverridden
|
|
226
|
+
hostOverridden
|
|
212
227
|
})
|
|
213
228
|
}
|
|
214
229
|
|
|
@@ -239,7 +254,7 @@ async function delegateToAddCi(siteDir, host, dryRun) {
|
|
|
239
254
|
try {
|
|
240
255
|
execSync(`node ${JSON.stringify(process.argv[1])} ${cmd}`, {
|
|
241
256
|
cwd: workspaceRoot,
|
|
242
|
-
stdio: 'inherit'
|
|
257
|
+
stdio: 'inherit'
|
|
243
258
|
})
|
|
244
259
|
} catch {
|
|
245
260
|
// add ci already printed the reason and set the exit code.
|
|
@@ -254,10 +269,15 @@ async function delegateToAddCi(siteDir, host, dryRun) {
|
|
|
254
269
|
// `uniweb build` (bundle mode + prerender) first, then hands dist/ to the
|
|
255
270
|
// adapter's deploy hook for upload + invalidation.
|
|
256
271
|
|
|
257
|
-
async function deployStaticHost(
|
|
272
|
+
async function deployStaticHost(
|
|
273
|
+
siteDir,
|
|
274
|
+
hostName,
|
|
275
|
+
resolved,
|
|
276
|
+
{ dryRun, autoSave, hostOverridden }
|
|
277
|
+
) {
|
|
258
278
|
let getAdapter
|
|
259
279
|
try {
|
|
260
|
-
({ getAdapter } = await import('@uniweb/build/hosts'))
|
|
280
|
+
;({ getAdapter } = await import('@uniweb/build/hosts'))
|
|
261
281
|
} catch (err) {
|
|
262
282
|
say.err('Failed to load host adapter registry from @uniweb/build/hosts.')
|
|
263
283
|
say.dim(err.message)
|
|
@@ -269,14 +289,20 @@ async function deployStaticHost(siteDir, hostName, resolved, { dryRun, autoSave,
|
|
|
269
289
|
adapter = getAdapter(hostName)
|
|
270
290
|
} catch (err) {
|
|
271
291
|
say.err(err.message)
|
|
272
|
-
say.dim(
|
|
292
|
+
say.dim(
|
|
293
|
+
'Set the host in deploy.yml or pass --host=<name>. See `uniweb deploy --help`.'
|
|
294
|
+
)
|
|
273
295
|
process.exit(1)
|
|
274
296
|
}
|
|
275
297
|
|
|
276
298
|
if (typeof adapter.deploy !== 'function') {
|
|
277
299
|
say.err(`Host adapter '${hostName}' does not implement a deploy step.`)
|
|
278
|
-
say.dim(
|
|
279
|
-
|
|
300
|
+
say.dim(
|
|
301
|
+
`Build with \`uniweb build --host=${hostName}\` and upload \`dist/\` manually,`
|
|
302
|
+
)
|
|
303
|
+
say.dim(
|
|
304
|
+
`or use a host whose adapter ships a deploy hook (e.g., s3-cloudfront).`
|
|
305
|
+
)
|
|
280
306
|
process.exit(1)
|
|
281
307
|
}
|
|
282
308
|
|
|
@@ -284,11 +310,16 @@ async function deployStaticHost(siteDir, hostName, resolved, { dryRun, autoSave,
|
|
|
284
310
|
const distDir = join(siteDir, 'dist')
|
|
285
311
|
|
|
286
312
|
if (dryRun) {
|
|
287
|
-
say.info(
|
|
313
|
+
say.info(
|
|
314
|
+
`Dry run — would deploy via host adapter: ${c.bold}${adapter.name}${c.reset}`
|
|
315
|
+
)
|
|
288
316
|
say.dim(`Site dir : ${siteDir}`)
|
|
289
|
-
say.dim(
|
|
317
|
+
say.dim(
|
|
318
|
+
`dist/ : ${existsSync(distDir) ? 'exists (would not rebuild)' : 'missing (would build)'}`
|
|
319
|
+
)
|
|
290
320
|
say.dim(`Target : ${resolved.targetName}`)
|
|
291
|
-
if (adapter.display?.pushWith)
|
|
321
|
+
if (adapter.display?.pushWith)
|
|
322
|
+
say.dim(`Uploads by: ${adapter.display.pushWith}`)
|
|
292
323
|
// Echo the resolved target's config as-is. Each adapter reads
|
|
293
324
|
// different keys (bucket/distributionId for s3, projectName for
|
|
294
325
|
// Cloudflare, siteId for Netlify), so listing a fixed set would be
|
|
@@ -297,10 +328,14 @@ async function deployStaticHost(siteDir, hostName, resolved, { dryRun, autoSave,
|
|
|
297
328
|
if (configKeys.length) {
|
|
298
329
|
say.dim('Config :')
|
|
299
330
|
for (const key of configKeys.sort()) {
|
|
300
|
-
say.dim(
|
|
331
|
+
say.dim(
|
|
332
|
+
` ${key}: ${typeof deployConfig[key] === 'object' ? JSON.stringify(deployConfig[key]) : deployConfig[key]}`
|
|
333
|
+
)
|
|
301
334
|
}
|
|
302
335
|
} else {
|
|
303
|
-
say.dim(
|
|
336
|
+
say.dim(
|
|
337
|
+
'Config : (none in deploy.yml — the adapter will say what it needs)'
|
|
338
|
+
)
|
|
304
339
|
}
|
|
305
340
|
return
|
|
306
341
|
}
|
|
@@ -342,7 +377,7 @@ async function deployStaticHost(siteDir, hostName, resolved, { dryRun, autoSave,
|
|
|
342
377
|
distDir,
|
|
343
378
|
deployConfig,
|
|
344
379
|
env: process.env,
|
|
345
|
-
log: (m) => console.log(m)
|
|
380
|
+
log: (m) => console.log(m)
|
|
346
381
|
})
|
|
347
382
|
} catch (err) {
|
|
348
383
|
if (err && err.name === 'DeployError') {
|
|
@@ -358,10 +393,15 @@ async function deployStaticHost(siteDir, hostName, resolved, { dryRun, autoSave,
|
|
|
358
393
|
|
|
359
394
|
// Record a fresh lastDeploy.<target> entry. Skipped on --no-save and
|
|
360
395
|
// on ad-hoc --host overrides — see autoSave gating in deploy().
|
|
361
|
-
const gitStamp = {
|
|
396
|
+
const gitStamp = {
|
|
397
|
+
at: new Date().toISOString(),
|
|
398
|
+
git: headProvenance(siteDir)
|
|
399
|
+
}
|
|
362
400
|
await persistLastDeploy(siteDir, {
|
|
363
401
|
targetName: resolved.targetName,
|
|
364
|
-
targetConfig: resolved.fromFile
|
|
402
|
+
targetConfig: resolved.fromFile
|
|
403
|
+
? null
|
|
404
|
+
: { host: hostName, ...deployConfig },
|
|
365
405
|
autoSave,
|
|
366
406
|
lastDeploy: {
|
|
367
407
|
at: gitStamp.at,
|
|
@@ -374,11 +414,13 @@ async function deployStaticHost(siteDir, hostName, resolved, { dryRun, autoSave,
|
|
|
374
414
|
...(deployResult?.url ? { url: deployResult.url } : {}),
|
|
375
415
|
// Same provenance the Uniweb path records: which commit this artifact came
|
|
376
416
|
// from, and whether the tree was clean when it was built.
|
|
377
|
-
...(gitStamp.git ? { git: gitStamp.git } : {})
|
|
378
|
-
}
|
|
417
|
+
...(gitStamp.git ? { git: gitStamp.git } : {})
|
|
418
|
+
}
|
|
379
419
|
})
|
|
380
420
|
if (hostOverridden && !dryRun) {
|
|
381
|
-
say.dim(
|
|
421
|
+
say.dim(
|
|
422
|
+
'--host override active — did not write to deploy.yml. Edit deploy.yml to make this permanent.'
|
|
423
|
+
)
|
|
382
424
|
}
|
|
383
425
|
}
|
|
384
426
|
|
|
@@ -425,7 +467,9 @@ export async function resolveSiteDir(args, verb = 'deploy') {
|
|
|
425
467
|
}
|
|
426
468
|
const choice = await promptSelect('Which site?', sites)
|
|
427
469
|
if (!choice) {
|
|
428
|
-
console.log(
|
|
470
|
+
console.log(
|
|
471
|
+
`\n${verb.charAt(0).toUpperCase() + verb.slice(1)} cancelled.`
|
|
472
|
+
)
|
|
429
473
|
process.exit(0)
|
|
430
474
|
}
|
|
431
475
|
return resolve(workspaceRoot, choice)
|
|
@@ -434,9 +478,13 @@ export async function resolveSiteDir(args, verb = 'deploy') {
|
|
|
434
478
|
|
|
435
479
|
say.err('No site found in this workspace.')
|
|
436
480
|
if (verb === 'export') {
|
|
437
|
-
say.dim(
|
|
481
|
+
say.dim(
|
|
482
|
+
'`export` produces a self-contained dist/ artifact for third-party hosting.'
|
|
483
|
+
)
|
|
438
484
|
} else if (verb === 'deploy') {
|
|
439
|
-
say.dim(
|
|
485
|
+
say.dim(
|
|
486
|
+
'`deploy` ships a built site to a third-party host (use `uniweb publish` for Uniweb hosting).'
|
|
487
|
+
)
|
|
440
488
|
} else {
|
|
441
489
|
say.dim(`\`${verb}\` operates on a site.`)
|
|
442
490
|
}
|
|
@@ -455,7 +503,7 @@ export async function resolveSiteBackend(siteDir) {
|
|
|
455
503
|
try {
|
|
456
504
|
const deployYml = await loadDeployYml(siteDir)
|
|
457
505
|
const resolved = resolveTarget(deployYml, null)
|
|
458
|
-
return resolved.host === 'uniweb' ?
|
|
506
|
+
return resolved.host === 'uniweb' ? resolved.config?.backend || null : null
|
|
459
507
|
} catch {
|
|
460
508
|
return null
|
|
461
509
|
}
|
package/src/commands/dev.js
CHANGED
|
@@ -40,7 +40,10 @@ import { readWorkspaceConfig } from '../utils/config.js'
|
|
|
40
40
|
import { discoverSites } from '../utils/discover.js'
|
|
41
41
|
import { findWorkspaceRoot } from '../utils/workspace.js'
|
|
42
42
|
import { readFlagValue } from '../utils/args.js'
|
|
43
|
-
import {
|
|
43
|
+
import {
|
|
44
|
+
checkSiteInstall,
|
|
45
|
+
readDeclaredFoundation
|
|
46
|
+
} from '../utils/install-integrity.js'
|
|
44
47
|
import { discoverFoundations } from '../utils/discover.js'
|
|
45
48
|
|
|
46
49
|
const RED = '\x1b[31m'
|
|
@@ -62,8 +65,12 @@ export async function dev(args = []) {
|
|
|
62
65
|
workspaceConfig = { packages: [] }
|
|
63
66
|
}
|
|
64
67
|
if (workspaceConfig.packages.length === 0) {
|
|
65
|
-
console.error(
|
|
66
|
-
|
|
68
|
+
console.error(
|
|
69
|
+
`${RED}✗${RESET} Not in a Uniweb workspace (no pnpm-workspace.yaml or package.json::workspaces).`
|
|
70
|
+
)
|
|
71
|
+
console.error(
|
|
72
|
+
` Run \`uniweb create <name>\` to scaffold a project, or cd into an existing one.`
|
|
73
|
+
)
|
|
67
74
|
process.exit(1)
|
|
68
75
|
}
|
|
69
76
|
|
|
@@ -76,24 +83,29 @@ export async function dev(args = []) {
|
|
|
76
83
|
|
|
77
84
|
// Pick the site
|
|
78
85
|
const siteFlag = readFlagValue(args, '--site')
|
|
79
|
-
const positional = args.find(a => !a.startsWith('-'))
|
|
80
|
-
const requested =
|
|
86
|
+
const positional = args.find((a) => !a.startsWith('-'))
|
|
87
|
+
const requested =
|
|
88
|
+
(typeof siteFlag === 'string' ? siteFlag : null) || positional || null
|
|
81
89
|
|
|
82
90
|
let site
|
|
83
91
|
if (requested) {
|
|
84
|
-
site =
|
|
92
|
+
site =
|
|
93
|
+
sites.find((s) => s.name === requested) ||
|
|
94
|
+
sites.find((s) => s.path === requested)
|
|
85
95
|
if (!site) {
|
|
86
96
|
console.error(`${RED}✗${RESET} Site "${requested}" not found.`)
|
|
87
|
-
console.error(` Available: ${sites.map(s => s.name).join(', ')}`)
|
|
97
|
+
console.error(` Available: ${sites.map((s) => s.name).join(', ')}`)
|
|
88
98
|
process.exit(1)
|
|
89
99
|
}
|
|
90
100
|
} else if (sites.length === 1) {
|
|
91
101
|
site = sites[0]
|
|
92
102
|
} else {
|
|
93
103
|
site = sites[0]
|
|
94
|
-
console.error(
|
|
104
|
+
console.error(
|
|
105
|
+
`${YELLOW}⚠${RESET} Multiple sites found; using ${CYAN}${site.name}${RESET}.`
|
|
106
|
+
)
|
|
95
107
|
console.error(` Pick a different one with \`uniweb dev --site <name>\`.`)
|
|
96
|
-
console.error(` Available: ${sites.map(s => s.name).join(', ')}`)
|
|
108
|
+
console.error(` Available: ${sites.map((s) => s.name).join(', ')}`)
|
|
97
109
|
console.error('')
|
|
98
110
|
}
|
|
99
111
|
|
|
@@ -109,12 +121,14 @@ export async function dev(args = []) {
|
|
|
109
121
|
const [bin, ...rest] = command.split(' ')
|
|
110
122
|
const sitePath = join(rootDir, site.path)
|
|
111
123
|
|
|
112
|
-
console.error(
|
|
124
|
+
console.error(
|
|
125
|
+
`${DIM}→ ${command}${RESET} ${DIM}(site: ${site.name}, dir: ${sitePath})${RESET}`
|
|
126
|
+
)
|
|
113
127
|
console.error('')
|
|
114
128
|
|
|
115
129
|
const child = spawn(bin, rest, { cwd: rootDir, stdio: 'inherit' })
|
|
116
|
-
child.on('close', code => process.exit(code ?? 0))
|
|
117
|
-
child.on('error', err => {
|
|
130
|
+
child.on('close', (code) => process.exit(code ?? 0))
|
|
131
|
+
child.on('error', (err) => {
|
|
118
132
|
console.error(`${RED}✗${RESET} Failed to start dev server: ${err.message}`)
|
|
119
133
|
process.exit(1)
|
|
120
134
|
})
|
|
@@ -133,18 +147,26 @@ async function warnOnStaleInstall(rootDir, site) {
|
|
|
133
147
|
|
|
134
148
|
const foundations = await discoverFoundations(rootDir)
|
|
135
149
|
const foundation = foundations
|
|
136
|
-
.map(f => ({ ...f, path: join(rootDir, f.path) }))
|
|
137
|
-
.find(f => f.name === foundationName)
|
|
150
|
+
.map((f) => ({ ...f, path: join(rootDir, f.path) }))
|
|
151
|
+
.find((f) => f.name === foundationName)
|
|
138
152
|
if (!foundation) return
|
|
139
153
|
|
|
140
|
-
const findings = checkSiteInstall(
|
|
154
|
+
const findings = checkSiteInstall(
|
|
155
|
+
{ name: site.name, path: sitePath },
|
|
156
|
+
foundation,
|
|
157
|
+
rootDir
|
|
158
|
+
)
|
|
141
159
|
if (!findings.length) return
|
|
142
160
|
|
|
143
|
-
console.error(
|
|
161
|
+
console.error(
|
|
162
|
+
`${YELLOW}⚠${RESET} Dev will not build against your workspace foundation:`
|
|
163
|
+
)
|
|
144
164
|
for (const finding of findings) {
|
|
145
165
|
console.error(` ${finding.message}`)
|
|
146
166
|
}
|
|
147
|
-
console.error(
|
|
167
|
+
console.error(
|
|
168
|
+
` ${findings[0].remedy}. \`uniweb doctor\` explains in full.`
|
|
169
|
+
)
|
|
148
170
|
console.error('')
|
|
149
171
|
} catch {
|
|
150
172
|
// A diagnostic must never be the reason dev fails to start.
|