uniweb 0.12.6 → 0.12.8
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 +6 -1
- package/src/commands/build.js +35 -25
- package/src/commands/deploy.js +254 -353
- package/src/commands/export.js +85 -0
- package/src/commands/publish.js +288 -116
- package/src/framework-index.json +8 -8
- package/src/index.js +28 -21
- package/src/utils/env.js +22 -0
- package/src/utils/registry.js +4 -5
- package/src/utils/receipt.js +0 -91
package/src/index.js
CHANGED
|
@@ -440,20 +440,9 @@ async function main() {
|
|
|
440
440
|
return
|
|
441
441
|
}
|
|
442
442
|
|
|
443
|
-
// Global install launcher: delegate project-bound commands to local CLI
|
|
444
|
-
//
|
|
445
|
-
// Escape hatch: `UNIWEB_DISABLE_LOCAL_DELEGATION=1` forces the in-process
|
|
446
|
-
// CLI to handle the command itself, even when a project-local copy of
|
|
447
|
-
// `uniweb` is installed. This exists for the workspace-ergonomics eval
|
|
448
|
-
// harness — when it points the eval at `node $WORKSPACE_ROOT/.../index.js`
|
|
449
|
-
// it expects to exercise the workspace source, not whatever `uniweb`
|
|
450
|
-
// version is symlinked under the test fixture's `node_modules`. Without
|
|
451
|
-
// the escape, evals silently test the published npm version and any
|
|
452
|
-
// unpublished workspace fixes are invisible. See
|
|
453
|
-
// `kb/framework/build/workspace-ergonomics-runbook.md` (`--cli=workspace`).
|
|
443
|
+
// Global install launcher: delegate project-bound commands to local CLI
|
|
454
444
|
const global = isGlobalInstall()
|
|
455
|
-
|
|
456
|
-
if (global && !skipDelegation && command && !STANDALONE_COMMANDS.has(command)) {
|
|
445
|
+
if (global && command && !STANDALONE_COMMANDS.has(command)) {
|
|
457
446
|
const localCli = findLocalCli()
|
|
458
447
|
if (localCli) {
|
|
459
448
|
await delegateToLocal(localCli)
|
|
@@ -550,6 +539,13 @@ async function main() {
|
|
|
550
539
|
return
|
|
551
540
|
}
|
|
552
541
|
|
|
542
|
+
// Handle export command (dynamic import — depends on @uniweb/build)
|
|
543
|
+
if (command === 'export') {
|
|
544
|
+
const { exportSite } = await importProjectCommand('./commands/export.js')
|
|
545
|
+
await exportSite(args.slice(1))
|
|
546
|
+
return
|
|
547
|
+
}
|
|
548
|
+
|
|
553
549
|
// Handle login command
|
|
554
550
|
if (command === 'login') {
|
|
555
551
|
await login(args.slice(1))
|
|
@@ -836,7 +832,8 @@ ${colors.bright}Commands:${colors.reset}
|
|
|
836
832
|
rename <type> Rename a workspace package (foundation today)
|
|
837
833
|
build Build the current project
|
|
838
834
|
deploy Deploy a site to Uniweb hosting
|
|
839
|
-
|
|
835
|
+
export Export a self-contained site for third-party hosting
|
|
836
|
+
publish Publish a foundation to the Uniweb catalog (deliberate; for site-bound foundations, use deploy)
|
|
840
837
|
invite <email> Create a foundation invite for a client
|
|
841
838
|
handoff <email> Hand off a site to a client
|
|
842
839
|
inspect <path> Inspect parsed content shape of a markdown file or folder
|
|
@@ -866,10 +863,20 @@ ${colors.bright}Global Options:${colors.reset}
|
|
|
866
863
|
Auto-detected when CI=true or no TTY (pipes, agents)
|
|
867
864
|
|
|
868
865
|
${colors.bright}Publish Options:${colors.reset}
|
|
866
|
+
--catalog Confirm publish to the public catalog (required in CI)
|
|
867
|
+
--propagate Walk trusting sites' policy waves (default: silent)
|
|
868
|
+
--name <id> Foundation id (overrides package.json::uniweb.id)
|
|
869
|
+
--namespace <ns> Force org-scope namespace (overrides package.json)
|
|
869
870
|
--local Publish to the local registry (.unicloud/) instead of Uniweb Registry
|
|
871
|
+
--registry <url> Use a specific registry URL
|
|
870
872
|
--edit-access <p> Set edit access policy: "open" or "restricted" (default: restricted)
|
|
871
873
|
--dry-run Show what would be published without uploading
|
|
872
874
|
|
|
875
|
+
uniweb publish is for cataloging a foundation as a product. For
|
|
876
|
+
site-bound foundations (one foundation, one site), use uniweb deploy
|
|
877
|
+
instead — it auto-publishes under a site-scoped slot, no naming
|
|
878
|
+
ceremony.
|
|
879
|
+
|
|
873
880
|
${colors.bright}Invite Options:${colors.reset}
|
|
874
881
|
--uses <n> Max sites per invite (default: 1)
|
|
875
882
|
--expires <days> Days until expiry (default: 30)
|
|
@@ -889,16 +896,16 @@ ${colors.bright}Template Options:${colors.reset}
|
|
|
889
896
|
--registry <url> Registry URL (default: http://localhost:4001)
|
|
890
897
|
|
|
891
898
|
${colors.bright}Deploy Options:${colors.reset}
|
|
892
|
-
--dry-run Resolve site.yml + foundation/runtime
|
|
893
|
-
--
|
|
894
|
-
|
|
899
|
+
--dry-run Resolve site.yml + foundation/runtime; print summary; no writes
|
|
900
|
+
--no-auto-publish Don't auto-publish workspace-local foundation as part of deploy
|
|
901
|
+
|
|
902
|
+
${colors.bright}Export Options:${colors.reset}
|
|
903
|
+
--no-prerender Skip per-page prerendered HTML
|
|
895
904
|
|
|
896
905
|
${colors.bright}Build Options:${colors.reset}
|
|
897
906
|
--target <type> Build target (foundation, site) - auto-detected if not specified
|
|
898
|
-
--
|
|
899
|
-
--
|
|
900
|
-
--prerender Force pre-rendering (bundle mode only; overrides site.yml)
|
|
901
|
-
--no-prerender Skip pre-rendering (bundle mode only; overrides site.yml)
|
|
907
|
+
--prerender Force pre-rendering (overrides site.yml)
|
|
908
|
+
--no-prerender Skip pre-rendering (overrides site.yml)
|
|
902
909
|
--foundation-dir Path to foundation directory (for prerendering)
|
|
903
910
|
--platform <name> Deployment platform (e.g., vercel) for platform-specific output
|
|
904
911
|
|
package/src/utils/env.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal env-var helpers.
|
|
3
|
+
*
|
|
4
|
+
* UNIWEB_* env vars are escape hatches for developers, operators, and the
|
|
5
|
+
* platform test team — not user-facing settings. Documented in
|
|
6
|
+
* `framework/cli/docs/env-vars.md`. Anything truly user-facing should be a
|
|
7
|
+
* flag, not an env var.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Parse a boolean-shaped env var. Returns true for "1", "true", "yes" (any
|
|
12
|
+
* case); false otherwise (including unset, empty, or any other string).
|
|
13
|
+
*
|
|
14
|
+
* @param {string} name - Env var name (e.g., "UNIWEB_SKIP_BUILD").
|
|
15
|
+
* @returns {boolean}
|
|
16
|
+
*/
|
|
17
|
+
export function parseBoolEnv(name) {
|
|
18
|
+
const raw = process.env[name]
|
|
19
|
+
if (!raw) return false
|
|
20
|
+
const v = String(raw).trim().toLowerCase()
|
|
21
|
+
return v === '1' || v === 'true' || v === 'yes'
|
|
22
|
+
}
|
package/src/utils/registry.js
CHANGED
|
@@ -144,9 +144,8 @@ export class LocalRegistry {
|
|
|
144
144
|
|
|
145
145
|
/**
|
|
146
146
|
* Get the full version entry for `name@version`, or null if absent.
|
|
147
|
-
* Used by `publish`
|
|
148
|
-
*
|
|
149
|
-
* `kb/framework/build/workspace-ergonomics.md` (receipt-as-cache).
|
|
147
|
+
* Used by `publish` (pre-flight duplicate check) and `deploy`
|
|
148
|
+
* (staleness check via git provenance comparison).
|
|
150
149
|
*
|
|
151
150
|
* @param {string} name
|
|
152
151
|
* @param {string} version
|
|
@@ -270,8 +269,8 @@ export class RemoteRegistry {
|
|
|
270
269
|
|
|
271
270
|
/**
|
|
272
271
|
* Get the full version entry for `name@version`, or null if absent.
|
|
273
|
-
* Used by `publish`
|
|
274
|
-
*
|
|
272
|
+
* Used by `publish` (pre-flight duplicate check) and `deploy`
|
|
273
|
+
* (staleness check via git provenance comparison).
|
|
275
274
|
*
|
|
276
275
|
* @param {string} name
|
|
277
276
|
* @param {string} version
|
package/src/utils/receipt.js
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `dist/publish.json` receipt — shared shape used by `publish` and `deploy`.
|
|
3
|
-
*
|
|
4
|
-
* The receipt is a per-checkout cache of the last publish; it lets the
|
|
5
|
-
* deploy verb decide whether a workspace-local foundation needs republishing
|
|
6
|
-
* without a network round-trip on the happy path. It's gitignored, so it
|
|
7
|
-
* never travels with the source — fresh clones, CI runs, and teammates all
|
|
8
|
-
* start with no cache. Both verbs refill the cache lazily by reading the
|
|
9
|
-
* registry's index when the local file is missing.
|
|
10
|
-
*
|
|
11
|
-
* See `kb/framework/build/workspace-ergonomics.md` for the full rationale.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Compose the canonical six-field receipt body. All callers MUST go through
|
|
16
|
-
* this helper so the runbook's pp-10 schema check stays meaningful.
|
|
17
|
-
*
|
|
18
|
-
* @param {Object} params
|
|
19
|
-
* @param {string|null} params.gitSha
|
|
20
|
-
* @param {boolean} params.gitDirty
|
|
21
|
-
* @param {string} params.url
|
|
22
|
-
* @param {string} params.publishedAt
|
|
23
|
-
* @param {string} params.classification 'propagate' | 'silent'
|
|
24
|
-
* @returns {Object}
|
|
25
|
-
*/
|
|
26
|
-
export function composeReceipt({ gitSha, gitDirty, url, publishedAt, classification }) {
|
|
27
|
-
return {
|
|
28
|
-
schemaVersion: 1,
|
|
29
|
-
publishedFromGitSha: gitSha,
|
|
30
|
-
publishedFromGitDirty: gitDirty,
|
|
31
|
-
url,
|
|
32
|
-
publishedAt,
|
|
33
|
-
classification,
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Resolve the canonical receipt URL given (in priority order):
|
|
39
|
-
* 1. A `publishResult.url` from a fresh upload — server-rendered, handles
|
|
40
|
-
* empty-scope rewrites the CLI can't synthesize.
|
|
41
|
-
* 2. An `existingEntry.url` recorded by a previous publish (refill path).
|
|
42
|
-
* 3. A synthesized canonical form — `file://` for local registries,
|
|
43
|
-
* `<apiUrl>/foundations/<name>@<version>/foundation.js` for remote.
|
|
44
|
-
* The remote form mirrors the path the worker returns in `publishResult.url`,
|
|
45
|
-
* which keeps the receipt's URL parseable by the regex in
|
|
46
|
-
* `deploy.js::deriveLocalFoundationRef` even when the registry's index
|
|
47
|
-
* entry doesn't carry an explicit `url` field. (Unicloud's index entries
|
|
48
|
-
* don't; uniweb-edge's index entries don't either — both rely on the
|
|
49
|
-
* response shape, not the index shape.)
|
|
50
|
-
*
|
|
51
|
-
* Path-shaped candidates (e.g. `/foundations/...`) are joined with the
|
|
52
|
-
* registry's `apiUrl` so the receipt always carries an absolute URL.
|
|
53
|
-
*/
|
|
54
|
-
export function deriveReceiptUrl({ publishResult, existingEntry, registry, name, version, isLocal }) {
|
|
55
|
-
const candidate = publishResult?.url || existingEntry?.url
|
|
56
|
-
if (candidate) {
|
|
57
|
-
if (candidate.startsWith('http') || candidate.startsWith('file://')) return candidate
|
|
58
|
-
if (registry?.apiUrl) return new URL(candidate, registry.apiUrl).toString()
|
|
59
|
-
}
|
|
60
|
-
if (isLocal) return `file://${registry.getPackagePath(name, version)}/`
|
|
61
|
-
return `${registry.apiUrl.replace(/\/$/, '')}/foundations/${name}@${version}/foundation.js`
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Build a receipt from an existing registry version entry, used to refill a
|
|
66
|
-
* missing `dist/publish.json` from server-of-record state. Returns null if
|
|
67
|
-
* the entry doesn't carry enough provenance to make the receipt useful for
|
|
68
|
-
* staleness checks (the only field that strictly must be present is
|
|
69
|
-
* `publishedFromGitSha` — without it, the deploy verb can't compare against
|
|
70
|
-
* HEAD, and refilling would just re-trigger the auto-publish next run).
|
|
71
|
-
*/
|
|
72
|
-
export function receiptFromRegistryEntry({ existingEntry, registry, name, version, isLocal, isPropagateDefault }) {
|
|
73
|
-
if (!existingEntry || !existingEntry.publishedFromGitSha) return null
|
|
74
|
-
return composeReceipt({
|
|
75
|
-
gitSha: existingEntry.publishedFromGitSha,
|
|
76
|
-
gitDirty: existingEntry.publishedFromGitDirty ?? false,
|
|
77
|
-
url: deriveReceiptUrl({ existingEntry, registry, name, version, isLocal }),
|
|
78
|
-
publishedAt: existingEntry.publishedAt || new Date().toISOString(),
|
|
79
|
-
classification: existingEntry.classification || (isPropagateDefault ? 'propagate' : 'silent'),
|
|
80
|
-
})
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Split `@ns/name@ver`, `~user/name@ver`, or `name@ver` into name + version.
|
|
85
|
-
* Returns null on any shape we don't recognize.
|
|
86
|
-
*/
|
|
87
|
-
export function splitRegistryRef(ref) {
|
|
88
|
-
if (typeof ref !== 'string') return null
|
|
89
|
-
const m = /^(@[^/]+\/[^@]+|~[^/]+\/[^@]+|[^@]+)@(.+)$/.exec(ref)
|
|
90
|
-
return m ? { name: m[1], version: m[2] } : null
|
|
91
|
-
}
|