uniweb 0.87.3 → 0.88.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 +3 -1
- package/src/commands/add.js +2 -5
- package/src/commands/template.js +35 -46
- package/src/framework-index.json +23 -98
- package/src/index.js +84 -86
- package/src/templates/fetchers/release.js +16 -62
- package/src/templates/index.js +15 -32
- package/src/templates/resolver.js +46 -73
- package/src/templates/validator.js +15 -187
- package/src/utils/exit.js +30 -0
- package/src/utils/scaffold.js +3 -20
- package/src/utils/update-check.js +73 -38
- package/src/versions.js +4 -1
package/src/index.js
CHANGED
|
@@ -41,7 +41,8 @@ import { template } from './commands/template.js'
|
|
|
41
41
|
import {
|
|
42
42
|
resolveTemplate,
|
|
43
43
|
parseTemplateId,
|
|
44
|
-
buildTemplateChoices
|
|
44
|
+
buildTemplateChoices,
|
|
45
|
+
templatePickerPageSize
|
|
45
46
|
} from './templates/index.js'
|
|
46
47
|
import { validateTemplate } from './templates/validator.js'
|
|
47
48
|
import {
|
|
@@ -72,6 +73,7 @@ import {
|
|
|
72
73
|
formatOptions
|
|
73
74
|
} from './utils/interactive.js'
|
|
74
75
|
import { findWorkspaceRoot } from './utils/workspace.js'
|
|
76
|
+
import { exitWhenDrained } from './utils/exit.js'
|
|
75
77
|
|
|
76
78
|
// Colors for terminal output
|
|
77
79
|
const colors = {
|
|
@@ -85,9 +87,8 @@ const colors = {
|
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
// Template choices for the interactive prompt — built-ins first, then every
|
|
88
|
-
// official template
|
|
89
|
-
//
|
|
90
|
-
// buildTemplateChoices() in templates/resolver.js.
|
|
90
|
+
// official template in this CLI's snapshot (the list `create` can download),
|
|
91
|
+
// with "Blank" last. Composed by buildTemplateChoices() in templates/resolver.js.
|
|
91
92
|
const TEMPLATE_CHOICES = buildTemplateChoices()
|
|
92
93
|
|
|
93
94
|
// Files that may pre-exist in the target dir during `uniweb create .` and
|
|
@@ -223,7 +224,7 @@ function delegateToLocal(localCliPath) {
|
|
|
223
224
|
[localCliPath, ...process.argv.slice(2)],
|
|
224
225
|
{ stdio: 'inherit' }
|
|
225
226
|
)
|
|
226
|
-
child.on('close', (code) =>
|
|
227
|
+
child.on('close', (code) => exitWhenDrained(code ?? 0))
|
|
227
228
|
child.on('error', reject)
|
|
228
229
|
})
|
|
229
230
|
}
|
|
@@ -320,7 +321,7 @@ async function importProjectCommand(modulePath) {
|
|
|
320
321
|
log(` ${colors.cyan}npm install uniweb ${missing}${colors.reset}`)
|
|
321
322
|
}
|
|
322
323
|
|
|
323
|
-
|
|
324
|
+
await exitWhenDrained(1)
|
|
324
325
|
}
|
|
325
326
|
}
|
|
326
327
|
|
|
@@ -554,11 +555,7 @@ async function createFromContentTemplate(
|
|
|
554
555
|
contentDir.dir,
|
|
555
556
|
fullPath,
|
|
556
557
|
{ projectName },
|
|
557
|
-
{
|
|
558
|
-
onProgress,
|
|
559
|
-
onWarning,
|
|
560
|
-
renames: contentDir.renames
|
|
561
|
-
}
|
|
558
|
+
{ onProgress, onWarning }
|
|
562
559
|
)
|
|
563
560
|
ensureFoundationName(fullPath, registryName)
|
|
564
561
|
}
|
|
@@ -651,7 +648,7 @@ async function main() {
|
|
|
651
648
|
// beside the new one it is ignored, so a script can carry both while CLIs of both ages run it.
|
|
652
649
|
if (process.env.UNIWEB_REGISTER_URL && !process.env.UNIWEB_SERVER) {
|
|
653
650
|
error('UNIWEB_REGISTER_URL is now UNIWEB_SERVER — rename it, and the command goes to the same backend.')
|
|
654
|
-
|
|
651
|
+
await exitWhenDrained(2)
|
|
655
652
|
}
|
|
656
653
|
|
|
657
654
|
// Handle --version / -v
|
|
@@ -676,9 +673,9 @@ async function main() {
|
|
|
676
673
|
const { fetchAndNotifyIfNewer, maybeNotifyFromCache } =
|
|
677
674
|
await import('./utils/update-check.js')
|
|
678
675
|
if (process.stdout.isTTY) {
|
|
679
|
-
await fetchAndNotifyIfNewer(getCliVersion()
|
|
676
|
+
await fetchAndNotifyIfNewer(getCliVersion())
|
|
680
677
|
} else {
|
|
681
|
-
maybeNotifyFromCache(getCliVersion()
|
|
678
|
+
maybeNotifyFromCache(getCliVersion())
|
|
682
679
|
}
|
|
683
680
|
} catch {
|
|
684
681
|
/* ignore */
|
|
@@ -699,30 +696,36 @@ async function main() {
|
|
|
699
696
|
// Commands that need @uniweb/build will get a helpful error via importProjectCommand().
|
|
700
697
|
}
|
|
701
698
|
|
|
702
|
-
//
|
|
699
|
+
// Update check for global installs.
|
|
703
700
|
//
|
|
704
|
-
//
|
|
705
|
-
//
|
|
706
|
-
//
|
|
707
|
-
//
|
|
708
|
-
//
|
|
709
|
-
//
|
|
710
|
-
//
|
|
711
|
-
//
|
|
712
|
-
//
|
|
713
|
-
// that case.
|
|
701
|
+
// - `create` / `clone` STOP when a newer release exists, before anything is
|
|
702
|
+
// written: a project starts on the packages and templates of the CLI that
|
|
703
|
+
// creates it, and a global install runs these two itself — there is no
|
|
704
|
+
// project CLI to delegate to yet (refuseOutdatedProjectStart). Their
|
|
705
|
+
// `--help` still prints.
|
|
706
|
+
// - every other verb gets a trailing notice (showUpdateNotification): they
|
|
707
|
+
// are project-bound (delegated to local node_modules) or do not depend on
|
|
708
|
+
// the version, and the local-vs-global mismatch warning in
|
|
709
|
+
// delegateToLocal covers the first case.
|
|
714
710
|
let showUpdateNotification = () => {}
|
|
715
711
|
if (global) {
|
|
712
|
+
let updateCheck = null
|
|
716
713
|
try {
|
|
717
|
-
|
|
718
|
-
await import('./utils/update-check.js')
|
|
719
|
-
showUpdateNotification = startUpdateCheck(getCliVersion())
|
|
720
|
-
if (command === 'create') {
|
|
721
|
-
maybeEagerNotification(getCliVersion())
|
|
722
|
-
}
|
|
714
|
+
updateCheck = await import('./utils/update-check.js')
|
|
723
715
|
} catch {
|
|
724
716
|
// Update check is optional — don't fail if the module is missing
|
|
725
717
|
}
|
|
718
|
+
const startsProject = command === 'create' || command === 'clone'
|
|
719
|
+
const asksForHelp = args.slice(1).some((a) => a === '--help' || a === '-h')
|
|
720
|
+
if (
|
|
721
|
+
updateCheck &&
|
|
722
|
+
startsProject &&
|
|
723
|
+
!asksForHelp &&
|
|
724
|
+
(await updateCheck.refuseOutdatedProjectStart(getCliVersion(), args))
|
|
725
|
+
) {
|
|
726
|
+
await exitWhenDrained(1)
|
|
727
|
+
}
|
|
728
|
+
if (updateCheck) showUpdateNotification = updateCheck.startUpdateCheck(getCliVersion())
|
|
726
729
|
}
|
|
727
730
|
|
|
728
731
|
// Show help
|
|
@@ -794,14 +797,14 @@ async function main() {
|
|
|
794
797
|
if (command === 'doctor') {
|
|
795
798
|
const { doctor } = await importProjectCommand('./commands/doctor.js')
|
|
796
799
|
const result = await doctor(args.slice(1))
|
|
797
|
-
|
|
800
|
+
await exitWhenDrained(result?.errors > 0 ? 1 : 0)
|
|
798
801
|
}
|
|
799
802
|
|
|
800
803
|
// Handle validate command (dynamic import — depends on @uniweb/build)
|
|
801
804
|
if (command === 'validate') {
|
|
802
805
|
const { validate } = await importProjectCommand('./commands/validate.js')
|
|
803
806
|
const result = await validate(args.slice(1))
|
|
804
|
-
|
|
807
|
+
await exitWhenDrained(result?.exitCode ?? 0)
|
|
805
808
|
}
|
|
806
809
|
|
|
807
810
|
// Handle register command (dynamic import — depends on @uniweb/build).
|
|
@@ -810,35 +813,35 @@ async function main() {
|
|
|
810
813
|
if (command === 'register' || command === 'release') {
|
|
811
814
|
const { register } = await importProjectCommand('./commands/register.js')
|
|
812
815
|
const result = await register(args.slice(1))
|
|
813
|
-
|
|
816
|
+
await exitWhenDrained(result?.exitCode ?? 0)
|
|
814
817
|
}
|
|
815
818
|
|
|
816
819
|
// Handle push command (dynamic import — depends on @uniweb/build)
|
|
817
820
|
if (command === 'push') {
|
|
818
821
|
const { push } = await importProjectCommand('./commands/push.js')
|
|
819
822
|
const result = await push(args.slice(1))
|
|
820
|
-
|
|
823
|
+
await exitWhenDrained(result?.exitCode ?? 0)
|
|
821
824
|
}
|
|
822
825
|
|
|
823
826
|
// Handle pull command (dynamic import — depends on @uniweb/build)
|
|
824
827
|
if (command === 'pull') {
|
|
825
828
|
const { pull } = await importProjectCommand('./commands/pull.js')
|
|
826
829
|
const result = await pull(args.slice(1))
|
|
827
|
-
|
|
830
|
+
await exitWhenDrained(result?.exitCode ?? 0)
|
|
828
831
|
}
|
|
829
832
|
|
|
830
833
|
// Handle sync command (refresh + push; both halves imported, never reimplemented)
|
|
831
834
|
if (command === 'sync') {
|
|
832
835
|
const { sync } = await importProjectCommand('./commands/sync.js')
|
|
833
836
|
const result = await sync(args.slice(1))
|
|
834
|
-
|
|
837
|
+
await exitWhenDrained(result?.exitCode ?? 0)
|
|
835
838
|
}
|
|
836
839
|
|
|
837
840
|
// Handle refresh command (dynamic import — depends on @uniweb/build via pull)
|
|
838
841
|
if (command === 'refresh') {
|
|
839
842
|
const { refresh } = await importProjectCommand('./commands/refresh.js')
|
|
840
843
|
const result = await refresh(args.slice(1))
|
|
841
|
-
|
|
844
|
+
await exitWhenDrained(result?.exitCode ?? 0)
|
|
842
845
|
}
|
|
843
846
|
|
|
844
847
|
// Handle forget — remove one backend's local records (sync.json, cache, deploy
|
|
@@ -847,14 +850,14 @@ async function main() {
|
|
|
847
850
|
if (command === 'forget') {
|
|
848
851
|
const { forget } = await importProjectCommand('./commands/forget.js')
|
|
849
852
|
const result = await forget(args.slice(1))
|
|
850
|
-
|
|
853
|
+
await exitWhenDrained(result?.exitCode ?? 0)
|
|
851
854
|
}
|
|
852
855
|
|
|
853
856
|
// Handle status command (dynamic import — offline emit via @uniweb/build)
|
|
854
857
|
if (command === 'status') {
|
|
855
858
|
const { status } = await importProjectCommand('./commands/status.js')
|
|
856
859
|
const result = await status(args.slice(1))
|
|
857
|
-
|
|
860
|
+
await exitWhenDrained(result?.exitCode ?? 0)
|
|
858
861
|
}
|
|
859
862
|
|
|
860
863
|
// Handle clone command (global — bootstraps a new project from a backend site;
|
|
@@ -864,7 +867,7 @@ async function main() {
|
|
|
864
867
|
// `uniweb pull` after install.)
|
|
865
868
|
if (command === 'clone') {
|
|
866
869
|
const result = await clone(args.slice(1))
|
|
867
|
-
|
|
870
|
+
await exitWhenDrained(result?.exitCode ?? 0)
|
|
868
871
|
}
|
|
869
872
|
|
|
870
873
|
// Handle update command
|
|
@@ -899,7 +902,7 @@ async function main() {
|
|
|
899
902
|
if (command === 'publish') {
|
|
900
903
|
const { publish } = await importProjectCommand('./commands/publish.js')
|
|
901
904
|
const result = await publish(args.slice(1))
|
|
902
|
-
|
|
905
|
+
await exitWhenDrained(result?.exitCode ?? 0)
|
|
903
906
|
}
|
|
904
907
|
|
|
905
908
|
// Handle deploy command — third-party hosts only (dynamic import — @uniweb/build)
|
|
@@ -946,7 +949,7 @@ async function main() {
|
|
|
946
949
|
// dropped, the login would go to the default backend.
|
|
947
950
|
if (loginArgs.some((a) => a === '--backend' || a.startsWith('--backend='))) {
|
|
948
951
|
console.error('\x1b[31m✗\x1b[0m `--backend` is now `--server`: uniweb login --server <url>')
|
|
949
|
-
|
|
952
|
+
await exitWhenDrained(2)
|
|
950
953
|
}
|
|
951
954
|
let apiBase
|
|
952
955
|
try {
|
|
@@ -955,7 +958,7 @@ async function main() {
|
|
|
955
958
|
console.error(
|
|
956
959
|
`\x1b[31m✗\x1b[0m ${err.message} — e.g. uniweb login --server http://localhost:8080`
|
|
957
960
|
)
|
|
958
|
-
|
|
961
|
+
await exitWhenDrained(2)
|
|
959
962
|
}
|
|
960
963
|
await runRegistryLogin({ apiBase, args: loginArgs })
|
|
961
964
|
return
|
|
@@ -971,7 +974,7 @@ async function main() {
|
|
|
971
974
|
console.error(
|
|
972
975
|
`\x1b[31m✗\x1b[0m \`uniweb logout\` takes no options (got ${stray}) — it logs you out of the backend you are logged in to.`
|
|
973
976
|
)
|
|
974
|
-
|
|
977
|
+
await exitWhenDrained(2)
|
|
975
978
|
}
|
|
976
979
|
const { clearRegistryAuth } = await import('./utils/registry-auth.js')
|
|
977
980
|
const { loggedInOrigin } = await import('./utils/config.js')
|
|
@@ -994,12 +997,9 @@ async function main() {
|
|
|
994
997
|
if (command === 'site') {
|
|
995
998
|
const { site } = await import('./commands/site.js')
|
|
996
999
|
const result = await site(args.slice(1))
|
|
997
|
-
//
|
|
998
|
-
//
|
|
999
|
-
|
|
1000
|
-
// into a file (synchronous) was whole. Pinned by test/site-list-pipe.test.js.
|
|
1001
|
-
await new Promise((resolve) => process.stdout.write('', resolve))
|
|
1002
|
-
process.exit(result?.exitCode ?? 0)
|
|
1000
|
+
// Its long list is where a bare exit was first caught cutting a pipe short — pinned by
|
|
1001
|
+
// test/site-list-pipe.test.js; why every exit waits: utils/exit.js.
|
|
1002
|
+
await exitWhenDrained(result?.exitCode ?? 0)
|
|
1003
1003
|
}
|
|
1004
1004
|
|
|
1005
1005
|
// Handle org command (new-backend orgs/units — publish-scope management)
|
|
@@ -1030,15 +1030,15 @@ async function main() {
|
|
|
1030
1030
|
|
|
1031
1031
|
// Handle template command
|
|
1032
1032
|
if (command === 'template') {
|
|
1033
|
-
await template(args.slice(1))
|
|
1034
|
-
|
|
1033
|
+
const result = await template(args.slice(1))
|
|
1034
|
+
await exitWhenDrained(result?.exitCode ?? 0)
|
|
1035
1035
|
}
|
|
1036
1036
|
|
|
1037
1037
|
// Handle create command
|
|
1038
1038
|
if (command !== 'create') {
|
|
1039
1039
|
error(`Unknown command: ${command}`)
|
|
1040
1040
|
showHelp()
|
|
1041
|
-
|
|
1041
|
+
await exitWhenDrained(1)
|
|
1042
1042
|
}
|
|
1043
1043
|
|
|
1044
1044
|
title('Uniweb Project Generator')
|
|
@@ -1053,7 +1053,7 @@ async function main() {
|
|
|
1053
1053
|
log(
|
|
1054
1054
|
` ${colors.cyan}uniweb add foundation --from <template>${colors.reset}\n`
|
|
1055
1055
|
)
|
|
1056
|
-
|
|
1056
|
+
await exitWhenDrained(1)
|
|
1057
1057
|
}
|
|
1058
1058
|
|
|
1059
1059
|
// Parse arguments
|
|
@@ -1081,7 +1081,7 @@ async function main() {
|
|
|
1081
1081
|
parseTemplateId(templateType)
|
|
1082
1082
|
} catch (err) {
|
|
1083
1083
|
error(`Invalid template: ${err.message}`)
|
|
1084
|
-
|
|
1084
|
+
await exitWhenDrained(1)
|
|
1085
1085
|
}
|
|
1086
1086
|
}
|
|
1087
1087
|
|
|
@@ -1127,7 +1127,7 @@ async function main() {
|
|
|
1127
1127
|
`Could not derive a valid project name from the current directory ("${dirName}").`
|
|
1128
1128
|
)
|
|
1129
1129
|
log(`Re-run with ${colors.cyan}--name=<your-name>${colors.reset}.`)
|
|
1130
|
-
|
|
1130
|
+
await exitWhenDrained(1)
|
|
1131
1131
|
}
|
|
1132
1132
|
projectName = slug
|
|
1133
1133
|
if (slug !== dirName) {
|
|
@@ -1144,7 +1144,7 @@ async function main() {
|
|
|
1144
1144
|
if (nonInteractive && !projectName) {
|
|
1145
1145
|
error(`Missing project name.\n`)
|
|
1146
1146
|
log(`Usage: ${prefix} create <project-name> [--template <name>] [--blank]`)
|
|
1147
|
-
|
|
1147
|
+
await exitWhenDrained(1)
|
|
1148
1148
|
}
|
|
1149
1149
|
|
|
1150
1150
|
// Non-interactive: default to starter when no template specified
|
|
@@ -1172,7 +1172,7 @@ async function main() {
|
|
|
1172
1172
|
{
|
|
1173
1173
|
onCancel: () => {
|
|
1174
1174
|
log('\nScaffolding cancelled.')
|
|
1175
|
-
process.exit(0)
|
|
1175
|
+
process.exit(0) // a prompt: a terminal, so nothing to drain — and it must stop here, now
|
|
1176
1176
|
}
|
|
1177
1177
|
}
|
|
1178
1178
|
)
|
|
@@ -1181,7 +1181,7 @@ async function main() {
|
|
|
1181
1181
|
|
|
1182
1182
|
if (!projectName) {
|
|
1183
1183
|
error('Missing project name')
|
|
1184
|
-
|
|
1184
|
+
await exitWhenDrained(1)
|
|
1185
1185
|
}
|
|
1186
1186
|
|
|
1187
1187
|
// Prompt for template if not specified via --template or --blank
|
|
@@ -1192,12 +1192,13 @@ async function main() {
|
|
|
1192
1192
|
name: 'template',
|
|
1193
1193
|
message: 'Template:',
|
|
1194
1194
|
choices: TEMPLATE_CHOICES,
|
|
1195
|
-
initial: 1
|
|
1195
|
+
initial: 1,
|
|
1196
|
+
optionsPerPage: templatePickerPageSize(TEMPLATE_CHOICES.length)
|
|
1196
1197
|
},
|
|
1197
1198
|
{
|
|
1198
1199
|
onCancel: () => {
|
|
1199
1200
|
log('\nScaffolding cancelled.')
|
|
1200
|
-
process.exit(0)
|
|
1201
|
+
process.exit(0) // a prompt: a terminal, so nothing to drain — and it must stop here, now
|
|
1201
1202
|
}
|
|
1202
1203
|
}
|
|
1203
1204
|
)
|
|
@@ -1219,7 +1220,7 @@ async function main() {
|
|
|
1219
1220
|
|
|
1220
1221
|
if (!inPlace && existsSync(projectDir)) {
|
|
1221
1222
|
error(`Directory already exists: ${projectName}`)
|
|
1222
|
-
|
|
1223
|
+
await exitWhenDrained(1)
|
|
1223
1224
|
}
|
|
1224
1225
|
|
|
1225
1226
|
if (inPlace) {
|
|
@@ -1245,7 +1246,7 @@ async function main() {
|
|
|
1245
1246
|
log(
|
|
1246
1247
|
`Move or remove them, then re-run ${colors.cyan}uniweb create .${colors.reset}.`
|
|
1247
1248
|
)
|
|
1248
|
-
|
|
1249
|
+
await exitWhenDrained(1)
|
|
1249
1250
|
}
|
|
1250
1251
|
}
|
|
1251
1252
|
|
|
@@ -1292,7 +1293,7 @@ async function main() {
|
|
|
1292
1293
|
)
|
|
1293
1294
|
|
|
1294
1295
|
// Validate and apply as format 2 content template
|
|
1295
|
-
const metadata = await validateTemplate(resolved.path
|
|
1296
|
+
const metadata = await validateTemplate(resolved.path)
|
|
1296
1297
|
|
|
1297
1298
|
try {
|
|
1298
1299
|
await createFromContentTemplate(
|
|
@@ -1320,7 +1321,7 @@ async function main() {
|
|
|
1320
1321
|
log(
|
|
1321
1322
|
` • Try the starter template instead: ${colors.cyan}uniweb create ${projectName} --template starter${colors.reset}`
|
|
1322
1323
|
)
|
|
1323
|
-
|
|
1324
|
+
await exitWhenDrained(1)
|
|
1324
1325
|
}
|
|
1325
1326
|
}
|
|
1326
1327
|
|
|
@@ -1506,7 +1507,8 @@ ${colors.bright}Usage:${colors.reset}
|
|
|
1506
1507
|
|
|
1507
1508
|
${colors.bright}Options:${colors.reset}
|
|
1508
1509
|
--template <type> Project template (default: starter)
|
|
1509
|
-
Built-in: starter, none,
|
|
1510
|
+
Built-in: starter, none, blank
|
|
1511
|
+
Official: marketing, docs, … (\`uniweb template list\`)
|
|
1510
1512
|
Local: ./path/to/template
|
|
1511
1513
|
npm: @scope/template-name
|
|
1512
1514
|
GitHub: github:user/repo or https://github.com/user/repo
|
|
@@ -1666,12 +1668,13 @@ ${colors.dim}Hosts that support PR previews: cloudflare-pages, netlify, vercel.
|
|
|
1666
1668
|
GitHub Pages has no preview environment, so it scaffolds a deploy workflow only.${colors.reset}
|
|
1667
1669
|
|
|
1668
1670
|
${colors.bright}Examples:${colors.reset}
|
|
1669
|
-
uniweb add project docs # Create docs/
|
|
1671
|
+
uniweb add project docs # Create docs/src/ + docs/site/
|
|
1670
1672
|
uniweb add project docs --from academic # Co-located pair + academic content
|
|
1671
|
-
uniweb add foundation # Create ./
|
|
1672
|
-
uniweb add foundation ui # Create ./
|
|
1673
|
+
uniweb add foundation # Create ./src/ at root
|
|
1674
|
+
uniweb add foundation ui # Create ./ui/ — the folder you name
|
|
1675
|
+
uniweb add foundation foundations/ui # Create ./foundations/ui/
|
|
1673
1676
|
uniweb add site # Create ./site/ at root
|
|
1674
|
-
uniweb add site blog --foundation marketing # Create ./
|
|
1677
|
+
uniweb add site blog --foundation marketing # Create ./blog/ wired to marketing
|
|
1675
1678
|
uniweb add extension effects --site site # Create ./extensions/effects/
|
|
1676
1679
|
uniweb add section Hero # Create Hero section type
|
|
1677
1680
|
uniweb add section Hero --foundation ui # Target specific foundation
|
|
@@ -1679,7 +1682,7 @@ ${colors.bright}Examples:${colors.reset}
|
|
|
1679
1682
|
uniweb add section Pricing --starter # Scaffold Pricing + content that fills it
|
|
1680
1683
|
uniweb add section Hero --starter --preset split # Frontmatter it with the 'split' preset
|
|
1681
1684
|
uniweb add section Hero --starter --json # The structure + ProseMirror, for a script
|
|
1682
|
-
uniweb add foundation --project docs # Create ./docs/
|
|
1685
|
+
uniweb add foundation --project docs # Create ./docs/src/ (co-located)
|
|
1683
1686
|
uniweb add site --project docs # Create ./docs/site/ (co-located)
|
|
1684
1687
|
uniweb add ci # Pick a host, add a deploy workflow
|
|
1685
1688
|
uniweb add ci --host github-pages --site marketing # Pick host + site explicitly
|
|
@@ -1971,18 +1974,12 @@ ${colors.bright}Usage:${colors.reset}
|
|
|
1971
1974
|
|
|
1972
1975
|
${colors.bright}What \`list\` reports:${colors.reset}
|
|
1973
1976
|
The roster ${colors.bright}this CLI ships with${colors.reset} — a snapshot baked in at publish time,
|
|
1974
|
-
not a live fetch.
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
+
not a live fetch. \`create\` downloads these templates from the release the same
|
|
1978
|
+
snapshot names, which is why \`--json\` stamps \`cliVersion\`. A template newer than
|
|
1979
|
+
your CLI is not listed: \`npx uniweb@latest template list\` shows the current roster.
|
|
1977
1980
|
|
|
1978
1981
|
It is the ${colors.bright}official${colors.reset} roster, not the set of resolvable names: an unknown
|
|
1979
1982
|
name is not fatal, since \`create\` falls through to npm \`@uniweb/template-<name>\`.
|
|
1980
|
-
|
|
1981
|
-
${colors.bright}Reserved:${colors.reset}
|
|
1982
|
-
\`uniweb template register\` is not available on the new backend yet. Submitting
|
|
1983
|
-
a site as a cloud template was retired with the legacy backend; when rebuilt, a
|
|
1984
|
-
template is REGISTERED (like a foundation) — \`publish\` is for sites only.
|
|
1985
|
-
Scaffolding FROM a template is unaffected: \`uniweb create --template <name>\`.
|
|
1986
1983
|
`,
|
|
1987
1984
|
docs: `
|
|
1988
1985
|
${colors.cyan}${colors.bright}uniweb docs${colors.reset} ${colors.dim}— Generate component documentation${colors.reset}
|
|
@@ -2095,6 +2092,7 @@ ${colors.bright}Commands:${colors.reset}
|
|
|
2095
2092
|
inspect <path> Inspect parsed content shape of a markdown file or folder
|
|
2096
2093
|
docs Generate component documentation
|
|
2097
2094
|
families List the standard section families (for meta.js family:)
|
|
2095
|
+
template list List the official templates (for create --template)
|
|
2098
2096
|
doctor Diagnose project configuration issues
|
|
2099
2097
|
validate Check your content against your foundation's data schemas
|
|
2100
2098
|
update Align workspace deps + AGENTS.md to the running CLI
|
|
@@ -2216,9 +2214,9 @@ ${colors.bright}Examples:${colors.reset}
|
|
|
2216
2214
|
cd my-project
|
|
2217
2215
|
uniweb add project docs # Add docs/src/ + docs/site/
|
|
2218
2216
|
uniweb add project docs --from academic # Co-located pair + academic content
|
|
2219
|
-
uniweb add marketing
|
|
2220
|
-
uniweb add site blog --foundation marketing # Add
|
|
2221
|
-
uniweb add extension effects --site site # Add effects/
|
|
2217
|
+
uniweb add foundation marketing # Add marketing/ at root
|
|
2218
|
+
uniweb add site blog --foundation marketing # Add blog/ wired to marketing
|
|
2219
|
+
uniweb add extension effects --site site # Add extensions/effects/
|
|
2222
2220
|
|
|
2223
2221
|
uniweb build
|
|
2224
2222
|
uniweb build --target src # Build src/ package
|
|
@@ -2231,7 +2229,7 @@ ${colors.bright}Install:${colors.reset}
|
|
|
2231
2229
|
}
|
|
2232
2230
|
|
|
2233
2231
|
// Run CLI
|
|
2234
|
-
main().catch((err) => {
|
|
2232
|
+
main().catch(async (err) => {
|
|
2235
2233
|
error(err.message)
|
|
2236
|
-
|
|
2234
|
+
await exitWhenDrained(1)
|
|
2237
2235
|
})
|
|
@@ -14,43 +14,32 @@ import { fetchWithRetry as sharedFetchWithRetry } from '../../utils/fetch-retry.
|
|
|
14
14
|
const TEMPLATES_REPO = 'uniweb/templates'
|
|
15
15
|
const GITHUB_API = 'https://api.github.com'
|
|
16
16
|
|
|
17
|
-
// Cache for manifest (avoid re-fetching in same session)
|
|
18
|
-
let manifestCache = null
|
|
19
|
-
|
|
20
17
|
/**
|
|
21
|
-
* Fetch
|
|
18
|
+
* Fetch manifest.json from one release of the official templates.
|
|
19
|
+
*
|
|
20
|
+
* ⛔ Always a named release — the one the CLI was published with — and never
|
|
21
|
+
* `latest`: the latest templates can be newer than the packages this CLI pins
|
|
22
|
+
* (templates/resolver.js, OFFICIAL_TEMPLATES_RELEASE).
|
|
22
23
|
*
|
|
23
24
|
* @param {Object} options - Fetch options
|
|
24
|
-
* @param {string} options.version -
|
|
25
|
+
* @param {string} options.version - The release tag, e.g. `v0.18.4`
|
|
25
26
|
* @param {Function} options.onProgress - Progress callback
|
|
26
|
-
* @returns {Promise<Object>} { version, templates,
|
|
27
|
+
* @returns {Promise<Object>} { version, templates, downloadUrlBase }
|
|
27
28
|
*/
|
|
28
29
|
export async function fetchManifest(options = {}) {
|
|
29
30
|
const { version, onProgress } = options
|
|
30
|
-
|
|
31
|
-
// Return cached manifest if available and no specific version requested
|
|
32
|
-
if (manifestCache && !version) {
|
|
33
|
-
return manifestCache
|
|
34
|
-
}
|
|
31
|
+
if (!version) throw new Error('fetchManifest needs the release tag to read')
|
|
35
32
|
|
|
36
33
|
onProgress?.('Fetching template manifest...')
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const releaseResponse = await fetchWithRetry(releaseUrl, {
|
|
44
|
-
headers: getGitHubHeaders()
|
|
45
|
-
})
|
|
35
|
+
const releaseResponse = await fetchWithRetry(
|
|
36
|
+
`${GITHUB_API}/repos/${TEMPLATES_REPO}/releases/tags/${version}`,
|
|
37
|
+
{ headers: getGitHubHeaders() }
|
|
38
|
+
)
|
|
46
39
|
|
|
47
40
|
if (!releaseResponse.ok) {
|
|
48
41
|
if (releaseResponse.status === 404) {
|
|
49
|
-
throw new Error(
|
|
50
|
-
version
|
|
51
|
-
? `Release ${version} not found for ${TEMPLATES_REPO}`
|
|
52
|
-
: `No releases found for ${TEMPLATES_REPO}`
|
|
53
|
-
)
|
|
42
|
+
throw new Error(`Release ${version} not found for ${TEMPLATES_REPO}`)
|
|
54
43
|
}
|
|
55
44
|
await handleGitHubError(releaseResponse)
|
|
56
45
|
}
|
|
@@ -80,28 +69,20 @@ export async function fetchManifest(options = {}) {
|
|
|
80
69
|
|
|
81
70
|
const manifest = await manifestResponse.json()
|
|
82
71
|
|
|
83
|
-
|
|
84
|
-
const result = {
|
|
72
|
+
return {
|
|
85
73
|
version: release.tag_name,
|
|
86
74
|
templates: manifest.templates || {},
|
|
87
75
|
// Base URL for downloading template tarballs
|
|
88
76
|
downloadUrlBase: `https://github.com/${TEMPLATES_REPO}/releases/download/${release.tag_name}`
|
|
89
77
|
}
|
|
90
|
-
|
|
91
|
-
// Cache if this was a "latest" fetch
|
|
92
|
-
if (!version) {
|
|
93
|
-
manifestCache = result
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return result
|
|
97
78
|
}
|
|
98
79
|
|
|
99
80
|
/**
|
|
100
|
-
* Fetch a
|
|
81
|
+
* Fetch a template from one release of the official templates
|
|
101
82
|
*
|
|
102
83
|
* @param {string} name - Template name (e.g., 'marketing')
|
|
103
84
|
* @param {Object} options - Fetch options
|
|
104
|
-
* @param {string} options.version -
|
|
85
|
+
* @param {string} options.version - The release tag, e.g. `v0.18.4`
|
|
105
86
|
* @param {Function} options.onProgress - Progress callback
|
|
106
87
|
* @returns {Promise<Object>} { tempDir, version, metadata }
|
|
107
88
|
*/
|
|
@@ -166,33 +147,6 @@ export async function fetchOfficialTemplate(name, options = {}) {
|
|
|
166
147
|
}
|
|
167
148
|
}
|
|
168
149
|
|
|
169
|
-
/**
|
|
170
|
-
* List available templates from the latest release
|
|
171
|
-
*
|
|
172
|
-
* @param {Object} options - Fetch options
|
|
173
|
-
* @param {Function} options.onProgress - Progress callback
|
|
174
|
-
* @returns {Promise<Array>} List of template metadata
|
|
175
|
-
*/
|
|
176
|
-
export async function listOfficialTemplates(options = {}) {
|
|
177
|
-
try {
|
|
178
|
-
const manifest = await fetchManifest(options)
|
|
179
|
-
return Object.entries(manifest.templates).map(([id, info]) => ({
|
|
180
|
-
id,
|
|
181
|
-
...info
|
|
182
|
-
}))
|
|
183
|
-
} catch {
|
|
184
|
-
// Return empty list if can't fetch
|
|
185
|
-
return []
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Clear the manifest cache
|
|
191
|
-
*/
|
|
192
|
-
export function clearManifestCache() {
|
|
193
|
-
manifestCache = null
|
|
194
|
-
}
|
|
195
|
-
|
|
196
150
|
/**
|
|
197
151
|
* Get GitHub API headers
|
|
198
152
|
*/
|
package/src/templates/index.js
CHANGED
|
@@ -12,15 +12,13 @@ import {
|
|
|
12
12
|
getTemplateDisplayName,
|
|
13
13
|
BUILTIN_TEMPLATES,
|
|
14
14
|
OFFICIAL_TEMPLATES,
|
|
15
|
-
|
|
15
|
+
OFFICIAL_TEMPLATES_RELEASE,
|
|
16
|
+
buildTemplateChoices,
|
|
17
|
+
templatePickerPageSize
|
|
16
18
|
} from './resolver.js'
|
|
17
19
|
import { fetchNpmTemplate } from './fetchers/npm.js'
|
|
18
20
|
import { fetchGitHubTemplate } from './fetchers/github.js'
|
|
19
|
-
import {
|
|
20
|
-
fetchOfficialTemplate,
|
|
21
|
-
listOfficialTemplates
|
|
22
|
-
} from './fetchers/release.js'
|
|
23
|
-
import { validateTemplate } from './validator.js'
|
|
21
|
+
import { fetchOfficialTemplate } from './fetchers/release.js'
|
|
24
22
|
|
|
25
23
|
/**
|
|
26
24
|
* Resolve a template identifier and return the template path
|
|
@@ -61,12 +59,20 @@ export async function resolveTemplate(identifier, options = {}) {
|
|
|
61
59
|
}
|
|
62
60
|
|
|
63
61
|
/**
|
|
64
|
-
* Resolve an official template from
|
|
62
|
+
* Resolve an official template from the release this CLI was published with —
|
|
63
|
+
* never `latest`, so its content matches the package versions the scaffold pins
|
|
64
|
+
* (resolver.js, OFFICIAL_TEMPLATES_RELEASE).
|
|
65
65
|
*/
|
|
66
66
|
async function resolveOfficialTemplate(name, options = {}) {
|
|
67
67
|
const { onProgress } = options
|
|
68
68
|
|
|
69
|
+
if (!OFFICIAL_TEMPLATES_RELEASE) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
"This CLI's framework index names no @uniweb/templates release to download from."
|
|
72
|
+
)
|
|
73
|
+
}
|
|
69
74
|
const { tempDir, baseTempDir, version } = await fetchOfficialTemplate(name, {
|
|
75
|
+
version: OFFICIAL_TEMPLATES_RELEASE,
|
|
70
76
|
onProgress
|
|
71
77
|
})
|
|
72
78
|
|
|
@@ -173,35 +179,12 @@ async function resolveLocalTemplate(templatePath, options = {}) {
|
|
|
173
179
|
}
|
|
174
180
|
}
|
|
175
181
|
|
|
176
|
-
/**
|
|
177
|
-
* List all available templates
|
|
178
|
-
*/
|
|
179
|
-
export async function listAvailableTemplates() {
|
|
180
|
-
const templates = []
|
|
181
|
-
|
|
182
|
-
// Official templates from GitHub releases
|
|
183
|
-
try {
|
|
184
|
-
const official = await listOfficialTemplates()
|
|
185
|
-
for (const t of official) {
|
|
186
|
-
templates.push({
|
|
187
|
-
type: 'official',
|
|
188
|
-
id: t.id,
|
|
189
|
-
name: t.name || t.id,
|
|
190
|
-
description: t.description || ''
|
|
191
|
-
})
|
|
192
|
-
}
|
|
193
|
-
} catch {
|
|
194
|
-
// Ignore errors - templates just won't be listed
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return templates
|
|
198
|
-
}
|
|
199
|
-
|
|
200
182
|
// Re-export for convenience
|
|
201
183
|
export {
|
|
202
184
|
parseTemplateId,
|
|
203
185
|
getTemplateDisplayName,
|
|
204
186
|
BUILTIN_TEMPLATES,
|
|
205
187
|
OFFICIAL_TEMPLATES,
|
|
206
|
-
buildTemplateChoices
|
|
188
|
+
buildTemplateChoices,
|
|
189
|
+
templatePickerPageSize
|
|
207
190
|
}
|