@uniweb/build 0.29.1 → 0.30.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/package.json +5 -5
- package/src/content/index.js +6 -6
- package/src/dev-backend.js +31 -31
- package/src/i18n/freeform.js +44 -24
- package/src/i18n/index.js +22 -22
- package/src/i18n/{collections.js → records.js} +114 -51
- package/src/i18n/sync.js +9 -8
- package/src/site/build-site-data.js +9 -12
- package/src/site/config.js +1 -1
- package/src/site/content-collector.js +35 -40
- package/src/site/data-fetcher.js +23 -10
- package/src/site/entity-pool.js +211 -0
- package/src/site/fetch-shapes.js +13 -12
- package/src/site/foundation-ref.js +1 -1
- package/src/site/index.js +4 -4
- package/src/site/plugin.js +58 -63
- package/src/site/queries-config.js +324 -0
- package/src/site/{collection-processor.js → query-processor.js} +180 -95
- package/src/site/records-config.js +299 -0
- package/src/site/schemaless-data.js +2 -2
- package/src/utils/numeric-prefix.js +63 -0
- package/src/uwx/backfill.js +5 -5
- package/src/uwx/data-schema.js +2 -2
- package/src/uwx/entity-source.js +122 -0
- package/src/uwx/folder.js +92 -77
- package/src/uwx/index.js +33 -13
- package/src/uwx/locale-sync.js +2 -2
- package/src/uwx/project-writer.js +36 -10
- package/src/uwx/queries-config.js +11 -0
- package/src/uwx/records-project.js +535 -0
- package/src/uwx/{collections.js → records.js} +152 -69
- package/src/uwx/site-diff.js +6 -6
- package/src/uwx/site-project.js +30 -5
- package/src/uwx/site.js +295 -27
- package/src/uwx/sync-package.js +32 -18
- package/src/validate-data.js +17 -19
- package/src/site/collections-config.js +0 -260
- package/src/uwx/collection-source.js +0 -180
- package/src/uwx/collections-config.js +0 -9
- package/src/uwx/collections-project.js +0 -335
- /package/src/search/{collections.js → records-index.js} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.1",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -60,14 +60,14 @@
|
|
|
60
60
|
"sharp": "^0.35.3",
|
|
61
61
|
"yaml": "^2.5.0",
|
|
62
62
|
"@uniweb/content-reader": "^1.2.4",
|
|
63
|
+
"@uniweb/projections": "^0.5.2",
|
|
64
|
+
"@uniweb/semantic-parser": "^1.3.1",
|
|
63
65
|
"@uniweb/schemas": "^0.2.11",
|
|
64
66
|
"@uniweb/content-writer": "^0.3.4",
|
|
65
|
-
"@uniweb/projections": "^0.5.1",
|
|
66
|
-
"@uniweb/semantic-parser": "^1.3.1",
|
|
67
67
|
"@uniweb/theming": "^0.1.15"
|
|
68
68
|
},
|
|
69
69
|
"optionalDependencies": {
|
|
70
|
-
"@uniweb/runtime": "^0.13.
|
|
70
|
+
"@uniweb/runtime": "^0.13.3"
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
73
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"@tailwindcss/vite": "^4.0.0",
|
|
77
77
|
"@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
|
|
78
78
|
"vite-plugin-svgr": "^4.0.0",
|
|
79
|
-
"@uniweb/core": "^0.
|
|
79
|
+
"@uniweb/core": "^0.14.1"
|
|
80
80
|
},
|
|
81
81
|
"peerDependenciesMeta": {
|
|
82
82
|
"vite": {
|
package/src/content/index.js
CHANGED
|
@@ -12,15 +12,15 @@
|
|
|
12
12
|
|
|
13
13
|
export { collectSiteContent } from '../site/content-collector.js'
|
|
14
14
|
|
|
15
|
-
//
|
|
15
|
+
// Query + fetch resolution. Pure functions on the clean dep
|
|
16
16
|
// chain — re-exported here so headless callers (unipress, sidecars)
|
|
17
|
-
// can resolve `
|
|
17
|
+
// can resolve `queries:` declarations without importing
|
|
18
18
|
// `@uniweb/build/site` (which pulls Vite via its plugin index).
|
|
19
19
|
export {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
} from '../site/
|
|
20
|
+
processQueries,
|
|
21
|
+
writeQueryFiles,
|
|
22
|
+
getQueryLastModified,
|
|
23
|
+
} from '../site/query-processor.js'
|
|
24
24
|
export {
|
|
25
25
|
parseFetchConfig,
|
|
26
26
|
executeFetch,
|
package/src/dev-backend.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Dev backend for testing Uniweb sites with `supports: [where, limit, sort]`.
|
|
4
4
|
*
|
|
5
|
-
* Reads a directory of YAML
|
|
5
|
+
* Reads a directory of YAML recordsByQuery (each subfolder is a collection,
|
|
6
6
|
* each .yml file inside is a record) and exposes them via HTTP. Evaluates
|
|
7
7
|
* where-objects on the server side using @uniweb/core's matchWhere — the
|
|
8
8
|
* exact same evaluator the runtime uses as a fallback. This lets you
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* Wire format matches the framework default fetcher's pushdown conventions
|
|
12
12
|
* (see framework/runtime/src/default-fetcher.js):
|
|
13
13
|
*
|
|
14
|
-
* GET /api/{collection} —
|
|
14
|
+
* GET /api/{collection} — all records
|
|
15
15
|
* GET /api/{collection}?_where=<JSON> — filtered by where-object
|
|
16
16
|
* GET /api/{collection}?_limit=N — first N records
|
|
17
17
|
* GET /api/{collection}?_sort=field:dir — sorted
|
|
@@ -19,11 +19,11 @@
|
|
|
19
19
|
* GET /api/{collection}/{slug} — single record
|
|
20
20
|
*
|
|
21
21
|
* Usage:
|
|
22
|
-
* node scripts/framework/dev-backend.js --
|
|
22
|
+
* node scripts/framework/dev-backend.js --recordsByQuery <path> [--port N]
|
|
23
23
|
*
|
|
24
24
|
* Example (academic-metrics):
|
|
25
25
|
* node scripts/framework/dev-backend.js \
|
|
26
|
-
* --
|
|
26
|
+
* --recordsByQuery framework/templates/academic-metrics/site/recordsByQuery \
|
|
27
27
|
* --port 8080
|
|
28
28
|
*
|
|
29
29
|
* Then in the site's site.yml:
|
|
@@ -45,27 +45,27 @@ import { matchWhere } from '@uniweb/core'
|
|
|
45
45
|
|
|
46
46
|
const { values } = parseArgs({
|
|
47
47
|
options: {
|
|
48
|
-
|
|
48
|
+
entities: { type: 'string', short: 'e' },
|
|
49
49
|
port: { type: 'string', short: 'p', default: '8080' },
|
|
50
50
|
},
|
|
51
51
|
})
|
|
52
52
|
|
|
53
|
-
if (!values.
|
|
54
|
-
console.error('Usage: dev-backend.js --
|
|
53
|
+
if (!values.entities) {
|
|
54
|
+
console.error('Usage: dev-backend.js --entities <path> [--port N]')
|
|
55
55
|
process.exit(1)
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
const
|
|
58
|
+
const ENTITIES_ROOT = resolve(values.entities)
|
|
59
59
|
const PORT = Number(values.port)
|
|
60
60
|
|
|
61
|
-
if (!existsSync(
|
|
62
|
-
console.error(`
|
|
61
|
+
if (!existsSync(ENTITIES_ROOT)) {
|
|
62
|
+
console.error(`Entities directory not found: ${ENTITIES_ROOT}`)
|
|
63
63
|
process.exit(1)
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
// ─── Load
|
|
66
|
+
// ─── Load recordsByQuery from disk ─────────────────────────────────────────────
|
|
67
67
|
|
|
68
|
-
async function
|
|
68
|
+
async function loadRecords(dir) {
|
|
69
69
|
const files = await readdir(dir)
|
|
70
70
|
const items = []
|
|
71
71
|
for (const file of files) {
|
|
@@ -94,17 +94,17 @@ async function loadCollection(dir) {
|
|
|
94
94
|
return items
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
async function
|
|
98
|
-
const entries = await readdir(
|
|
99
|
-
const
|
|
97
|
+
async function loadAllRecords() {
|
|
98
|
+
const entries = await readdir(ENTITIES_ROOT)
|
|
99
|
+
const recordsByQuery = {}
|
|
100
100
|
for (const name of entries) {
|
|
101
|
-
const fullPath = join(
|
|
101
|
+
const fullPath = join(ENTITIES_ROOT, name)
|
|
102
102
|
const s = await stat(fullPath)
|
|
103
103
|
if (!s.isDirectory()) continue
|
|
104
|
-
|
|
105
|
-
console.log(`[dev-backend] Loaded ${
|
|
104
|
+
recordsByQuery[name] = await loadRecords(fullPath)
|
|
105
|
+
console.log(`[dev-backend] Loaded ${recordsByQuery[name].length} items from "${name}"`)
|
|
106
106
|
}
|
|
107
|
-
return
|
|
107
|
+
return recordsByQuery
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
// ─── Operator handling (mirrors default-fetcher pushdown wire format) ───────
|
|
@@ -182,16 +182,16 @@ function send(res, status, body) {
|
|
|
182
182
|
res.end(typeof body === 'string' ? body : JSON.stringify(body))
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
async function handleRequest(req, res,
|
|
185
|
+
async function handleRequest(req, res, recordsByQuery) {
|
|
186
186
|
if (req.method === 'OPTIONS') return send(res, 204, '')
|
|
187
187
|
|
|
188
188
|
const url = new URL(req.url, `http://${req.headers.host}`)
|
|
189
189
|
const match = url.pathname.match(/^\/api\/([^/]+)(?:\/([^/]+))?$/)
|
|
190
190
|
if (!match) return send(res, 404, { error: 'Not found' })
|
|
191
191
|
|
|
192
|
-
const [,
|
|
193
|
-
const items =
|
|
194
|
-
if (!items) return send(res, 404, { error: `Unknown
|
|
192
|
+
const [, queryName, slug] = match
|
|
193
|
+
const items = recordsByQuery[queryName]
|
|
194
|
+
if (!items) return send(res, 404, { error: `Unknown query: ${queryName}` })
|
|
195
195
|
|
|
196
196
|
// Single record by slug.
|
|
197
197
|
if (slug) {
|
|
@@ -221,14 +221,14 @@ async function handleRequest(req, res, collections) {
|
|
|
221
221
|
|
|
222
222
|
// ─── Boot ───────────────────────────────────────────────────────────────────
|
|
223
223
|
|
|
224
|
-
const
|
|
225
|
-
const
|
|
226
|
-
if (
|
|
227
|
-
console.warn('[dev-backend] No
|
|
224
|
+
const recordsByQuery = await loadAllRecords()
|
|
225
|
+
const knownQueries = Object.keys(recordsByQuery)
|
|
226
|
+
if (knownQueries.length === 0) {
|
|
227
|
+
console.warn('[dev-backend] No recordsByQuery found.')
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
const server = createServer((req, res) => {
|
|
231
|
-
handleRequest(req, res,
|
|
231
|
+
handleRequest(req, res, recordsByQuery).catch((err) => {
|
|
232
232
|
console.error('[dev-backend] Request handler threw:', err)
|
|
233
233
|
send(res, 500, { error: 'Internal server error' })
|
|
234
234
|
})
|
|
@@ -236,10 +236,10 @@ const server = createServer((req, res) => {
|
|
|
236
236
|
|
|
237
237
|
server.listen(PORT, () => {
|
|
238
238
|
console.log(`[dev-backend] Listening on http://localhost:${PORT}`)
|
|
239
|
-
console.log(`[dev-backend]
|
|
239
|
+
console.log(`[dev-backend] Queries: ${knownQueries.join(', ') || '(none)'}`)
|
|
240
240
|
console.log('[dev-backend] Endpoints:')
|
|
241
|
-
for (const name of
|
|
242
|
-
console.log(` GET /api/${name} —
|
|
241
|
+
for (const name of knownQueries) {
|
|
242
|
+
console.log(` GET /api/${name} — all records`)
|
|
243
243
|
console.log(` GET /api/${name}?_where=<JSON> — filtered`)
|
|
244
244
|
console.log(` GET /api/${name}/{slug} — single record`)
|
|
245
245
|
console.log(` POST /api/${name} body: { where } — operators in body`)
|
package/src/i18n/freeform.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* locales/freeform/{locale}/
|
|
11
11
|
* pages/{pageRoute}/{stableId}.md - By route
|
|
12
12
|
* page-ids/{pageId}/{stableId}.md - By page ID (stable)
|
|
13
|
-
* collections/{
|
|
13
|
+
* collections/{queryName}/{slug}.md - Collection items
|
|
14
14
|
*
|
|
15
15
|
* Resolution order for sections:
|
|
16
16
|
* 1. page-ids/{pageId}/{stableId}.md (if page has id:)
|
|
@@ -22,6 +22,7 @@ import { readFile, readdir, stat } from 'fs/promises'
|
|
|
22
22
|
import { existsSync } from 'node:fs'
|
|
23
23
|
import { join, relative, dirname } from 'node:path'
|
|
24
24
|
import yaml from 'js-yaml'
|
|
25
|
+
import { poolDirsForSchema, ENTITIES_DIR } from '../site/entity-pool.js'
|
|
25
26
|
|
|
26
27
|
// Try to import content-reader for markdown → ProseMirror conversion
|
|
27
28
|
let markdownToProseMirror
|
|
@@ -140,24 +141,35 @@ export async function loadFreeformTranslation(section, page, locale, localesDir)
|
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
/**
|
|
143
|
-
* Load free-form translation for
|
|
144
|
+
* Load a free-form translation for one ENTITY.
|
|
144
145
|
*
|
|
145
|
-
* Path:
|
|
146
|
+
* Path: entities/{schema dirs}/{slug}.md — mirroring the pool exactly.
|
|
146
147
|
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
148
|
+
* ⛔ KEYED BY THE ENTITY, NOT BY A QUERY. It used to be
|
|
149
|
+
* `collections/{queryName}/{slug}.md`, which was fine only while a
|
|
150
|
+
* collection was also a directory of files. With `entities/{schema}/` as the pool,
|
|
151
|
+
* TWO queries can cover one schema — so a query-keyed path would make an author
|
|
152
|
+
* write the same translation once per query, and finding neither from the other.
|
|
153
|
+
* ⚠️ And a query is renameable where a record's model is not: keying on the query
|
|
154
|
+
* orphaned every translation the moment someone renamed one.
|
|
155
|
+
*
|
|
156
|
+
* @param {Object} item - entity with a slug
|
|
157
|
+
* @param {string} schema - the entity's model ref (`@/name` or `@org/name`)
|
|
149
158
|
* @param {string} locale - Locale code
|
|
150
159
|
* @param {string} localesDir - Path to locales directory
|
|
151
160
|
* @returns {Promise<Object|null>} Parsed translation { frontmatter, content } or null
|
|
152
161
|
*/
|
|
153
|
-
export async function
|
|
162
|
+
export async function loadFreeformRecord(item, schema, locale, localesDir) {
|
|
154
163
|
const slug = item.slug
|
|
155
164
|
if (!slug) return null
|
|
156
165
|
|
|
166
|
+
const rel = buildFreeformRecordPath(schema, slug)
|
|
167
|
+
if (!rel) return null
|
|
168
|
+
|
|
157
169
|
const freeformDir = join(localesDir, 'freeform', locale)
|
|
158
170
|
if (!existsSync(freeformDir)) return null
|
|
159
171
|
|
|
160
|
-
const filePath = join(freeformDir,
|
|
172
|
+
const filePath = join(freeformDir, rel)
|
|
161
173
|
if (!existsSync(filePath)) return null
|
|
162
174
|
|
|
163
175
|
try {
|
|
@@ -174,7 +186,7 @@ export async function loadFreeformCollectionItem(item, collectionName, locale, l
|
|
|
174
186
|
relativePath: relative(join(localesDir, 'freeform', locale), filePath)
|
|
175
187
|
}
|
|
176
188
|
} catch (err) {
|
|
177
|
-
console.warn(`[i18n] Failed to load free-form
|
|
189
|
+
console.warn(`[i18n] Failed to load free-form record ${filePath}: ${err.message}`)
|
|
178
190
|
return null
|
|
179
191
|
}
|
|
180
192
|
}
|
|
@@ -221,7 +233,7 @@ export async function discoverFreeformTranslations(locale, localesDir) {
|
|
|
221
233
|
const result = {
|
|
222
234
|
pages: [],
|
|
223
235
|
pageIds: [],
|
|
224
|
-
|
|
236
|
+
records: []
|
|
225
237
|
}
|
|
226
238
|
|
|
227
239
|
if (!existsSync(freeformDir)) return result
|
|
@@ -238,10 +250,10 @@ export async function discoverFreeformTranslations(locale, localesDir) {
|
|
|
238
250
|
result.pageIds = await discoverMarkdownFiles(pageIdsDir, pageIdsDir)
|
|
239
251
|
}
|
|
240
252
|
|
|
241
|
-
// Discover
|
|
242
|
-
const
|
|
243
|
-
if (existsSync(
|
|
244
|
-
result.
|
|
253
|
+
// Discover entity translations
|
|
254
|
+
const entitiesDir = join(freeformDir, ENTITIES_DIR)
|
|
255
|
+
if (existsSync(entitiesDir)) {
|
|
256
|
+
result.records = await discoverMarkdownFiles(entitiesDir, entitiesDir)
|
|
245
257
|
}
|
|
246
258
|
|
|
247
259
|
return result
|
|
@@ -267,7 +279,7 @@ export async function getFreeformFileMeta(filePath) {
|
|
|
267
279
|
* Parse a free-form translation file path to extract metadata
|
|
268
280
|
*
|
|
269
281
|
* @param {string} relativePath - Path relative to locale's freeform dir
|
|
270
|
-
* @returns {Object} { type, pageRoute?, pageId?,
|
|
282
|
+
* @returns {Object} { type, pageRoute?, pageId?, queryName?, stableId, slug? }
|
|
271
283
|
*/
|
|
272
284
|
export function parseFreeformPath(relativePath) {
|
|
273
285
|
const parts = relativePath.split('/')
|
|
@@ -287,11 +299,11 @@ export function parseFreeformPath(relativePath) {
|
|
|
287
299
|
return { type: 'pageId', pageId, stableId }
|
|
288
300
|
}
|
|
289
301
|
|
|
290
|
-
if (parts[0] ===
|
|
291
|
-
// collections/articles/getting-started.md → { type: 'collection',
|
|
302
|
+
if (parts[0] === ENTITIES_DIR) {
|
|
303
|
+
// collections/articles/getting-started.md → { type: 'collection', queryName: 'articles', slug: 'getting-started' }
|
|
292
304
|
const slug = parts[parts.length - 1].replace('.md', '')
|
|
293
|
-
const
|
|
294
|
-
return { type: '
|
|
305
|
+
const queryName = parts[1]
|
|
306
|
+
return { type: 'record', queryName, slug }
|
|
295
307
|
}
|
|
296
308
|
|
|
297
309
|
return { type: 'unknown', relativePath }
|
|
@@ -325,12 +337,20 @@ export function buildFreeformPath(section, page, preferPageId = true) {
|
|
|
325
337
|
}
|
|
326
338
|
|
|
327
339
|
/**
|
|
328
|
-
*
|
|
340
|
+
* The free-form translation path for one entity — the pool's own layout, under
|
|
341
|
+
* `locales/freeform/{locale}/`.
|
|
342
|
+
*
|
|
343
|
+
* ⛔ ONE DERIVATION, shared by the producer (which looks a translation up) and the
|
|
344
|
+
* projector (which writes one). `poolDirsForSchema` is the same inverse the pull
|
|
345
|
+
* side places records with, so a translation cannot land beside a record it is not
|
|
346
|
+
* for.
|
|
329
347
|
*
|
|
330
|
-
* @param {string}
|
|
331
|
-
* @param {string} slug -
|
|
332
|
-
* @returns {string}
|
|
348
|
+
* @param {string} schema - the entity's model ref (`@/name` or `@org/name`)
|
|
349
|
+
* @param {string} slug - the entity's slug
|
|
350
|
+
* @returns {string|null} e.g. `entities/article/getting-started.md`, or null for a
|
|
351
|
+
* ref this layout cannot express
|
|
333
352
|
*/
|
|
334
|
-
export function
|
|
335
|
-
|
|
353
|
+
export function buildFreeformRecordPath(schema, slug) {
|
|
354
|
+
const dirs = poolDirsForSchema(schema)
|
|
355
|
+
return dirs ? `${ENTITIES_DIR}/${dirs.join('/')}/${slug}.md` : null
|
|
336
356
|
}
|
package/src/i18n/index.js
CHANGED
|
@@ -17,23 +17,23 @@ import { syncManifests, formatSyncReport } from './sync.js'
|
|
|
17
17
|
import { mergeTranslations, generateAllLocales } from './merge.js'
|
|
18
18
|
import { auditLocale, cleanLocale, formatAuditReport } from './audit.js'
|
|
19
19
|
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
} from './
|
|
20
|
+
extractRecordContent,
|
|
21
|
+
buildLocalizedRecords,
|
|
22
|
+
getRecordLocales,
|
|
23
|
+
translateRecordData,
|
|
24
|
+
RECORDS_DIR
|
|
25
|
+
} from './records.js'
|
|
26
26
|
import { generateSearchIndex, isSearchEnabled } from '../search/index.js'
|
|
27
27
|
|
|
28
28
|
// Free-form translation support
|
|
29
29
|
import {
|
|
30
30
|
loadFreeformTranslation,
|
|
31
|
-
|
|
31
|
+
loadFreeformRecord,
|
|
32
32
|
discoverFreeformTranslations,
|
|
33
33
|
getFreeformFileMeta,
|
|
34
34
|
parseFreeformPath,
|
|
35
35
|
buildFreeformPath,
|
|
36
|
-
|
|
36
|
+
buildFreeformRecordPath
|
|
37
37
|
} from './freeform.js'
|
|
38
38
|
import {
|
|
39
39
|
computeSourceHash,
|
|
@@ -67,11 +67,11 @@ export {
|
|
|
67
67
|
formatAuditReport,
|
|
68
68
|
|
|
69
69
|
// Collection functions
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
extractRecordContent,
|
|
71
|
+
buildLocalizedRecords,
|
|
72
|
+
getRecordLocales,
|
|
73
|
+
translateRecordData,
|
|
74
|
+
RECORDS_DIR,
|
|
75
75
|
|
|
76
76
|
// Locale resolution
|
|
77
77
|
getAvailableLocales,
|
|
@@ -79,12 +79,12 @@ export {
|
|
|
79
79
|
|
|
80
80
|
// Free-form translation functions
|
|
81
81
|
loadFreeformTranslation,
|
|
82
|
-
|
|
82
|
+
loadFreeformRecord,
|
|
83
83
|
discoverFreeformTranslations,
|
|
84
84
|
getFreeformFileMeta,
|
|
85
85
|
parseFreeformPath,
|
|
86
86
|
buildFreeformPath,
|
|
87
|
-
|
|
87
|
+
buildFreeformRecordPath,
|
|
88
88
|
|
|
89
89
|
// Free-form manifest functions
|
|
90
90
|
computeSourceHash,
|
|
@@ -221,19 +221,19 @@ export async function extractManifest(siteRoot, siteContent, options = {}) {
|
|
|
221
221
|
* @param {Object} options - Options
|
|
222
222
|
* @returns {Promise<Object>} { manifest, report }
|
|
223
223
|
*/
|
|
224
|
-
export async function
|
|
224
|
+
export async function extractRecordManifest(siteRoot, options = {}) {
|
|
225
225
|
const { localesDir = DEFAULTS.localesDir, dryRun = false } = options
|
|
226
226
|
|
|
227
227
|
// Extract translatable content from collections
|
|
228
|
-
const manifest = await
|
|
228
|
+
const manifest = await extractRecordContent(siteRoot)
|
|
229
229
|
|
|
230
230
|
// Ensure collections locales directory exists
|
|
231
|
-
const
|
|
232
|
-
if (!existsSync(
|
|
233
|
-
await mkdir(
|
|
231
|
+
const recordLocalesDir = join(siteRoot, localesDir, RECORDS_DIR)
|
|
232
|
+
if (!existsSync(recordLocalesDir)) {
|
|
233
|
+
await mkdir(recordLocalesDir, { recursive: true })
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
-
const manifestPath = join(
|
|
236
|
+
const manifestPath = join(recordLocalesDir, 'manifest.json')
|
|
237
237
|
|
|
238
238
|
// Load previous manifest for comparison
|
|
239
239
|
let previousManifest = null
|
|
@@ -458,7 +458,7 @@ async function warnAboutFreeformIssues(locale, freeformDir, siteContent) {
|
|
|
458
458
|
|
|
459
459
|
// Check for unregistered translations (new files)
|
|
460
460
|
const discovered = await discoverFreeformTranslations(locale, dirname(dirname(freeformDir)))
|
|
461
|
-
const allPaths = [...discovered.pages, ...discovered.pageIds, ...discovered.
|
|
461
|
+
const allPaths = [...discovered.pages, ...discovered.pageIds, ...discovered.records]
|
|
462
462
|
const unregistered = await getUnregisteredTranslations(freeformDir, allPaths)
|
|
463
463
|
for (const path of unregistered) {
|
|
464
464
|
// Auto-register new free-form translations
|