uniweb 1.7.2 → 1.7.4

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": "1.7.2",
3
+ "version": "1.7.4",
4
4
  "description": "Create structured Vite + React sites with content/code separation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,15 +41,15 @@
41
41
  "js-yaml": "^4.1.0",
42
42
  "prompts": "^2.4.2",
43
43
  "tar": "^7.0.0",
44
- "@uniweb/content-writer": "~1.1.2",
45
44
  "@uniweb/core": "~1.2.2",
46
- "@uniweb/semantic-parser": "~1.8.1",
47
- "@uniweb/schemas": "~1.3.1"
45
+ "@uniweb/content-writer": "~1.1.2",
46
+ "@uniweb/schemas": "~1.3.1",
47
+ "@uniweb/semantic-parser": "~1.8.1"
48
48
  },
49
49
  "peerDependencies": {
50
- "@uniweb/build": "~1.3.7",
50
+ "@uniweb/semantic-parser": "~1.8.1",
51
51
  "@uniweb/content-reader": "~1.4.0",
52
- "@uniweb/semantic-parser": "~1.8.1"
52
+ "@uniweb/build": "~1.3.9"
53
53
  },
54
54
  "peerDependenciesMeta": {
55
55
  "@uniweb/build": {
@@ -236,6 +236,33 @@ async function loadSiteConfig(siteRoot) {
236
236
  }
237
237
  }
238
238
 
239
+ /**
240
+ * The site's content as its source says it now — what the free-form commands (`status
241
+ * --freeform`, `init-freeform`, `update-hash`, `prune --freeform`) judge a translation by,
242
+ * read as `extract` reads the strings it offers. No build needed.
243
+ *
244
+ * ⛔ Not `dist/site-content.json`, which they read until 2026-10-17. A prerendered build that
245
+ * splits its content rewrites that file without page sections, so `status --freeform`
246
+ * compared no page translation and counted each one up to date, `update-hash --all-stale`
247
+ * found nothing stale and `init-freeform` found no section. A build also leaves out `hidden:`
248
+ * pages, so `prune --freeform` deleted their translations. And the file says what the source
249
+ * said at the last build.
250
+ *
251
+ * @param {string} siteRoot
252
+ * @returns {Promise<Object>} the collected site content
253
+ */
254
+ async function collectSourceContent(siteRoot) {
255
+ const { collectSiteContent } = await import('@uniweb/build/site')
256
+ // The collector reports progress on stdout; send it to stderr, so `--json` prints JSON alone.
257
+ const origConsoleLog = console.log
258
+ console.log = (...a) => process.stderr.write(a.join(' ') + '\n')
259
+ try {
260
+ return await collectSiteContent(siteRoot)
261
+ } finally {
262
+ console.log = origConsoleLog
263
+ }
264
+ }
265
+
239
266
  /**
240
267
  * Extract command - extract translatable strings from site content
241
268
  */
@@ -650,15 +677,7 @@ async function runStatusFreeform(siteRoot, config, locale, options = {}) {
650
677
  }
651
678
 
652
679
  try {
653
- // Load site content for source hashes
654
- const siteContentPath = join(siteRoot, 'dist', 'site-content.json')
655
- if (!existsSync(siteContentPath)) {
656
- error('Site content not found. Run "uniweb build" first.')
657
- process.exit(1)
658
- }
659
-
660
- const siteContentRaw = await readFile(siteContentPath, 'utf-8')
661
- const siteContent = JSON.parse(siteContentRaw)
680
+ const siteContent = await collectSourceContent(siteRoot)
662
681
 
663
682
  const {
664
683
  discoverFreeformTranslations,
@@ -667,9 +686,10 @@ async function runStatusFreeform(siteRoot, config, locale, options = {}) {
667
686
  getOrphanedTranslations
668
687
  } = await import('@uniweb/build/i18n')
669
688
 
670
- // What the built content says about its free-form translations — every path the
689
+ // What the site's content says about its free-form translations — every path the
671
690
  // renderer reads one from, with its source's hash. The build's own check uses this
672
- // index, so the two cannot disagree about which files are orphaned or stale.
691
+ // index too, so the two judge by one rule; they differ only where the build leaves
692
+ // content out — a `hidden:` page or section, which the build calls orphaned.
673
693
  const { validPaths, sourceHashes, canJudge } = freeformSourceIndex(siteContent)
674
694
 
675
695
  // Find all locales
@@ -692,15 +712,17 @@ async function runStatusFreeform(siteRoot, config, locale, options = {}) {
692
712
  ]
693
713
 
694
714
  // Check staleness. Orphaned only when the content can judge it — never a record's
695
- // translation, nor one for a page whose sections the built content does not carry.
715
+ // translation, nor one for a page whose sections the content does not carry.
696
716
  const stale = await getStaleTranslations(localeDir, sourceHashes)
697
717
  const orphaned = (await getOrphanedTranslations(localeDir, validPaths)).filter((o) => canJudge(o.path))
698
718
 
699
- const upToDate = allPaths.filter(
700
- (p) =>
701
- !stale.some((s) => s.path === p) &&
702
- !orphaned.some((o) => o.path === p)
703
- )
719
+ // ⭐ Up to date only where a source was found to compare with. A translation with none
720
+ // — a record's, whose record the site content does not hold — is NOT CHECKED, and said
721
+ // so. ⛔ Until 2026-10-17 it was counted up to date, like every page translation of a
722
+ // prerendered site while this read the build's split content.
723
+ const judged = (p) => stale.some((s) => s.path === p) || orphaned.some((o) => o.path === p)
724
+ const upToDate = allPaths.filter((p) => !judged(p) && sourceHashes[p])
725
+ const notChecked = allPaths.filter((p) => !judged(p) && !sourceHashes[p])
704
726
 
705
727
  results[loc] = {
706
728
  total: allPaths.length,
@@ -712,7 +734,8 @@ async function runStatusFreeform(siteRoot, config, locale, options = {}) {
712
734
  orphaned: orphaned.map((o) => ({
713
735
  path: o.path,
714
736
  recordedDate: o.recordedDate
715
- }))
737
+ })),
738
+ notChecked: notChecked.map((path) => ({ path }))
716
739
  }
717
740
  }
718
741
 
@@ -748,8 +771,15 @@ async function runStatusFreeform(siteRoot, config, locale, options = {}) {
748
771
  }
749
772
  }
750
773
 
774
+ if (info.notChecked.length > 0) {
775
+ log(` ${colors.dim}Not checked (no source to compare with):${colors.reset}`)
776
+ for (const item of info.notChecked) {
777
+ log(` ${colors.dim}· ${item.path}${colors.reset}`)
778
+ }
779
+ }
780
+
751
781
  log(
752
- ` ${colors.dim}Summary: ${info.upToDate} up to date, ${info.stale.length} stale, ${info.orphaned.length} orphaned${colors.reset}`
782
+ ` ${colors.dim}Summary: ${info.upToDate} up to date, ${info.stale.length} stale, ${info.orphaned.length} orphaned${info.notChecked.length > 0 ? `, ${info.notChecked.length} not checked` : ''}${colors.reset}`
753
783
  )
754
784
  log('')
755
785
  }
@@ -1077,16 +1107,6 @@ async function runInitFreeform(siteRoot, config, args) {
1077
1107
  }
1078
1108
 
1079
1109
  try {
1080
- // Load site content to get source
1081
- const siteContentPath = join(siteRoot, 'dist', 'site-content.json')
1082
- if (!existsSync(siteContentPath)) {
1083
- error('Site content not found. Run "uniweb build" first.')
1084
- process.exit(1)
1085
- }
1086
-
1087
- const siteContentRaw = await readFile(siteContentPath, 'utf-8')
1088
- const siteContent = JSON.parse(siteContentRaw)
1089
-
1090
1110
  // Find the source content
1091
1111
  let sourceContent = null
1092
1112
  let sourceHash = null
@@ -1095,6 +1115,7 @@ async function runInitFreeform(siteRoot, config, args) {
1095
1115
  // Find section in pages
1096
1116
  const isPageId = pathType.startsWith('page-ids/')
1097
1117
  const pageIdentifier = pathType.replace(/^(pages|page-ids)\//, '')
1118
+ const siteContent = await collectSourceContent(siteRoot)
1098
1119
 
1099
1120
  for (const page of siteContent.pages || []) {
1100
1121
  const match = isPageId
@@ -1273,15 +1294,7 @@ async function runUpdateHash(siteRoot, config, args) {
1273
1294
  }
1274
1295
 
1275
1296
  try {
1276
- // Load site content
1277
- const siteContentPath = join(siteRoot, 'dist', 'site-content.json')
1278
- if (!existsSync(siteContentPath)) {
1279
- error('Site content not found. Run "uniweb build" first.')
1280
- process.exit(1)
1281
- }
1282
-
1283
- const siteContentRaw = await readFile(siteContentPath, 'utf-8')
1284
- const siteContent = JSON.parse(siteContentRaw)
1297
+ const siteContent = await collectSourceContent(siteRoot)
1285
1298
 
1286
1299
  const {
1287
1300
  updateHash,
@@ -1510,15 +1523,7 @@ async function runPrune(siteRoot, config, args) {
1510
1523
  )
1511
1524
 
1512
1525
  try {
1513
- // Load site content
1514
- const siteContentPath = join(siteRoot, 'dist', 'site-content.json')
1515
- if (!existsSync(siteContentPath)) {
1516
- error('Site content not found. Run "uniweb build" first.')
1517
- process.exit(1)
1518
- }
1519
-
1520
- const siteContentRaw = await readFile(siteContentPath, 'utf-8')
1521
- const siteContent = JSON.parse(siteContentRaw)
1526
+ const siteContent = await collectSourceContent(siteRoot)
1522
1527
 
1523
1528
  const {
1524
1529
  freeformSourceIndex,
@@ -1530,8 +1535,10 @@ async function runPrune(siteRoot, config, args) {
1530
1535
  // reads one (`freeformSourceIndex`, the build's own index): every path a section's
1531
1536
  // translation is read from — ⛔ until 2026-09-14 only `page-ids/<id>/…` on a page with
1532
1537
  // an `id`, so a route-addressed translation the page rendered was deleted — and only
1533
- // what the built content can see: never a record's translation (`records/…`), and
1534
- // never one for a page whose sections the content does not carry.
1538
+ // what the content can see: never a record's translation (`records/…`), and never one
1539
+ // for a page whose sections the content does not carry. The content is the SOURCE's
1540
+ // (`collectSourceContent`), `hidden:` pages included: ⛔ read from a build until
1541
+ // 2026-10-17, which leaves those out, so their translations were deleted.
1535
1542
  const { validPaths, canJudge } = freeformSourceIndex(siteContent)
1536
1543
 
1537
1544
  // Find all locales
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-10-17T13:42:01.369Z",
3
+ "generatedAt": "2026-10-17T17:49:32.100Z",
4
4
  "packages": {
5
5
  "@uniweb/api": {
6
6
  "version": "1.4.0"
7
7
  },
8
8
  "@uniweb/build": {
9
- "version": "1.3.7"
9
+ "version": "1.3.9"
10
10
  },
11
11
  "@uniweb/content-reader": {
12
12
  "version": "1.4.0"