uniweb 0.13.16 → 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 +16 -14
- 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/index.js
CHANGED
|
@@ -41,12 +41,31 @@ import { template } from './commands/template.js'
|
|
|
41
41
|
import {
|
|
42
42
|
resolveTemplate,
|
|
43
43
|
parseTemplateId,
|
|
44
|
-
buildTemplateChoices
|
|
44
|
+
buildTemplateChoices
|
|
45
45
|
} from './templates/index.js'
|
|
46
46
|
import { validateTemplate } from './templates/validator.js'
|
|
47
|
-
import {
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
import {
|
|
48
|
+
scaffoldWorkspace,
|
|
49
|
+
scaffoldFoundation,
|
|
50
|
+
scaffoldSite,
|
|
51
|
+
applyContent,
|
|
52
|
+
applyStarter,
|
|
53
|
+
mergeTemplateDependencies,
|
|
54
|
+
getWorkspaceTemplateOutputs
|
|
55
|
+
} from './utils/scaffold.js'
|
|
56
|
+
import {
|
|
57
|
+
detectPackageManager,
|
|
58
|
+
filterCmd,
|
|
59
|
+
installCmd,
|
|
60
|
+
runCmd,
|
|
61
|
+
isPnpmAvailable
|
|
62
|
+
} from './utils/pm.js'
|
|
63
|
+
import {
|
|
64
|
+
isNonInteractive,
|
|
65
|
+
getCliPrefix,
|
|
66
|
+
stripNonInteractiveFlag,
|
|
67
|
+
formatOptions
|
|
68
|
+
} from './utils/interactive.js'
|
|
50
69
|
import { findWorkspaceRoot } from './utils/workspace.js'
|
|
51
70
|
|
|
52
71
|
// Colors for terminal output
|
|
@@ -57,7 +76,7 @@ const colors = {
|
|
|
57
76
|
cyan: '\x1b[36m',
|
|
58
77
|
green: '\x1b[32m',
|
|
59
78
|
yellow: '\x1b[33m',
|
|
60
|
-
red: '\x1b[31m'
|
|
79
|
+
red: '\x1b[31m'
|
|
61
80
|
}
|
|
62
81
|
|
|
63
82
|
// Template choices for the interactive prompt — built-ins first, then every
|
|
@@ -111,7 +130,9 @@ const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
|
111
130
|
let _cliVersion = null
|
|
112
131
|
function getCliVersion() {
|
|
113
132
|
if (!_cliVersion) {
|
|
114
|
-
const pkg = JSON.parse(
|
|
133
|
+
const pkg = JSON.parse(
|
|
134
|
+
readFileSync(join(__dirname, '..', 'package.json'), 'utf8')
|
|
135
|
+
)
|
|
115
136
|
_cliVersion = pkg.version
|
|
116
137
|
}
|
|
117
138
|
return _cliVersion
|
|
@@ -126,7 +147,15 @@ function getCliVersion() {
|
|
|
126
147
|
// install that's the whole point; delegating to the project-local copy
|
|
127
148
|
// would align the project to the version it already has, i.e. a no-op.
|
|
128
149
|
const STANDALONE_COMMANDS = new Set([
|
|
129
|
-
'create',
|
|
150
|
+
'create',
|
|
151
|
+
'clone',
|
|
152
|
+
'--help',
|
|
153
|
+
'-h',
|
|
154
|
+
'--version',
|
|
155
|
+
'-v',
|
|
156
|
+
'login',
|
|
157
|
+
'logout',
|
|
158
|
+
'update'
|
|
130
159
|
])
|
|
131
160
|
|
|
132
161
|
/**
|
|
@@ -138,8 +167,10 @@ function isGlobalInstall() {
|
|
|
138
167
|
const scriptPath = process.argv[1]
|
|
139
168
|
if (!scriptPath) return false
|
|
140
169
|
// Normalize path separators for Windows compatibility
|
|
141
|
-
return
|
|
142
|
-
|
|
170
|
+
return (
|
|
171
|
+
!scriptPath.split('/').includes('node_modules') &&
|
|
172
|
+
!scriptPath.split('\\').includes('node_modules')
|
|
173
|
+
)
|
|
143
174
|
}
|
|
144
175
|
|
|
145
176
|
/**
|
|
@@ -173,9 +204,13 @@ function delegateToLocal(localCliPath) {
|
|
|
173
204
|
const yellow = '\x1b[33m'
|
|
174
205
|
const dim = '\x1b[2m'
|
|
175
206
|
const reset = '\x1b[0m'
|
|
176
|
-
console.error(
|
|
207
|
+
console.error(
|
|
208
|
+
`${yellow}Note:${reset} Global CLI is ${dim}${globalVersion}${reset}, project has ${dim}${localPkg.version}${reset} ${dim}(using project version)${reset}`
|
|
209
|
+
)
|
|
177
210
|
}
|
|
178
|
-
} catch {
|
|
211
|
+
} catch {
|
|
212
|
+
/* ignore — version check is best-effort */
|
|
213
|
+
}
|
|
179
214
|
|
|
180
215
|
return new Promise((resolve, reject) => {
|
|
181
216
|
const child = spawnChild(
|
|
@@ -198,10 +233,15 @@ async function importProjectCommand(modulePath) {
|
|
|
198
233
|
try {
|
|
199
234
|
return await import(modulePath)
|
|
200
235
|
} catch (err) {
|
|
201
|
-
if (
|
|
236
|
+
if (
|
|
237
|
+
err.code === 'ERR_MODULE_NOT_FOUND' &&
|
|
238
|
+
err.message?.includes('@uniweb/')
|
|
239
|
+
) {
|
|
202
240
|
error('This command must be run from inside a Uniweb project.')
|
|
203
241
|
log('')
|
|
204
|
-
log(
|
|
242
|
+
log(
|
|
243
|
+
`Make sure you're in a project directory with dependencies installed:`
|
|
244
|
+
)
|
|
205
245
|
log(` ${colors.cyan}cd your-project${colors.reset}`)
|
|
206
246
|
log(` ${colors.cyan}npm install${colors.reset}`)
|
|
207
247
|
log('')
|
|
@@ -216,7 +256,11 @@ async function importProjectCommand(modulePath) {
|
|
|
216
256
|
/**
|
|
217
257
|
* Create a project using the new package template flow (default)
|
|
218
258
|
*/
|
|
219
|
-
async function createFromPackageTemplates(
|
|
259
|
+
async function createFromPackageTemplates(
|
|
260
|
+
projectDir,
|
|
261
|
+
projectName,
|
|
262
|
+
options = {}
|
|
263
|
+
) {
|
|
220
264
|
const { onProgress, onWarning, pm = 'pnpm', includeStarter = true } = options
|
|
221
265
|
|
|
222
266
|
onProgress?.('Setting up workspace...')
|
|
@@ -226,36 +270,48 @@ async function createFromPackageTemplates(projectDir, projectName, options = {})
|
|
|
226
270
|
// (the verb resolves the right PM at runtime instead of locking the
|
|
227
271
|
// root scripts to whichever PM ran `npx uniweb create`). preview stays
|
|
228
272
|
// PM-filtered until a `uniweb preview` verb exists.
|
|
229
|
-
await scaffoldWorkspace(
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
273
|
+
await scaffoldWorkspace(
|
|
274
|
+
projectDir,
|
|
275
|
+
{
|
|
276
|
+
projectName,
|
|
277
|
+
workspaceGlobs: ['site', 'src'],
|
|
278
|
+
scripts: {
|
|
279
|
+
dev: 'uniweb dev',
|
|
280
|
+
build: 'uniweb build',
|
|
281
|
+
preview: filterCmd(pm, 'site', 'preview')
|
|
282
|
+
}
|
|
236
283
|
},
|
|
237
|
-
|
|
284
|
+
{ onProgress, onWarning }
|
|
285
|
+
)
|
|
238
286
|
|
|
239
287
|
// 2. Scaffold foundation (folder: src/, package name: src)
|
|
240
288
|
// The folder name 'src' carries the meaning — a foundation is the site's
|
|
241
289
|
// source code. The package name 'src' keeps it unique within the
|
|
242
290
|
// workspace, since 'site' is taken by the site package.
|
|
243
291
|
onProgress?.('Creating foundation...')
|
|
244
|
-
await scaffoldFoundation(
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
292
|
+
await scaffoldFoundation(
|
|
293
|
+
join(projectDir, 'src'),
|
|
294
|
+
{
|
|
295
|
+
name: 'src',
|
|
296
|
+
projectName,
|
|
297
|
+
isExtension: false
|
|
298
|
+
},
|
|
299
|
+
{ onProgress, onWarning }
|
|
300
|
+
)
|
|
249
301
|
|
|
250
302
|
// 3. Scaffold site
|
|
251
303
|
onProgress?.('Creating site...')
|
|
252
|
-
await scaffoldSite(
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
304
|
+
await scaffoldSite(
|
|
305
|
+
join(projectDir, 'site'),
|
|
306
|
+
{
|
|
307
|
+
name: 'site',
|
|
308
|
+
projectName,
|
|
309
|
+
foundationName: 'src',
|
|
310
|
+
foundationPath: 'file:../src',
|
|
311
|
+
foundationRef: 'src'
|
|
312
|
+
},
|
|
313
|
+
{ onProgress, onWarning }
|
|
314
|
+
)
|
|
259
315
|
|
|
260
316
|
// 4. Apply starter content (unless creating a "none" project)
|
|
261
317
|
if (includeStarter) {
|
|
@@ -274,13 +330,17 @@ async function createBlankWorkspace(projectDir, projectName, options = {}) {
|
|
|
274
330
|
|
|
275
331
|
onProgress?.('Setting up blank workspace...')
|
|
276
332
|
|
|
277
|
-
await scaffoldWorkspace(
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
333
|
+
await scaffoldWorkspace(
|
|
334
|
+
projectDir,
|
|
335
|
+
{
|
|
336
|
+
projectName,
|
|
337
|
+
workspaceGlobs: [],
|
|
338
|
+
scripts: {
|
|
339
|
+
build: 'uniweb build'
|
|
340
|
+
}
|
|
282
341
|
},
|
|
283
|
-
|
|
342
|
+
{ onProgress, onWarning }
|
|
343
|
+
)
|
|
284
344
|
|
|
285
345
|
success(`Created blank workspace: ${projectName}`)
|
|
286
346
|
}
|
|
@@ -291,7 +351,13 @@ async function createBlankWorkspace(projectDir, projectName, options = {}) {
|
|
|
291
351
|
* Scaffolds workspace structure from package templates, then overlays
|
|
292
352
|
* content (sections, pages, theme) from the content template.
|
|
293
353
|
*/
|
|
294
|
-
async function createFromContentTemplate(
|
|
354
|
+
async function createFromContentTemplate(
|
|
355
|
+
projectDir,
|
|
356
|
+
projectName,
|
|
357
|
+
metadata,
|
|
358
|
+
templateRootPath,
|
|
359
|
+
options = {}
|
|
360
|
+
) {
|
|
295
361
|
const { onProgress, onWarning, pm = 'pnpm' } = options
|
|
296
362
|
|
|
297
363
|
// Determine packages to create
|
|
@@ -300,17 +366,17 @@ async function createFromContentTemplate(projectDir, projectName, metadata, temp
|
|
|
300
366
|
// convention is set in computePlacement() below.
|
|
301
367
|
const packages = metadata.packages || [
|
|
302
368
|
{ type: 'foundation', name: 'src' },
|
|
303
|
-
{ type: 'site', name: 'site', foundation: 'src' }
|
|
369
|
+
{ type: 'site', name: 'site', foundation: 'src' }
|
|
304
370
|
]
|
|
305
371
|
|
|
306
372
|
// Compute placement for each package
|
|
307
373
|
const placed = computePlacement(packages)
|
|
308
374
|
|
|
309
375
|
// Compute workspace globs and scripts from placement
|
|
310
|
-
const workspaceGlobs = placed.map(p => p.relativePath)
|
|
311
|
-
const sites = placed.filter(p => p.type === 'site')
|
|
376
|
+
const workspaceGlobs = placed.map((p) => p.relativePath)
|
|
377
|
+
const sites = placed.filter((p) => p.type === 'site')
|
|
312
378
|
const scripts = {
|
|
313
|
-
build: 'uniweb build'
|
|
379
|
+
build: 'uniweb build'
|
|
314
380
|
}
|
|
315
381
|
// dev goes through `uniweb` (PM-agnostic; see computeRootScripts).
|
|
316
382
|
// preview stays PM-filtered until a `uniweb preview` verb exists.
|
|
@@ -331,11 +397,15 @@ async function createFromContentTemplate(projectDir, projectName, metadata, temp
|
|
|
331
397
|
|
|
332
398
|
// 1. Scaffold workspace
|
|
333
399
|
onProgress?.('Setting up workspace...')
|
|
334
|
-
await scaffoldWorkspace(
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
400
|
+
await scaffoldWorkspace(
|
|
401
|
+
projectDir,
|
|
402
|
+
{
|
|
403
|
+
projectName,
|
|
404
|
+
workspaceGlobs,
|
|
405
|
+
scripts
|
|
406
|
+
},
|
|
407
|
+
{ onProgress, onWarning }
|
|
408
|
+
)
|
|
339
409
|
|
|
340
410
|
// 2. Scaffold and apply content for each package
|
|
341
411
|
for (const pkg of placed) {
|
|
@@ -343,19 +413,26 @@ async function createFromContentTemplate(projectDir, projectName, metadata, temp
|
|
|
343
413
|
|
|
344
414
|
if (pkg.type === 'foundation' || pkg.type === 'extension') {
|
|
345
415
|
onProgress?.(`Creating ${pkg.type}: ${pkg.name}...`)
|
|
346
|
-
await scaffoldFoundation(
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
416
|
+
await scaffoldFoundation(
|
|
417
|
+
fullPath,
|
|
418
|
+
{
|
|
419
|
+
name: pkg.name,
|
|
420
|
+
projectName,
|
|
421
|
+
isExtension: pkg.type === 'extension'
|
|
422
|
+
},
|
|
423
|
+
{ onProgress, onWarning }
|
|
424
|
+
)
|
|
351
425
|
} else if (pkg.type === 'site') {
|
|
352
426
|
// Find the foundation this site wires to
|
|
353
427
|
const foundationName = pkg.foundation || 'src'
|
|
354
|
-
const foundationPkg = placed.find(
|
|
355
|
-
(p.type === 'foundation'
|
|
428
|
+
const foundationPkg = placed.find(
|
|
429
|
+
(p) => p.type === 'foundation' && p.name === foundationName
|
|
356
430
|
)
|
|
357
431
|
const foundationPath = foundationPkg
|
|
358
|
-
? computeFoundationFilePath(
|
|
432
|
+
? computeFoundationFilePath(
|
|
433
|
+
pkg.relativePath,
|
|
434
|
+
foundationPkg.relativePath
|
|
435
|
+
)
|
|
359
436
|
: 'file:../src'
|
|
360
437
|
|
|
361
438
|
onProgress?.(`Creating site: ${pkg.name}...`)
|
|
@@ -363,29 +440,39 @@ async function createFromContentTemplate(projectDir, projectName, metadata, temp
|
|
|
363
440
|
// never the implicit default in the new layout (the build's
|
|
364
441
|
// `detectFoundationType` defaults to 'foundation' when absent,
|
|
365
442
|
// which doesn't match 'src').
|
|
366
|
-
await scaffoldSite(
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
443
|
+
await scaffoldSite(
|
|
444
|
+
fullPath,
|
|
445
|
+
{
|
|
446
|
+
name: pkg.name,
|
|
447
|
+
projectName,
|
|
448
|
+
foundationName,
|
|
449
|
+
foundationPath,
|
|
450
|
+
foundationRef: foundationName
|
|
451
|
+
},
|
|
452
|
+
{ onProgress, onWarning }
|
|
453
|
+
)
|
|
373
454
|
}
|
|
374
455
|
|
|
375
456
|
// Apply content from the matching content directory
|
|
376
457
|
const contentDir = findContentDirFor(metadata.contentDirs, pkg)
|
|
377
458
|
if (contentDir) {
|
|
378
459
|
onProgress?.(`Applying ${metadata.name} content to ${pkg.name}...`)
|
|
379
|
-
await applyContent(
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
460
|
+
await applyContent(
|
|
461
|
+
contentDir.dir,
|
|
462
|
+
fullPath,
|
|
463
|
+
{ projectName },
|
|
464
|
+
{
|
|
465
|
+
onProgress,
|
|
466
|
+
onWarning,
|
|
467
|
+
renames: contentDir.renames
|
|
468
|
+
}
|
|
469
|
+
)
|
|
384
470
|
}
|
|
385
471
|
|
|
386
472
|
// Merge template dependencies into package.json
|
|
387
473
|
if (metadata.dependencies) {
|
|
388
|
-
const deps =
|
|
474
|
+
const deps =
|
|
475
|
+
metadata.dependencies[pkg.name] || metadata.dependencies[pkg.type]
|
|
389
476
|
if (deps) {
|
|
390
477
|
await mergeTemplateDependencies(join(fullPath, 'package.json'), deps)
|
|
391
478
|
}
|
|
@@ -408,9 +495,9 @@ async function createFromContentTemplate(projectDir, projectName, metadata, temp
|
|
|
408
495
|
* - Multiple sites → sites/{name}/
|
|
409
496
|
*/
|
|
410
497
|
function computePlacement(packages) {
|
|
411
|
-
const foundations = packages.filter(p => p.type === 'foundation')
|
|
412
|
-
const extensions = packages.filter(p => p.type === 'extension')
|
|
413
|
-
const sites = packages.filter(p => p.type === 'site')
|
|
498
|
+
const foundations = packages.filter((p) => p.type === 'foundation')
|
|
499
|
+
const extensions = packages.filter((p) => p.type === 'extension')
|
|
500
|
+
const sites = packages.filter((p) => p.type === 'site')
|
|
414
501
|
|
|
415
502
|
const placed = []
|
|
416
503
|
|
|
@@ -443,8 +530,10 @@ function computePlacement(packages) {
|
|
|
443
530
|
function findContentDirFor(contentDirs, pkg) {
|
|
444
531
|
if (!contentDirs) return null
|
|
445
532
|
// Match by name first, then by type
|
|
446
|
-
return
|
|
447
|
-
|
|
533
|
+
return (
|
|
534
|
+
contentDirs.find((d) => d.name === pkg.name) ||
|
|
535
|
+
contentDirs.find((d) => d.type === pkg.type && d.name === pkg.type)
|
|
536
|
+
)
|
|
448
537
|
}
|
|
449
538
|
|
|
450
539
|
/**
|
|
@@ -481,13 +570,16 @@ async function main() {
|
|
|
481
570
|
console.log(`uniweb ${getCliVersion()}`)
|
|
482
571
|
if (isGlobalInstall()) {
|
|
483
572
|
try {
|
|
484
|
-
const { fetchAndNotifyIfNewer, maybeNotifyFromCache } =
|
|
573
|
+
const { fetchAndNotifyIfNewer, maybeNotifyFromCache } =
|
|
574
|
+
await import('./utils/update-check.js')
|
|
485
575
|
if (process.stdout.isTTY) {
|
|
486
576
|
await fetchAndNotifyIfNewer(getCliVersion(), { tone: 'soft' })
|
|
487
577
|
} else {
|
|
488
578
|
maybeNotifyFromCache(getCliVersion(), 'soft')
|
|
489
579
|
}
|
|
490
|
-
} catch {
|
|
580
|
+
} catch {
|
|
581
|
+
/* ignore */
|
|
582
|
+
}
|
|
491
583
|
}
|
|
492
584
|
return
|
|
493
585
|
}
|
|
@@ -519,7 +611,8 @@ async function main() {
|
|
|
519
611
|
let showUpdateNotification = () => {}
|
|
520
612
|
if (global) {
|
|
521
613
|
try {
|
|
522
|
-
const { startUpdateCheck, maybeEagerNotification } =
|
|
614
|
+
const { startUpdateCheck, maybeEagerNotification } =
|
|
615
|
+
await import('./utils/update-check.js')
|
|
523
616
|
showUpdateNotification = startUpdateCheck(getCliVersion())
|
|
524
617
|
if (command === 'create') {
|
|
525
618
|
maybeEagerNotification(getCliVersion())
|
|
@@ -540,7 +633,7 @@ async function main() {
|
|
|
540
633
|
// Critical for `deploy --help` (used to open a browser to production for
|
|
541
634
|
// login because deploy.js doesn't parse --help and ensureAuth ran first).
|
|
542
635
|
// Falls back to the global help when a command has no dedicated block.
|
|
543
|
-
if (args.slice(1).some(a => a === '--help' || a === '-h')) {
|
|
636
|
+
if (args.slice(1).some((a) => a === '--help' || a === '-h')) {
|
|
544
637
|
const printed = printCommandHelp(command)
|
|
545
638
|
if (printed) {
|
|
546
639
|
await showUpdateNotification()
|
|
@@ -718,18 +811,28 @@ async function main() {
|
|
|
718
811
|
const { resolveBackendOrigin } = await import('./backend/client.js')
|
|
719
812
|
const { readFlagValue } = await import('./utils/args.js')
|
|
720
813
|
const { runRegistryLogin } = await import('./utils/registry-auth.js')
|
|
721
|
-
const originFlag =
|
|
722
|
-
|
|
814
|
+
const originFlag =
|
|
815
|
+
readFlagValue(loginArgs, '--backend') ||
|
|
816
|
+
readFlagValue(loginArgs, '--registry')
|
|
817
|
+
await runRegistryLogin({
|
|
818
|
+
apiBase: resolveBackendOrigin(originFlag),
|
|
819
|
+
args: loginArgs
|
|
820
|
+
})
|
|
723
821
|
return
|
|
724
822
|
}
|
|
725
823
|
|
|
726
824
|
// Handle logout command — clear the stored backend session.
|
|
727
825
|
if (command === 'logout') {
|
|
728
|
-
const { clearRegistryAuth, getRegistryAuthPath } =
|
|
826
|
+
const { clearRegistryAuth, getRegistryAuthPath } =
|
|
827
|
+
await import('./utils/registry-auth.js')
|
|
729
828
|
const { existsSync } = await import('node:fs')
|
|
730
829
|
const had = existsSync(getRegistryAuthPath())
|
|
731
830
|
await clearRegistryAuth()
|
|
732
|
-
console.log(
|
|
831
|
+
console.log(
|
|
832
|
+
had
|
|
833
|
+
? '\x1b[32m✓\x1b[0m Logged out (cleared the stored session).'
|
|
834
|
+
: 'Not logged in — nothing to clear.'
|
|
835
|
+
)
|
|
733
836
|
return
|
|
734
837
|
}
|
|
735
838
|
|
|
@@ -741,7 +844,8 @@ async function main() {
|
|
|
741
844
|
}
|
|
742
845
|
|
|
743
846
|
// Handle runtime command — `runtime register` uploads a built @uniweb/runtime to
|
|
744
|
-
// the backend's runtime registry
|
|
847
|
+
// the backend's runtime registry; @std-gated. Where it is served from is the
|
|
848
|
+
// backend's to report, not ours to know.
|
|
745
849
|
if (command === 'runtime') {
|
|
746
850
|
const { runtime } = await import('./commands/runtime.js')
|
|
747
851
|
const result = await runtime(args.slice(1))
|
|
@@ -782,13 +886,15 @@ async function main() {
|
|
|
782
886
|
log(`\nTo add packages to this workspace, use:`)
|
|
783
887
|
log(` ${colors.cyan}uniweb add foundation [name]${colors.reset}`)
|
|
784
888
|
log(` ${colors.cyan}uniweb add site [name]${colors.reset}`)
|
|
785
|
-
log(
|
|
889
|
+
log(
|
|
890
|
+
` ${colors.cyan}uniweb add foundation --from <template>${colors.reset}\n`
|
|
891
|
+
)
|
|
786
892
|
process.exit(1)
|
|
787
893
|
}
|
|
788
894
|
|
|
789
895
|
// Parse arguments
|
|
790
896
|
let projectName = args[1]
|
|
791
|
-
let templateType = null
|
|
897
|
+
let templateType = null // null = use new package template flow
|
|
792
898
|
|
|
793
899
|
// In-place mode: `uniweb create .` scaffolds into the current working
|
|
794
900
|
// directory instead of creating a new one. Pairs with the GitHub-first
|
|
@@ -821,7 +927,7 @@ async function main() {
|
|
|
821
927
|
if (nameIndex !== -1 && args[nameIndex + 1]) {
|
|
822
928
|
displayName = args[nameIndex + 1]
|
|
823
929
|
} else {
|
|
824
|
-
const nameEq = args.find(a => a.startsWith('--name='))
|
|
930
|
+
const nameEq = args.find((a) => a.startsWith('--name='))
|
|
825
931
|
if (nameEq) displayName = nameEq.slice('--name='.length)
|
|
826
932
|
}
|
|
827
933
|
|
|
@@ -853,13 +959,17 @@ async function main() {
|
|
|
853
959
|
const dirName = basename(process.cwd())
|
|
854
960
|
const slug = slugifyName(dirName)
|
|
855
961
|
if (!slug) {
|
|
856
|
-
error(
|
|
962
|
+
error(
|
|
963
|
+
`Could not derive a valid project name from the current directory ("${dirName}").`
|
|
964
|
+
)
|
|
857
965
|
log(`Re-run with ${colors.cyan}--name=<your-name>${colors.reset}.`)
|
|
858
966
|
process.exit(1)
|
|
859
967
|
}
|
|
860
968
|
projectName = slug
|
|
861
969
|
if (slug !== dirName) {
|
|
862
|
-
log(
|
|
970
|
+
log(
|
|
971
|
+
`${colors.dim}Project name:${colors.reset} ${slug} ${colors.dim}(slugified from "${dirName}")${colors.reset}`
|
|
972
|
+
)
|
|
863
973
|
} else {
|
|
864
974
|
log(`${colors.dim}Project name:${colors.reset} ${slug}`)
|
|
865
975
|
}
|
|
@@ -879,26 +989,29 @@ async function main() {
|
|
|
879
989
|
}
|
|
880
990
|
|
|
881
991
|
// Interactive prompts (skipped in in-place mode — name was derived above)
|
|
882
|
-
const response = await prompts(
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
992
|
+
const response = await prompts(
|
|
993
|
+
[
|
|
994
|
+
{
|
|
995
|
+
type: projectName ? null : 'text',
|
|
996
|
+
name: 'projectName',
|
|
997
|
+
message: 'Project name:',
|
|
998
|
+
initial: 'website',
|
|
999
|
+
validate: (value) => {
|
|
1000
|
+
if (!value) return 'Project name is required'
|
|
1001
|
+
if (!/^[a-z0-9-]+$/.test(value)) {
|
|
1002
|
+
return 'Project name can only contain lowercase letters, numbers, and hyphens'
|
|
1003
|
+
}
|
|
1004
|
+
return true
|
|
892
1005
|
}
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
}
|
|
901
|
-
|
|
1006
|
+
}
|
|
1007
|
+
],
|
|
1008
|
+
{
|
|
1009
|
+
onCancel: () => {
|
|
1010
|
+
log('\nScaffolding cancelled.')
|
|
1011
|
+
process.exit(0)
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
)
|
|
902
1015
|
|
|
903
1016
|
projectName = projectName || response.projectName
|
|
904
1017
|
|
|
@@ -909,18 +1022,21 @@ async function main() {
|
|
|
909
1022
|
|
|
910
1023
|
// Prompt for template if not specified via --template or --blank
|
|
911
1024
|
if (!templateType && !isBlank) {
|
|
912
|
-
const templateResponse = await prompts(
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
onCancel: () => {
|
|
920
|
-
log('\nScaffolding cancelled.')
|
|
921
|
-
process.exit(0)
|
|
1025
|
+
const templateResponse = await prompts(
|
|
1026
|
+
{
|
|
1027
|
+
type: 'select',
|
|
1028
|
+
name: 'template',
|
|
1029
|
+
message: 'Template:',
|
|
1030
|
+
choices: TEMPLATE_CHOICES,
|
|
1031
|
+
initial: 1
|
|
922
1032
|
},
|
|
923
|
-
|
|
1033
|
+
{
|
|
1034
|
+
onCancel: () => {
|
|
1035
|
+
log('\nScaffolding cancelled.')
|
|
1036
|
+
process.exit(0)
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
)
|
|
924
1040
|
templateType = templateResponse.template
|
|
925
1041
|
// Handle "blank" selection from interactive prompt
|
|
926
1042
|
if (templateType === 'blank') {
|
|
@@ -933,7 +1049,9 @@ async function main() {
|
|
|
933
1049
|
|
|
934
1050
|
// Resolve target directory. In-place mode scaffolds into the cwd;
|
|
935
1051
|
// otherwise create `./<projectName>`.
|
|
936
|
-
const projectDir = inPlace
|
|
1052
|
+
const projectDir = inPlace
|
|
1053
|
+
? process.cwd()
|
|
1054
|
+
: resolve(process.cwd(), projectName)
|
|
937
1055
|
|
|
938
1056
|
if (!inPlace && existsSync(projectDir)) {
|
|
939
1057
|
error(`Directory already exists: ${projectName}`)
|
|
@@ -960,21 +1078,24 @@ async function main() {
|
|
|
960
1078
|
error(`Cannot scaffold in place — these files would be overwritten:`)
|
|
961
1079
|
for (const c of conflicts) log(` ${colors.yellow}${c}${colors.reset}`)
|
|
962
1080
|
log('')
|
|
963
|
-
log(
|
|
1081
|
+
log(
|
|
1082
|
+
`Move or remove them, then re-run ${colors.cyan}uniweb create .${colors.reset}.`
|
|
1083
|
+
)
|
|
964
1084
|
process.exit(1)
|
|
965
1085
|
}
|
|
966
1086
|
}
|
|
967
1087
|
|
|
968
1088
|
// Template routing logic
|
|
969
1089
|
const progressCb = (msg) => log(` ${colors.dim}${msg}${colors.reset}`)
|
|
970
|
-
const warningCb = (msg) =>
|
|
1090
|
+
const warningCb = (msg) =>
|
|
1091
|
+
log(` ${colors.yellow}Warning: ${msg}${colors.reset}`)
|
|
971
1092
|
|
|
972
1093
|
if (isBlank) {
|
|
973
1094
|
// Blank workspace (--blank or --template blank)
|
|
974
1095
|
log('\nCreating blank workspace...')
|
|
975
1096
|
await createBlankWorkspace(projectDir, effectiveName, {
|
|
976
1097
|
onProgress: progressCb,
|
|
977
|
-
onWarning: warningCb
|
|
1098
|
+
onWarning: warningCb
|
|
978
1099
|
})
|
|
979
1100
|
} else if (templateType === 'none') {
|
|
980
1101
|
// Foundation + site with no content
|
|
@@ -983,7 +1104,7 @@ async function main() {
|
|
|
983
1104
|
onProgress: progressCb,
|
|
984
1105
|
onWarning: warningCb,
|
|
985
1106
|
pm,
|
|
986
|
-
includeStarter: false
|
|
1107
|
+
includeStarter: false
|
|
987
1108
|
})
|
|
988
1109
|
} else if (templateType === 'starter') {
|
|
989
1110
|
// Starter: foundation + site + sample content
|
|
@@ -991,7 +1112,7 @@ async function main() {
|
|
|
991
1112
|
await createFromPackageTemplates(projectDir, effectiveName, {
|
|
992
1113
|
onProgress: progressCb,
|
|
993
1114
|
onWarning: warningCb,
|
|
994
|
-
pm
|
|
1115
|
+
pm
|
|
995
1116
|
})
|
|
996
1117
|
} else {
|
|
997
1118
|
// External: official/npm/github/local
|
|
@@ -999,20 +1120,28 @@ async function main() {
|
|
|
999
1120
|
|
|
1000
1121
|
try {
|
|
1001
1122
|
const resolved = await resolveTemplate(templateType, {
|
|
1002
|
-
onProgress: progressCb
|
|
1123
|
+
onProgress: progressCb
|
|
1003
1124
|
})
|
|
1004
1125
|
|
|
1005
|
-
log(
|
|
1126
|
+
log(
|
|
1127
|
+
`\nCreating project from ${resolved.name || resolved.package || `${resolved.owner}/${resolved.repo}`}...`
|
|
1128
|
+
)
|
|
1006
1129
|
|
|
1007
1130
|
// Validate and apply as format 2 content template
|
|
1008
1131
|
const metadata = await validateTemplate(resolved.path, {})
|
|
1009
1132
|
|
|
1010
1133
|
try {
|
|
1011
|
-
await createFromContentTemplate(
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1134
|
+
await createFromContentTemplate(
|
|
1135
|
+
projectDir,
|
|
1136
|
+
effectiveName,
|
|
1137
|
+
metadata,
|
|
1138
|
+
resolved.path,
|
|
1139
|
+
{
|
|
1140
|
+
onProgress: progressCb,
|
|
1141
|
+
onWarning: warningCb,
|
|
1142
|
+
pm
|
|
1143
|
+
}
|
|
1144
|
+
)
|
|
1016
1145
|
} finally {
|
|
1017
1146
|
if (resolved.cleanup) await resolved.cleanup()
|
|
1018
1147
|
}
|
|
@@ -1021,8 +1150,12 @@ async function main() {
|
|
|
1021
1150
|
log('')
|
|
1022
1151
|
log(`${colors.yellow}Troubleshooting:${colors.reset}`)
|
|
1023
1152
|
log(` • Check your network connection`)
|
|
1024
|
-
log(
|
|
1025
|
-
|
|
1153
|
+
log(
|
|
1154
|
+
` • Official templates require GitHub access (may be blocked by corporate networks)`
|
|
1155
|
+
)
|
|
1156
|
+
log(
|
|
1157
|
+
` • Try the starter template instead: ${colors.cyan}uniweb create ${projectName} --template starter${colors.reset}`
|
|
1158
|
+
)
|
|
1026
1159
|
process.exit(1)
|
|
1027
1160
|
}
|
|
1028
1161
|
}
|
|
@@ -1032,25 +1165,37 @@ async function main() {
|
|
|
1032
1165
|
// Skip git init if already inside a git repo (common for monorepos/workspaces)
|
|
1033
1166
|
let insideGitRepo = false
|
|
1034
1167
|
try {
|
|
1035
|
-
execSync('git rev-parse --is-inside-work-tree', {
|
|
1168
|
+
execSync('git rev-parse --is-inside-work-tree', {
|
|
1169
|
+
cwd: projectDir,
|
|
1170
|
+
stdio: 'ignore'
|
|
1171
|
+
})
|
|
1036
1172
|
insideGitRepo = true
|
|
1037
1173
|
} catch {
|
|
1038
1174
|
// Not inside a git repo — proceed with init
|
|
1039
1175
|
}
|
|
1040
1176
|
|
|
1041
1177
|
if (insideGitRepo) {
|
|
1042
|
-
log(
|
|
1178
|
+
log(
|
|
1179
|
+
` ${colors.dim}Skipping git init — already inside a git repository${colors.reset}`
|
|
1180
|
+
)
|
|
1043
1181
|
} else {
|
|
1044
1182
|
try {
|
|
1045
1183
|
execSync('git --version', { stdio: 'ignore' })
|
|
1046
1184
|
try {
|
|
1047
1185
|
execSync('git init', { cwd: projectDir, stdio: 'ignore' })
|
|
1048
1186
|
execSync('git add -A', { cwd: projectDir, stdio: 'ignore' })
|
|
1049
|
-
execSync('git commit -m "Initial commit from uniweb"', {
|
|
1187
|
+
execSync('git commit -m "Initial commit from uniweb"', {
|
|
1188
|
+
cwd: projectDir,
|
|
1189
|
+
stdio: 'ignore'
|
|
1190
|
+
})
|
|
1050
1191
|
success('Git repository initialized')
|
|
1051
1192
|
} catch {
|
|
1052
|
-
log(
|
|
1053
|
-
|
|
1193
|
+
log(
|
|
1194
|
+
` ${colors.yellow}Warning: Git repository initialized but initial commit failed${colors.reset}`
|
|
1195
|
+
)
|
|
1196
|
+
log(
|
|
1197
|
+
` ${colors.dim}Run 'git commit -m "Initial commit"' after configuring git${colors.reset}`
|
|
1198
|
+
)
|
|
1054
1199
|
}
|
|
1055
1200
|
} catch {
|
|
1056
1201
|
// git not available — skip silently
|
|
@@ -1072,19 +1217,27 @@ async function main() {
|
|
|
1072
1217
|
if (!inPlace) log(` ${colors.cyan}cd ${projectName}${colors.reset}`)
|
|
1073
1218
|
log(` ${colors.cyan}${prefix} add project${colors.reset}`)
|
|
1074
1219
|
log(` ${colors.cyan}${installCmd(recPm)}${colors.reset}`)
|
|
1075
|
-
log(
|
|
1220
|
+
log(
|
|
1221
|
+
` ${colors.cyan}${runCmd(recPm, 'dev')}${colors.reset} ${colors.dim}# start the dev server${colors.reset}`
|
|
1222
|
+
)
|
|
1076
1223
|
} else {
|
|
1077
1224
|
log(`Next steps:\n`)
|
|
1078
1225
|
if (!inPlace) log(` ${colors.cyan}cd ${projectName}${colors.reset}`)
|
|
1079
1226
|
log(` ${colors.cyan}${installCmd(recPm)}${colors.reset}`)
|
|
1080
|
-
log(
|
|
1227
|
+
log(
|
|
1228
|
+
` ${colors.cyan}${runCmd(recPm, 'dev')}${colors.reset} ${colors.dim}# start the dev server${colors.reset}`
|
|
1229
|
+
)
|
|
1081
1230
|
}
|
|
1082
1231
|
if (recPm !== 'pnpm') {
|
|
1083
1232
|
log('')
|
|
1084
|
-
log(
|
|
1233
|
+
log(
|
|
1234
|
+
` ${colors.dim}Tip: pnpm is recommended (https://pnpm.io) — npm works too.${colors.reset}`
|
|
1235
|
+
)
|
|
1085
1236
|
}
|
|
1086
1237
|
log('')
|
|
1087
|
-
log(
|
|
1238
|
+
log(
|
|
1239
|
+
` ${colors.dim}See ${colors.reset}${colors.cyan}${prefix} <command> --help${colors.reset}${colors.dim} for command-specific options.${colors.reset}`
|
|
1240
|
+
)
|
|
1088
1241
|
log('')
|
|
1089
1242
|
|
|
1090
1243
|
await showUpdateNotification()
|
|
@@ -1474,7 +1627,7 @@ ${colors.bright}Project-local installs:${colors.reset}
|
|
|
1474
1627
|
When run from a project-local CLI (in node_modules), it aligns the
|
|
1475
1628
|
project to that pinned version — bump \`uniweb\` in package.json (or use
|
|
1476
1629
|
\`npx uniweb@latest update\`) to align to something newer.
|
|
1477
|
-
|
|
1630
|
+
`
|
|
1478
1631
|
}
|
|
1479
1632
|
|
|
1480
1633
|
if (!blocks[command]) return false
|