uniweb 0.13.17 → 0.14.1
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
|
@@ -33,13 +33,13 @@ export async function fetchGitHubTemplate(owner, repo, options = {}) {
|
|
|
33
33
|
try {
|
|
34
34
|
const response = await fetchWithRetry(tarballUrl, {
|
|
35
35
|
headers: {
|
|
36
|
-
|
|
36
|
+
Accept: 'application/vnd.github+json',
|
|
37
37
|
'User-Agent': 'uniweb-cli',
|
|
38
38
|
// Support private repos if GITHUB_TOKEN is set
|
|
39
39
|
...(process.env.GITHUB_TOKEN && {
|
|
40
|
-
|
|
41
|
-
})
|
|
42
|
-
}
|
|
40
|
+
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
43
|
})
|
|
44
44
|
|
|
45
45
|
if (!response.ok) {
|
|
@@ -69,7 +69,7 @@ export async function fetchGitHubTemplate(owner, repo, options = {}) {
|
|
|
69
69
|
|
|
70
70
|
return {
|
|
71
71
|
tempDir,
|
|
72
|
-
ref: displayRef
|
|
72
|
+
ref: displayRef
|
|
73
73
|
}
|
|
74
74
|
} catch (err) {
|
|
75
75
|
// Clean up on error
|
|
@@ -86,13 +86,13 @@ async function fetchWithRetry(url, options = {}, maxRetries = 3) {
|
|
|
86
86
|
try {
|
|
87
87
|
const response = await fetch(url, {
|
|
88
88
|
...options,
|
|
89
|
-
signal: AbortSignal.timeout(60000)
|
|
89
|
+
signal: AbortSignal.timeout(60000) // 60s timeout for GitHub (can be slow)
|
|
90
90
|
})
|
|
91
91
|
return response
|
|
92
92
|
} catch (err) {
|
|
93
93
|
if (attempt === maxRetries) throw err
|
|
94
94
|
const delay = Math.min(1000 * Math.pow(2, attempt), 10000)
|
|
95
|
-
await new Promise(r => setTimeout(r, delay))
|
|
95
|
+
await new Promise((r) => setTimeout(r, delay))
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
}
|
|
@@ -67,7 +67,7 @@ export async function fetchNpmTemplate(packageName, options = {}) {
|
|
|
67
67
|
return {
|
|
68
68
|
tempDir,
|
|
69
69
|
version,
|
|
70
|
-
metadata: versionMeta
|
|
70
|
+
metadata: versionMeta
|
|
71
71
|
}
|
|
72
72
|
} catch (err) {
|
|
73
73
|
// Clean up on error
|
|
@@ -84,13 +84,13 @@ async function fetchWithRetry(url, options = {}, maxRetries = 3) {
|
|
|
84
84
|
try {
|
|
85
85
|
const response = await fetch(url, {
|
|
86
86
|
...options,
|
|
87
|
-
signal: AbortSignal.timeout(30000)
|
|
87
|
+
signal: AbortSignal.timeout(30000) // 30s timeout
|
|
88
88
|
})
|
|
89
89
|
return response
|
|
90
90
|
} catch (err) {
|
|
91
91
|
if (attempt === maxRetries) throw err
|
|
92
92
|
const delay = Math.min(1000 * Math.pow(2, attempt), 10000)
|
|
93
|
-
await new Promise(r => setTimeout(r, delay))
|
|
93
|
+
await new Promise((r) => setTimeout(r, delay))
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
}
|
|
@@ -40,7 +40,7 @@ export async function fetchManifest(options = {}) {
|
|
|
40
40
|
: `${GITHUB_API}/repos/${TEMPLATES_REPO}/releases/latest`
|
|
41
41
|
|
|
42
42
|
const releaseResponse = await fetchWithRetry(releaseUrl, {
|
|
43
|
-
headers: getGitHubHeaders()
|
|
43
|
+
headers: getGitHubHeaders()
|
|
44
44
|
})
|
|
45
45
|
|
|
46
46
|
if (!releaseResponse.ok) {
|
|
@@ -57,18 +57,21 @@ export async function fetchManifest(options = {}) {
|
|
|
57
57
|
const release = await releaseResponse.json()
|
|
58
58
|
|
|
59
59
|
// Find manifest.json asset
|
|
60
|
-
const manifestAsset = release.assets?.find(a => a.name === 'manifest.json')
|
|
60
|
+
const manifestAsset = release.assets?.find((a) => a.name === 'manifest.json')
|
|
61
61
|
if (!manifestAsset) {
|
|
62
62
|
throw new Error(
|
|
63
63
|
`Release ${release.tag_name} does not contain manifest.json. ` +
|
|
64
|
-
|
|
64
|
+
`This may be an older release format.`
|
|
65
65
|
)
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
// Download manifest
|
|
69
|
-
const manifestResponse = await fetchWithRetry(
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
const manifestResponse = await fetchWithRetry(
|
|
70
|
+
manifestAsset.browser_download_url,
|
|
71
|
+
{
|
|
72
|
+
headers: getGitHubHeaders()
|
|
73
|
+
}
|
|
74
|
+
)
|
|
72
75
|
|
|
73
76
|
if (!manifestResponse.ok) {
|
|
74
77
|
throw new Error(`Failed to download manifest: ${manifestResponse.status}`)
|
|
@@ -81,7 +84,7 @@ export async function fetchManifest(options = {}) {
|
|
|
81
84
|
version: release.tag_name,
|
|
82
85
|
templates: manifest.templates || {},
|
|
83
86
|
// Base URL for downloading template tarballs
|
|
84
|
-
downloadUrlBase: `https://github.com/${TEMPLATES_REPO}/releases/download/${release.tag_name}
|
|
87
|
+
downloadUrlBase: `https://github.com/${TEMPLATES_REPO}/releases/download/${release.tag_name}`
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
// Cache if this was a "latest" fetch
|
|
@@ -113,7 +116,7 @@ export async function fetchOfficialTemplate(name, options = {}) {
|
|
|
113
116
|
const available = Object.keys(manifest.templates).join(', ')
|
|
114
117
|
throw new Error(
|
|
115
118
|
`Template "${name}" not found in release ${manifest.version}.\n` +
|
|
116
|
-
|
|
119
|
+
`Available templates: ${available || 'none'}`
|
|
117
120
|
)
|
|
118
121
|
}
|
|
119
122
|
|
|
@@ -122,14 +125,14 @@ export async function fetchOfficialTemplate(name, options = {}) {
|
|
|
122
125
|
// Download template tarball
|
|
123
126
|
const tarballUrl = `${manifest.downloadUrlBase}/${name}.tar.gz`
|
|
124
127
|
const tarballResponse = await fetchWithRetry(tarballUrl, {
|
|
125
|
-
headers: getGitHubHeaders()
|
|
128
|
+
headers: getGitHubHeaders()
|
|
126
129
|
})
|
|
127
130
|
|
|
128
131
|
if (!tarballResponse.ok) {
|
|
129
132
|
if (tarballResponse.status === 404) {
|
|
130
133
|
throw new Error(
|
|
131
134
|
`Template tarball not found: ${name}.tar.gz\n` +
|
|
132
|
-
|
|
135
|
+
`The release may be incomplete or corrupted.`
|
|
133
136
|
)
|
|
134
137
|
}
|
|
135
138
|
throw new Error(`Failed to download template: ${tarballResponse.status}`)
|
|
@@ -153,7 +156,7 @@ export async function fetchOfficialTemplate(name, options = {}) {
|
|
|
153
156
|
tempDir: join(tempDir, name),
|
|
154
157
|
baseTempDir: tempDir, // For cleanup
|
|
155
158
|
version: manifest.version,
|
|
156
|
-
metadata: templateInfo
|
|
159
|
+
metadata: templateInfo
|
|
157
160
|
}
|
|
158
161
|
} catch (err) {
|
|
159
162
|
// Clean up on error
|
|
@@ -174,7 +177,7 @@ export async function listOfficialTemplates(options = {}) {
|
|
|
174
177
|
const manifest = await fetchManifest(options)
|
|
175
178
|
return Object.entries(manifest.templates).map(([id, info]) => ({
|
|
176
179
|
id,
|
|
177
|
-
...info
|
|
180
|
+
...info
|
|
178
181
|
}))
|
|
179
182
|
} catch {
|
|
180
183
|
// Return empty list if can't fetch
|
|
@@ -194,12 +197,12 @@ export function clearManifestCache() {
|
|
|
194
197
|
*/
|
|
195
198
|
function getGitHubHeaders() {
|
|
196
199
|
return {
|
|
197
|
-
|
|
200
|
+
Accept: 'application/vnd.github+json',
|
|
198
201
|
'User-Agent': 'uniweb-cli',
|
|
199
202
|
// Support private repos or higher rate limits if GITHUB_TOKEN is set
|
|
200
203
|
...(process.env.GITHUB_TOKEN && {
|
|
201
|
-
|
|
202
|
-
})
|
|
204
|
+
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`
|
|
205
|
+
})
|
|
203
206
|
}
|
|
204
207
|
}
|
|
205
208
|
|
|
@@ -212,7 +215,7 @@ async function handleGitHubError(response) {
|
|
|
212
215
|
if (remaining === '0') {
|
|
213
216
|
throw new Error(
|
|
214
217
|
'GitHub API rate limit exceeded.\n' +
|
|
215
|
-
|
|
218
|
+
'Set GITHUB_TOKEN environment variable for higher limits.'
|
|
216
219
|
)
|
|
217
220
|
}
|
|
218
221
|
}
|
|
@@ -228,13 +231,13 @@ async function fetchWithRetry(url, options = {}, maxRetries = 3) {
|
|
|
228
231
|
const response = await fetch(url, {
|
|
229
232
|
...options,
|
|
230
233
|
redirect: 'follow',
|
|
231
|
-
signal: AbortSignal.timeout(60000)
|
|
234
|
+
signal: AbortSignal.timeout(60000) // 60s timeout
|
|
232
235
|
})
|
|
233
236
|
return response
|
|
234
237
|
} catch (err) {
|
|
235
238
|
if (attempt === maxRetries) throw err
|
|
236
239
|
const delay = Math.min(1000 * Math.pow(2, attempt), 10000)
|
|
237
|
-
await new Promise(r => setTimeout(r, delay))
|
|
240
|
+
await new Promise((r) => setTimeout(r, delay))
|
|
238
241
|
}
|
|
239
242
|
}
|
|
240
243
|
}
|
package/src/templates/index.js
CHANGED
|
@@ -7,10 +7,19 @@ import { readFile, rm } from 'node:fs/promises'
|
|
|
7
7
|
import { join, resolve } from 'node:path'
|
|
8
8
|
import { homedir } from 'node:os'
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
parseTemplateId,
|
|
12
|
+
getTemplateDisplayName,
|
|
13
|
+
BUILTIN_TEMPLATES,
|
|
14
|
+
OFFICIAL_TEMPLATES,
|
|
15
|
+
buildTemplateChoices
|
|
16
|
+
} from './resolver.js'
|
|
11
17
|
import { fetchNpmTemplate } from './fetchers/npm.js'
|
|
12
18
|
import { fetchGitHubTemplate } from './fetchers/github.js'
|
|
13
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
fetchOfficialTemplate,
|
|
21
|
+
listOfficialTemplates
|
|
22
|
+
} from './fetchers/release.js'
|
|
14
23
|
import { validateTemplate } from './validator.js'
|
|
15
24
|
|
|
16
25
|
/**
|
|
@@ -29,7 +38,7 @@ export async function resolveTemplate(identifier, options = {}) {
|
|
|
29
38
|
case 'builtin':
|
|
30
39
|
return {
|
|
31
40
|
type: 'builtin',
|
|
32
|
-
name: parsed.name
|
|
41
|
+
name: parsed.name
|
|
33
42
|
// Built-in templates are handled programmatically by the CLI
|
|
34
43
|
// No path or cleanup needed
|
|
35
44
|
}
|
|
@@ -57,7 +66,9 @@ export async function resolveTemplate(identifier, options = {}) {
|
|
|
57
66
|
async function resolveOfficialTemplate(name, options = {}) {
|
|
58
67
|
const { onProgress } = options
|
|
59
68
|
|
|
60
|
-
const { tempDir, baseTempDir, version } = await fetchOfficialTemplate(name, {
|
|
69
|
+
const { tempDir, baseTempDir, version } = await fetchOfficialTemplate(name, {
|
|
70
|
+
onProgress
|
|
71
|
+
})
|
|
61
72
|
|
|
62
73
|
onProgress?.(`Using official template: ${name} (${version})`)
|
|
63
74
|
|
|
@@ -69,7 +80,7 @@ async function resolveOfficialTemplate(name, options = {}) {
|
|
|
69
80
|
cleanup: async () => {
|
|
70
81
|
// Clean up the base temp directory (parent of the template)
|
|
71
82
|
await rm(baseTempDir, { recursive: true, force: true }).catch(() => {})
|
|
72
|
-
}
|
|
83
|
+
}
|
|
73
84
|
}
|
|
74
85
|
}
|
|
75
86
|
|
|
@@ -81,7 +92,9 @@ async function resolveNpmTemplate(packageName, options = {}) {
|
|
|
81
92
|
|
|
82
93
|
onProgress?.(`Resolving npm template: ${packageName}`)
|
|
83
94
|
|
|
84
|
-
const { tempDir, version, metadata } = await fetchNpmTemplate(packageName, {
|
|
95
|
+
const { tempDir, version, metadata } = await fetchNpmTemplate(packageName, {
|
|
96
|
+
onProgress
|
|
97
|
+
})
|
|
85
98
|
|
|
86
99
|
return {
|
|
87
100
|
type: 'npm',
|
|
@@ -90,7 +103,7 @@ async function resolveNpmTemplate(packageName, options = {}) {
|
|
|
90
103
|
path: tempDir,
|
|
91
104
|
cleanup: async () => {
|
|
92
105
|
await rm(tempDir, { recursive: true, force: true }).catch(() => {})
|
|
93
|
-
}
|
|
106
|
+
}
|
|
94
107
|
}
|
|
95
108
|
}
|
|
96
109
|
|
|
@@ -103,7 +116,10 @@ async function resolveGitHubTemplate(parsed, options = {}) {
|
|
|
103
116
|
|
|
104
117
|
onProgress?.(`Resolving GitHub template: ${owner}/${repo}`)
|
|
105
118
|
|
|
106
|
-
const { tempDir } = await fetchGitHubTemplate(owner, repo, {
|
|
119
|
+
const { tempDir } = await fetchGitHubTemplate(owner, repo, {
|
|
120
|
+
ref,
|
|
121
|
+
onProgress
|
|
122
|
+
})
|
|
107
123
|
|
|
108
124
|
return {
|
|
109
125
|
type: 'github',
|
|
@@ -113,7 +129,7 @@ async function resolveGitHubTemplate(parsed, options = {}) {
|
|
|
113
129
|
path: tempDir,
|
|
114
130
|
cleanup: async () => {
|
|
115
131
|
await rm(tempDir, { recursive: true, force: true }).catch(() => {})
|
|
116
|
-
}
|
|
132
|
+
}
|
|
117
133
|
}
|
|
118
134
|
}
|
|
119
135
|
|
|
@@ -153,7 +169,7 @@ async function resolveLocalTemplate(templatePath, options = {}) {
|
|
|
153
169
|
type: 'local',
|
|
154
170
|
name,
|
|
155
171
|
path: resolvedPath,
|
|
156
|
-
cleanup: null
|
|
172
|
+
cleanup: null
|
|
157
173
|
}
|
|
158
174
|
}
|
|
159
175
|
|
|
@@ -171,7 +187,7 @@ export async function listAvailableTemplates() {
|
|
|
171
187
|
type: 'official',
|
|
172
188
|
id: t.id,
|
|
173
189
|
name: t.name || t.id,
|
|
174
|
-
description: t.description || ''
|
|
190
|
+
description: t.description || ''
|
|
175
191
|
})
|
|
176
192
|
}
|
|
177
193
|
} catch {
|
|
@@ -182,4 +198,10 @@ export async function listAvailableTemplates() {
|
|
|
182
198
|
}
|
|
183
199
|
|
|
184
200
|
// Re-export for convenience
|
|
185
|
-
export {
|
|
201
|
+
export {
|
|
202
|
+
parseTemplateId,
|
|
203
|
+
getTemplateDisplayName,
|
|
204
|
+
BUILTIN_TEMPLATES,
|
|
205
|
+
OFFICIAL_TEMPLATES,
|
|
206
|
+
buildTemplateChoices
|
|
207
|
+
}
|
|
@@ -69,7 +69,7 @@ function registerPartials() {
|
|
|
69
69
|
* Handlebars helper to get a package version
|
|
70
70
|
* Usage: {{version "@uniweb/build"}} or {{version "build"}}
|
|
71
71
|
*/
|
|
72
|
-
Handlebars.registerHelper('version', function(packageName) {
|
|
72
|
+
Handlebars.registerHelper('version', function (packageName) {
|
|
73
73
|
// Try exact match first
|
|
74
74
|
if (versionData[packageName]) {
|
|
75
75
|
return versionData[packageName]
|
|
@@ -85,10 +85,27 @@ Handlebars.registerHelper('version', function(packageName) {
|
|
|
85
85
|
|
|
86
86
|
// Text file extensions that should be processed for variables
|
|
87
87
|
const TEXT_EXTENSIONS = new Set([
|
|
88
|
-
'.js',
|
|
89
|
-
'.
|
|
90
|
-
'.
|
|
91
|
-
'.
|
|
88
|
+
'.js',
|
|
89
|
+
'.jsx',
|
|
90
|
+
'.ts',
|
|
91
|
+
'.tsx',
|
|
92
|
+
'.mjs',
|
|
93
|
+
'.cjs',
|
|
94
|
+
'.json',
|
|
95
|
+
'.yml',
|
|
96
|
+
'.yaml',
|
|
97
|
+
'.md',
|
|
98
|
+
'.mdx',
|
|
99
|
+
'.html',
|
|
100
|
+
'.htm',
|
|
101
|
+
'.css',
|
|
102
|
+
'.scss',
|
|
103
|
+
'.less',
|
|
104
|
+
'.txt',
|
|
105
|
+
'.xml',
|
|
106
|
+
'.svg',
|
|
107
|
+
'.vue',
|
|
108
|
+
'.astro'
|
|
92
109
|
])
|
|
93
110
|
|
|
94
111
|
/**
|
|
@@ -96,7 +113,7 @@ const TEXT_EXTENSIONS = new Set([
|
|
|
96
113
|
*/
|
|
97
114
|
function findUnresolvedPlaceholders(content) {
|
|
98
115
|
const patterns = [
|
|
99
|
-
/\{\{([^#/}>][^}]*)\}\}/g
|
|
116
|
+
/\{\{([^#/}>][^}]*)\}\}/g // {{variable}} - not blocks or partials
|
|
100
117
|
]
|
|
101
118
|
|
|
102
119
|
const unresolved = []
|
|
@@ -146,7 +163,9 @@ async function processFile(sourcePath, targetPath, data, options = {}) {
|
|
|
146
163
|
// Check for unresolved placeholders
|
|
147
164
|
const unresolved = findUnresolvedPlaceholders(content)
|
|
148
165
|
if (unresolved.length > 0 && options.onWarning) {
|
|
149
|
-
options.onWarning(
|
|
166
|
+
options.onWarning(
|
|
167
|
+
`Unresolved placeholders in ${path.basename(targetPath)}: ${unresolved.join(', ')}`
|
|
168
|
+
)
|
|
150
169
|
}
|
|
151
170
|
|
|
152
171
|
await fs.writeFile(targetPath, content)
|
|
@@ -182,7 +201,9 @@ async function processFile(sourcePath, targetPath, data, options = {}) {
|
|
|
182
201
|
* directory-only behavior.
|
|
183
202
|
*/
|
|
184
203
|
function dotfileRename(name) {
|
|
185
|
-
return name.startsWith('_') && !name.startsWith('__')
|
|
204
|
+
return name.startsWith('_') && !name.startsWith('__')
|
|
205
|
+
? `.${name.slice(1)}`
|
|
206
|
+
: name
|
|
186
207
|
}
|
|
187
208
|
|
|
188
209
|
/**
|
|
@@ -191,7 +212,9 @@ function dotfileRename(name) {
|
|
|
191
212
|
* `.gitignore` and `_env.local.hbs` → `.env.local`.
|
|
192
213
|
*/
|
|
193
214
|
function templateOutputName(sourceName) {
|
|
194
|
-
const base = sourceName.endsWith('.hbs')
|
|
215
|
+
const base = sourceName.endsWith('.hbs')
|
|
216
|
+
? sourceName.slice(0, -4)
|
|
217
|
+
: sourceName
|
|
195
218
|
return dotfileRename(base)
|
|
196
219
|
}
|
|
197
220
|
|
|
@@ -205,7 +228,12 @@ function templateOutputName(sourceName) {
|
|
|
205
228
|
* @param {Function} options.onWarning - Warning callback
|
|
206
229
|
* @param {Function} options.onProgress - Progress callback
|
|
207
230
|
*/
|
|
208
|
-
export async function copyTemplateDirectory(
|
|
231
|
+
export async function copyTemplateDirectory(
|
|
232
|
+
sourcePath,
|
|
233
|
+
targetPath,
|
|
234
|
+
data,
|
|
235
|
+
options = {}
|
|
236
|
+
) {
|
|
209
237
|
const { onWarning, onProgress, skip } = options
|
|
210
238
|
|
|
211
239
|
await fs.mkdir(targetPath, { recursive: true })
|
|
@@ -219,7 +247,11 @@ export async function copyTemplateDirectory(sourcePath, targetPath, data, option
|
|
|
219
247
|
// Rename _prefix directories to .prefix (e.g., _vscode → .vscode).
|
|
220
248
|
const targetFullPath = path.join(targetPath, dotfileRename(sourceName))
|
|
221
249
|
|
|
222
|
-
await copyTemplateDirectory(sourceFullPath, targetFullPath, data, {
|
|
250
|
+
await copyTemplateDirectory(sourceFullPath, targetFullPath, data, {
|
|
251
|
+
onWarning,
|
|
252
|
+
onProgress,
|
|
253
|
+
skip
|
|
254
|
+
})
|
|
223
255
|
} else {
|
|
224
256
|
// Skip template.json as it's metadata for the template, not for the output
|
|
225
257
|
if (sourceName === 'template.json') {
|
|
@@ -282,7 +314,7 @@ async function enumerateInto(sourcePath, relPath, outputs, skip) {
|
|
|
282
314
|
path.join(sourcePath, sourceName),
|
|
283
315
|
relPath ? path.join(relPath, targetName) : targetName,
|
|
284
316
|
outputs,
|
|
285
|
-
skip
|
|
317
|
+
skip
|
|
286
318
|
)
|
|
287
319
|
} else {
|
|
288
320
|
if (sourceName === 'template.json') continue
|
|
@@ -45,7 +45,14 @@ export const BUILTIN_TEMPLATES = ['blank', 'starter', 'none']
|
|
|
45
45
|
function loadOfficialTemplateMap() {
|
|
46
46
|
// Local dev: framework/templates/manifest.json relative to this file
|
|
47
47
|
// at framework/cli/src/templates/resolver.js
|
|
48
|
-
const workspaceManifest = join(
|
|
48
|
+
const workspaceManifest = join(
|
|
49
|
+
__dirname,
|
|
50
|
+
'..',
|
|
51
|
+
'..',
|
|
52
|
+
'..',
|
|
53
|
+
'templates',
|
|
54
|
+
'manifest.json'
|
|
55
|
+
)
|
|
49
56
|
const picked = tryReadTemplateMap(workspaceManifest)
|
|
50
57
|
if (picked) return picked
|
|
51
58
|
|
|
@@ -85,8 +92,16 @@ export const OFFICIAL_TEMPLATES = Object.keys(OFFICIAL_TEMPLATE_MAP)
|
|
|
85
92
|
// Built-in (programmatic) picker entries — not in the manifest; the CLI
|
|
86
93
|
// generates these itself. "Blank" trails the official templates.
|
|
87
94
|
const BUILTIN_LEAD_CHOICES = [
|
|
88
|
-
{
|
|
89
|
-
|
|
95
|
+
{
|
|
96
|
+
title: 'None',
|
|
97
|
+
value: 'none',
|
|
98
|
+
description: 'Foundation + site with no content'
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
title: 'Starter',
|
|
102
|
+
value: 'starter',
|
|
103
|
+
description: 'Foundation + site + sample content'
|
|
104
|
+
}
|
|
90
105
|
]
|
|
91
106
|
const BLANK_CHOICE = {
|
|
92
107
|
title: 'Blank workspace',
|
|
@@ -130,7 +145,7 @@ export function parseTemplateId(identifier) {
|
|
|
130
145
|
if (BUILTIN_TEMPLATES.includes(identifier)) {
|
|
131
146
|
return {
|
|
132
147
|
type: 'builtin',
|
|
133
|
-
name: identifier
|
|
148
|
+
name: identifier
|
|
134
149
|
}
|
|
135
150
|
}
|
|
136
151
|
|
|
@@ -138,7 +153,7 @@ export function parseTemplateId(identifier) {
|
|
|
138
153
|
if (OFFICIAL_TEMPLATES.includes(identifier)) {
|
|
139
154
|
return {
|
|
140
155
|
type: 'official',
|
|
141
|
-
name: identifier
|
|
156
|
+
name: identifier
|
|
142
157
|
}
|
|
143
158
|
}
|
|
144
159
|
|
|
@@ -149,19 +164,25 @@ export function parseTemplateId(identifier) {
|
|
|
149
164
|
}
|
|
150
165
|
|
|
151
166
|
// GitHub URL: https://github.com/user/repo
|
|
152
|
-
if (
|
|
167
|
+
if (
|
|
168
|
+
identifier.startsWith('https://github.com/') ||
|
|
169
|
+
identifier.startsWith('http://github.com/')
|
|
170
|
+
) {
|
|
153
171
|
const url = new URL(identifier)
|
|
154
172
|
const pathParts = url.pathname.split('/').filter(Boolean)
|
|
155
173
|
if (pathParts.length >= 2) {
|
|
156
174
|
const [owner, repo] = pathParts
|
|
157
175
|
// Check for tree/branch in URL
|
|
158
176
|
const treeIndex = pathParts.indexOf('tree')
|
|
159
|
-
const ref =
|
|
177
|
+
const ref =
|
|
178
|
+
treeIndex >= 0 && pathParts[treeIndex + 1]
|
|
179
|
+
? pathParts[treeIndex + 1]
|
|
180
|
+
: undefined
|
|
160
181
|
return {
|
|
161
182
|
type: 'github',
|
|
162
183
|
owner,
|
|
163
184
|
repo: repo.replace(/\.git$/, ''),
|
|
164
|
-
ref
|
|
185
|
+
ref
|
|
165
186
|
}
|
|
166
187
|
}
|
|
167
188
|
throw new Error(`Invalid GitHub URL: ${identifier}`)
|
|
@@ -171,16 +192,20 @@ export function parseTemplateId(identifier) {
|
|
|
171
192
|
if (identifier.startsWith('@')) {
|
|
172
193
|
return {
|
|
173
194
|
type: 'npm',
|
|
174
|
-
package: identifier
|
|
195
|
+
package: identifier
|
|
175
196
|
}
|
|
176
197
|
}
|
|
177
198
|
|
|
178
199
|
// Local path (relative, absolute, or home directory)
|
|
179
|
-
if (
|
|
180
|
-
|
|
200
|
+
if (
|
|
201
|
+
identifier.startsWith('./') ||
|
|
202
|
+
identifier.startsWith('../') ||
|
|
203
|
+
identifier.startsWith('/') ||
|
|
204
|
+
identifier.startsWith('~')
|
|
205
|
+
) {
|
|
181
206
|
return {
|
|
182
207
|
type: 'local',
|
|
183
|
-
path: identifier
|
|
208
|
+
path: identifier
|
|
184
209
|
}
|
|
185
210
|
}
|
|
186
211
|
|
|
@@ -188,7 +213,7 @@ export function parseTemplateId(identifier) {
|
|
|
188
213
|
// This allows users to type `uniweb create foo --template blog` for @uniweb/template-blog
|
|
189
214
|
return {
|
|
190
215
|
type: 'npm',
|
|
191
|
-
package: `@uniweb/template-${identifier}
|
|
216
|
+
package: `@uniweb/template-${identifier}`
|
|
192
217
|
}
|
|
193
218
|
}
|
|
194
219
|
|
|
@@ -200,14 +225,16 @@ function parseGitHubIdentifier(identifier) {
|
|
|
200
225
|
const [owner, repo] = repoPath.split('/')
|
|
201
226
|
|
|
202
227
|
if (!owner || !repo) {
|
|
203
|
-
throw new Error(
|
|
228
|
+
throw new Error(
|
|
229
|
+
`Invalid GitHub identifier: ${identifier}. Expected format: user/repo or user/repo#ref`
|
|
230
|
+
)
|
|
204
231
|
}
|
|
205
232
|
|
|
206
233
|
return {
|
|
207
234
|
type: 'github',
|
|
208
235
|
owner,
|
|
209
236
|
repo: repo.replace(/\.git$/, ''),
|
|
210
|
-
ref: ref || undefined
|
|
237
|
+
ref: ref || undefined
|
|
211
238
|
}
|
|
212
239
|
}
|
|
213
240
|
|
|
@@ -43,7 +43,8 @@ export function satisfiesVersion(version, range) {
|
|
|
43
43
|
// ^x.y.z means >=x.y.z and <(x+1).0.0
|
|
44
44
|
const min = parseVersion(range.slice(1))
|
|
45
45
|
if (!min) return true
|
|
46
|
-
if (current.major !== min.major)
|
|
46
|
+
if (current.major !== min.major)
|
|
47
|
+
return current.major > min.major && min.major === 0
|
|
47
48
|
if (current.minor > min.minor) return true
|
|
48
49
|
if (current.minor < min.minor) return false
|
|
49
50
|
return current.patch >= min.patch
|
|
@@ -71,9 +72,11 @@ export function satisfiesVersion(version, range) {
|
|
|
71
72
|
// Exact match
|
|
72
73
|
const exact = parseVersion(range)
|
|
73
74
|
if (!exact) return true
|
|
74
|
-
return
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
return (
|
|
76
|
+
current.major === exact.major &&
|
|
77
|
+
current.minor === exact.minor &&
|
|
78
|
+
current.patch === exact.patch
|
|
79
|
+
)
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
/**
|
|
@@ -93,7 +96,7 @@ export const ErrorCodes = {
|
|
|
93
96
|
INVALID_TEMPLATE_JSON: 'INVALID_TEMPLATE_JSON',
|
|
94
97
|
MISSING_CONTENT_DIR: 'MISSING_CONTENT_DIR',
|
|
95
98
|
MISSING_REQUIRED_FIELD: 'MISSING_REQUIRED_FIELD',
|
|
96
|
-
VERSION_MISMATCH: 'VERSION_MISMATCH'
|
|
99
|
+
VERSION_MISMATCH: 'VERSION_MISMATCH'
|
|
97
100
|
}
|
|
98
101
|
|
|
99
102
|
/**
|
|
@@ -189,7 +192,7 @@ export function resolveContentDirs(templateRoot, metadata) {
|
|
|
189
192
|
type: pkg.type,
|
|
190
193
|
name: pkg.name,
|
|
191
194
|
dir,
|
|
192
|
-
...(pkg.foundation ? { foundation: pkg.foundation } : {})
|
|
195
|
+
...(pkg.foundation ? { foundation: pkg.foundation } : {})
|
|
193
196
|
}
|
|
194
197
|
if (entry.type === 'foundation' || entry.type === 'extension') {
|
|
195
198
|
applyLegacyFoundationLayout(entry)
|
|
@@ -201,7 +204,11 @@ export function resolveContentDirs(templateRoot, metadata) {
|
|
|
201
204
|
// Standard template: look for foundation/ and site/
|
|
202
205
|
const foundationDir = path.join(templateRoot, 'foundation')
|
|
203
206
|
if (existsSync(foundationDir)) {
|
|
204
|
-
const entry = {
|
|
207
|
+
const entry = {
|
|
208
|
+
type: 'foundation',
|
|
209
|
+
name: 'foundation',
|
|
210
|
+
dir: foundationDir
|
|
211
|
+
}
|
|
205
212
|
applyLegacyFoundationLayout(entry)
|
|
206
213
|
dirs.push(entry)
|
|
207
214
|
}
|