@uniweb/build 0.56.0 → 0.58.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 +6 -6
- package/src/content/index.js +3 -0
- package/src/foundation-name.js +63 -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 +10 -6
- package/src/index.js +8 -0
- package/src/prerender.js +1 -1
- package/src/schema.js +67 -10
- package/src/site/build-site-data.js +4 -3
- package/src/site/config.js +1 -1
- package/src/site/content-collector.js +5 -4
- package/src/site/entity-pool.js +186 -44
- package/src/site/plugin.js +20 -15
- package/src/site/queries-config.js +16 -6
- package/src/site/query-processor.js +129 -114
- package/src/site/record-draft.js +40 -0
- package/src/site/records-config.js +156 -107
- package/src/utils/numeric-prefix.js +1 -1
- package/src/uwx/asset-map.js +2 -1
- package/src/uwx/backfill.js +24 -4
- package/src/uwx/entity-source.js +4 -4
- package/src/uwx/folder.js +89 -49
- package/src/uwx/index.js +12 -11
- package/src/uwx/project-writer.js +13 -11
- package/src/uwx/records-project.js +108 -75
- package/src/uwx/records.js +319 -227
- package/src/uwx/registry-package.js +8 -1
- package/src/uwx/self-scope.js +5 -5
- package/src/uwx/site-project.js +3 -3
- package/src/uwx/site.js +45 -39
- package/src/uwx/sync-package.js +19 -15
- package/src/validate-data.js +3 -1
|
@@ -1,46 +1,44 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// (
|
|
4
|
-
// `@uniweb/folder` entity + one section-keyed
|
|
5
|
-
// those documents back and renders them to files.
|
|
1
|
+
// Records projection — write a pulled folder and its records back to the site's
|
|
2
|
+
// `records/**` files and the folder's organization, `records/folder.yml`. The
|
|
3
|
+
// inverse of the producer (`records.js` + `folder.js`): the producer reads the
|
|
4
|
+
// records directory and emits the `@uniweb/folder` entity + one section-keyed
|
|
5
|
+
// `$`-document per record; this takes 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
|
-
// (a comment-preserving config rewrite is a separate quality bar). The folder itself
|
|
31
|
-
// carries no `$uuid` — the backend owns it, keyed by the site-content uuid — so
|
|
32
|
-
// nothing is written into `collections.yml` here. Nothing is silently dropped: an
|
|
33
|
-
// unplaceable or unresolvable record is reported.
|
|
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.
|
|
34
31
|
|
|
35
32
|
import { readBackendState, updateBackendMap } from './sync-store.js'
|
|
36
|
-
import { readFileSync, readdirSync, existsSync } from 'node:fs'
|
|
33
|
+
import { readFileSync, readdirSync, existsSync, unlinkSync } from 'node:fs'
|
|
37
34
|
import { join, resolve, relative, extname, basename, sep } from 'node:path'
|
|
38
35
|
import yaml from 'js-yaml'
|
|
39
36
|
import { YAML_OPTIONS } from '../utils/yaml-schema.js'
|
|
40
37
|
import { parseFrontmatter } from './entity-source.js'
|
|
41
38
|
import { writeRecordFile, writeQueriesConfig, writeRecordsConfig } from './project-writer.js'
|
|
42
39
|
import { defaultSchema, deferredFromSchema, foundationDataSchemas } from './queries-config.js'
|
|
43
|
-
import { poolDirsForSchema,
|
|
40
|
+
import { poolDirsForSchema, resolveRecordsDir } from '../site/entity-pool.js'
|
|
41
|
+
import { folderYmlPath } from '../site/records-config.js'
|
|
44
42
|
import { isContentBodyField } from './data-schema.js'
|
|
45
43
|
import { unresolveSelfScope } from './self-scope.js'
|
|
46
44
|
import { unwrapLocalized } from './backfill.js'
|
|
@@ -145,7 +143,7 @@ function indexFolder(folderDoc) {
|
|
|
145
143
|
// Where a pulled record is written: the pool folder its MODEL names.
|
|
146
144
|
//
|
|
147
145
|
// ⛔ NOT THE QUERY'S DIRECTORY — a query has none. A record's home is decided by
|
|
148
|
-
// what it IS, and `
|
|
146
|
+
// what it IS, and `records/{schema}/` is the one place a record of that model
|
|
149
147
|
// lives. That is also why the placement survives a query being renamed, added or
|
|
150
148
|
// deleted, none of which is a fact about the record.
|
|
151
149
|
//
|
|
@@ -153,9 +151,9 @@ function indexFolder(folderDoc) {
|
|
|
153
151
|
// `schemaForPoolDirs`, and deliberately not a second rule: if the two disagreed,
|
|
154
152
|
// a pulled record would land somewhere the next build reads as a different
|
|
155
153
|
// schema — silently, because both paths are well-formed.
|
|
156
|
-
function recordDirFor(
|
|
154
|
+
function recordDirFor(recordsRoot, model, selfOrg) {
|
|
157
155
|
const dirs = poolDirsForSchema(unresolveSelfScope(model, selfOrg))
|
|
158
|
-
return dirs ? join(
|
|
156
|
+
return dirs ? join(recordsRoot, ...dirs) : null
|
|
159
157
|
}
|
|
160
158
|
|
|
161
159
|
// ⚠️ Undoing the producer's self-scope resolution (`unresolveSelfScope`) lives in
|
|
@@ -175,7 +173,7 @@ function locate(document, folderIndex) {
|
|
|
175
173
|
return fromFolder || null
|
|
176
174
|
}
|
|
177
175
|
|
|
178
|
-
/** `site.
|
|
176
|
+
/** The site's own org on `backend` — its `site.org` in `sync.json` — bare (`acme`), or null. Stored bare — see `writeSiteOrg`. */
|
|
179
177
|
// ⛔ `site.yml::$org` IS GONE (2026-09-20) — the org lives in
|
|
180
178
|
// `sync.json::backends.<origin>.site.org`. This read `site.yml` and, once step 4
|
|
181
179
|
// moved the key, silently returned null: every `@org/x` model a pull met would have
|
|
@@ -263,8 +261,8 @@ function declToFileShape(wire, dataSchemas = null, selfOrg = null) {
|
|
|
263
261
|
setIf(decl, 'transform', source.transform)
|
|
264
262
|
setIf(decl, 'record', source.record)
|
|
265
263
|
} else if (typeof source.path === 'string') {
|
|
266
|
-
// ⛔ A FILE-BASED QUERY HAS NO PATH TO WRITE BACK. `
|
|
267
|
-
//
|
|
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
|
|
268
266
|
// stale storage or something only a remote source could have meant. Dropping
|
|
269
267
|
// it keeps the author's file saying what the build actually reads.
|
|
270
268
|
decl.path = source.path
|
|
@@ -332,8 +330,10 @@ function declToFileShape(wire, dataSchemas = null, selfOrg = null) {
|
|
|
332
330
|
* @param {object} params.document - a site-content `$`-document (`{ queries }`)
|
|
333
331
|
* @param {string} params.siteRoot
|
|
334
332
|
* @param {string} [params.org] - the site's own org, so a `schema` the producer
|
|
335
|
-
* qualified from `@/x` is written back as `@/x`. Defaults to
|
|
336
|
-
* 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
|
|
337
337
|
* @returns {{ collections?: 'updated'|'unchanged' }}
|
|
338
338
|
*/
|
|
339
339
|
export function declarationsToQueriesYml({ document, siteRoot, org, backend = null }) {
|
|
@@ -370,33 +370,42 @@ export function declarationsToQueriesYml({ document, siteRoot, org, backend = nu
|
|
|
370
370
|
}
|
|
371
371
|
|
|
372
372
|
/**
|
|
373
|
-
* Project a pulled `@uniweb/folder` document back to `
|
|
373
|
+
* Project a pulled `@uniweb/folder` document back to `folder.yml` in the records
|
|
374
|
+
* directory (`records/folder.yml`, or under `site.yml::paths.records`).
|
|
374
375
|
*
|
|
375
|
-
* ⭐ THE FOLDER
|
|
376
|
-
*
|
|
377
|
-
*
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
* Taking queries out of the folder is what dissolved that.
|
|
376
|
+
* ⭐ THE FOLDER ROUND-TRIPS TRIVIALLY, and that is by design rather than luck:
|
|
377
|
+
* `folder.yml` holds concrete paths on both sides, so there is nothing to invert.
|
|
378
|
+
* The old shape put QUERY MACROS in the folder — a virtual `folders:` tree naming
|
|
379
|
+
* collections — and inverting a macro is not possible in general. Taking queries
|
|
380
|
+
* out of the folder is what dissolved that.
|
|
381
381
|
*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
382
|
+
* ⭐ ONLY THE SUB-FOLDERS ARE WRITTEN (ruled 2026-09-21 [Diego]). A record at the
|
|
383
|
+
* top of the folder needs no line — being in the records directory is what puts it
|
|
384
|
+
* there — so `folder.yml` carries the branches and the records placed in them, and
|
|
385
|
+
* a folder with no branches is a site with no `folder.yml`: a local one is REMOVED
|
|
386
|
+
* (it could only describe sub-folders the backend's folder no longer has). No state
|
|
387
|
+
* of the file removes a record, so this cannot empty anything.
|
|
388
|
+
*
|
|
389
|
+
* ⛔ A PULL THAT CARRIED NO FOLDER, or one with a placed record that was not written
|
|
390
|
+
* locally, leaves the file alone: it has nothing true to write.
|
|
386
391
|
*
|
|
387
392
|
* @param {object} params
|
|
388
393
|
* @param {object} params.folderDoc - the stored `@uniweb/folder` document
|
|
389
394
|
* @param {string} params.siteRoot
|
|
390
395
|
* @param {Map<string,string>} params.poolPathByUuid - record `$uuid` → the path
|
|
391
|
-
* under `
|
|
396
|
+
* under `records/` of the file just written for it. Supplied by
|
|
392
397
|
* `recordsToProject`, which is the only thing that knows the extension
|
|
393
398
|
* each record landed with.
|
|
394
|
-
* @returns {{ status: 'updated'|'unchanged'|'skipped', entries: Array, warnings: string[] }}
|
|
399
|
+
* @returns {{ status: 'updated'|'unchanged'|'removed'|'skipped', entries: Array, warnings: string[], file: string }}
|
|
400
|
+
* `file` is where the organization lives, as the author would write it
|
|
395
401
|
*/
|
|
396
|
-
export function
|
|
402
|
+
export function folderToFolderYml({ folderDoc, siteRoot, poolPathByUuid, sourceLocale = 'en' }) {
|
|
397
403
|
const warnings = []
|
|
404
|
+
const { abs, rel: file } = folderYmlPath(siteRoot)
|
|
405
|
+
if (!folderDoc) return { status: 'skipped', entries: [], warnings, file }
|
|
406
|
+
let unplaceable = false
|
|
398
407
|
|
|
399
|
-
const walk = (nodes) => {
|
|
408
|
+
const walk = (nodes, inBranch) => {
|
|
400
409
|
const out = []
|
|
401
410
|
for (const node of nodes || []) {
|
|
402
411
|
if (!node || typeof node !== 'object') continue
|
|
@@ -404,37 +413,54 @@ export function folderToRecordsYml({ folderDoc, siteRoot, poolPathByUuid, source
|
|
|
404
413
|
const entry = { folder: node.name }
|
|
405
414
|
// Only a BRANCH takes a label. A record carries its own title; the folder
|
|
406
415
|
// does not caption its rows. On the wire the label is a localized map;
|
|
407
|
-
//
|
|
416
|
+
// folder.yml carries the source-locale string (a bare string passes).
|
|
408
417
|
if (node.label !== undefined) entry.label = unwrapLocalized(node.label, sourceLocale)
|
|
409
|
-
entry.records = walk(node.$children)
|
|
418
|
+
entry.records = walk(node.$children, true)
|
|
410
419
|
out.push(entry)
|
|
411
420
|
continue
|
|
412
421
|
}
|
|
413
422
|
const uuid = node.entry?.entity ?? node.entry
|
|
414
423
|
const rel = typeof uuid === 'string' ? poolPathByUuid.get(uuid) : null
|
|
424
|
+
// A record at the top of the folder needs no line — but one that did not land
|
|
425
|
+
// is still said: the next push sends the folder the directory holds, which
|
|
426
|
+
// would not have it.
|
|
427
|
+
if (!inBranch) {
|
|
428
|
+
if (!rel) {
|
|
429
|
+
warnings.push(
|
|
430
|
+
`the folder holds a record ("${node.name ?? '?'}") that was not written locally — ` +
|
|
431
|
+
`a push from here would remove it from the folder.`
|
|
432
|
+
)
|
|
433
|
+
}
|
|
434
|
+
continue
|
|
435
|
+
}
|
|
415
436
|
if (!rel) {
|
|
416
|
-
// ⚠️ Reported, never dropped in silence. A
|
|
417
|
-
// means the folder and the
|
|
418
|
-
// would
|
|
437
|
+
// ⚠️ Reported, never dropped in silence. A placed record that did not land
|
|
438
|
+
// means the folder and the directory disagree, and writing the file without
|
|
439
|
+
// it would move that record to the top of the folder on the next push.
|
|
419
440
|
warnings.push(
|
|
420
|
-
|
|
421
|
-
`
|
|
441
|
+
`${file}: the folder places a record ("${node.name ?? '?'}") that was not ` +
|
|
442
|
+
`written locally — the file was left unchanged rather than dropping it.`
|
|
422
443
|
)
|
|
423
|
-
|
|
444
|
+
unplaceable = true
|
|
445
|
+
continue
|
|
424
446
|
}
|
|
425
447
|
out.push(rel)
|
|
426
448
|
}
|
|
427
449
|
return out
|
|
428
450
|
}
|
|
429
451
|
|
|
430
|
-
const entries = walk(folderDoc
|
|
431
|
-
if (
|
|
432
|
-
if (entries.length === 0)
|
|
433
|
-
|
|
452
|
+
const entries = walk(folderDoc.contents, false)
|
|
453
|
+
if (unplaceable) return { status: 'skipped', entries: [], warnings, file }
|
|
454
|
+
if (entries.length === 0) {
|
|
455
|
+
if (!existsSync(abs)) return { status: 'unchanged', entries, warnings, file }
|
|
456
|
+
unlinkSync(abs)
|
|
457
|
+
return { status: 'removed', entries, warnings, file }
|
|
458
|
+
}
|
|
459
|
+
return { status: writeRecordsConfig(siteRoot, entries), entries, warnings, file }
|
|
434
460
|
}
|
|
435
461
|
|
|
436
462
|
/**
|
|
437
|
-
* Project a pulled folder + its record entities to `
|
|
463
|
+
* Project a pulled folder + its record entities to `records/**` files.
|
|
438
464
|
*
|
|
439
465
|
* @param {object} params
|
|
440
466
|
* @param {object} params.folderDoc - the `@uniweb/folder` document `{ contents }` (no `$uuid`)
|
|
@@ -445,24 +471,31 @@ export function folderToRecordsYml({ folderDoc, siteRoot, poolPathByUuid, source
|
|
|
445
471
|
* - resolve a Model's data-schema declaration by name (`$model`).
|
|
446
472
|
* @param {string} [params.opts.org] - the site's own org, so a `@org/x` model the
|
|
447
473
|
* producer resolved from `@/x` is placed back where the author wrote it.
|
|
448
|
-
* Defaults to `
|
|
474
|
+
* Defaults to the org `sync.json` records for `opts.backend`.
|
|
475
|
+
* @param {string} [params.opts.backend] - whose record map to read and extend, and
|
|
476
|
+
* whose recorded org is that default
|
|
449
477
|
* @param {string} [params.opts.sourceLocale]
|
|
450
478
|
* @returns {{ updated: string[], placed: string[], unchanged: string[], skipped: object[], warnings: string[], locales: object }}
|
|
451
479
|
*/
|
|
452
480
|
export function recordsToProject({ folderDoc, recordDocs = [], siteRoot, opts = {} }) {
|
|
453
481
|
const { resolveDeclaration, sourceLocale = 'en' } = opts
|
|
454
482
|
// The site's own org, so a `@org/x` model the producer resolved from `@/x` is
|
|
455
|
-
// placed back where the author wrote it. Read from `
|
|
456
|
-
// caller already has it.
|
|
483
|
+
// placed back where the author wrote it. Read from `sync.json` (this backend's
|
|
484
|
+
// `site.org`) unless the caller already has it.
|
|
457
485
|
const selfOrg = opts.org ?? readSiteOrg(siteRoot, opts.backend)
|
|
458
486
|
if (typeof resolveDeclaration !== 'function') {
|
|
459
487
|
throw new Error('uwx/records-project: opts.resolveDeclaration(modelName) is required')
|
|
460
488
|
}
|
|
461
489
|
|
|
462
490
|
const folderIndex = indexFolder(folderDoc)
|
|
491
|
+
// Where records land — `site.yml::paths.records`, else `records/`: the one resolver
|
|
492
|
+
// the build and the push read too, so a pulled record lands where the next build
|
|
493
|
+
// looks. ⛔ This wrote into the default directory until 2026-09-21, whatever the
|
|
494
|
+
// site had moved its records to.
|
|
495
|
+
const recordsRoot = resolveRecordsDir(siteRoot).abs
|
|
463
496
|
// Captures target-locale translations of localized record fields: SCALARs →
|
|
464
497
|
// locales/records/{locale}.json (structural maps too), and a prosemirror
|
|
465
|
-
// BODY's free-form per-locale override → locales/freeform/{locale}/
|
|
498
|
+
// BODY's free-form per-locale override → locales/freeform/{locale}/records/.
|
|
466
499
|
const collector = createTranslationCollector(sourceLocale)
|
|
467
500
|
const updated = []
|
|
468
501
|
const placed = []
|
|
@@ -474,8 +507,8 @@ export function recordsToProject({ folderDoc, recordDocs = [], siteRoot, opts =
|
|
|
474
507
|
const ownIdByTheirs = new Map(Object.entries(recordMap).map(([own, theirs]) => [theirs, own]))
|
|
475
508
|
// own id → their uuid, for every record this pull wrote. Recorded at the end.
|
|
476
509
|
const learned = {}
|
|
477
|
-
// uuid → the path under `
|
|
478
|
-
// the extension each one got, so `
|
|
510
|
+
// uuid → the path under `records/` the record landed at. Only this loop knows
|
|
511
|
+
// the extension each one got, so `folder.yml` is written from it rather than
|
|
479
512
|
// re-derived (a second rule could pick a different extension and the folder
|
|
480
513
|
// would name a file that is not there).
|
|
481
514
|
const poolPathByUuid = new Map()
|
|
@@ -492,7 +525,7 @@ export function recordsToProject({ folderDoc, recordDocs = [], siteRoot, opts =
|
|
|
492
525
|
continue
|
|
493
526
|
}
|
|
494
527
|
|
|
495
|
-
const poolDir = recordDirFor(
|
|
528
|
+
const poolDir = recordDirFor(recordsRoot, document.$model, selfOrg)
|
|
496
529
|
if (!poolDir) {
|
|
497
530
|
skipped.push({
|
|
498
531
|
uuid: document.$uuid,
|
|
@@ -540,20 +573,20 @@ export function recordsToProject({ folderDoc, recordDocs = [], siteRoot, opts =
|
|
|
540
573
|
else if (isNew) placed.push(filePath)
|
|
541
574
|
else updated.push(filePath)
|
|
542
575
|
// Keyed by THEIR uuid: the folder document references records in the backend's
|
|
543
|
-
// terms, so that is what `
|
|
576
|
+
// terms, so that is what `folderToFolderYml` will look them up by.
|
|
544
577
|
if (theirs) {
|
|
545
|
-
poolPathByUuid.set(theirs, relative(
|
|
578
|
+
poolPathByUuid.set(theirs, relative(recordsRoot, filePath).split(sep).join('/'))
|
|
546
579
|
if (own) learned[own] = theirs
|
|
547
580
|
}
|
|
548
581
|
}
|
|
549
582
|
|
|
550
|
-
// ⭐ THE FOLDER ITSELF, written back as `
|
|
583
|
+
// ⭐ THE FOLDER ITSELF, written back as `folder.yml`. Steps that only touched the
|
|
551
584
|
// READ path would leave every pull authoring the old shape — the site would build
|
|
552
585
|
// from the new layout and be projected back into the one it replaced.
|
|
553
586
|
//
|
|
554
587
|
// The folder ENTITY still carries no `$uuid` we persist: the backend owns the
|
|
555
588
|
// site's folder, keyed by the site-content uuid.
|
|
556
|
-
const records =
|
|
589
|
+
const records = folderToFolderYml({ folderDoc, siteRoot, poolPathByUuid, sourceLocale })
|
|
557
590
|
warnings.push(...records.warnings)
|
|
558
591
|
|
|
559
592
|
// Flush localized record-field translations to locales/records/{locale}.json,
|
|
@@ -565,5 +598,5 @@ export function recordsToProject({ folderDoc, recordDocs = [], siteRoot, opts =
|
|
|
565
598
|
updateBackendMap(siteRoot, opts.backend, 'records', learned)
|
|
566
599
|
}
|
|
567
600
|
|
|
568
|
-
return { updated, placed, unchanged, skipped, warnings, locales, freeform, records: records.status }
|
|
601
|
+
return { updated, placed, unchanged, skipped, warnings, locales, freeform, records: records.status, recordsFile: records.file }
|
|
569
602
|
}
|