uniweb 0.10.12 → 0.10.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uniweb",
3
- "version": "0.10.12",
3
+ "version": "0.10.13",
4
4
  "description": "Create structured Vite + React sites with content/code separation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,12 +41,12 @@
41
41
  "js-yaml": "^4.1.0",
42
42
  "prompts": "^2.4.2",
43
43
  "tar": "^7.0.0",
44
- "@uniweb/kit": "0.9.8",
45
44
  "@uniweb/core": "0.7.8",
45
+ "@uniweb/kit": "0.9.8",
46
46
  "@uniweb/runtime": "0.8.9"
47
47
  },
48
48
  "peerDependencies": {
49
- "@uniweb/build": "0.11.8",
49
+ "@uniweb/build": "0.11.9",
50
50
  "@uniweb/content-reader": "1.1.9",
51
51
  "@uniweb/semantic-parser": "1.1.15"
52
52
  },
@@ -38,7 +38,7 @@
38
38
  import { createServer } from 'node:http'
39
39
  import { existsSync } from 'node:fs'
40
40
  import { readFile, writeFile, readdir, stat } from 'node:fs/promises'
41
- import { resolve, join, basename, sep } from 'node:path'
41
+ import { resolve, join, basename, relative, sep } from 'node:path'
42
42
  import { execSync } from 'node:child_process'
43
43
  import yaml from 'js-yaml'
44
44
 
@@ -178,6 +178,7 @@ export async function deploy(args = []) {
178
178
  // and the whole object for the publish payload.
179
179
  const siteContent = JSON.parse(await readFile(contentPath, 'utf8'))
180
180
  const languages = extractLanguages(siteContent)
181
+ const languageLabels = extractLanguageLabels(siteContent)
181
182
  const defaultLanguage = siteContent?.config?.defaultLanguage || languages[0] || 'en'
182
183
  const theme = await readTheme(siteDir, siteContent)
183
184
 
@@ -223,6 +224,11 @@ export async function deploy(args = []) {
223
224
  foundation,
224
225
  runtimeVersion,
225
226
  languages,
227
+ // Optional `{ code: label }` map from site.yml's object-form
228
+ // languages. PHP stamps this into the session JWT so CliDeployReview
229
+ // can use real labels (English, Français, …) when provisioning the
230
+ // site, instead of falling back to `lang.toUpperCase()`.
231
+ ...(languageLabels ? { languageLabels } : {}),
226
232
  // `name` from site.yml is a hint for the create-flow review page so
227
233
  // the handle input is pre-filled. Ignored by authorize in other
228
234
  // branches (fast path, intent=authorize).
@@ -312,6 +318,20 @@ export async function deploy(args = []) {
312
318
  loopback.close()
313
319
  }
314
320
 
321
+ // Write site.id / handle to site.yml AS SOON as we have them, before any
322
+ // step that can fail (validate, asset upload, publish). On first deploy
323
+ // the user has already paid by this point — losing the link to the
324
+ // server's site row would force a duplicate-create on the next attempt
325
+ // (and a second subscription). The features write happens later after
326
+ // publish; this early write only covers id/handle.
327
+ if (siteIdResolved && !siteYml.site?.id) {
328
+ await writeSiteYmlUpdates(siteYmlPath, siteYml, {
329
+ site: { id: siteIdResolved, handle: handleResolved },
330
+ })
331
+ siteYml.site = { ...(siteYml.site || {}), id: siteIdResolved, handle: handleResolved }
332
+ say.dim(`Linked site.yml to site.id=${siteIdResolved}`)
333
+ }
334
+
315
335
  // Pre-flight against the Worker. Surfaces "foundation not published" /
316
336
  // "runtime not found" / namespace mismatch BEFORE we ship content.
317
337
  say.info('Validating foundation + runtime…')
@@ -349,6 +369,16 @@ export async function deploy(args = []) {
349
369
  say.dim('Skipping asset upload (--skip-assets).')
350
370
  }
351
371
 
372
+ // Collect compiled collection JSON files from dist/data/. The framework
373
+ // emits these for `collection:` data sources — `<name>.json` cascade
374
+ // payloads plus per-record `<name>/<slug>.json` files when `deferred:` is
375
+ // declared. Editor publish has no equivalent (collections live in the DB);
376
+ // CLI sites need them shipped as static R2 objects.
377
+ const dataFiles = await collectDataFiles(distDir)
378
+ if (Object.keys(dataFiles).length > 0) {
379
+ say.dim(`Data files : ${Object.keys(dataFiles).length} (collection JSON)`)
380
+ }
381
+
352
382
  say.info('Publishing…')
353
383
  const publishPayload = {
354
384
  foundation,
@@ -356,6 +386,10 @@ export async function deploy(args = []) {
356
386
  theme,
357
387
  languages,
358
388
  defaultLanguage,
389
+ // Compiled collection JSON files (relative-path → utf8 content). Worker
390
+ // publish writes each to ${sitePrefix}/data/<key>; worker serve allows
391
+ // /data/* paths from R2 alongside _pages/*.
392
+ ...(Object.keys(dataFiles).length > 0 ? { dataFiles } : {}),
359
393
  // Same shape as Editor publish — one entry per language. Single-locale
360
394
  // sites end up with `{ [defaultLanguage]: siteContent }`; multi-locale
361
395
  // sites carry per-locale translated content emitted by buildLocalizedContent.
@@ -530,6 +564,40 @@ function extractLanguages(siteContent) {
530
564
  return langs.map((l) => (typeof l === 'string' ? l : l?.value || l?.code)).filter(Boolean)
531
565
  }
532
566
 
567
+ // Collect compiled collection JSON files from dist/data/ recursively.
568
+ // Returns `{ '<relPath>': '<utf8-content>' }` keyed by the path under data/
569
+ // so the worker can write each to `${sitePrefix}/data/<relPath>` in R2.
570
+ // Empty object when the site has no `collection:` data sources.
571
+ async function collectDataFiles(distDir) {
572
+ const dataDir = join(distDir, 'data')
573
+ if (!existsSync(dataDir)) return {}
574
+ const files = {}
575
+ const entries = await readdir(dataDir, { withFileTypes: true, recursive: true })
576
+ for (const entry of entries) {
577
+ if (!entry.isFile()) continue
578
+ if (!entry.name.endsWith('.json')) continue
579
+ const fullPath = join(entry.parentPath || entry.path, entry.name)
580
+ const relPath = relative(dataDir, fullPath)
581
+ files[relPath] = await readFile(fullPath, 'utf8')
582
+ }
583
+ return files
584
+ }
585
+
586
+ // Optional per-language labels from site.yml's object form. Returns null when
587
+ // site.yml uses the plain-string form (no labels declared) — server falls back
588
+ // to its own defaults in that case.
589
+ function extractLanguageLabels(siteContent) {
590
+ const langs = siteContent?.config?.languages
591
+ if (!Array.isArray(langs)) return null
592
+ const labels = {}
593
+ for (const l of langs) {
594
+ if (typeof l === 'string') continue
595
+ const code = l?.value || l?.code
596
+ if (code && l?.label) labels[code] = l.label
597
+ }
598
+ return Object.keys(labels).length > 0 ? labels : null
599
+ }
600
+
533
601
  /**
534
602
  * Resolve theme config.
535
603
  *
@@ -742,6 +810,7 @@ async function uploadAssetsAndRewriteContent({ siteDir, localeContents, siteYml,
742
810
  const failed = []
743
811
  await runInPool(queue, ASSET_UPLOAD_CONCURRENCY, async ({ f, plan }) => {
744
812
  if (!plan) {
813
+ say.warn(`Server didn't return an upload plan for ${f.filename} — skipping.`)
745
814
  failed.push(f.filename)
746
815
  return
747
816
  }
@@ -1017,9 +1086,16 @@ async function putToS3WithRetry(file, presigned, maxRetries) {
1017
1086
  const res = await fetch(presigned.url, { method: 'POST', body: form })
1018
1087
  if (res.ok || res.status === 204) return true
1019
1088
  if (res.status >= 500 && attempt < maxRetries) continue
1089
+ // Surface the server's response so failures are diagnosable. S3
1090
+ // returns XML with a useful <Code>/<Message> on rejection (e.g.
1091
+ // AccessDenied + reason); silently retrying without surfacing it
1092
+ // hides real config issues like bucket-permission mismatches.
1093
+ const errBody = await res.text().catch(() => '')
1094
+ say.warn(`Upload of ${file.filename} rejected by S3 (HTTP ${res.status}):\n ${errBody.slice(0, 500)}`)
1020
1095
  return false
1021
- } catch {
1096
+ } catch (err) {
1022
1097
  if (attempt < maxRetries) continue
1098
+ say.warn(`Upload of ${file.filename} failed: ${err?.message || err}`)
1023
1099
  return false
1024
1100
  }
1025
1101
  }
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-04-27T18:45:58.952Z",
3
+ "generatedAt": "2026-04-27T20:54:30.546Z",
4
4
  "packages": {
5
5
  "@uniweb/build": {
6
- "version": "0.11.8",
6
+ "version": "0.11.9",
7
7
  "path": "framework/build",
8
8
  "deps": [
9
9
  "@uniweb/content-reader",
@@ -92,7 +92,7 @@
92
92
  "deps": []
93
93
  },
94
94
  "@uniweb/unipress": {
95
- "version": "0.2.8",
95
+ "version": "0.2.9",
96
96
  "path": "framework/unipress",
97
97
  "deps": [
98
98
  "@uniweb/build",