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.
Files changed (65) hide show
  1. package/README.md +1 -1
  2. package/package.json +7 -7
  3. package/partials/agents.md +51 -0
  4. package/src/backend/client.js +173 -40
  5. package/src/backend/data-bundle.js +15 -3
  6. package/src/backend/foundation-bring-along.js +76 -21
  7. package/src/backend/payment-handoff.js +28 -9
  8. package/src/backend/site-media.js +147 -19
  9. package/src/backend/site-sync.js +362 -40
  10. package/src/commands/add.js +575 -273
  11. package/src/commands/build.js +160 -57
  12. package/src/commands/clone.js +79 -26
  13. package/src/commands/content.js +11 -7
  14. package/src/commands/deploy.js +80 -32
  15. package/src/commands/dev.js +39 -17
  16. package/src/commands/docs.js +44 -16
  17. package/src/commands/doctor.js +338 -82
  18. package/src/commands/export.js +15 -7
  19. package/src/commands/handoff.js +6 -2
  20. package/src/commands/i18n.js +248 -99
  21. package/src/commands/inspect.js +48 -19
  22. package/src/commands/invite.js +9 -3
  23. package/src/commands/org.js +19 -5
  24. package/src/commands/publish.js +229 -60
  25. package/src/commands/pull.js +157 -48
  26. package/src/commands/push.js +144 -42
  27. package/src/commands/refresh.js +37 -10
  28. package/src/commands/register.js +235 -64
  29. package/src/commands/rename.js +126 -46
  30. package/src/commands/runtime.js +74 -24
  31. package/src/commands/status.js +48 -15
  32. package/src/commands/sync.js +7 -2
  33. package/src/commands/template.js +12 -4
  34. package/src/commands/update.js +263 -87
  35. package/src/commands/validate.js +115 -32
  36. package/src/framework-index.json +16 -14
  37. package/src/index.js +298 -145
  38. package/src/templates/fetchers/github.js +7 -7
  39. package/src/templates/fetchers/npm.js +3 -3
  40. package/src/templates/fetchers/release.js +21 -18
  41. package/src/templates/index.js +34 -12
  42. package/src/templates/processor.js +44 -12
  43. package/src/templates/resolver.js +42 -15
  44. package/src/templates/validator.js +14 -7
  45. package/src/utils/asset-upload.js +90 -22
  46. package/src/utils/code-upload.js +62 -37
  47. package/src/utils/config.js +31 -10
  48. package/src/utils/dep-survey.js +33 -7
  49. package/src/utils/destination-prompt.js +27 -17
  50. package/src/utils/git.js +66 -22
  51. package/src/utils/host-prompt.js +4 -3
  52. package/src/utils/install-integrity.js +8 -4
  53. package/src/utils/interactive.js +3 -3
  54. package/src/utils/json-file.js +6 -2
  55. package/src/utils/names.js +15 -6
  56. package/src/utils/placement.js +16 -8
  57. package/src/utils/registry-auth.js +185 -57
  58. package/src/utils/registry-orgs.js +142 -53
  59. package/src/utils/runtime-upload.js +52 -14
  60. package/src/utils/scaffold.js +55 -14
  61. package/src/utils/site-content-refs.js +3 -1
  62. package/src/utils/update-check.js +43 -17
  63. package/src/utils/workspace.js +13 -7
  64. package/src/versions.js +18 -7
  65. 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
- 'Accept': 'application/vnd.github+json',
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
- 'Authorization': `Bearer ${process.env.GITHUB_TOKEN}`
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), // 60s timeout for GitHub (can be slow)
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), // 30s timeout
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
- `This may be an older release format.`
64
+ `This may be an older release format.`
65
65
  )
66
66
  }
67
67
 
68
68
  // Download manifest
69
- const manifestResponse = await fetchWithRetry(manifestAsset.browser_download_url, {
70
- headers: getGitHubHeaders(),
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
- `Available templates: ${available || 'none'}`
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
- `The release may be incomplete or corrupted.`
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
- 'Accept': 'application/vnd.github+json',
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
- 'Authorization': `Bearer ${process.env.GITHUB_TOKEN}`
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
- 'Set GITHUB_TOKEN environment variable for higher limits.'
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), // 60s timeout
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
  }
@@ -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 { parseTemplateId, getTemplateDisplayName, BUILTIN_TEMPLATES, OFFICIAL_TEMPLATES, buildTemplateChoices } from './resolver.js'
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 { fetchOfficialTemplate, listOfficialTemplates } from './fetchers/release.js'
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, { onProgress })
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, { onProgress })
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, { ref, onProgress })
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 { parseTemplateId, getTemplateDisplayName, BUILTIN_TEMPLATES, OFFICIAL_TEMPLATES, buildTemplateChoices }
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', '.jsx', '.ts', '.tsx', '.mjs', '.cjs',
89
- '.json', '.yml', '.yaml', '.md', '.mdx',
90
- '.html', '.htm', '.css', '.scss', '.less',
91
- '.txt', '.xml', '.svg', '.vue', '.astro'
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, // {{variable}} - not blocks or partials
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(`Unresolved placeholders in ${path.basename(targetPath)}: ${unresolved.join(', ')}`)
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('__') ? `.${name.slice(1)}` : name
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') ? sourceName.slice(0, -4) : sourceName
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(sourcePath, targetPath, data, options = {}) {
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, { onWarning, onProgress, skip })
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(__dirname, '..', '..', '..', 'templates', 'manifest.json')
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
- { title: 'None', value: 'none', description: 'Foundation + site with no content' },
89
- { title: 'Starter', value: 'starter', description: 'Foundation + site + sample content' }
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 (identifier.startsWith('https://github.com/') || identifier.startsWith('http://github.com/')) {
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 = treeIndex >= 0 && pathParts[treeIndex + 1] ? pathParts[treeIndex + 1] : undefined
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 (identifier.startsWith('./') || identifier.startsWith('../') ||
180
- identifier.startsWith('/') || identifier.startsWith('~')) {
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(`Invalid GitHub identifier: ${identifier}. Expected format: user/repo or user/repo#ref`)
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) return current.major > min.major && min.major === 0
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 current.major === exact.major &&
75
- current.minor === exact.minor &&
76
- current.patch === exact.patch
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 = { type: 'foundation', name: 'foundation', dir: foundationDir }
207
+ const entry = {
208
+ type: 'foundation',
209
+ name: 'foundation',
210
+ dir: foundationDir
211
+ }
205
212
  applyLegacyFoundationLayout(entry)
206
213
  dirs.push(entry)
207
214
  }