uniweb 0.62.0 → 0.63.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/partials/agents.md +4 -3
- package/src/backend/client.js +231 -20
- package/src/backend/foundation-bring-along.js +12 -13
- package/src/backend/site-sync.js +235 -126
- package/src/commands/clone.js +35 -5
- package/src/commands/publish.js +33 -20
- package/src/commands/pull.js +55 -10
- package/src/commands/push.js +23 -17
- package/src/commands/register.js +136 -26
- package/src/commands/status.js +26 -5
- package/src/framework-index.json +5 -5
- package/src/index.js +14 -8
- package/src/utils/args.js +6 -14
- package/src/utils/flag-guard.js +17 -9
- package/src/utils/registry-orgs.js +3 -2
- package/src/utils/site-data-upload.js +13 -2
package/src/commands/clone.js
CHANGED
|
@@ -39,6 +39,10 @@
|
|
|
39
39
|
* uniweb clone <uuid> --path sites Place under sites/ (segregated layout)
|
|
40
40
|
* uniweb clone <uuid> --project docs Co-located docs/site
|
|
41
41
|
* uniweb clone <uuid> --no-records Pull pages only; skip records
|
|
42
|
+
* uniweb clone <uuid> --org @org Read it as @org — stops if the backend works on
|
|
43
|
+
* the site from another workspace. Without it,
|
|
44
|
+
* the one the backend names is recorded in
|
|
45
|
+
* sync.json for every command after.
|
|
42
46
|
*
|
|
43
47
|
* Backend: via BackendClient (the site-content pull lane). Origin from
|
|
44
48
|
* UNIWEB_REGISTER_URL > the local default (internal dev overrides;
|
|
@@ -55,7 +59,13 @@ import { resolvePlacement, SITE_KIND } from '../utils/placement.js'
|
|
|
55
59
|
import { findWorkspaceRoot } from '../utils/workspace.js'
|
|
56
60
|
import { addWorkspaceGlob } from '../utils/config.js'
|
|
57
61
|
import { detectWorkspacePm, installCmd } from '../utils/pm.js'
|
|
58
|
-
import {
|
|
62
|
+
import {
|
|
63
|
+
BackendClient,
|
|
64
|
+
describeRequestError,
|
|
65
|
+
refusalDetail,
|
|
66
|
+
workspaceHandle
|
|
67
|
+
} from '../backend/client.js'
|
|
68
|
+
import { readOrgFlag } from '../utils/args.js'
|
|
59
69
|
import { isNonInteractive, getCliPrefix } from '../utils/interactive.js'
|
|
60
70
|
import { extractFoundationRef } from '../utils/site-content-refs.js'
|
|
61
71
|
import { readUwxDocuments } from '../utils/uwx-read.js'
|
|
@@ -143,7 +153,7 @@ export function extractCloneSeeds(document) {
|
|
|
143
153
|
// `site.yml`'s equivalent needed a hand-rolled dependency-free WRITER for exactly
|
|
144
154
|
// this. The store stays the one writer everywhere else; this seeds a fresh file.
|
|
145
155
|
// `test/clone.test.js` covers it end to end with `skipInstall: true`.
|
|
146
|
-
function seedSyncJson(siteDir, origin, uuid) {
|
|
156
|
+
function seedSyncJson(siteDir, origin, uuid, workspace = null) {
|
|
147
157
|
const file = join(siteDir, 'sync.json')
|
|
148
158
|
let doc = { version: 1, backends: {} }
|
|
149
159
|
if (existsSync(file)) {
|
|
@@ -155,7 +165,20 @@ function seedSyncJson(siteDir, origin, uuid) {
|
|
|
155
165
|
}
|
|
156
166
|
}
|
|
157
167
|
const key = new URL(origin).origin
|
|
158
|
-
|
|
168
|
+
// The workspace the read named — `--org`, or the one the backend answered with — so
|
|
169
|
+
// the delegated pull, and every command after it, names it from the first request.
|
|
170
|
+
// As the create records it: a handle bare in `org`, a handle-less unit's uuid in
|
|
171
|
+
// `unit` (site-sync.js::readSiteWorkspace).
|
|
172
|
+
const w = typeof workspace === 'string' && workspace ? workspace : null
|
|
173
|
+
const named = w
|
|
174
|
+
? w.startsWith('@')
|
|
175
|
+
? { org: w.slice(1) }
|
|
176
|
+
: { unit: w }
|
|
177
|
+
: {}
|
|
178
|
+
doc.backends[key] = {
|
|
179
|
+
...(doc.backends[key] || {}),
|
|
180
|
+
site: { ...(doc.backends[key]?.site || {}), uuid, ...named }
|
|
181
|
+
}
|
|
159
182
|
mkdirSync(dirname(file), { recursive: true })
|
|
160
183
|
writeFileSync(file, JSON.stringify(doc, null, 2) + '\n')
|
|
161
184
|
}
|
|
@@ -206,6 +229,11 @@ export async function clone(args = [], deps = {}) {
|
|
|
206
229
|
args,
|
|
207
230
|
command: 'Cloning'
|
|
208
231
|
})
|
|
232
|
+
// The workspace the reads name: `--org` explicitly — a mismatch then stops the clone —
|
|
233
|
+
// else none, and the client adopts the one the backend works on the site from. There
|
|
234
|
+
// is no project yet to record it in; `seedSyncJson` does, below.
|
|
235
|
+
const orgFlag = readOrgFlag(args)
|
|
236
|
+
client.setWorkspace(orgFlag ? workspaceHandle(orgFlag) : null, { explicit: Boolean(orgFlag) })
|
|
209
237
|
|
|
210
238
|
// 1. GET the site-content document.
|
|
211
239
|
//
|
|
@@ -228,13 +256,15 @@ export async function clone(args = [], deps = {}) {
|
|
|
228
256
|
}
|
|
229
257
|
if (!res.ok) {
|
|
230
258
|
error(`Could not read the site: HTTP ${res.status} ${res.statusText}`)
|
|
259
|
+
const detail = await refusalDetail(res)
|
|
260
|
+
if (detail) note(detail)
|
|
231
261
|
if (res.status === 401 || res.status === 403)
|
|
232
262
|
note('Run `uniweb login` first.')
|
|
233
263
|
return { exitCode: 1 }
|
|
234
264
|
}
|
|
235
265
|
documents = readUwxDocuments(Buffer.from(await res.arrayBuffer()))
|
|
236
266
|
} catch (err) {
|
|
237
|
-
error(
|
|
267
|
+
error(describeRequestError(err, client.origin))
|
|
238
268
|
return { exitCode: 1 }
|
|
239
269
|
}
|
|
240
270
|
|
|
@@ -355,7 +385,7 @@ export async function clone(args = [], deps = {}) {
|
|
|
355
385
|
// the old `$backend`: the uuid came from whoever we read and nothing on disk said
|
|
356
386
|
// so, and a teammate who pulled while logged in elsewhere sent it to the wrong
|
|
357
387
|
// backend. Stored under the origin, that cannot be expressed.
|
|
358
|
-
seedSyncJson(siteDir, client.origin, siteUuid)
|
|
388
|
+
seedSyncJson(siteDir, client.origin, siteUuid, client.workspace)
|
|
359
389
|
success(
|
|
360
390
|
`Scaffolded the site harness${foundationRef ? ` (foundation: ${foundationRef})` : ''}.`
|
|
361
391
|
)
|
package/src/commands/publish.js
CHANGED
|
@@ -29,15 +29,15 @@
|
|
|
29
29
|
* uniweb publish --dry-run Resolve everything; POST nothing
|
|
30
30
|
* uniweb publish --yes Skip confirmations (CI); never block on a prompt
|
|
31
31
|
* uniweb publish --force Overwrite upstream app-side edits (drop the push gate)
|
|
32
|
-
* uniweb publish --org @org Publish under @org
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
32
|
+
* uniweb publish --org @org Publish under @org. On the FIRST publish of a
|
|
33
|
+
* site it decides which org owns the site and whose
|
|
34
|
+
* storage its assets are charged to; recorded in
|
|
35
|
+
* `sync.json`, and every request after names it, so
|
|
36
|
+
* it never has to be re-typed. Later, it must match
|
|
37
|
+
* the workspace the backend works on the site from,
|
|
38
|
+
* or publish stops.
|
|
39
|
+
* uniweb publish --personal Own the new site personally, deliberately: its
|
|
40
|
+
* requests name no workspace.
|
|
41
41
|
* uniweb publish --no-save Do not record this publish in deploy.yml
|
|
42
42
|
*/
|
|
43
43
|
|
|
@@ -65,7 +65,11 @@ import {
|
|
|
65
65
|
import { isSiteRelativeExtensionUrl } from '@uniweb/build'
|
|
66
66
|
import { resolveDefaultLocale } from '@uniweb/core/locale-config'
|
|
67
67
|
|
|
68
|
-
import {
|
|
68
|
+
import {
|
|
69
|
+
BackendClient,
|
|
70
|
+
describeRequestError,
|
|
71
|
+
WorkspaceMismatchError
|
|
72
|
+
} from '../backend/client.js'
|
|
69
73
|
import { DEFAULT_BACKEND_ORIGIN } from '../utils/config.js'
|
|
70
74
|
import { resolveSiteDir } from './deploy.js'
|
|
71
75
|
import { warnIfContentDoesNotConform } from '../utils/conformance.js'
|
|
@@ -90,7 +94,9 @@ import {
|
|
|
90
94
|
clearRemoteSyncStateIfUnbound,
|
|
91
95
|
dropSiteBoundValues,
|
|
92
96
|
pushSyncPackages,
|
|
93
|
-
resolveSiteOrgForCreate
|
|
97
|
+
resolveSiteOrgForCreate,
|
|
98
|
+
nameSiteWorkspace,
|
|
99
|
+
EXPLICIT_OWNER
|
|
94
100
|
} from '../backend/site-sync.js'
|
|
95
101
|
import { uploadSiteMedia, describeAssetRefusal } from '../backend/site-media.js'
|
|
96
102
|
import {
|
|
@@ -320,7 +326,6 @@ export async function publish(args = []) {
|
|
|
320
326
|
say.dim(org.reason)
|
|
321
327
|
return { exitCode: 2 }
|
|
322
328
|
}
|
|
323
|
-
const asOrg = org.asOrg
|
|
324
329
|
|
|
325
330
|
// ⛔ There is no capability gate here any more, deliberately.
|
|
326
331
|
//
|
|
@@ -508,6 +513,17 @@ export async function publish(args = []) {
|
|
|
508
513
|
// be the silent-wrong-success this whole branch exists to prevent.
|
|
509
514
|
if (!fnd.proceed) return { exitCode: fnd.refused ? 1 : 0 }
|
|
510
515
|
|
|
516
|
+
// ⭐ Name the workspace every site request works in — the create, the push, the
|
|
517
|
+
// publish and the data lane. The user's own choice is explicit, so a `409
|
|
518
|
+
// wrong_workspace` stops the publish; a recorded one is adopted afresh when stale
|
|
519
|
+
// (`nameSiteWorkspace`).
|
|
520
|
+
nameSiteWorkspace(client, {
|
|
521
|
+
siteDir,
|
|
522
|
+
workspace: org.workspace,
|
|
523
|
+
explicit: EXPLICIT_OWNER.has(org.source),
|
|
524
|
+
note: (m) => say.dim(m)
|
|
525
|
+
})
|
|
526
|
+
|
|
511
527
|
// 2. Build the site data (link mode): dist/site-content.json (+ per-locale),
|
|
512
528
|
// dist/data/*, dist/assets/*. Spawn the SAME CLI binary so the inner
|
|
513
529
|
// build can't resolve to a different installed version.
|
|
@@ -562,8 +578,6 @@ export async function publish(args = []) {
|
|
|
562
578
|
backend: client.origin,
|
|
563
579
|
// Placement identity for the folder — see writeFolderItemUuids.
|
|
564
580
|
folderItemUuids: readFolderItemUuids(siteDir, client.origin),
|
|
565
|
-
// Resolves a foundation-relative `@/x` model ref into `@org/x`.
|
|
566
|
-
...(asOrg ? { org: asOrg } : {}),
|
|
567
581
|
...(foundationDir ? { foundationDir } : {}),
|
|
568
582
|
resolveModel
|
|
569
583
|
})
|
|
@@ -612,7 +626,6 @@ export async function publish(args = []) {
|
|
|
612
626
|
siteDir,
|
|
613
627
|
name: siteYml.name,
|
|
614
628
|
foundation: fnd.ref || siteYml.foundation,
|
|
615
|
-
asOrg,
|
|
616
629
|
note: (m) => say.dim(m)
|
|
617
630
|
})
|
|
618
631
|
if (!site.uuid) {
|
|
@@ -727,6 +740,8 @@ export async function publish(args = []) {
|
|
|
727
740
|
apiBase: client.origin,
|
|
728
741
|
token: await client.token(),
|
|
729
742
|
siteUuid: site.uuid,
|
|
743
|
+
// The plan and every direct PUT name the workspace the rest of the publish did.
|
|
744
|
+
workspace: client.workspace,
|
|
730
745
|
ball,
|
|
731
746
|
onProgress: (m) => say.dim(` ${m}`)
|
|
732
747
|
})
|
|
@@ -949,8 +964,6 @@ export async function publish(args = []) {
|
|
|
949
964
|
...(declaration.declare ? {} : { declareServices: false }),
|
|
950
965
|
// Placement identity for the folder — see writeFolderItemUuids.
|
|
951
966
|
folderItemUuids: readFolderItemUuids(siteDir, client.origin),
|
|
952
|
-
// Resolves a foundation-relative `@/x` model ref into `@org/x`.
|
|
953
|
-
...(asOrg ? { org: asOrg } : {}),
|
|
954
967
|
...(foundationDir ? { foundationDir } : {}),
|
|
955
968
|
resolveModel,
|
|
956
969
|
priorHashes,
|
|
@@ -978,7 +991,6 @@ export async function publish(args = []) {
|
|
|
978
991
|
client,
|
|
979
992
|
siteDir,
|
|
980
993
|
pkg,
|
|
981
|
-
asOrg,
|
|
982
994
|
report
|
|
983
995
|
})
|
|
984
996
|
if (pushResult.exitCode !== 0) return { exitCode: pushResult.exitCode }
|
|
@@ -998,8 +1010,9 @@ export async function publish(args = []) {
|
|
|
998
1010
|
...(languages ? { languages } : {})
|
|
999
1011
|
})
|
|
1000
1012
|
} catch (err) {
|
|
1001
|
-
say.err(
|
|
1002
|
-
|
|
1013
|
+
say.err(describeRequestError(err, client.origin))
|
|
1014
|
+
if (!(err instanceof WorkspaceMismatchError))
|
|
1015
|
+
say.dim('Is that the backend you meant? Switch with: uniweb login --backend <url>')
|
|
1003
1016
|
return { exitCode: 1 }
|
|
1004
1017
|
}
|
|
1005
1018
|
if (!pubRes.ok) {
|
package/src/commands/pull.js
CHANGED
|
@@ -72,6 +72,7 @@ import {
|
|
|
72
72
|
readBackendState,
|
|
73
73
|
siteContentDocumentToProject,
|
|
74
74
|
recordsToProject,
|
|
75
|
+
siteSelfScope,
|
|
75
76
|
readZip,
|
|
76
77
|
computeUnitHashes,
|
|
77
78
|
collectUnitUuids,
|
|
@@ -89,8 +90,11 @@ import {
|
|
|
89
90
|
mergeBaseVersions,
|
|
90
91
|
mergeItemBaseVersions,
|
|
91
92
|
writeUnitBases,
|
|
92
|
-
writeItemUuids
|
|
93
|
+
writeItemUuids,
|
|
94
|
+
nameSiteWorkspace,
|
|
95
|
+
readSiteWorkspace
|
|
93
96
|
} from '../backend/site-sync.js'
|
|
97
|
+
import { readOrgFlag } from '../utils/args.js'
|
|
94
98
|
import {
|
|
95
99
|
uncommittedUnder,
|
|
96
100
|
siteContentRoots,
|
|
@@ -98,7 +102,13 @@ import {
|
|
|
98
102
|
mergeFile
|
|
99
103
|
} from '../utils/git.js'
|
|
100
104
|
import { isNonInteractive } from '../utils/interactive.js'
|
|
101
|
-
import {
|
|
105
|
+
import {
|
|
106
|
+
BackendClient,
|
|
107
|
+
describeRequestError,
|
|
108
|
+
refusalDetail,
|
|
109
|
+
workspaceHandle,
|
|
110
|
+
WorkspaceMismatchError
|
|
111
|
+
} from '../backend/client.js'
|
|
102
112
|
import { resolveSiteDir as defaultResolveSiteDir } from './deploy.js'
|
|
103
113
|
import { checkFlags } from '../utils/flag-guard.js'
|
|
104
114
|
import {
|
|
@@ -591,6 +601,15 @@ export async function pull(args = [], deps = {}) {
|
|
|
591
601
|
args,
|
|
592
602
|
command: 'Pulling'
|
|
593
603
|
})
|
|
604
|
+
// The workspace these requests name: `--org` explicitly, else the one this project
|
|
605
|
+
// recorded for this backend — adopted afresh from the backend's answer when stale.
|
|
606
|
+
const orgFlag = readOrgFlag(args)
|
|
607
|
+
nameSiteWorkspace(client, {
|
|
608
|
+
siteDir,
|
|
609
|
+
workspace: orgFlag ? workspaceHandle(orgFlag) : readSiteWorkspace(siteDir, client.origin),
|
|
610
|
+
explicit: Boolean(orgFlag),
|
|
611
|
+
note
|
|
612
|
+
})
|
|
594
613
|
|
|
595
614
|
// ⭐ No scope check any more — and none is needed. This project's identity on
|
|
596
615
|
// `client.origin` is read from that origin's own section of sync.json, so a
|
|
@@ -649,7 +668,8 @@ export async function pull(args = [], deps = {}) {
|
|
|
649
668
|
// body — `readPullDocuments` reads the entity files out of it (JSON fallback). A
|
|
650
669
|
// conditional request whose ETag matches returns `{ notModified: true }` (304, empty
|
|
651
670
|
// body). `doRequest` is a thunk returning the client's Response promise. 404 / any
|
|
652
|
-
// failure → null (the lane is skipped, not fatal)
|
|
671
|
+
// failure → null (the lane is skipped, not fatal) — except a workspace mismatch,
|
|
672
|
+
// `{ refused: true }`, which stops the pull: the other lane would only say it again.
|
|
653
673
|
const getDocs = async (label, doRequest) => {
|
|
654
674
|
info(
|
|
655
675
|
`Pulling ${colors.bright}${label}${colors.reset} from ${colors.dim}${client.origin}${colors.reset} …`
|
|
@@ -658,7 +678,8 @@ export async function pull(args = [], deps = {}) {
|
|
|
658
678
|
try {
|
|
659
679
|
res = await doRequest()
|
|
660
680
|
} catch (err) {
|
|
661
|
-
error(
|
|
681
|
+
error(describeRequestError(err, client.origin))
|
|
682
|
+
if (err instanceof WorkspaceMismatchError) return { refused: true }
|
|
662
683
|
note('Is that the backend you meant? Switch with: uniweb login --backend <url>')
|
|
663
684
|
return null
|
|
664
685
|
}
|
|
@@ -672,6 +693,8 @@ export async function pull(args = [], deps = {}) {
|
|
|
672
693
|
}
|
|
673
694
|
if (!res.ok) {
|
|
674
695
|
error(`${label} pull failed: HTTP ${res.status} ${res.statusText}`)
|
|
696
|
+
const detail = await refusalDetail(res)
|
|
697
|
+
if (detail) note(detail)
|
|
675
698
|
if (res.status === 401 || res.status === 403)
|
|
676
699
|
note(
|
|
677
700
|
"Credentials weren't accepted — log in again (`uniweb login --backend <url>`), or check UNIWEB_TOKEN."
|
|
@@ -709,11 +732,28 @@ export async function pull(args = [], deps = {}) {
|
|
|
709
732
|
let etagContent = cache.content
|
|
710
733
|
let etagFolder = cache.folder
|
|
711
734
|
|
|
735
|
+
// ⭐ THE SCOPE THE PULLED RECORDS AND QUERIES WERE QUALIFIED WITH — the pushed site's
|
|
736
|
+
// FOUNDATION's: the scope of the pinned ref it carries (`info.foundation`), else the
|
|
737
|
+
// local foundation's name. A push qualifies `@/x` with it, so this is what undoes it,
|
|
738
|
+
// and a record lands back in `records/article/` rather than `records/acme/article/`.
|
|
739
|
+
// Resolved once, up front — it may load main.js — and handed to the synchronous
|
|
740
|
+
// projections, as Models are. ⛔ It was the site owner's org, from sync.json, until
|
|
741
|
+
// 2026-09-22; a foundation may be registered under any org its author belongs to.
|
|
742
|
+
let pullScope
|
|
743
|
+
const scopeFor = async (doc) => {
|
|
744
|
+
if (pullScope === undefined) {
|
|
745
|
+
const pinned = doc?.info?.foundation
|
|
746
|
+
pullScope = await siteSelfScope(siteDir, pinned !== undefined ? { foundation: pinned } : {})
|
|
747
|
+
}
|
|
748
|
+
return pullScope
|
|
749
|
+
}
|
|
750
|
+
|
|
712
751
|
// Lane 1 — content → config + pages/** + layout/**. The .uwx carries a single
|
|
713
752
|
// entity (the site-content document). A 304 (unchanged) leaves local files as-is.
|
|
714
753
|
const content = await getDocs('content', () =>
|
|
715
754
|
client.pullSiteContent(siteContentUuid, { etag: etagContent })
|
|
716
755
|
)
|
|
756
|
+
if (content?.refused) return { exitCode: 1 }
|
|
717
757
|
if (content && !content.notModified) {
|
|
718
758
|
const siteDoc =
|
|
719
759
|
content.docs &&
|
|
@@ -806,7 +846,9 @@ export async function pull(args = [], deps = {}) {
|
|
|
806
846
|
// it a pull leaves every image pointing at a backend route.
|
|
807
847
|
backend: client.origin,
|
|
808
848
|
prune,
|
|
809
|
-
keepAuthoredFoundation
|
|
849
|
+
keepAuthoredFoundation,
|
|
850
|
+
// So a query's `@acme/member` comes back as the author's `@/member`.
|
|
851
|
+
scope: await scopeFor(siteDoc)
|
|
810
852
|
})
|
|
811
853
|
wrote.push(...report.pages, ...report.sections, ...report.layout)
|
|
812
854
|
removed.push(
|
|
@@ -828,6 +870,7 @@ export async function pull(args = [], deps = {}) {
|
|
|
828
870
|
const folder = await getDocs('records', () =>
|
|
829
871
|
client.pullFolder(siteContentUuid, { etag: etagFolder })
|
|
830
872
|
)
|
|
873
|
+
if (folder?.refused) return { exitCode: 1 }
|
|
831
874
|
if (folder && !folder.notModified && folder.docs?.length) {
|
|
832
875
|
const { folderDoc, recordDocs } = splitRecordsPull(folder.docs)
|
|
833
876
|
const resolveModel = makeModelResolver({ client })
|
|
@@ -843,17 +886,19 @@ export async function pull(args = [], deps = {}) {
|
|
|
843
886
|
}
|
|
844
887
|
// ⛔ NO QUERY CONFIG. A record's home is decided by what it IS — its
|
|
845
888
|
// `$model` names the pool folder — not by any query that happens to select
|
|
846
|
-
// it.
|
|
847
|
-
//
|
|
848
|
-
// back where the author wrote it.
|
|
889
|
+
// it. Handed the foundation's scope (`scopeFor`), `recordsToProject` places a
|
|
890
|
+
// `@/x` model the producer resolved to `@acme/x` back where the author wrote it.
|
|
849
891
|
const report = recordsToProject({
|
|
850
892
|
folderDoc,
|
|
851
893
|
recordDocs,
|
|
852
894
|
siteRoot: siteDir,
|
|
853
895
|
opts: {
|
|
854
|
-
// Whose record map to read and extend
|
|
896
|
+
// Whose record map to read and extend.
|
|
855
897
|
backend: client.origin,
|
|
856
|
-
resolveDeclaration: (name) => declByModel.get(name) || null
|
|
898
|
+
resolveDeclaration: (name) => declByModel.get(name) || null,
|
|
899
|
+
// The scope a model's `@/x` was qualified with, so `@acme/article` is placed
|
|
900
|
+
// back in `records/article/` — see `scopeFor`.
|
|
901
|
+
scope: await scopeFor(null)
|
|
857
902
|
}
|
|
858
903
|
})
|
|
859
904
|
// The folder's organization, `folder.yml` in the records directory — named as
|
package/src/commands/push.js
CHANGED
|
@@ -27,15 +27,15 @@
|
|
|
27
27
|
*
|
|
28
28
|
* Usage:
|
|
29
29
|
* uniweb push Build, push both lanes, back-fill $uuid
|
|
30
|
-
* uniweb push --org @org Own the new site under @org
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
30
|
+
* uniweb push --org @org Own the new site under @org. On the FIRST
|
|
31
|
+
* push it decides which org owns the site, and
|
|
32
|
+
* whose storage its assets are charged to;
|
|
33
|
+
* recorded in `sync.json`, and every request
|
|
34
|
+
* after names it. Without it, you are asked
|
|
35
|
+
* once. Later, it must match the workspace the
|
|
36
|
+
* backend works on the site from, or push stops.
|
|
37
|
+
* uniweb push --personal Own the new site personally, deliberately:
|
|
38
|
+
* its requests name no workspace.
|
|
39
39
|
* uniweb push --dry-run Report what would be pushed; submit nothing
|
|
40
40
|
* uniweb push -o out.uwx Write the .uwx file(s) per lane; submit nothing
|
|
41
41
|
* uniweb push --foundation <dir> Use this local foundation for the Model schema
|
|
@@ -92,7 +92,9 @@ import {
|
|
|
92
92
|
clearRemoteSyncStateIfUnbound,
|
|
93
93
|
dropSiteBoundValues,
|
|
94
94
|
pushSyncPackages,
|
|
95
|
-
resolveSiteOrgForCreate
|
|
95
|
+
resolveSiteOrgForCreate,
|
|
96
|
+
nameSiteWorkspace,
|
|
97
|
+
EXPLICIT_OWNER
|
|
96
98
|
} from '../backend/site-sync.js'
|
|
97
99
|
|
|
98
100
|
// Re-exported for downstream importers (pull.js, push.test.js) that read these
|
|
@@ -231,7 +233,6 @@ export async function push(args = [], deps = {}) {
|
|
|
231
233
|
note(org.reason)
|
|
232
234
|
return { exitCode: 2 }
|
|
233
235
|
}
|
|
234
|
-
const asOrg = org.asOrg
|
|
235
236
|
|
|
236
237
|
// Bring the foundation along — BEFORE any asset upload, because an upload is
|
|
237
238
|
// chargeable and a push that aborts after one has spent the user's money for
|
|
@@ -288,6 +289,17 @@ export async function push(args = [], deps = {}) {
|
|
|
288
289
|
}
|
|
289
290
|
if (!fnd.proceed) return { exitCode: 1 }
|
|
290
291
|
|
|
292
|
+
// ⭐ Name the workspace every site request works in. The user's own choice (`--org`,
|
|
293
|
+
// `--personal`, the picker) is explicit, so a `409 wrong_workspace` stops the push; a
|
|
294
|
+
// recorded one is adopted afresh from the backend's answer when stale
|
|
295
|
+
// (`nameSiteWorkspace`).
|
|
296
|
+
nameSiteWorkspace(client, {
|
|
297
|
+
siteDir,
|
|
298
|
+
workspace: org.workspace,
|
|
299
|
+
explicit: EXPLICIT_OWNER.has(org.source),
|
|
300
|
+
note
|
|
301
|
+
})
|
|
302
|
+
|
|
291
303
|
// Build BOTH directional packages (the producer side). Each carries its own
|
|
292
304
|
// `index` — the per-entity source-file map for back-fill, correlated by submission
|
|
293
305
|
// position. Non-local Models are fetched from the registry on demand. `priorHashes`
|
|
@@ -406,8 +418,6 @@ export async function push(args = [], deps = {}) {
|
|
|
406
418
|
backend: client.origin,
|
|
407
419
|
// Placement identity for the folder — see writeFolderItemUuids.
|
|
408
420
|
folderItemUuids: readFolderItemUuids(siteDir, client.origin),
|
|
409
|
-
// Resolves a foundation-relative `@/x` model ref into `@org/x`.
|
|
410
|
-
...(asOrg ? { org: asOrg } : {}),
|
|
411
421
|
...(foundationDir ? { foundationDir } : {}),
|
|
412
422
|
resolveModel: makeModelResolver({ client, offline: false })
|
|
413
423
|
})
|
|
@@ -433,7 +443,6 @@ export async function push(args = [], deps = {}) {
|
|
|
433
443
|
const site = await ensureSiteExists({
|
|
434
444
|
client,
|
|
435
445
|
siteDir,
|
|
436
|
-
asOrg,
|
|
437
446
|
note,
|
|
438
447
|
...(fnd.ref ? { foundation: fnd.ref } : {})
|
|
439
448
|
})
|
|
@@ -508,8 +517,6 @@ export async function push(args = [], deps = {}) {
|
|
|
508
517
|
// Identity for the `queries` section — see readQueryUuids. Keyed by
|
|
509
518
|
// name, because a declaration has no file for a path-keyed map to hold.
|
|
510
519
|
queryUuids: readQueryUuids(siteDir, client.origin),
|
|
511
|
-
// Resolves a foundation-relative `@/x` model ref into `@org/x`.
|
|
512
|
-
...(asOrg ? { org: asOrg } : {}),
|
|
513
520
|
...(foundationDir ? { foundationDir } : {}),
|
|
514
521
|
resolveModel: makeModelResolver({
|
|
515
522
|
client,
|
|
@@ -602,7 +609,6 @@ export async function push(args = [], deps = {}) {
|
|
|
602
609
|
client,
|
|
603
610
|
siteDir,
|
|
604
611
|
pkg,
|
|
605
|
-
asOrg,
|
|
606
612
|
report: {
|
|
607
613
|
info,
|
|
608
614
|
note,
|