uniweb 0.12.7 → 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 +4 -4
- 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 +3 -3
- package/src/index.js +26 -8
- 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
|
@@ -539,6 +539,13 @@ async function main() {
|
|
|
539
539
|
return
|
|
540
540
|
}
|
|
541
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
|
+
|
|
542
549
|
// Handle login command
|
|
543
550
|
if (command === 'login') {
|
|
544
551
|
await login(args.slice(1))
|
|
@@ -825,7 +832,8 @@ ${colors.bright}Commands:${colors.reset}
|
|
|
825
832
|
rename <type> Rename a workspace package (foundation today)
|
|
826
833
|
build Build the current project
|
|
827
834
|
deploy Deploy a site to Uniweb hosting
|
|
828
|
-
|
|
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)
|
|
829
837
|
invite <email> Create a foundation invite for a client
|
|
830
838
|
handoff <email> Hand off a site to a client
|
|
831
839
|
inspect <path> Inspect parsed content shape of a markdown file or folder
|
|
@@ -855,10 +863,20 @@ ${colors.bright}Global Options:${colors.reset}
|
|
|
855
863
|
Auto-detected when CI=true or no TTY (pipes, agents)
|
|
856
864
|
|
|
857
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)
|
|
858
870
|
--local Publish to the local registry (.unicloud/) instead of Uniweb Registry
|
|
871
|
+
--registry <url> Use a specific registry URL
|
|
859
872
|
--edit-access <p> Set edit access policy: "open" or "restricted" (default: restricted)
|
|
860
873
|
--dry-run Show what would be published without uploading
|
|
861
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
|
+
|
|
862
880
|
${colors.bright}Invite Options:${colors.reset}
|
|
863
881
|
--uses <n> Max sites per invite (default: 1)
|
|
864
882
|
--expires <days> Days until expiry (default: 30)
|
|
@@ -878,16 +896,16 @@ ${colors.bright}Template Options:${colors.reset}
|
|
|
878
896
|
--registry <url> Registry URL (default: http://localhost:4001)
|
|
879
897
|
|
|
880
898
|
${colors.bright}Deploy Options:${colors.reset}
|
|
881
|
-
--dry-run Resolve site.yml + foundation/runtime
|
|
882
|
-
--
|
|
883
|
-
|
|
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
|
|
884
904
|
|
|
885
905
|
${colors.bright}Build Options:${colors.reset}
|
|
886
906
|
--target <type> Build target (foundation, site) - auto-detected if not specified
|
|
887
|
-
--
|
|
888
|
-
--
|
|
889
|
-
--prerender Force pre-rendering (bundle mode only; overrides site.yml)
|
|
890
|
-
--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)
|
|
891
909
|
--foundation-dir Path to foundation directory (for prerendering)
|
|
892
910
|
--platform <name> Deployment platform (e.g., vercel) for platform-specific output
|
|
893
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
|
-
}
|