uniweb 1.16.0 → 1.16.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 +4 -4
- package/src/backend/workspace.js +48 -5
- package/src/commands/register.js +15 -9
- package/src/index.js +3 -1
- package/src/utils/registry-auth.js +21 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uniweb",
|
|
3
|
-
"version": "1.16.
|
|
3
|
+
"version": "1.16.1",
|
|
4
4
|
"description": "Create structured Vite + React sites with content/code separation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,15 +41,15 @@
|
|
|
41
41
|
"js-yaml": "^4.1.0",
|
|
42
42
|
"prompts": "^2.4.2",
|
|
43
43
|
"tar": "^7.0.0",
|
|
44
|
-
"@uniweb/core": "~1.4.0",
|
|
45
44
|
"@uniweb/schemas": "~1.6.5",
|
|
45
|
+
"@uniweb/core": "~1.4.0",
|
|
46
46
|
"@uniweb/semantic-parser": "~1.12.2",
|
|
47
47
|
"@uniweb/content-writer": "~1.3.0"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"@uniweb/build": "~1.7.4",
|
|
51
50
|
"@uniweb/semantic-parser": "~1.12.2",
|
|
52
|
-
"@uniweb/content-reader": "~1.5.4"
|
|
51
|
+
"@uniweb/content-reader": "~1.5.4",
|
|
52
|
+
"@uniweb/build": "~1.7.4"
|
|
53
53
|
},
|
|
54
54
|
"peerDependenciesMeta": {
|
|
55
55
|
"@uniweb/build": {
|
package/src/backend/workspace.js
CHANGED
|
@@ -91,6 +91,33 @@ export function listedWorkspaces(envelope) {
|
|
|
91
91
|
.filter((w) => w.name)
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
/**
|
|
95
|
+
* The top-level organization of a unit named by its child handle — `@studio` for `@studio:acme`,
|
|
96
|
+
* the part before the colon (backend, 2026-10-22).
|
|
97
|
+
*
|
|
98
|
+
* @param {string} handle - `@top:part`
|
|
99
|
+
* @returns {string} `@top`
|
|
100
|
+
*/
|
|
101
|
+
export function topOfChildHandle(handle) {
|
|
102
|
+
return `@${String(handle).replace(/^@/, '').split(':')[0]}`
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The account's own handle, as the header would name it (`@jane`), from the orgs read — or null.
|
|
107
|
+
*
|
|
108
|
+
* ⛔ **An account's handle names no workspace** (backend, 2026-10-22, pinned by its tests). A
|
|
109
|
+
* handle names a person or an org, and only an org is a workspace: the header answers
|
|
110
|
+
* `@<account handle>` with `400`, never as the personal workspace, which a request names by
|
|
111
|
+
* naming none. So `--org @<your handle>` is refused, saying what names your own.
|
|
112
|
+
*/
|
|
113
|
+
export function ownHandle(envelope) {
|
|
114
|
+
return envelope?.account_handle ? workspaceHandle(envelope.account_handle) : null
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** What naming your own handle as a workspace is told — `--personal`, or `UNIWEB_WORKSPACE=personal`. */
|
|
118
|
+
export const ownHandleNamesNone = (named, personal) =>
|
|
119
|
+
`${named} is your own handle, which names no workspace — your personal workspace is ${personal}.`
|
|
120
|
+
|
|
94
121
|
/**
|
|
95
122
|
* What a backend whose orgs read lists no workspaces is told. ⛔ The CLI does not check
|
|
96
123
|
* against `orgs` in its place: that list leaves out every unit below an org.
|
|
@@ -185,22 +212,36 @@ export async function resolveWorkspace({ client, args = [], offline = false }) {
|
|
|
185
212
|
* decides which SITE a command works on; here it only answers the question a bare name
|
|
186
213
|
* asks the first time it registers.
|
|
187
214
|
*
|
|
215
|
+
* ⭐ **A unit below an organization — a sub-org, a project — takes its organization's scope**
|
|
216
|
+
* *[Diego, 2026-10-22]*: its child handle (`@studio:acme`) names no scope (backend, the same day),
|
|
217
|
+
* and a bare name registered from it defaults to the top-level org's handle, `@studio`, when that
|
|
218
|
+
* org is among the account's organizations — the orgs read's `orgs`, the scopes. Not otherwise: a
|
|
219
|
+
* project's guest belongs to the project alone and has only their own scope, and so do the people
|
|
220
|
+
* of a sub-org whose belonging stops there — which the orgs read cannot tell apart, so membership is
|
|
221
|
+
* read from `orgs` and nothing else.
|
|
222
|
+
*
|
|
188
223
|
* Null when the workspace names no scope, and the caller derives one as before
|
|
189
224
|
* (`deriveScope`): none is chosen (you belong to organizations and none is named), the
|
|
190
225
|
* command is a preview, the workspace is a unit without a handle, the workspace is a unit
|
|
191
|
-
* below an organization
|
|
192
|
-
*
|
|
226
|
+
* below an organization the account is not a member of, or the account has no handle (a
|
|
227
|
+
* service account).
|
|
193
228
|
*
|
|
194
229
|
* @param {{ workspace?: string|null, source?: string, refused?: boolean }} ws -
|
|
195
230
|
* `resolveWorkspace`'s answer
|
|
196
231
|
* @param {string|null} accountHandle - the account's own handle (`GET /dev/orgs`), read
|
|
197
232
|
* only for the personal workspace
|
|
233
|
+
* @param {Array<{ handle?: string }>} [orgs] - the orgs read's `orgs`, read only for a unit
|
|
234
|
+
* below an organization
|
|
198
235
|
* @returns {string|null} `@handle`
|
|
199
236
|
*/
|
|
200
|
-
export function scopeOfWorkspace(ws, accountHandle) {
|
|
237
|
+
export function scopeOfWorkspace(ws, accountHandle, orgs = []) {
|
|
201
238
|
if (!ws || ws.refused || ws.source === 'offline') return null
|
|
202
239
|
if (typeof ws.workspace === 'string') {
|
|
203
|
-
|
|
240
|
+
if (!ws.workspace.startsWith('@')) return null
|
|
241
|
+
if (!ws.workspace.includes(':')) return ws.workspace
|
|
242
|
+
const top = topOfChildHandle(ws.workspace)
|
|
243
|
+
const member = (Array.isArray(orgs) ? orgs : []).some((o) => `@${bareHandle(o?.handle)}` === top)
|
|
244
|
+
return member ? top : null
|
|
204
245
|
}
|
|
205
246
|
const h = bareHandle(accountHandle || '')
|
|
206
247
|
return h && !validateHandle(h) ? `@${h}` : null
|
|
@@ -228,12 +269,14 @@ export async function chooseWorkspace({ apiBase, token, args = [] }) {
|
|
|
228
269
|
const flag = readOrgFlag(args)
|
|
229
270
|
if (!flag && args.includes('--personal')) return { choice: PERSONAL }
|
|
230
271
|
|
|
231
|
-
const
|
|
272
|
+
const envelope = await fetchOrgs({ apiBase, token })
|
|
273
|
+
const listed = listedWorkspaces(envelope)
|
|
232
274
|
if (!listed) return { refused: true, reason: noWorkspaceList(apiBase) }
|
|
233
275
|
const mine = listed.map((w) => w.name)
|
|
234
276
|
if (flag) {
|
|
235
277
|
const named = namedWorkspace(flag)
|
|
236
278
|
if (!named) return { refused: true, reason: `--org ${flag} names no workspace — give @org, or --personal.` }
|
|
279
|
+
if (named === ownHandle(envelope)) return { refused: true, reason: ownHandleNamesNone(named, '--personal') }
|
|
237
280
|
if (!mine.includes(named)) {
|
|
238
281
|
return {
|
|
239
282
|
refused: true,
|
package/src/commands/register.js
CHANGED
|
@@ -90,7 +90,7 @@ import {
|
|
|
90
90
|
} from '../utils/code-upload.js'
|
|
91
91
|
import { deriveScope, publishScope, belongsToScope } from '../utils/registry-orgs.js'
|
|
92
92
|
import { BackendClient } from '../backend/client.js'
|
|
93
|
-
import { resolveWorkspace, scopeOfWorkspace, SOURCE_LABEL } from '../backend/workspace.js'
|
|
93
|
+
import { resolveWorkspace, scopeOfWorkspace, topOfChildHandle, SOURCE_LABEL } from '../backend/workspace.js'
|
|
94
94
|
import { writeJsonPreservingStyleAsync } from '../utils/json-file.js'
|
|
95
95
|
import {
|
|
96
96
|
findWorkspaceRoot,
|
|
@@ -395,23 +395,29 @@ export async function settleFoundationScope(targetDir, { args, isPreview, flagSc
|
|
|
395
395
|
async function deriveScopeFromLogin(client, args) {
|
|
396
396
|
const token = await client.token()
|
|
397
397
|
const ws = await resolveWorkspace({ client, args })
|
|
398
|
-
const
|
|
399
|
-
const
|
|
400
|
-
|
|
398
|
+
const live = !ws.refused && ws.source !== 'offline'
|
|
399
|
+
const personal = live && ws.workspace === null
|
|
400
|
+
// A unit below an organization — a sub-org, a project — names no scope; its organization's is
|
|
401
|
+
// the default for the organization's members (`scopeOfWorkspace`), read from the same answer.
|
|
402
|
+
const child = live && typeof ws.workspace === 'string' && ws.workspace.includes(':')
|
|
403
|
+
const envelope = personal || child ? await client.fetchOrgs() : null
|
|
404
|
+
const fromWorkspace = scopeOfWorkspace(ws, envelope?.account_handle ?? null, envelope?.orgs)
|
|
401
405
|
if (fromWorkspace) {
|
|
402
406
|
const named = `${colors.bright}${fromWorkspace}${colors.reset}`
|
|
403
407
|
console.error(
|
|
404
408
|
personal
|
|
405
409
|
? `Registering under your personal scope ${named} — you work in your personal workspace (${SOURCE_LABEL[ws.source]}). For an org: --scope @org.`
|
|
406
|
-
:
|
|
410
|
+
: child
|
|
411
|
+
? `Registering under ${named} — the organization of ${ws.workspace}, the workspace you work in (${SOURCE_LABEL[ws.source]}). For another scope: --scope @org.`
|
|
412
|
+
: `Registering under ${named} — the workspace you work in (${SOURCE_LABEL[ws.source]}). For another scope: --scope @org.`
|
|
407
413
|
)
|
|
408
414
|
return fromWorkspace
|
|
409
415
|
}
|
|
410
|
-
//
|
|
411
|
-
//
|
|
412
|
-
if (
|
|
416
|
+
// Not a member of the unit's organization — a project's guest — so it gives no scope, and a bare
|
|
417
|
+
// name is asked for one as when no workspace names any.
|
|
418
|
+
if (child) {
|
|
413
419
|
console.error(
|
|
414
|
-
`${colors.dim}${ws.workspace}, the workspace you work in,
|
|
420
|
+
`${colors.dim}${ws.workspace}, the workspace you work in, names no scope, and ${topOfChildHandle(ws.workspace)} is not among your organizations.${colors.reset}`
|
|
415
421
|
)
|
|
416
422
|
}
|
|
417
423
|
const sess = await readRegistryAuth(client.origin)
|
package/src/index.js
CHANGED
|
@@ -1759,7 +1759,9 @@ ${colors.bright}Options:${colors.reset}
|
|
|
1759
1759
|
org's — and write it into the name (refused when the name has another
|
|
1760
1760
|
scope). A schemas-only package: publish under @scope; default:
|
|
1761
1761
|
package.json uniweb.scope
|
|
1762
|
-
--org @org Work in @org for this command: a bare name registers under it
|
|
1762
|
+
--org @org Work in @org for this command: a bare name registers under it —
|
|
1763
|
+
a sub-org's or project's (@studio:acme): under its organization's,
|
|
1764
|
+
when you are a member of it
|
|
1763
1765
|
--personal Work in your personal workspace: a bare name registers under your
|
|
1764
1766
|
personal scope
|
|
1765
1767
|
--dry-run Print the .uwx; submit nothing
|
|
@@ -952,8 +952,16 @@ async function reportTokenProcess({ named, token }) {
|
|
|
952
952
|
// Their workspace is UNIWEB_WORKSPACE's — never the stored session's — and, with none
|
|
953
953
|
// named, personal for an account in no organization or project (resolveWorkspace,
|
|
954
954
|
// workspace.js). Checked, as a login's `--org` is, against the workspaces the orgs read lists.
|
|
955
|
-
const {
|
|
956
|
-
|
|
955
|
+
const {
|
|
956
|
+
WORKSPACE_ENV,
|
|
957
|
+
PERSONAL,
|
|
958
|
+
parseWorkspaceChoice,
|
|
959
|
+
headerOf,
|
|
960
|
+
describeWorkspace,
|
|
961
|
+
listedWorkspaces,
|
|
962
|
+
noWorkspaceList,
|
|
963
|
+
ownHandle
|
|
964
|
+
} = await import('../backend/workspace.js')
|
|
957
965
|
const unsettled = (reason, { commandsRefuse }) => {
|
|
958
966
|
console.error(`\x1b[31m✗\x1b[0m ${reason}`)
|
|
959
967
|
if (commandsRefuse) {
|
|
@@ -968,18 +976,27 @@ async function reportTokenProcess({ named, token }) {
|
|
|
968
976
|
commandsRefuse: true
|
|
969
977
|
})
|
|
970
978
|
}
|
|
971
|
-
let
|
|
979
|
+
let envelope
|
|
972
980
|
try {
|
|
973
|
-
|
|
981
|
+
envelope = await (await import('./registry-orgs.js')).fetchOrgs({ apiBase: origin, token })
|
|
974
982
|
} catch (err) {
|
|
975
983
|
console.error(`\x1b[33m⚠\x1b[0m ${err.message} — the workspace is not checked.`)
|
|
976
984
|
return
|
|
977
985
|
}
|
|
986
|
+
const listed = listedWorkspaces(envelope)
|
|
978
987
|
if (!listed) {
|
|
979
988
|
console.error(`\x1b[33m⚠\x1b[0m ${noWorkspaceList(origin)}`)
|
|
980
989
|
return
|
|
981
990
|
}
|
|
982
991
|
const mine = listed.map((w) => w.name)
|
|
992
|
+
if (choice && choice === ownHandle(envelope)) {
|
|
993
|
+
// As a login refuses `--org @<own handle>` (chooseWorkspace): a handle names a person or an org,
|
|
994
|
+
// and only an org is a workspace.
|
|
995
|
+
unsettled(
|
|
996
|
+
`${WORKSPACE_ENV}=${raw} names the account's own handle, which names no workspace — its personal workspace is ${WORKSPACE_ENV}=personal.`,
|
|
997
|
+
{ commandsRefuse: false }
|
|
998
|
+
)
|
|
999
|
+
}
|
|
983
1000
|
if (choice && choice !== PERSONAL && !mine.includes(choice)) {
|
|
984
1001
|
// As a login refuses `--org` for one (chooseWorkspace).
|
|
985
1002
|
unsettled(`${WORKSPACE_ENV}=${raw} is not one of this account's workspaces on ${origin}.`, {
|