uniweb 0.13.16 → 0.14.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/README.md +1 -1
- package/package.json +7 -7
- package/partials/agents.md +51 -0
- package/src/backend/client.js +173 -40
- package/src/backend/data-bundle.js +15 -3
- package/src/backend/foundation-bring-along.js +76 -21
- package/src/backend/payment-handoff.js +28 -9
- package/src/backend/site-media.js +147 -19
- package/src/backend/site-sync.js +362 -40
- package/src/commands/add.js +575 -273
- package/src/commands/build.js +160 -57
- package/src/commands/clone.js +79 -26
- package/src/commands/content.js +11 -7
- package/src/commands/deploy.js +80 -32
- package/src/commands/dev.js +39 -17
- package/src/commands/docs.js +44 -16
- package/src/commands/doctor.js +338 -82
- package/src/commands/export.js +15 -7
- package/src/commands/handoff.js +6 -2
- package/src/commands/i18n.js +248 -99
- package/src/commands/inspect.js +48 -19
- package/src/commands/invite.js +9 -3
- package/src/commands/org.js +19 -5
- package/src/commands/publish.js +229 -60
- package/src/commands/pull.js +157 -48
- package/src/commands/push.js +144 -42
- package/src/commands/refresh.js +37 -10
- package/src/commands/register.js +235 -64
- package/src/commands/rename.js +126 -46
- package/src/commands/runtime.js +74 -24
- package/src/commands/status.js +48 -15
- package/src/commands/sync.js +7 -2
- package/src/commands/template.js +12 -4
- package/src/commands/update.js +263 -87
- package/src/commands/validate.js +115 -32
- package/src/framework-index.json +16 -14
- package/src/index.js +298 -145
- package/src/templates/fetchers/github.js +7 -7
- package/src/templates/fetchers/npm.js +3 -3
- package/src/templates/fetchers/release.js +21 -18
- package/src/templates/index.js +34 -12
- package/src/templates/processor.js +44 -12
- package/src/templates/resolver.js +42 -15
- package/src/templates/validator.js +14 -7
- package/src/utils/asset-upload.js +90 -22
- package/src/utils/code-upload.js +62 -37
- package/src/utils/config.js +31 -10
- package/src/utils/dep-survey.js +33 -7
- package/src/utils/destination-prompt.js +27 -17
- package/src/utils/git.js +66 -22
- package/src/utils/host-prompt.js +4 -3
- package/src/utils/install-integrity.js +8 -4
- package/src/utils/interactive.js +3 -3
- package/src/utils/json-file.js +6 -2
- package/src/utils/names.js +15 -6
- package/src/utils/placement.js +16 -8
- package/src/utils/registry-auth.js +185 -57
- package/src/utils/registry-orgs.js +142 -53
- package/src/utils/runtime-upload.js +52 -14
- package/src/utils/scaffold.js +55 -14
- package/src/utils/site-content-refs.js +3 -1
- package/src/utils/update-check.js +43 -17
- package/src/utils/workspace.js +13 -7
- package/src/versions.js +18 -7
- package/templates/site/_gitignore +5 -0
package/src/commands/pull.js
CHANGED
|
@@ -56,7 +56,13 @@
|
|
|
56
56
|
* against the playground backend, 2026-06-17.
|
|
57
57
|
*/
|
|
58
58
|
|
|
59
|
-
import {
|
|
59
|
+
import {
|
|
60
|
+
readFileSync,
|
|
61
|
+
writeFileSync,
|
|
62
|
+
mkdirSync,
|
|
63
|
+
mkdtempSync,
|
|
64
|
+
rmSync
|
|
65
|
+
} from 'node:fs'
|
|
60
66
|
import { tmpdir } from 'node:os'
|
|
61
67
|
import { createHash } from 'node:crypto'
|
|
62
68
|
import { createInterface } from 'node:readline/promises'
|
|
@@ -68,23 +74,38 @@ import {
|
|
|
68
74
|
resolveCollectionsConfig,
|
|
69
75
|
readZip,
|
|
70
76
|
computeUnitHashes,
|
|
71
|
-
collectUnitUuids
|
|
77
|
+
collectUnitUuids
|
|
72
78
|
} from '@uniweb/build/uwx'
|
|
73
79
|
import { makeModelResolver } from './push.js'
|
|
74
80
|
import {
|
|
75
81
|
mergeBaseVersions,
|
|
76
82
|
mergeItemBaseVersions,
|
|
77
83
|
writeUnitBases,
|
|
78
|
-
writeItemUuids
|
|
84
|
+
writeItemUuids
|
|
79
85
|
} from '../backend/site-sync.js'
|
|
80
|
-
import {
|
|
86
|
+
import {
|
|
87
|
+
uncommittedUnder,
|
|
88
|
+
siteContentRoots,
|
|
89
|
+
showAtHead,
|
|
90
|
+
mergeFile
|
|
91
|
+
} from '../utils/git.js'
|
|
81
92
|
import { isNonInteractive } from '../utils/interactive.js'
|
|
82
93
|
import { BackendClient } from '../backend/client.js'
|
|
83
|
-
import {
|
|
94
|
+
import {
|
|
95
|
+
resolveSiteDir as defaultResolveSiteDir,
|
|
96
|
+
resolveSiteBackend
|
|
97
|
+
} from './deploy.js'
|
|
84
98
|
|
|
85
99
|
const FOLDER_MODEL = '@uniweb/folder'
|
|
86
100
|
|
|
87
|
-
const colors = {
|
|
101
|
+
const colors = {
|
|
102
|
+
reset: '\x1b[0m',
|
|
103
|
+
bright: '\x1b[1m',
|
|
104
|
+
dim: '\x1b[2m',
|
|
105
|
+
red: '\x1b[31m',
|
|
106
|
+
green: '\x1b[32m',
|
|
107
|
+
blue: '\x1b[36m'
|
|
108
|
+
}
|
|
88
109
|
const log = console.log
|
|
89
110
|
const success = (m) => log(`${colors.green}✓${colors.reset} ${m}`)
|
|
90
111
|
const error = (m) => console.error(`${colors.red}✗${colors.reset} ${m}`)
|
|
@@ -95,7 +116,8 @@ function flagValue(args, name) {
|
|
|
95
116
|
const eq = args.find((a) => a.startsWith(`${name}=`))
|
|
96
117
|
if (eq) return eq.slice(name.length + 1)
|
|
97
118
|
const i = args.indexOf(name)
|
|
98
|
-
if (i !== -1 && args[i + 1] && !args[i + 1].startsWith('-'))
|
|
119
|
+
if (i !== -1 && args[i + 1] && !args[i + 1].startsWith('-'))
|
|
120
|
+
return args[i + 1]
|
|
99
121
|
return null
|
|
100
122
|
}
|
|
101
123
|
|
|
@@ -127,7 +149,10 @@ function readPullCache(siteDir) {
|
|
|
127
149
|
function writePullCache(siteDir, { content, folder }) {
|
|
128
150
|
const p = pullCachePath(siteDir)
|
|
129
151
|
mkdirSync(dirname(p), { recursive: true })
|
|
130
|
-
writeFileSync(
|
|
152
|
+
writeFileSync(
|
|
153
|
+
p,
|
|
154
|
+
JSON.stringify({ version: 1, content, folder }, null, 2) + '\n'
|
|
155
|
+
)
|
|
131
156
|
}
|
|
132
157
|
|
|
133
158
|
// Extract a single entity `$`-document from a pull response. Tolerant of a raw
|
|
@@ -142,7 +167,8 @@ export function extractDocument(payload) {
|
|
|
142
167
|
// folder document and the record documents. Tolerant of an array, an
|
|
143
168
|
// `{ entities }` / `{ documents }` list, or an explicit `{ folder, records }`.
|
|
144
169
|
export function splitCollectionsPull(payload) {
|
|
145
|
-
if (payload?.folder)
|
|
170
|
+
if (payload?.folder)
|
|
171
|
+
return { folderDoc: payload.folder, recordDocs: payload.records || [] }
|
|
146
172
|
const list = Array.isArray(payload)
|
|
147
173
|
? payload
|
|
148
174
|
: Array.isArray(payload?.entities)
|
|
@@ -154,7 +180,7 @@ export function splitCollectionsPull(payload) {
|
|
|
154
180
|
const docs = list.map(extractDocument).filter(Boolean)
|
|
155
181
|
return {
|
|
156
182
|
folderDoc: docs.find((d) => d.$model === FOLDER_MODEL) || null,
|
|
157
|
-
recordDocs: docs.filter((d) => d.$model !== FOLDER_MODEL)
|
|
183
|
+
recordDocs: docs.filter((d) => d.$model !== FOLDER_MODEL)
|
|
158
184
|
}
|
|
159
185
|
}
|
|
160
186
|
|
|
@@ -186,8 +212,10 @@ export function readPullDocuments(buf) {
|
|
|
186
212
|
} catch {
|
|
187
213
|
return []
|
|
188
214
|
}
|
|
189
|
-
if (Array.isArray(payload))
|
|
190
|
-
|
|
215
|
+
if (Array.isArray(payload))
|
|
216
|
+
return payload.map(extractDocument).filter(Boolean)
|
|
217
|
+
if (payload?.folder)
|
|
218
|
+
return [payload.folder, ...(payload.records || [])].filter(Boolean)
|
|
191
219
|
const list = Array.isArray(payload?.entities)
|
|
192
220
|
? payload.entities
|
|
193
221
|
: Array.isArray(payload?.documents)
|
|
@@ -249,7 +277,8 @@ function readManifestTokens(buf) {
|
|
|
249
277
|
try {
|
|
250
278
|
const manifest = JSON.parse(data.toString('utf8'))
|
|
251
279
|
for (const entry of manifest?.entries || []) {
|
|
252
|
-
if (entry?.uuid && typeof entry.version === 'string')
|
|
280
|
+
if (entry?.uuid && typeof entry.version === 'string')
|
|
281
|
+
out.entity[entry.uuid] = entry.version
|
|
253
282
|
const items = entry?.item_versions
|
|
254
283
|
if (items && typeof items === 'object') {
|
|
255
284
|
for (const [uuid, v] of Object.entries(items)) {
|
|
@@ -286,12 +315,20 @@ async function checkWorkingTree(siteDir, args) {
|
|
|
286
315
|
// Not a git work tree. Nothing to fall back on if this goes wrong.
|
|
287
316
|
if (isNonInteractive(args)) {
|
|
288
317
|
error('Refusing to pull: this site is not in a git repository.')
|
|
289
|
-
note(
|
|
290
|
-
|
|
291
|
-
|
|
318
|
+
note(
|
|
319
|
+
'Pull rewrites pages, sections and layout from the backend and deletes what it no longer has.'
|
|
320
|
+
)
|
|
321
|
+
note(
|
|
322
|
+
'Without version control there is no way back, and this session cannot ask.'
|
|
323
|
+
)
|
|
324
|
+
note(
|
|
325
|
+
'Re-run with --force to accept that, or put the site under git first.'
|
|
326
|
+
)
|
|
292
327
|
return { exitCode: 1 }
|
|
293
328
|
}
|
|
294
|
-
const ok = await confirm(
|
|
329
|
+
const ok = await confirm(
|
|
330
|
+
`Pull will overwrite ${roots.length} content location(s) and this site is not in git. Continue?`
|
|
331
|
+
)
|
|
295
332
|
if (!ok) {
|
|
296
333
|
info('Nothing pulled.')
|
|
297
334
|
return { exitCode: 0 }
|
|
@@ -303,11 +340,17 @@ async function checkWorkingTree(siteDir, args) {
|
|
|
303
340
|
dirty = dirty.filter((f) => !isPullOutput(siteDir, f, written))
|
|
304
341
|
if (!dirty.length) return null
|
|
305
342
|
|
|
306
|
-
error(
|
|
343
|
+
error(
|
|
344
|
+
`Refusing to pull: ${dirty.length} uncommitted change(s) under the files pull rewrites.`
|
|
345
|
+
)
|
|
307
346
|
for (const f of dirty.slice(0, 12)) note(` ${f}`)
|
|
308
347
|
if (dirty.length > 12) note(` … and ${dirty.length - 12} more`)
|
|
309
|
-
note(
|
|
310
|
-
|
|
348
|
+
note(
|
|
349
|
+
'Pull reconciles the working tree to the backend, so these would be overwritten or deleted.'
|
|
350
|
+
)
|
|
351
|
+
note(
|
|
352
|
+
'Commit or stash them first — then pull, and your work is still in git either way.'
|
|
353
|
+
)
|
|
311
354
|
note('To discard them and take the backend version, re-run with --force.')
|
|
312
355
|
return { exitCode: 1 }
|
|
313
356
|
}
|
|
@@ -327,7 +370,7 @@ function readWritten(siteDir) {
|
|
|
327
370
|
const o = JSON.parse(readFileSync(writtenCachePath(siteDir), 'utf8'))
|
|
328
371
|
return {
|
|
329
372
|
files: o && typeof o.files === 'object' ? o.files : {},
|
|
330
|
-
deleted: Array.isArray(o?.deleted) ? o.deleted : []
|
|
373
|
+
deleted: Array.isArray(o?.deleted) ? o.deleted : []
|
|
331
374
|
}
|
|
332
375
|
} catch {
|
|
333
376
|
return { files: {}, deleted: [] }
|
|
@@ -344,24 +387,40 @@ function recordWritten(siteDir, absPaths, deletedAbs = []) {
|
|
|
344
387
|
const files = prior.files
|
|
345
388
|
for (const abs of absPaths) {
|
|
346
389
|
try {
|
|
347
|
-
files[relative(siteDir, abs)] = createHash('sha256')
|
|
348
|
-
|
|
390
|
+
files[relative(siteDir, abs)] = createHash('sha256')
|
|
391
|
+
.update(readFileSync(abs))
|
|
392
|
+
.digest('hex')
|
|
393
|
+
} catch {
|
|
394
|
+
/* deleted or unreadable — nothing to remember */
|
|
395
|
+
}
|
|
349
396
|
}
|
|
350
397
|
// Pull PRUNES too, and a deletion is a dirty path git reports just like an edit.
|
|
351
398
|
// Without recording them, pull's own pruning reads as the user having deleted
|
|
352
399
|
// files — the same false alarm as its writes, arriving by the other door.
|
|
353
|
-
const deleted = [
|
|
400
|
+
const deleted = [
|
|
401
|
+
...new Set([
|
|
402
|
+
...prior.deleted,
|
|
403
|
+
...deletedAbs.map((a) => relative(siteDir, a))
|
|
404
|
+
])
|
|
405
|
+
]
|
|
354
406
|
try {
|
|
355
407
|
mkdirSync(dirname(writtenCachePath(siteDir)), { recursive: true })
|
|
356
|
-
writeFileSync(
|
|
357
|
-
|
|
408
|
+
writeFileSync(
|
|
409
|
+
writtenCachePath(siteDir),
|
|
410
|
+
JSON.stringify({ version: 1, files, deleted }, null, 2) + '\n'
|
|
411
|
+
)
|
|
412
|
+
} catch {
|
|
413
|
+
/* best-effort: losing it only costs a spurious refusal */
|
|
414
|
+
}
|
|
358
415
|
}
|
|
359
416
|
// Is this dirty path just pull's own untouched output?
|
|
360
417
|
function isPullOutput(siteDir, relPath, written) {
|
|
361
418
|
let exists = true
|
|
362
419
|
let hash = null
|
|
363
420
|
try {
|
|
364
|
-
hash = createHash('sha256')
|
|
421
|
+
hash = createHash('sha256')
|
|
422
|
+
.update(readFileSync(join(siteDir, relPath)))
|
|
423
|
+
.digest('hex')
|
|
365
424
|
} catch {
|
|
366
425
|
exists = false
|
|
367
426
|
}
|
|
@@ -398,7 +457,10 @@ function captureLocalWork(siteDir) {
|
|
|
398
457
|
for (const rel of dirty) {
|
|
399
458
|
if (isPullOutput(siteDir, rel, written)) continue // pull's own output, not work
|
|
400
459
|
try {
|
|
401
|
-
out.set(rel, {
|
|
460
|
+
out.set(rel, {
|
|
461
|
+
content: readFileSync(join(siteDir, rel)),
|
|
462
|
+
inHead: showAtHead(siteDir, rel) !== null
|
|
463
|
+
})
|
|
402
464
|
} catch {
|
|
403
465
|
// Locally deleted. Pull will restore the backend's copy, which is the
|
|
404
466
|
// sensible reading of "I removed this and then asked for their version".
|
|
@@ -415,7 +477,11 @@ function mergeLocalWork(siteDir, captured) {
|
|
|
415
477
|
for (const [rel, { content: mine, inHead }] of captured) {
|
|
416
478
|
const abs = join(siteDir, rel)
|
|
417
479
|
let theirs = null
|
|
418
|
-
try {
|
|
480
|
+
try {
|
|
481
|
+
theirs = readFileSync(abs)
|
|
482
|
+
} catch {
|
|
483
|
+
/* pruned by the pull */
|
|
484
|
+
}
|
|
419
485
|
|
|
420
486
|
if (theirs === null) {
|
|
421
487
|
// The backend no longer has it, but we changed it. Restoring is the
|
|
@@ -478,7 +544,8 @@ export async function pull(args = [], deps = {}) {
|
|
|
478
544
|
const dryRun = args.includes('--dry-run')
|
|
479
545
|
const tokenFlag = flagValue(args, '--token')
|
|
480
546
|
const prune = !(args.includes('--no-delete') || args.includes('--no-prune')) // git-like by default
|
|
481
|
-
const noCollections =
|
|
547
|
+
const noCollections =
|
|
548
|
+
args.includes('--no-collections') || args.includes('--content-only')
|
|
482
549
|
const force = args.includes('--force')
|
|
483
550
|
const mergeMode = args.includes('--merge')
|
|
484
551
|
|
|
@@ -501,8 +568,12 @@ export async function pull(args = [], deps = {}) {
|
|
|
501
568
|
captured = captureLocalWork(siteDir)
|
|
502
569
|
if (captured === null) {
|
|
503
570
|
error('Cannot merge: this site is not in a git repository.')
|
|
504
|
-
note(
|
|
505
|
-
|
|
571
|
+
note(
|
|
572
|
+
'The common ancestor a merge needs is the committed version of each file.'
|
|
573
|
+
)
|
|
574
|
+
note(
|
|
575
|
+
'Without a repo there is nothing to merge against — use `uniweb pull --force` to take the backend version.'
|
|
576
|
+
)
|
|
506
577
|
return { exitCode: 1 }
|
|
507
578
|
}
|
|
508
579
|
} else if (!dryRun && !force) {
|
|
@@ -517,19 +588,23 @@ export async function pull(args = [], deps = {}) {
|
|
|
517
588
|
getToken: deps.getToken,
|
|
518
589
|
fetchImpl: deps.fetch,
|
|
519
590
|
args,
|
|
520
|
-
command: 'Pulling'
|
|
591
|
+
command: 'Pulling'
|
|
521
592
|
})
|
|
522
593
|
// One identity per site: `site.yml::$uuid`. Both lanes (content + folder) are keyed
|
|
523
594
|
// by it — the backend resolves the site's `@uniweb/folder` from this uuid.
|
|
524
595
|
const siteContentUuid = readYamlUuid(join(siteDir, 'site.yml'))
|
|
525
596
|
|
|
526
597
|
if (!siteContentUuid) {
|
|
527
|
-
info(
|
|
598
|
+
info(
|
|
599
|
+
'Nothing to pull — this project has no $uuid yet. Run `uniweb push` first.'
|
|
600
|
+
)
|
|
528
601
|
return { exitCode: 0 }
|
|
529
602
|
}
|
|
530
603
|
|
|
531
604
|
if (dryRun) {
|
|
532
|
-
info(
|
|
605
|
+
info(
|
|
606
|
+
`Dry run — would pull content from ${colors.dim}${client.origin}${colors.reset}`
|
|
607
|
+
)
|
|
533
608
|
if (!noCollections) info(`Dry run — would also pull collections`)
|
|
534
609
|
return { exitCode: 0 }
|
|
535
610
|
}
|
|
@@ -540,7 +615,9 @@ export async function pull(args = [], deps = {}) {
|
|
|
540
615
|
// body). `doRequest` is a thunk returning the client's Response promise. 404 / any
|
|
541
616
|
// failure → null (the lane is skipped, not fatal).
|
|
542
617
|
const getDocs = async (label, doRequest) => {
|
|
543
|
-
info(
|
|
618
|
+
info(
|
|
619
|
+
`Pulling ${colors.bright}${label}${colors.reset} from ${colors.dim}${client.origin}${colors.reset} …`
|
|
620
|
+
)
|
|
544
621
|
let res
|
|
545
622
|
try {
|
|
546
623
|
res = await doRequest()
|
|
@@ -559,7 +636,10 @@ export async function pull(args = [], deps = {}) {
|
|
|
559
636
|
}
|
|
560
637
|
if (!res.ok) {
|
|
561
638
|
error(`${label} pull failed: HTTP ${res.status} ${res.statusText}`)
|
|
562
|
-
if (res.status === 401 || res.status === 403)
|
|
639
|
+
if (res.status === 401 || res.status === 403)
|
|
640
|
+
note(
|
|
641
|
+
"Credentials weren't accepted — supply a bearer with --token <bearer>."
|
|
642
|
+
)
|
|
563
643
|
return null
|
|
564
644
|
}
|
|
565
645
|
try {
|
|
@@ -595,9 +675,15 @@ export async function pull(args = [], deps = {}) {
|
|
|
595
675
|
|
|
596
676
|
// Lane 1 — content → config + pages/** + layout/**. The .uwx carries a single
|
|
597
677
|
// entity (the site-content document). A 304 (unchanged) leaves local files as-is.
|
|
598
|
-
const content = await getDocs('content', () =>
|
|
678
|
+
const content = await getDocs('content', () =>
|
|
679
|
+
client.pullSiteContent(siteContentUuid, { etag: etagContent })
|
|
680
|
+
)
|
|
599
681
|
if (content && !content.notModified) {
|
|
600
|
-
const siteDoc =
|
|
682
|
+
const siteDoc =
|
|
683
|
+
content.docs &&
|
|
684
|
+
(content.docs.find((d) => d?.info || d?.$model) ||
|
|
685
|
+
content.docs[0] ||
|
|
686
|
+
null)
|
|
601
687
|
if (siteDoc) {
|
|
602
688
|
// Re-base the page attribution. The pulled document IS the backend's own
|
|
603
689
|
// representation, so it becomes the remote base directly. We deliberately
|
|
@@ -610,9 +696,16 @@ export async function pull(args = [], deps = {}) {
|
|
|
610
696
|
// Per-item identity for the next push. Without it the backend reads our
|
|
611
697
|
// records as new and re-mints every page and section row.
|
|
612
698
|
writeItemUuids(siteDir, collectUnitUuids(siteDoc))
|
|
613
|
-
const report = siteContentDocumentToProject({
|
|
699
|
+
const report = siteContentDocumentToProject({
|
|
700
|
+
document: siteDoc,
|
|
701
|
+
siteRoot: siteDir,
|
|
702
|
+
prune
|
|
703
|
+
})
|
|
614
704
|
wrote.push(...report.pages, ...report.sections, ...report.layout)
|
|
615
|
-
removed.push(
|
|
705
|
+
removed.push(
|
|
706
|
+
...(report.deleted || []),
|
|
707
|
+
...(report.renamed || []).map((r) => r.from)
|
|
708
|
+
)
|
|
616
709
|
pages += report.pages.length
|
|
617
710
|
sections += report.sections.length
|
|
618
711
|
deleted += report.deleted.length
|
|
@@ -625,27 +718,37 @@ export async function pull(args = [], deps = {}) {
|
|
|
625
718
|
// holds a folder uuid). Models are resolved by name (async) up front, so
|
|
626
719
|
// collectionsToProject keeps its synchronous contract. A 304 leaves files as-is.
|
|
627
720
|
if (!noCollections) {
|
|
628
|
-
const folder = await getDocs('collections', () =>
|
|
721
|
+
const folder = await getDocs('collections', () =>
|
|
722
|
+
client.pullFolder(siteContentUuid, { etag: etagFolder })
|
|
723
|
+
)
|
|
629
724
|
if (folder && !folder.notModified && folder.docs?.length) {
|
|
630
725
|
const { folderDoc, recordDocs } = splitCollectionsPull(folder.docs)
|
|
631
726
|
const resolveModel = makeModelResolver({ client })
|
|
632
727
|
const declByModel = new Map()
|
|
633
|
-
for (const model of [
|
|
728
|
+
for (const model of [
|
|
729
|
+
...new Set(recordDocs.map((d) => d.$model).filter(Boolean))
|
|
730
|
+
]) {
|
|
634
731
|
try {
|
|
635
732
|
declByModel.set(model, await resolveModel(model))
|
|
636
733
|
} catch (err) {
|
|
637
734
|
note(`! could not resolve model ${model}: ${err.message}`)
|
|
638
735
|
}
|
|
639
736
|
}
|
|
640
|
-
const collectionsConfig = await resolveCollectionsConfig(siteDir).catch(
|
|
737
|
+
const collectionsConfig = await resolveCollectionsConfig(siteDir).catch(
|
|
738
|
+
() => null
|
|
739
|
+
)
|
|
641
740
|
const report = collectionsToProject({
|
|
642
741
|
folderDoc,
|
|
643
742
|
recordDocs,
|
|
644
743
|
siteRoot: siteDir,
|
|
645
|
-
opts: {
|
|
744
|
+
opts: {
|
|
745
|
+
resolveDeclaration: (name) => declByModel.get(name) || null,
|
|
746
|
+
collectionsConfig
|
|
747
|
+
}
|
|
646
748
|
})
|
|
647
749
|
records += report.placed.length + report.updated.length
|
|
648
|
-
for (const s of report.skipped)
|
|
750
|
+
for (const s of report.skipped)
|
|
751
|
+
note(`↷ ${s.slug ?? s.uuid ?? '(record)'}: ${s.reason}`)
|
|
649
752
|
for (const w of report.warnings) note(`! ${w}`)
|
|
650
753
|
}
|
|
651
754
|
if (folder?.etag) etagFolder = folder.etag
|
|
@@ -674,12 +777,18 @@ export async function pull(args = [], deps = {}) {
|
|
|
674
777
|
|
|
675
778
|
recordWritten(
|
|
676
779
|
siteDir,
|
|
677
|
-
[
|
|
780
|
+
[
|
|
781
|
+
...wrote,
|
|
782
|
+
...['site.yml', 'theme.yml', 'head.html', 'collections.yml'].map((f) =>
|
|
783
|
+
join(siteDir, f)
|
|
784
|
+
)
|
|
785
|
+
],
|
|
678
786
|
removed
|
|
679
787
|
)
|
|
680
788
|
|
|
681
789
|
success(
|
|
682
|
-
`Pulled — ${pages} page(s), ${sections} section(s), ${records} record(s)` +
|
|
790
|
+
`Pulled — ${pages} page(s), ${sections} section(s), ${records} record(s)` +
|
|
791
|
+
(deleted ? `, ${deleted} deleted` : '')
|
|
683
792
|
)
|
|
684
793
|
// Unresolved conflicts are a non-zero exit, the way a conflicted `git merge` is.
|
|
685
794
|
// The pull itself worked; there is work left for a human. This is what makes a
|