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.
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 +15 -13
  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
@@ -34,7 +34,9 @@ const HANDLE_RE = /^[a-z0-9][a-z0-9-]{1,37}[a-z0-9]$/
34
34
 
35
35
  /** Strip a leading `@` and any `/suffix`, returning the bare handle segment. */
36
36
  export function bareHandle(scope) {
37
- return String(scope || '').replace(/^@/, '').replace(/\/.*$/, '')
37
+ return String(scope || '')
38
+ .replace(/^@/, '')
39
+ .replace(/\/.*$/, '')
38
40
  }
39
41
 
40
42
  /**
@@ -57,14 +59,17 @@ export function validateHandle(handle) {
57
59
  */
58
60
  export async function fetchOrgs({ apiBase, token }) {
59
61
  const res = await fetch(`${apiBase.replace(/\/$/, '')}${ORGS_PATH}`, {
60
- headers: { Authorization: `Bearer ${token}` },
62
+ headers: { Authorization: `Bearer ${token}` }
61
63
  })
62
- if (!res.ok) throw new Error(`Could not list your orgs: HTTP ${res.status} ${res.statusText}`)
64
+ if (!res.ok)
65
+ throw new Error(
66
+ `Could not list your orgs: HTTP ${res.status} ${res.statusText}`
67
+ )
63
68
  const data = await res.json().catch(() => null)
64
69
  return {
65
70
  account_handle: data?.account_handle ?? null,
66
71
  personal_org_exists: data?.personal_org_exists === true,
67
- orgs: Array.isArray(data?.orgs) ? data.orgs : [],
72
+ orgs: Array.isArray(data?.orgs) ? data.orgs : []
68
73
  }
69
74
  }
70
75
 
@@ -82,8 +87,11 @@ export async function createOrg({ apiBase, token, handle }) {
82
87
  const h = bareHandle(handle)
83
88
  const res = await fetch(`${apiBase.replace(/\/$/, '')}${ORGS_PATH}`, {
84
89
  method: 'POST',
85
- headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
86
- body: JSON.stringify({ handle: h }),
90
+ headers: {
91
+ 'Content-Type': 'application/json',
92
+ Authorization: `Bearer ${token}`
93
+ },
94
+ body: JSON.stringify({ handle: h })
87
95
  })
88
96
  if (res.ok) return res.json()
89
97
 
@@ -93,11 +101,15 @@ export async function createOrg({ apiBase, token, handle }) {
93
101
  try {
94
102
  const body = await res.json()
95
103
  detail = body?.detail || ''
96
- } catch { /* non-JSON body — keep the generic line */ }
104
+ } catch {
105
+ /* non-JSON body — keep the generic line */
106
+ }
97
107
  const fallback =
98
- res.status === 409 ? `@${h} is not available.`
99
- : res.status === 422 ? `@${h} is not a valid handle.`
100
- : `Could not create @${h}: HTTP ${res.status}`
108
+ res.status === 409
109
+ ? `@${h} is not available.`
110
+ : res.status === 422
111
+ ? `@${h} is not a valid handle.`
112
+ : `Could not create @${h}: HTTP ${res.status}`
101
113
  const e = new Error(detail || fallback)
102
114
  e.status = res.status
103
115
  throw e
@@ -118,10 +130,17 @@ export async function createOrg({ apiBase, token, handle }) {
118
130
  * @param {string[]} [p.args] - argv slice; checked for --non-interactive
119
131
  * @returns {Promise<string|null>}
120
132
  */
121
- export async function deriveScope({ apiBase, token, accountHandle = null, args = [] }) {
133
+ export async function deriveScope({
134
+ apiBase,
135
+ token,
136
+ accountHandle = null,
137
+ args = []
138
+ }) {
122
139
  const envelope = await fetchOrgs({ apiBase, token })
123
140
  const { orgs } = envelope
124
- const personal = envelope.account_handle || (accountHandle ? bareHandle(accountHandle) : null)
141
+ const personal =
142
+ envelope.account_handle ||
143
+ (accountHandle ? bareHandle(accountHandle) : null)
125
144
  const personalOrgExists = envelope.personal_org_exists
126
145
  const { isNonInteractive } = await import('./interactive.js')
127
146
  const nonInteractive = isNonInteractive(args)
@@ -131,15 +150,30 @@ export async function deriveScope({ apiBase, token, accountHandle = null, args =
131
150
  const h = orgs[0].handle
132
151
  const label = isPersonal(h) ? `your personal org @${h}` : `your org @${h}`
133
152
  if (nonInteractive) {
134
- console.log(`Publishing under ${label.replace(`@${h}`, `\x1b[1m@${h}\x1b[0m`)}.`)
153
+ console.log(
154
+ `Publishing under ${label.replace(`@${h}`, `\x1b[1m@${h}\x1b[0m`)}.`
155
+ )
135
156
  return h
136
157
  }
137
158
  const prompts = (await import('prompts')).default
138
- const { ok } = await prompts({
139
- type: 'confirm', name: 'ok', message: `Publish under ${label}?`, initial: true,
140
- }, { onCancel: () => { console.log('\nCancelled.'); process.exit(0) } })
159
+ const { ok } = await prompts(
160
+ {
161
+ type: 'confirm',
162
+ name: 'ok',
163
+ message: `Publish under ${label}?`,
164
+ initial: true
165
+ },
166
+ {
167
+ onCancel: () => {
168
+ console.log('\nCancelled.')
169
+ process.exit(0)
170
+ }
171
+ }
172
+ )
141
173
  if (!ok) {
142
- console.log('Pass --scope @org, or create another with `uniweb org create <handle>`.')
174
+ console.log(
175
+ 'Pass --scope @org, or create another with `uniweb org create <handle>`.'
176
+ )
143
177
  return null
144
178
  }
145
179
  return h
@@ -147,32 +181,54 @@ export async function deriveScope({ apiBase, token, accountHandle = null, args =
147
181
 
148
182
  if (orgs.length > 1) {
149
183
  // Personal org first; the rest in server order (primary-first).
150
- const ordered = [...orgs].sort((a, b) => (isPersonal(b.handle) ? 1 : 0) - (isPersonal(a.handle) ? 1 : 0))
184
+ const ordered = [...orgs].sort(
185
+ (a, b) => (isPersonal(b.handle) ? 1 : 0) - (isPersonal(a.handle) ? 1 : 0)
186
+ )
151
187
  if (nonInteractive) {
152
- const pick = ordered.find((u) => isPersonal(u.handle)) || orgs.find((u) => u.is_primary) || orgs[0]
153
- console.log(`Multiple orgs; using \x1b[1m@${pick.handle}\x1b[0m (non-interactive).`)
188
+ const pick =
189
+ ordered.find((u) => isPersonal(u.handle)) ||
190
+ orgs.find((u) => u.is_primary) ||
191
+ orgs[0]
192
+ console.log(
193
+ `Multiple orgs; using \x1b[1m@${pick.handle}\x1b[0m (non-interactive).`
194
+ )
154
195
  return pick.handle
155
196
  }
156
197
  const prompts = (await import('prompts')).default
157
- const { choice } = await prompts({
158
- type: 'select',
159
- name: 'choice',
160
- message: 'Publish under which org?',
161
- choices: ordered.map((u) => ({
162
- title: `@${u.handle}${isPersonal(u.handle) ? ' — your personal org' : u.is_primary ? ' (primary)' : ''}`,
163
- value: u.handle,
164
- })),
165
- initial: 0,
166
- }, { onCancel: () => { console.log('\nCancelled.'); process.exit(0) } })
198
+ const { choice } = await prompts(
199
+ {
200
+ type: 'select',
201
+ name: 'choice',
202
+ message: 'Publish under which org?',
203
+ choices: ordered.map((u) => ({
204
+ title: `@${u.handle}${isPersonal(u.handle) ? ' — your personal org' : u.is_primary ? ' (primary)' : ''}`,
205
+ value: u.handle
206
+ })),
207
+ initial: 0
208
+ },
209
+ {
210
+ onCancel: () => {
211
+ console.log('\nCancelled.')
212
+ process.exit(0)
213
+ }
214
+ }
215
+ )
167
216
  return choice || null
168
217
  }
169
218
 
170
219
  // 0 orgs → the first-publish picker.
171
220
  if (nonInteractive) {
172
- console.error('\x1b[31m✗\x1b[0m You have no org to publish under. Create one with `uniweb org create <handle>`, or pass --scope @org.')
221
+ console.error(
222
+ '\x1b[31m✗\x1b[0m You have no org to publish under. Create one with `uniweb org create <handle>`, or pass --scope @org.'
223
+ )
173
224
  process.exit(1)
174
225
  }
175
- return offerCreateOrg({ apiBase, token, accountHandle: personal, personalOrgExists })
226
+ return offerCreateOrg({
227
+ apiBase,
228
+ token,
229
+ accountHandle: personal,
230
+ personalOrgExists
231
+ })
176
232
  }
177
233
 
178
234
  /**
@@ -192,14 +248,22 @@ export async function deriveScope({ apiBase, token, accountHandle = null, args =
192
248
  * (created-then-left) → the lazy claim would 409; don't offer it.
193
249
  * Returns the chosen bare handle, or null if cancelled/failed.
194
250
  */
195
- export async function offerCreateOrg({ apiBase, token, accountHandle = null, personalOrgExists = false }) {
251
+ export async function offerCreateOrg({
252
+ apiBase,
253
+ token,
254
+ accountHandle = null,
255
+ personalOrgExists = false
256
+ }) {
196
257
  const prompts = (await import('prompts')).default
197
- const personal = accountHandle && !validateHandle(accountHandle) ? bareHandle(accountHandle) : null
258
+ const personal =
259
+ accountHandle && !validateHandle(accountHandle)
260
+ ? bareHandle(accountHandle)
261
+ : null
198
262
 
199
263
  if (!personal) {
200
264
  console.error(
201
- '\x1b[31m✗\x1b[0m This account has no handle (service accounts don\'t get one), so there is no ready-to-go org.\n' +
202
- ' Log in with a personal account or set a handle in the app — or pass --scope @org / `uniweb org create <handle>`.'
265
+ "\x1b[31m✗\x1b[0m This account has no handle (service accounts don't get one), so there is no ready-to-go org.\n" +
266
+ ' Log in with a personal account or set a handle in the app — or pass --scope @org / `uniweb org create <handle>`.'
203
267
  )
204
268
  return null
205
269
  }
@@ -207,38 +271,63 @@ export async function offerCreateOrg({ apiBase, token, accountHandle = null, per
207
271
  const canClaimPersonal = !personalOrgExists
208
272
  const choices = [
209
273
  ...(canClaimPersonal
210
- ? [{ title: `@${personal} — your personal org (created on first publish)`, value: personal }]
274
+ ? [
275
+ {
276
+ title: `@${personal} — your personal org (created on first publish)`,
277
+ value: personal
278
+ }
279
+ ]
211
280
  : []),
212
- { title: 'A new organization…', value: ':new' },
281
+ { title: 'A new organization…', value: ':new' }
213
282
  ]
214
283
  if (!canClaimPersonal) {
215
- console.log(`\x1b[2m@${personal} exists but you're not a member of it — ask its admin, or create another org.\x1b[0m`)
284
+ console.log(
285
+ `\x1b[2m@${personal} exists but you're not a member of it — ask its admin, or create another org.\x1b[0m`
286
+ )
216
287
  }
217
288
 
218
- const { choice } = await prompts({
219
- type: 'select',
220
- name: 'choice',
221
- message: 'Publish under which org?',
222
- choices,
223
- initial: 0,
224
- }, { onCancel: () => { console.log('\nCancelled.'); process.exit(0) } })
289
+ const { choice } = await prompts(
290
+ {
291
+ type: 'select',
292
+ name: 'choice',
293
+ message: 'Publish under which org?',
294
+ choices,
295
+ initial: 0
296
+ },
297
+ {
298
+ onCancel: () => {
299
+ console.log('\nCancelled.')
300
+ process.exit(0)
301
+ }
302
+ }
303
+ )
225
304
  if (!choice) return null
226
305
 
227
306
  let handle = choice
228
307
  if (choice === ':new') {
229
- const answer = await prompts({
230
- type: 'text',
231
- name: 'handle',
232
- message: 'Org handle (e.g. acme):',
233
- validate: (v) => validateHandle(v) || true,
234
- }, { onCancel: () => { console.log('\nCancelled.'); process.exit(0) } })
308
+ const answer = await prompts(
309
+ {
310
+ type: 'text',
311
+ name: 'handle',
312
+ message: 'Org handle (e.g. acme):',
313
+ validate: (v) => validateHandle(v) || true
314
+ },
315
+ {
316
+ onCancel: () => {
317
+ console.log('\nCancelled.')
318
+ process.exit(0)
319
+ }
320
+ }
321
+ )
235
322
  if (!answer.handle) return null
236
323
  handle = answer.handle
237
324
  }
238
325
 
239
326
  try {
240
327
  const org = await createOrg({ apiBase, token, handle })
241
- console.log(`\x1b[32m✓\x1b[0m Created \x1b[1m@${org.handle}\x1b[0m — you're a member${org.is_primary ? ' (primary)' : ''}.`)
328
+ console.log(
329
+ `\x1b[32m✓\x1b[0m Created \x1b[1m@${org.handle}\x1b[0m — you're a member${org.is_primary ? ' (primary)' : ''}.`
330
+ )
242
331
  return org.handle
243
332
  } catch (err) {
244
333
  console.error(`\x1b[31m✗\x1b[0m ${err.message}`)
@@ -51,7 +51,7 @@ function fileEntry(diskPath, path) {
51
51
  content_type: contentTypeFor(path),
52
52
  size: bytes.length,
53
53
  sha256: createHash('sha256').update(bytes).digest('hex'),
54
- diskPath,
54
+ diskPath
55
55
  }
56
56
  }
57
57
 
@@ -75,7 +75,8 @@ export function collectRuntimeFiles(distDir) {
75
75
  const rel = prefix ? `${prefix}/${name}` : name
76
76
  const st = statSync(full)
77
77
  if (st.isDirectory()) walk(full, rel)
78
- else if (st.isFile() && !rel.endsWith('.map')) files.push(fileEntry(full, rel))
78
+ else if (st.isFile() && !rel.endsWith('.map'))
79
+ files.push(fileEntry(full, rel))
79
80
  }
80
81
  }
81
82
  walk(appDir, '')
@@ -111,22 +112,40 @@ export function hasShims(files) {
111
112
  * @param {object} opts - { apiBase, token, version, distDir, files?, onProgress? }
112
113
  * @returns {Promise<{ mode, uploaded: string[], failed: Array, serveBase: string|null }>}
113
114
  */
114
- export async function uploadRuntime({ apiBase, token, version, distDir, files, onProgress = () => {} }) {
115
+ export async function uploadRuntime({
116
+ apiBase,
117
+ token,
118
+ version,
119
+ distDir,
120
+ files,
121
+ onProgress = () => {}
122
+ }) {
115
123
  const list = files || collectRuntimeFiles(distDir)
116
- if (!list.length) return { mode: 'none', uploaded: [], failed: [], serveBase: null }
124
+ if (!list.length)
125
+ return { mode: 'none', uploaded: [], failed: [], serveBase: null }
117
126
 
118
127
  const origin = apiBase.replace(/\/$/, '')
119
128
  const planRes = await fetch(`${origin}/dev/runtime`, {
120
129
  method: 'POST',
121
- headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
130
+ headers: {
131
+ 'Content-Type': 'application/json',
132
+ Authorization: `Bearer ${token}`
133
+ },
122
134
  body: JSON.stringify({
123
135
  version,
124
- files: list.map(({ path, content_type, size, sha256 }) => ({ path, content_type, size, sha256 })),
125
- }),
136
+ files: list.map(({ path, content_type, size, sha256 }) => ({
137
+ path,
138
+ content_type,
139
+ size,
140
+ sha256
141
+ }))
142
+ })
126
143
  })
127
144
  if (!planRes.ok) {
128
145
  const detail = await planRes.text().catch(() => '')
129
- const err = new Error(`runtime plan rejected: HTTP ${planRes.status}${detail ? ` — ${detail.slice(0, 300)}` : ''}`)
146
+ const err = new Error(
147
+ `runtime plan rejected: HTTP ${planRes.status}${detail ? ` — ${detail.slice(0, 300)}` : ''}`
148
+ )
130
149
  err.status = planRes.status
131
150
  throw err
132
151
  }
@@ -134,14 +153,19 @@ export async function uploadRuntime({ apiBase, token, version, distDir, files, o
134
153
  const targets = new Map((plan.uploads || []).map((u) => [u.path, u]))
135
154
  // The one mode-aware bit: direct PUTs are bearer-authed backend routes;
136
155
  // presigned URLs are self-authorizing and must NOT carry a foreign bearer.
137
- const authHeaders = plan.mode === 'presigned' ? {} : { Authorization: `Bearer ${token}` }
156
+ const authHeaders =
157
+ plan.mode === 'presigned' ? {} : { Authorization: `Bearer ${token}` }
138
158
 
139
159
  const uploaded = []
140
160
  const failed = []
141
161
  for (const f of list) {
142
162
  const target = targets.get(f.path)
143
163
  if (!target) {
144
- failed.push({ path: f.path, status: 0, detail: 'no upload target in plan' })
164
+ failed.push({
165
+ path: f.path,
166
+ status: 0,
167
+ detail: 'no upload target in plan'
168
+ })
145
169
  continue
146
170
  }
147
171
  onProgress(`↑ ${f.path}`)
@@ -149,15 +173,29 @@ export async function uploadRuntime({ apiBase, token, version, distDir, files, o
149
173
  try {
150
174
  res = await fetch(new URL(target.url, origin), {
151
175
  method: target.method || 'PUT',
152
- headers: { ...(target.headers || {}), ...authHeaders, 'x-uniweb-sha256': f.sha256 },
153
- body: readFileSync(f.diskPath),
176
+ headers: {
177
+ ...(target.headers || {}),
178
+ ...authHeaders,
179
+ 'x-uniweb-sha256': f.sha256
180
+ },
181
+ body: readFileSync(f.diskPath)
154
182
  })
155
183
  } catch (err) {
156
184
  failed.push({ path: f.path, status: 0, detail: err.message })
157
185
  continue
158
186
  }
159
187
  if (res.ok) uploaded.push(f.path)
160
- else failed.push({ path: f.path, status: res.status, detail: (await res.text().catch(() => '')).slice(0, 200) })
188
+ else
189
+ failed.push({
190
+ path: f.path,
191
+ status: res.status,
192
+ detail: (await res.text().catch(() => '')).slice(0, 200)
193
+ })
194
+ }
195
+ return {
196
+ mode: plan.mode || 'direct',
197
+ uploaded,
198
+ failed,
199
+ serveBase: plan.serve_base || null
161
200
  }
162
- return { mode: plan.mode || 'direct', uploaded, failed, serveBase: plan.serve_base || null }
163
201
  }
@@ -11,7 +11,11 @@ import { join, dirname } from 'node:path'
11
11
  import { fileURLToPath } from 'node:url'
12
12
  import yaml from 'js-yaml'
13
13
  import Handlebars from 'handlebars'
14
- import { copyTemplateDirectory, enumerateTemplateOutputs, registerVersions } from '../templates/processor.js'
14
+ import {
15
+ copyTemplateDirectory,
16
+ enumerateTemplateOutputs,
17
+ registerVersions
18
+ } from '../templates/processor.js'
15
19
  import { getVersionsForTemplates, getCliVersion } from '../versions.js'
16
20
  import { writeJsonPreservingStyleAsync } from './json-file.js'
17
21
 
@@ -45,7 +49,7 @@ export async function scaffoldWorkspace(targetDir, context, options = {}) {
45
49
  await copyTemplateDirectory(templatePath, targetDir, context, {
46
50
  onProgress: options.onProgress,
47
51
  onWarning: options.onWarning,
48
- skip,
52
+ skip
49
53
  })
50
54
  }
51
55
 
@@ -87,7 +91,7 @@ export async function scaffoldFoundation(targetDir, context, options = {}) {
87
91
  const templatePath = join(TEMPLATES_DIR, 'foundation')
88
92
  await copyTemplateDirectory(templatePath, targetDir, context, {
89
93
  onProgress: options.onProgress,
90
- onWarning: options.onWarning,
94
+ onWarning: options.onWarning
91
95
  })
92
96
  }
93
97
 
@@ -120,7 +124,7 @@ export async function scaffoldSite(targetDir, context, options = {}) {
120
124
  const templatePath = join(TEMPLATES_DIR, 'site')
121
125
  await copyTemplateDirectory(templatePath, targetDir, context, {
122
126
  onProgress: options.onProgress,
123
- onWarning: options.onWarning,
127
+ onWarning: options.onWarning
124
128
  })
125
129
  }
126
130
 
@@ -146,7 +150,12 @@ export async function scaffoldSite(targetDir, context, options = {}) {
146
150
  * directories. Used to migrate legacy `foundation/foundation.js`
147
151
  * templates onto the new flat `src/main.js` layout.
148
152
  */
149
- export async function applyContent(contentDir, targetDir, context, options = {}) {
153
+ export async function applyContent(
154
+ contentDir,
155
+ targetDir,
156
+ context,
157
+ options = {}
158
+ ) {
150
159
  if (!existsSync(contentDir)) return
151
160
 
152
161
  registerVersions(getVersionsForTemplates())
@@ -157,16 +166,24 @@ export async function applyContent(contentDir, targetDir, context, options = {})
157
166
  'vite.config.js',
158
167
  'entry.js',
159
168
  'index.html',
160
- '.gitignore',
169
+ '.gitignore'
161
170
  ])
162
171
 
163
172
  // Config files that should be merged, not overwritten.
164
173
  // Keys listed here are preserved from the scaffolded version.
165
174
  const MERGE_FILES = {
166
- 'site.yml': ['name', 'foundation'],
175
+ 'site.yml': ['name', 'foundation']
167
176
  }
168
177
 
169
- await copyContentRecursive(contentDir, targetDir, context, STRUCTURAL_FILES, MERGE_FILES, options, 0)
178
+ await copyContentRecursive(
179
+ contentDir,
180
+ targetDir,
181
+ context,
182
+ STRUCTURAL_FILES,
183
+ MERGE_FILES,
184
+ options,
185
+ 0
186
+ )
170
187
  }
171
188
 
172
189
  /**
@@ -178,7 +195,15 @@ export async function applyContent(contentDir, targetDir, context, options = {})
178
195
  * nested `sections/foo/foundation.js` if one existed, which is not the
179
196
  * intent.
180
197
  */
181
- async function copyContentRecursive(sourceDir, targetDir, context, structuralFiles, mergeFiles, options, depth = 0) {
198
+ async function copyContentRecursive(
199
+ sourceDir,
200
+ targetDir,
201
+ context,
202
+ structuralFiles,
203
+ mergeFiles,
204
+ options,
205
+ depth = 0
206
+ ) {
182
207
  await fs.mkdir(targetDir, { recursive: true })
183
208
 
184
209
  const entries = readdirSync(sourceDir, { withFileTypes: true })
@@ -189,7 +214,15 @@ async function copyContentRecursive(sourceDir, targetDir, context, structuralFil
189
214
 
190
215
  if (entry.isDirectory()) {
191
216
  const targetSubDir = join(targetDir, entry.name)
192
- await copyContentRecursive(sourcePath, targetSubDir, context, structuralFiles, mergeFiles, options, depth + 1)
217
+ await copyContentRecursive(
218
+ sourcePath,
219
+ targetSubDir,
220
+ context,
221
+ structuralFiles,
222
+ mergeFiles,
223
+ options,
224
+ depth + 1
225
+ )
193
226
  } else {
194
227
  // Determine the output filename (strip .hbs extension)
195
228
  let outputName = entry.name.endsWith('.hbs')
@@ -197,7 +230,10 @@ async function copyContentRecursive(sourceDir, targetDir, context, structuralFil
197
230
  : entry.name
198
231
 
199
232
  // Apply top-level rename (e.g. legacy `foundation.js` → `main.js`)
200
- if (renames && Object.prototype.hasOwnProperty.call(renames, outputName)) {
233
+ if (
234
+ renames &&
235
+ Object.prototype.hasOwnProperty.call(renames, outputName)
236
+ ) {
201
237
  outputName = renames[outputName]
202
238
  }
203
239
 
@@ -236,7 +272,7 @@ async function copyContentRecursive(sourceDir, targetDir, context, structuralFil
236
272
  if (preserveKeys && existsSync(targetPath)) {
237
273
  const existingContent = await fs.readFile(targetPath, 'utf-8')
238
274
  const existing = yaml.load(existingContent) || {}
239
- let merged = newContent ?? await fs.readFile(sourcePath, 'utf-8')
275
+ let merged = newContent ?? (await fs.readFile(sourcePath, 'utf-8'))
240
276
 
241
277
  for (const key of preserveKeys) {
242
278
  if (existing[key] === undefined) continue
@@ -292,7 +328,12 @@ export async function applyStarter(projectDir, context, options = {}) {
292
328
  // Apply foundation starter content
293
329
  const foundationContentDir = join(STARTER_DIR, 'foundation')
294
330
  const foundationTargetDir = join(projectDir, foundationDir)
295
- await applyContent(foundationContentDir, foundationTargetDir, context, options)
331
+ await applyContent(
332
+ foundationContentDir,
333
+ foundationTargetDir,
334
+ context,
335
+ options
336
+ )
296
337
 
297
338
  // Apply site starter content
298
339
  const siteContentDir = join(STARTER_DIR, 'site')
@@ -328,7 +369,7 @@ function matchTopLevelLine(content, key) {
328
369
  function replaceTopLevelLine(content, key, replacement) {
329
370
  return content.replace(
330
371
  new RegExp(`^${escapeRegex(key)}:.*$`, 'm'),
331
- () => replacement,
372
+ () => replacement
332
373
  )
333
374
  }
334
375
 
@@ -17,5 +17,7 @@
17
17
  * site.yml; the runtime loads it as a federated module.
18
18
  */
19
19
  export function extractFoundationRef(info = {}, document = {}) {
20
- return info?.foundation ?? info?.foundation_name ?? document?.foundation ?? null
20
+ return (
21
+ info?.foundation ?? info?.foundation_name ?? document?.foundation ?? null
22
+ )
21
23
  }