@uniweb/build 0.55.1 → 0.57.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.
- package/package.json +7 -7
- package/src/content/index.js +3 -0
- package/src/i18n/data-strings.js +1 -1
- package/src/i18n/freeform.js +20 -19
- package/src/i18n/index.js +4 -4
- package/src/i18n/records.js +9 -5
- package/src/site/build-site-data.js +4 -3
- package/src/site/config.js +1 -1
- package/src/site/content-collector.js +4 -3
- package/src/site/deploy-config-writer.js +137 -29
- package/src/site/deploy-config.js +87 -38
- package/src/site/entity-pool.js +136 -42
- package/src/site/index.js +2 -2
- package/src/site/plugin.js +12 -9
- package/src/site/queries-config.js +15 -5
- package/src/site/query-processor.js +126 -112
- package/src/site/record-draft.js +40 -0
- package/src/site/records-config.js +101 -93
- package/src/uwx/asset-map.js +28 -149
- package/src/uwx/backfill.js +31 -4
- package/src/uwx/entity-source.js +3 -3
- package/src/uwx/folder.js +89 -49
- package/src/uwx/index.js +29 -17
- package/src/uwx/records-project.js +131 -78
- package/src/uwx/records.js +324 -203
- package/src/uwx/self-scope.js +5 -5
- package/src/uwx/site-project.js +34 -15
- package/src/uwx/site.js +80 -90
- package/src/uwx/sync-package.js +23 -15
- package/src/uwx/sync-store.js +351 -0
- package/src/validate-data.js +3 -1
|
@@ -1,45 +1,44 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Records projection — write a pulled folder and its records back to the site's
|
|
2
|
+
// `records/**` files and `records.yml`. The inverse of the producer (`records.js`
|
|
3
|
+
// + `folder.js`): the producer reads the records directory and emits the
|
|
4
4
|
// `@uniweb/folder` entity + one section-keyed `$`-document per record; this takes
|
|
5
5
|
// those documents back and renders them to files.
|
|
6
6
|
//
|
|
7
|
-
// Identity & placement
|
|
8
|
-
// -
|
|
9
|
-
// `{ entry: { model, entity: <uuid> }, name: <slug> }
|
|
10
|
-
// (its `$children`) whose `name` names the folder
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// - the record's directory comes from its `$model` — `
|
|
15
|
-
//
|
|
16
|
-
// directory, and which query selects a record is not a
|
|
7
|
+
// Identity & placement:
|
|
8
|
+
// - a record's slug comes from the FOLDER document — each ref leaf is
|
|
9
|
+
// `{ entry: { model, entity: <uuid> }, name: <slug> }`, at the top of the folder
|
|
10
|
+
// or inside a branch (its `$children`) whose `name` names the sub-folder. The
|
|
11
|
+
// folder is the authoritative organization on a read (the record document's own
|
|
12
|
+
// `$id` envelope is not guaranteed to be echoed back), with the record
|
|
13
|
+
// document's `$id` (its position in the directory) as a fallback when present.
|
|
14
|
+
// - the record's directory comes from its `$model` — `records/{schema}/` is where
|
|
15
|
+
// a record of that model lives (`site.yml::paths.records` moves it). Not from any
|
|
16
|
+
// query: a query has no directory, and which query selects a record is not a
|
|
17
|
+
// fact about it.
|
|
17
18
|
// - an existing local file carrying the same `$uuid` is re-rendered in place;
|
|
18
19
|
// otherwise a new single-record file is placed at `<slug>.<ext>`, its format
|
|
19
|
-
// matched to the
|
|
20
|
+
// matched to the schema folder's existing files, else markdown when the Model's
|
|
20
21
|
// brief has a content body field, else YAML.
|
|
21
22
|
//
|
|
22
23
|
// Field rendering reuses renderEntityDocument (via writeRecordFile) — localized
|
|
23
24
|
// unwrap, date handling, content-body→body are already inverted there.
|
|
24
25
|
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
// unplaceable or unresolvable record is reported.
|
|
34
|
-
|
|
35
|
-
import { readFileSync, readdirSync, existsSync } from 'node:fs'
|
|
26
|
+
// Deferred: array-form & BibTeX multi-record files (a pulled record is placed as
|
|
27
|
+
// its own single-record file; merging into an existing array file is a later
|
|
28
|
+
// nicety). The folder itself carries no `$uuid` — the backend owns it, keyed by the
|
|
29
|
+
// site-content uuid. Nothing is silently dropped: an unplaceable or unresolvable
|
|
30
|
+
// record is reported.
|
|
31
|
+
|
|
32
|
+
import { readBackendState, updateBackendMap } from './sync-store.js'
|
|
33
|
+
import { readFileSync, readdirSync, existsSync, unlinkSync } from 'node:fs'
|
|
36
34
|
import { join, resolve, relative, extname, basename, sep } from 'node:path'
|
|
37
35
|
import yaml from 'js-yaml'
|
|
38
36
|
import { YAML_OPTIONS } from '../utils/yaml-schema.js'
|
|
39
37
|
import { parseFrontmatter } from './entity-source.js'
|
|
40
38
|
import { writeRecordFile, writeQueriesConfig, writeRecordsConfig } from './project-writer.js'
|
|
41
39
|
import { defaultSchema, deferredFromSchema, foundationDataSchemas } from './queries-config.js'
|
|
42
|
-
import { poolDirsForSchema,
|
|
40
|
+
import { poolDirsForSchema, resolveRecordsDir } from '../site/entity-pool.js'
|
|
41
|
+
import { recordsYmlPath } from '../site/records-config.js'
|
|
43
42
|
import { isContentBodyField } from './data-schema.js'
|
|
44
43
|
import { unresolveSelfScope } from './self-scope.js'
|
|
45
44
|
import { unwrapLocalized } from './backfill.js'
|
|
@@ -144,7 +143,7 @@ function indexFolder(folderDoc) {
|
|
|
144
143
|
// Where a pulled record is written: the pool folder its MODEL names.
|
|
145
144
|
//
|
|
146
145
|
// ⛔ NOT THE QUERY'S DIRECTORY — a query has none. A record's home is decided by
|
|
147
|
-
// what it IS, and `
|
|
146
|
+
// what it IS, and `records/{schema}/` is the one place a record of that model
|
|
148
147
|
// lives. That is also why the placement survives a query being renamed, added or
|
|
149
148
|
// deleted, none of which is a fact about the record.
|
|
150
149
|
//
|
|
@@ -152,9 +151,9 @@ function indexFolder(folderDoc) {
|
|
|
152
151
|
// `schemaForPoolDirs`, and deliberately not a second rule: if the two disagreed,
|
|
153
152
|
// a pulled record would land somewhere the next build reads as a different
|
|
154
153
|
// schema — silently, because both paths are well-formed.
|
|
155
|
-
function recordDirFor(
|
|
154
|
+
function recordDirFor(recordsRoot, model, selfOrg) {
|
|
156
155
|
const dirs = poolDirsForSchema(unresolveSelfScope(model, selfOrg))
|
|
157
|
-
return dirs ? join(
|
|
156
|
+
return dirs ? join(recordsRoot, ...dirs) : null
|
|
158
157
|
}
|
|
159
158
|
|
|
160
159
|
// ⚠️ Undoing the producer's self-scope resolution (`unresolveSelfScope`) lives in
|
|
@@ -174,14 +173,14 @@ function locate(document, folderIndex) {
|
|
|
174
173
|
return fromFolder || null
|
|
175
174
|
}
|
|
176
175
|
|
|
177
|
-
/** `site.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
176
|
+
/** The site's own org on `backend` — its `site.org` in `sync.json` — bare (`acme`), or null. Stored bare — see `writeSiteOrg`. */
|
|
177
|
+
// ⛔ `site.yml::$org` IS GONE (2026-09-20) — the org lives in
|
|
178
|
+
// `sync.json::backends.<origin>.site.org`. This read `site.yml` and, once step 4
|
|
179
|
+
// moved the key, silently returned null: every `@org/x` model a pull met would have
|
|
180
|
+
// been placed back as if the site had no org. No test spanned it.
|
|
181
|
+
function readSiteOrg(siteRoot, backend) {
|
|
182
|
+
const org = backend ? readBackendState(siteRoot, backend).site?.org : null
|
|
183
|
+
return typeof org === 'string' && org ? org : null
|
|
185
184
|
}
|
|
186
185
|
|
|
187
186
|
// Skip undefined when copying optional fields into a projected declaration.
|
|
@@ -262,8 +261,8 @@ function declToFileShape(wire, dataSchemas = null, selfOrg = null) {
|
|
|
262
261
|
setIf(decl, 'transform', source.transform)
|
|
263
262
|
setIf(decl, 'record', source.record)
|
|
264
263
|
} else if (typeof source.path === 'string') {
|
|
265
|
-
// ⛔ A FILE-BASED QUERY HAS NO PATH TO WRITE BACK. `
|
|
266
|
-
//
|
|
264
|
+
// ⛔ A FILE-BASED QUERY HAS NO PATH TO WRITE BACK. `records/{schema}/` holds its
|
|
265
|
+
// records and `schema:` addresses them, so a `path` arriving on the wire is either
|
|
267
266
|
// stale storage or something only a remote source could have meant. Dropping
|
|
268
267
|
// it keeps the author's file saying what the build actually reads.
|
|
269
268
|
decl.path = source.path
|
|
@@ -331,11 +330,13 @@ function declToFileShape(wire, dataSchemas = null, selfOrg = null) {
|
|
|
331
330
|
* @param {object} params.document - a site-content `$`-document (`{ queries }`)
|
|
332
331
|
* @param {string} params.siteRoot
|
|
333
332
|
* @param {string} [params.org] - the site's own org, so a `schema` the producer
|
|
334
|
-
* qualified from `@/x` is written back as `@/x`. Defaults to
|
|
335
|
-
* the same default `recordsToProject`
|
|
333
|
+
* qualified from `@/x` is written back as `@/x`. Defaults to the org
|
|
334
|
+
* `sync.json` records for `backend`, the same default `recordsToProject`
|
|
335
|
+
* places records by.
|
|
336
|
+
* @param {string|null} [params.backend] - whose recorded org is that default
|
|
336
337
|
* @returns {{ collections?: 'updated'|'unchanged' }}
|
|
337
338
|
*/
|
|
338
|
-
export function declarationsToQueriesYml({ document, siteRoot, org }) {
|
|
339
|
+
export function declarationsToQueriesYml({ document, siteRoot, org, backend = null }) {
|
|
339
340
|
const decls = Array.isArray(document?.queries) ? document.queries : []
|
|
340
341
|
const report = {}
|
|
341
342
|
if (decls.length === 0) return report
|
|
@@ -353,7 +354,7 @@ export function declarationsToQueriesYml({ document, siteRoot, org }) {
|
|
|
353
354
|
siteYml = null
|
|
354
355
|
}
|
|
355
356
|
const dataSchemas = siteYml ? foundationDataSchemas(siteRoot, siteYml) : null
|
|
356
|
-
const selfOrg = org ?? readSiteOrg(siteRoot)
|
|
357
|
+
const selfOrg = org ?? readSiteOrg(siteRoot, backend)
|
|
357
358
|
|
|
358
359
|
const queries = {}
|
|
359
360
|
for (const d of decls) {
|
|
@@ -371,31 +372,37 @@ export function declarationsToQueriesYml({ document, siteRoot, org }) {
|
|
|
371
372
|
/**
|
|
372
373
|
* Project a pulled `@uniweb/folder` document back to `records.yml`.
|
|
373
374
|
*
|
|
374
|
-
* ⭐ THE FOLDER
|
|
375
|
-
*
|
|
376
|
-
*
|
|
377
|
-
*
|
|
378
|
-
*
|
|
379
|
-
*
|
|
375
|
+
* ⭐ THE FOLDER ROUND-TRIPS TRIVIALLY, and that is by design rather than luck:
|
|
376
|
+
* `records.yml` holds concrete paths on both sides, so there is nothing to invert.
|
|
377
|
+
* The old shape put QUERY MACROS in the folder — a virtual `folders:` tree naming
|
|
378
|
+
* collections — and inverting a macro is not possible in general. Taking queries
|
|
379
|
+
* out of the folder is what dissolved that.
|
|
380
|
+
*
|
|
381
|
+
* ⭐ ONLY THE SUB-FOLDERS ARE WRITTEN (ruled 2026-09-21 [Diego]). A record at the
|
|
382
|
+
* top of the folder needs no line — being in the records directory is what puts it
|
|
383
|
+
* there — so `records.yml` carries the branches and the records placed in them, and
|
|
384
|
+
* a folder with no branches is a site with no `records.yml`: a local one is REMOVED
|
|
385
|
+
* (it could only describe sub-folders the backend's folder no longer has). No state
|
|
386
|
+
* of the file removes a record, so this cannot empty anything.
|
|
380
387
|
*
|
|
381
|
-
* ⛔
|
|
382
|
-
*
|
|
383
|
-
* whose leaves could not be placed — must leave the file alone rather than author
|
|
384
|
-
* the destructive state on the author's behalf.
|
|
388
|
+
* ⛔ A PULL THAT CARRIED NO FOLDER, or one with a placed record that was not written
|
|
389
|
+
* locally, leaves the file alone: it has nothing true to write.
|
|
385
390
|
*
|
|
386
391
|
* @param {object} params
|
|
387
392
|
* @param {object} params.folderDoc - the stored `@uniweb/folder` document
|
|
388
393
|
* @param {string} params.siteRoot
|
|
389
394
|
* @param {Map<string,string>} params.poolPathByUuid - record `$uuid` → the path
|
|
390
|
-
* under `
|
|
395
|
+
* under `records/` of the file just written for it. Supplied by
|
|
391
396
|
* `recordsToProject`, which is the only thing that knows the extension
|
|
392
397
|
* each record landed with.
|
|
393
|
-
* @returns {{ status: 'updated'|'unchanged'|'skipped', entries: Array, warnings: string[] }}
|
|
398
|
+
* @returns {{ status: 'updated'|'unchanged'|'removed'|'skipped', entries: Array, warnings: string[] }}
|
|
394
399
|
*/
|
|
395
400
|
export function folderToRecordsYml({ folderDoc, siteRoot, poolPathByUuid, sourceLocale = 'en' }) {
|
|
396
401
|
const warnings = []
|
|
402
|
+
if (!folderDoc) return { status: 'skipped', entries: [], warnings }
|
|
403
|
+
let unplaceable = false
|
|
397
404
|
|
|
398
|
-
const walk = (nodes) => {
|
|
405
|
+
const walk = (nodes, inBranch) => {
|
|
399
406
|
const out = []
|
|
400
407
|
for (const node of nodes || []) {
|
|
401
408
|
if (!node || typeof node !== 'object') continue
|
|
@@ -405,35 +412,53 @@ export function folderToRecordsYml({ folderDoc, siteRoot, poolPathByUuid, source
|
|
|
405
412
|
// does not caption its rows. On the wire the label is a localized map;
|
|
406
413
|
// records.yml carries the source-locale string (a bare string passes).
|
|
407
414
|
if (node.label !== undefined) entry.label = unwrapLocalized(node.label, sourceLocale)
|
|
408
|
-
entry.records = walk(node.$children)
|
|
415
|
+
entry.records = walk(node.$children, true)
|
|
409
416
|
out.push(entry)
|
|
410
417
|
continue
|
|
411
418
|
}
|
|
412
419
|
const uuid = node.entry?.entity ?? node.entry
|
|
413
420
|
const rel = typeof uuid === 'string' ? poolPathByUuid.get(uuid) : null
|
|
421
|
+
// A record at the top of the folder needs no line — but one that did not land
|
|
422
|
+
// is still said: the next push sends the folder the directory holds, which
|
|
423
|
+
// would not have it.
|
|
424
|
+
if (!inBranch) {
|
|
425
|
+
if (!rel) {
|
|
426
|
+
warnings.push(
|
|
427
|
+
`the folder holds a record ("${node.name ?? '?'}") that was not written locally — ` +
|
|
428
|
+
`a push from here would remove it from the folder.`
|
|
429
|
+
)
|
|
430
|
+
}
|
|
431
|
+
continue
|
|
432
|
+
}
|
|
414
433
|
if (!rel) {
|
|
415
|
-
// ⚠️ Reported, never dropped in silence. A
|
|
416
|
-
// means the folder and the
|
|
417
|
-
// would
|
|
434
|
+
// ⚠️ Reported, never dropped in silence. A placed record that did not land
|
|
435
|
+
// means the folder and the directory disagree, and writing the file without
|
|
436
|
+
// it would move that record to the top of the folder on the next push.
|
|
418
437
|
warnings.push(
|
|
419
|
-
`records.yml:
|
|
420
|
-
`
|
|
438
|
+
`records.yml: the folder places a record ("${node.name ?? '?'}") that was not ` +
|
|
439
|
+
`written locally — the file was left unchanged rather than dropping it.`
|
|
421
440
|
)
|
|
422
|
-
|
|
441
|
+
unplaceable = true
|
|
442
|
+
continue
|
|
423
443
|
}
|
|
424
444
|
out.push(rel)
|
|
425
445
|
}
|
|
426
446
|
return out
|
|
427
447
|
}
|
|
428
448
|
|
|
429
|
-
const entries = walk(folderDoc
|
|
430
|
-
if (
|
|
431
|
-
if (entries.length === 0)
|
|
449
|
+
const entries = walk(folderDoc.contents, false)
|
|
450
|
+
if (unplaceable) return { status: 'skipped', entries: [], warnings }
|
|
451
|
+
if (entries.length === 0) {
|
|
452
|
+
const file = recordsYmlPath(siteRoot)
|
|
453
|
+
if (!existsSync(file)) return { status: 'unchanged', entries, warnings }
|
|
454
|
+
unlinkSync(file)
|
|
455
|
+
return { status: 'removed', entries, warnings }
|
|
456
|
+
}
|
|
432
457
|
return { status: writeRecordsConfig(siteRoot, entries), entries, warnings }
|
|
433
458
|
}
|
|
434
459
|
|
|
435
460
|
/**
|
|
436
|
-
* Project a pulled folder + its record entities to `
|
|
461
|
+
* Project a pulled folder + its record entities to `records/**` files.
|
|
437
462
|
*
|
|
438
463
|
* @param {object} params
|
|
439
464
|
* @param {object} params.folderDoc - the `@uniweb/folder` document `{ contents }` (no `$uuid`)
|
|
@@ -444,31 +469,43 @@ export function folderToRecordsYml({ folderDoc, siteRoot, poolPathByUuid, source
|
|
|
444
469
|
* - resolve a Model's data-schema declaration by name (`$model`).
|
|
445
470
|
* @param {string} [params.opts.org] - the site's own org, so a `@org/x` model the
|
|
446
471
|
* producer resolved from `@/x` is placed back where the author wrote it.
|
|
447
|
-
* Defaults to `
|
|
472
|
+
* Defaults to the org `sync.json` records for `opts.backend`.
|
|
473
|
+
* @param {string} [params.opts.backend] - whose record map to read and extend, and
|
|
474
|
+
* whose recorded org is that default
|
|
448
475
|
* @param {string} [params.opts.sourceLocale]
|
|
449
476
|
* @returns {{ updated: string[], placed: string[], unchanged: string[], skipped: object[], warnings: string[], locales: object }}
|
|
450
477
|
*/
|
|
451
478
|
export function recordsToProject({ folderDoc, recordDocs = [], siteRoot, opts = {} }) {
|
|
452
479
|
const { resolveDeclaration, sourceLocale = 'en' } = opts
|
|
453
480
|
// The site's own org, so a `@org/x` model the producer resolved from `@/x` is
|
|
454
|
-
// placed back where the author wrote it. Read from `
|
|
455
|
-
// caller already has it.
|
|
456
|
-
const selfOrg = opts.org ?? readSiteOrg(siteRoot)
|
|
481
|
+
// placed back where the author wrote it. Read from `sync.json` (this backend's
|
|
482
|
+
// `site.org`) unless the caller already has it.
|
|
483
|
+
const selfOrg = opts.org ?? readSiteOrg(siteRoot, opts.backend)
|
|
457
484
|
if (typeof resolveDeclaration !== 'function') {
|
|
458
485
|
throw new Error('uwx/records-project: opts.resolveDeclaration(modelName) is required')
|
|
459
486
|
}
|
|
460
487
|
|
|
461
488
|
const folderIndex = indexFolder(folderDoc)
|
|
489
|
+
// Where records land — `site.yml::paths.records`, else `records/`: the one resolver
|
|
490
|
+
// the build and the push read too, so a pulled record lands where the next build
|
|
491
|
+
// looks. ⛔ This wrote into the default directory until 2026-09-21, whatever the
|
|
492
|
+
// site had moved its records to.
|
|
493
|
+
const recordsRoot = resolveRecordsDir(siteRoot).abs
|
|
462
494
|
// Captures target-locale translations of localized record fields: SCALARs →
|
|
463
495
|
// locales/records/{locale}.json (structural maps too), and a prosemirror
|
|
464
|
-
// BODY's free-form per-locale override → locales/freeform/{locale}/
|
|
496
|
+
// BODY's free-form per-locale override → locales/freeform/{locale}/records/.
|
|
465
497
|
const collector = createTranslationCollector(sourceLocale)
|
|
466
498
|
const updated = []
|
|
467
499
|
const placed = []
|
|
468
500
|
const unchanged = []
|
|
469
501
|
const skipped = []
|
|
470
502
|
const warnings = []
|
|
471
|
-
//
|
|
503
|
+
// This backend's record map, inverted: its uuid → the record's own id.
|
|
504
|
+
const recordMap = opts.backend ? readBackendState(siteRoot, opts.backend).records || {} : {}
|
|
505
|
+
const ownIdByTheirs = new Map(Object.entries(recordMap).map(([own, theirs]) => [theirs, own]))
|
|
506
|
+
// own id → their uuid, for every record this pull wrote. Recorded at the end.
|
|
507
|
+
const learned = {}
|
|
508
|
+
// uuid → the path under `records/` the record landed at. Only this loop knows
|
|
472
509
|
// the extension each one got, so `records.yml` is written from it rather than
|
|
473
510
|
// re-derived (a second rule could pick a different extension and the folder
|
|
474
511
|
// would name a file that is not there).
|
|
@@ -486,7 +523,7 @@ export function recordsToProject({ folderDoc, recordDocs = [], siteRoot, opts =
|
|
|
486
523
|
continue
|
|
487
524
|
}
|
|
488
525
|
|
|
489
|
-
const poolDir = recordDirFor(
|
|
526
|
+
const poolDir = recordDirFor(recordsRoot, document.$model, selfOrg)
|
|
490
527
|
if (!poolDir) {
|
|
491
528
|
skipped.push({
|
|
492
529
|
uuid: document.$uuid,
|
|
@@ -495,7 +532,13 @@ export function recordsToProject({ folderDoc, recordDocs = [], siteRoot, opts =
|
|
|
495
532
|
})
|
|
496
533
|
continue
|
|
497
534
|
}
|
|
498
|
-
|
|
535
|
+
// ⭐ The incoming `$uuid` is the BACKEND's. The file holds the record's OWN id, so
|
|
536
|
+
// reverse this backend's map to find it. With no mapping the two are the same —
|
|
537
|
+
// the first backend a record reaches minted the id it keeps — so the backend's
|
|
538
|
+
// uuid is the own id, exactly as before identity was keyed by backend.
|
|
539
|
+
const theirs = document.$uuid || null
|
|
540
|
+
const own = theirs ? ownIdByTheirs.get(theirs) || theirs : null
|
|
541
|
+
const existing = own ? findRecordFileByUuid(poolDir, own) : null
|
|
499
542
|
|
|
500
543
|
let filePath
|
|
501
544
|
let format
|
|
@@ -514,9 +557,12 @@ export function recordsToProject({ folderDoc, recordDocs = [], siteRoot, opts =
|
|
|
514
557
|
// target-locale full-doc body is written under locales/freeform/{locale}/here.
|
|
515
558
|
const freeformRelPath = buildFreeformRecordPath(document.$model, where.slug)
|
|
516
559
|
|
|
560
|
+
// ⛔ The file gets the record's OWN id, never this backend's — a pull from a second
|
|
561
|
+
// backend must not overwrite the identity the record already has.
|
|
562
|
+
const toWrite = own && own !== theirs ? { ...document, $uuid: own } : document
|
|
517
563
|
let status
|
|
518
564
|
try {
|
|
519
|
-
status = writeRecordFile({ filePath, document, declaration, format, sourceLocale, collector, freeformRelPath })
|
|
565
|
+
status = writeRecordFile({ filePath, document: toWrite, declaration, format, sourceLocale, collector, freeformRelPath })
|
|
520
566
|
} catch (err) {
|
|
521
567
|
warnings.push(`${where.slug}: ${err.message}`)
|
|
522
568
|
continue
|
|
@@ -524,8 +570,11 @@ export function recordsToProject({ folderDoc, recordDocs = [], siteRoot, opts =
|
|
|
524
570
|
if (status === 'unchanged') unchanged.push(filePath)
|
|
525
571
|
else if (isNew) placed.push(filePath)
|
|
526
572
|
else updated.push(filePath)
|
|
527
|
-
|
|
528
|
-
|
|
573
|
+
// Keyed by THEIR uuid: the folder document references records in the backend's
|
|
574
|
+
// terms, so that is what `folderToRecordsYml` will look them up by.
|
|
575
|
+
if (theirs) {
|
|
576
|
+
poolPathByUuid.set(theirs, relative(recordsRoot, filePath).split(sep).join('/'))
|
|
577
|
+
if (own) learned[own] = theirs
|
|
529
578
|
}
|
|
530
579
|
}
|
|
531
580
|
|
|
@@ -543,5 +592,9 @@ export function recordsToProject({ folderDoc, recordDocs = [], siteRoot, opts =
|
|
|
543
592
|
const locales = writeLocaleTranslations(siteRoot, collector.byLocale, 'records')
|
|
544
593
|
const freeform = writeFreeformTranslations(siteRoot, collector.freeformPending)
|
|
545
594
|
|
|
595
|
+
if (opts.backend && Object.keys(learned).length) {
|
|
596
|
+
updateBackendMap(siteRoot, opts.backend, 'records', learned)
|
|
597
|
+
}
|
|
598
|
+
|
|
546
599
|
return { updated, placed, unchanged, skipped, warnings, locales, freeform, records: records.status }
|
|
547
600
|
}
|