uniweb 0.12.18 → 0.12.20
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 +6 -6
- package/src/commands/build.js +139 -3
- package/src/commands/publish.js +8 -3
- package/src/framework-index.json +7 -7
- package/src/index.js +2 -2
- package/src/utils/config.js +2 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uniweb",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.20",
|
|
4
4
|
"description": "Create structured Vite + React sites with content/code separation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,13 +41,13 @@
|
|
|
41
41
|
"js-yaml": "^4.1.0",
|
|
42
42
|
"prompts": "^2.4.2",
|
|
43
43
|
"tar": "^7.0.0",
|
|
44
|
-
"@uniweb/
|
|
45
|
-
"@uniweb/runtime": "0.8.
|
|
46
|
-
"@uniweb/
|
|
44
|
+
"@uniweb/core": "0.7.11",
|
|
45
|
+
"@uniweb/runtime": "0.8.14",
|
|
46
|
+
"@uniweb/kit": "0.9.13"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"@uniweb/build": "0.14.
|
|
50
|
-
"@uniweb/content-reader": "1.1.
|
|
49
|
+
"@uniweb/build": "0.14.5",
|
|
50
|
+
"@uniweb/content-reader": "1.1.11",
|
|
51
51
|
"@uniweb/semantic-parser": "1.1.17"
|
|
52
52
|
},
|
|
53
53
|
"peerDependenciesMeta": {
|
package/src/commands/build.js
CHANGED
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
* --bundle # Full vite pipeline (third-party hosts)
|
|
46
46
|
*/
|
|
47
47
|
|
|
48
|
-
import { existsSync, readFileSync } from 'node:fs'
|
|
49
|
-
import { resolve, join, dirname } from 'node:path'
|
|
48
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'
|
|
49
|
+
import { resolve, join, dirname, basename } from 'node:path'
|
|
50
50
|
import { spawn } from 'node:child_process'
|
|
51
51
|
import { writeFile, mkdir } from 'node:fs/promises'
|
|
52
52
|
import { createRequire } from 'node:module'
|
|
@@ -243,6 +243,118 @@ async function buildFoundation(projectDir, options = {}) {
|
|
|
243
243
|
log(` ${colors.bright}uniweb handoff <email>${colors.reset} Hand off a site to a client`)
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
+
/**
|
|
247
|
+
* Ensure a local foundation's `dist/entry.js` is current.
|
|
248
|
+
*
|
|
249
|
+
* Whenever a build or deploy reads a local foundation from disk — bundle
|
|
250
|
+
* mode (vite imports it, prerender loads it for SSG), or link mode where
|
|
251
|
+
* the foundation is uploaded alongside the site — the foundation must be
|
|
252
|
+
* built and current. Otherwise the verb fails with "Foundation not found
|
|
253
|
+
* at .../dist/entry.js" or silently ships stale artifacts.
|
|
254
|
+
*
|
|
255
|
+
* `buildWorkspace()` already cascades when invoked from a workspace root,
|
|
256
|
+
* but verbs invoked from a site directory (`uniweb build` in `sites/x/`,
|
|
257
|
+
* `uniweb deploy`, `uniweb export`) used to skip the cascade and rely on
|
|
258
|
+
* the user having pre-built the foundation. That broke fresh checkouts
|
|
259
|
+
* where the foundation has never been built locally.
|
|
260
|
+
*
|
|
261
|
+
* This helper is idempotent: when the workspace-root cascade has already
|
|
262
|
+
* run, the freshness check sees a current `dist/entry.js` and returns
|
|
263
|
+
* without rebuilding. So adding the call inside `buildSite()` /
|
|
264
|
+
* `buildSiteLink()` does not double-build under `buildWorkspace()`.
|
|
265
|
+
*
|
|
266
|
+
* Freshness rule: a built artifact (`dist/entry.js` or the legacy
|
|
267
|
+
* `dist/foundation.js`) exists AND its mtime is >= the newest mtime of
|
|
268
|
+
* any tracked source file. Tracked sources: every file under
|
|
269
|
+
* `<foundation>/src/`, plus root-level `package.json`, `foundation.js`,
|
|
270
|
+
* `meta.js` (the structural files that drive entry generation and
|
|
271
|
+
* schema). Build outputs and node_modules are skipped.
|
|
272
|
+
*
|
|
273
|
+
* Both artifact names are accepted because the foundation build emitter
|
|
274
|
+
* was renamed `dist/foundation.js → dist/entry.js` in `@uniweb/build`
|
|
275
|
+
* v0.14.3. A foundation built with an older @uniweb/build still produces
|
|
276
|
+
* the legacy name; we don't want the cascade to keep rebuilding such a
|
|
277
|
+
* foundation forever just because the new name is missing.
|
|
278
|
+
*/
|
|
279
|
+
async function ensureFoundationFresh(foundationDir, label = 'foundation') {
|
|
280
|
+
const distArtifact = findFoundationDistArtifact(foundationDir)
|
|
281
|
+
|
|
282
|
+
if (!distArtifact) {
|
|
283
|
+
info(`Local ${label} not built yet — building ${basename(foundationDir)} first`)
|
|
284
|
+
log('')
|
|
285
|
+
await buildFoundation(foundationDir)
|
|
286
|
+
log('')
|
|
287
|
+
return { built: true, reason: 'missing' }
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const distMtime = statSync(distArtifact).mtimeMs
|
|
291
|
+
const stale = isFoundationSourceNewerThan(foundationDir, distMtime)
|
|
292
|
+
|
|
293
|
+
if (stale) {
|
|
294
|
+
info(`Local ${label} sources changed — rebuilding ${basename(foundationDir)}`)
|
|
295
|
+
log('')
|
|
296
|
+
await buildFoundation(foundationDir)
|
|
297
|
+
log('')
|
|
298
|
+
return { built: true, reason: 'stale' }
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return { built: false, reason: 'fresh' }
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Locate the foundation's built entry artifact. Returns the path of the
|
|
306
|
+
* first match, or null when neither file exists. Prefers the current
|
|
307
|
+
* name (`dist/entry.js`) over the legacy one (`dist/foundation.js`).
|
|
308
|
+
*/
|
|
309
|
+
function findFoundationDistArtifact(foundationDir) {
|
|
310
|
+
for (const name of ['entry.js', 'foundation.js']) {
|
|
311
|
+
const p = join(foundationDir, 'dist', name)
|
|
312
|
+
if (existsSync(p)) return p
|
|
313
|
+
}
|
|
314
|
+
return null
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Returns true if any tracked foundation source file has an mtime newer
|
|
319
|
+
* than `referenceMtime`. Walks `<foundationDir>/src/` recursively (skipping
|
|
320
|
+
* dotfiles, node_modules, and dist). Also stats the root structural files.
|
|
321
|
+
*/
|
|
322
|
+
function isFoundationSourceNewerThan(foundationDir, referenceMtime) {
|
|
323
|
+
const rootFiles = ['package.json', 'foundation.js', 'meta.js']
|
|
324
|
+
for (const f of rootFiles) {
|
|
325
|
+
const p = join(foundationDir, f)
|
|
326
|
+
if (existsSync(p) && statSync(p).mtimeMs > referenceMtime) return true
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const srcDir = join(foundationDir, 'src')
|
|
330
|
+
if (!existsSync(srcDir)) return false
|
|
331
|
+
|
|
332
|
+
const stack = [srcDir]
|
|
333
|
+
while (stack.length) {
|
|
334
|
+
const dir = stack.pop()
|
|
335
|
+
let entries
|
|
336
|
+
try {
|
|
337
|
+
entries = readdirSync(dir, { withFileTypes: true })
|
|
338
|
+
} catch {
|
|
339
|
+
continue
|
|
340
|
+
}
|
|
341
|
+
for (const e of entries) {
|
|
342
|
+
if (e.name === 'node_modules' || e.name === 'dist' || e.name.startsWith('.')) continue
|
|
343
|
+
const full = join(dir, e.name)
|
|
344
|
+
if (e.isDirectory()) {
|
|
345
|
+
stack.push(full)
|
|
346
|
+
continue
|
|
347
|
+
}
|
|
348
|
+
if (!e.isFile()) continue
|
|
349
|
+
try {
|
|
350
|
+
if (statSync(full).mtimeMs > referenceMtime) return true
|
|
351
|
+
} catch { /* ignore */ }
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return false
|
|
356
|
+
}
|
|
357
|
+
|
|
246
358
|
/**
|
|
247
359
|
* Load site i18n configuration
|
|
248
360
|
*
|
|
@@ -452,6 +564,11 @@ async function buildSiteLink(projectDir, options = {}) {
|
|
|
452
564
|
// null and theme defaults come from theme.yml only.
|
|
453
565
|
const foundationDir = await resolveFoundationDirForSite(projectDir, siteConfig).catch(() => null)
|
|
454
566
|
|
|
567
|
+
// Cascade: a local foundation in link mode is uploaded alongside the
|
|
568
|
+
// site (site-bound mode), so its dist must be current. Idempotent under
|
|
569
|
+
// buildWorkspace() — the freshness check no-ops when already built.
|
|
570
|
+
if (foundationDir) await ensureFoundationFresh(foundationDir)
|
|
571
|
+
|
|
455
572
|
await buildSiteData({
|
|
456
573
|
siteRoot: projectDir,
|
|
457
574
|
distDir,
|
|
@@ -548,6 +665,12 @@ async function buildSite(projectDir, options = {}) {
|
|
|
548
665
|
|
|
549
666
|
info('Building site...')
|
|
550
667
|
|
|
668
|
+
// Cascade: bundle mode imports the foundation through vite, and (when
|
|
669
|
+
// prerender is on) loads dist/entry.js for SSG. A local foundation must
|
|
670
|
+
// therefore be current. Idempotent under buildWorkspace() — when the
|
|
671
|
+
// workspace cascade has already built it, the freshness check no-ops.
|
|
672
|
+
if (foundationDir) await ensureFoundationFresh(foundationDir)
|
|
673
|
+
|
|
551
674
|
// Run vite build for sites
|
|
552
675
|
await runLocalVite(projectDir, ['build'])
|
|
553
676
|
|
|
@@ -924,7 +1047,20 @@ export async function build(args = []) {
|
|
|
924
1047
|
if (prerenderFlag) prerender = true
|
|
925
1048
|
if (noPrerenderFlag) prerender = false
|
|
926
1049
|
|
|
927
|
-
|
|
1050
|
+
// If `--foundation-dir` wasn't passed, resolve the local foundation
|
|
1051
|
+
// from site.yml + package.json. Required so buildSite() can cascade
|
|
1052
|
+
// to the local foundation when the user runs `uniweb build` from a
|
|
1053
|
+
// site dir on a fresh checkout where dist/ doesn't exist yet.
|
|
1054
|
+
const resolvedFoundationDir =
|
|
1055
|
+
foundationDir
|
|
1056
|
+
|| (await resolveFoundationDirForSite(projectDir, siteConfig).catch(() => null))
|
|
1057
|
+
|
|
1058
|
+
await buildSite(projectDir, {
|
|
1059
|
+
prerender,
|
|
1060
|
+
foundationDir: resolvedFoundationDir,
|
|
1061
|
+
siteConfig,
|
|
1062
|
+
host,
|
|
1063
|
+
})
|
|
928
1064
|
}
|
|
929
1065
|
} catch (err) {
|
|
930
1066
|
error(err.message)
|
package/src/commands/publish.js
CHANGED
|
@@ -225,7 +225,12 @@ export async function publish(args = []) {
|
|
|
225
225
|
// intends the NEW one. Without rebuilding we'd ship inconsistent
|
|
226
226
|
// bytes (schema says one version, registry record says another).
|
|
227
227
|
const distDir = join(foundationDir, 'dist')
|
|
228
|
+
// @uniweb/build@0.14.0+ emits dist/entry.js (Phase 5 of CDN migration);
|
|
229
|
+
// older builds emitted dist/foundation.js. Accept either so a single CLI
|
|
230
|
+
// works against both old and new foundations during the rollout window.
|
|
231
|
+
const entryJs = join(distDir, 'entry.js')
|
|
228
232
|
const foundationJs = join(distDir, 'foundation.js')
|
|
233
|
+
const hasMainArtifact = () => existsSync(entryJs) || existsSync(foundationJs)
|
|
229
234
|
const schemaJson = join(distDir, 'meta', 'schema.json')
|
|
230
235
|
|
|
231
236
|
// Pre-read package.json so we can compare its version against the
|
|
@@ -335,7 +340,7 @@ export async function publish(args = []) {
|
|
|
335
340
|
}
|
|
336
341
|
}
|
|
337
342
|
|
|
338
|
-
let needsBuild = !
|
|
343
|
+
let needsBuild = !hasMainArtifact() || !existsSync(schemaJson)
|
|
339
344
|
let buildReason = needsBuild ? 'no dist/ found' : null
|
|
340
345
|
|
|
341
346
|
if (!needsBuild) {
|
|
@@ -446,8 +451,8 @@ export async function publish(args = []) {
|
|
|
446
451
|
})
|
|
447
452
|
console.log('')
|
|
448
453
|
|
|
449
|
-
if (!
|
|
450
|
-
error('Build did not produce dist/foundation.js and dist/meta/schema.json')
|
|
454
|
+
if (!hasMainArtifact() || !existsSync(schemaJson)) {
|
|
455
|
+
error('Build did not produce dist/entry.js (or legacy dist/foundation.js) and dist/meta/schema.json')
|
|
451
456
|
process.exit(1)
|
|
452
457
|
}
|
|
453
458
|
}
|
package/src/framework-index.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-05-
|
|
3
|
+
"generatedAt": "2026-05-12T15:22:16.890Z",
|
|
4
4
|
"packages": {
|
|
5
5
|
"@uniweb/build": {
|
|
6
|
-
"version": "0.14.
|
|
6
|
+
"version": "0.14.5",
|
|
7
7
|
"path": "framework/build",
|
|
8
8
|
"deps": [
|
|
9
9
|
"@uniweb/content-reader",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
]
|
|
15
15
|
},
|
|
16
16
|
"@uniweb/content-reader": {
|
|
17
|
-
"version": "1.1.
|
|
17
|
+
"version": "1.1.11",
|
|
18
18
|
"path": "framework/content-reader",
|
|
19
19
|
"deps": []
|
|
20
20
|
},
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"deps": []
|
|
43
43
|
},
|
|
44
44
|
"@uniweb/kit": {
|
|
45
|
-
"version": "0.9.
|
|
45
|
+
"version": "0.9.13",
|
|
46
46
|
"path": "framework/kit",
|
|
47
47
|
"deps": [
|
|
48
48
|
"@uniweb/core"
|
|
@@ -54,12 +54,12 @@
|
|
|
54
54
|
"deps": []
|
|
55
55
|
},
|
|
56
56
|
"@uniweb/press": {
|
|
57
|
-
"version": "0.4.
|
|
57
|
+
"version": "0.4.6",
|
|
58
58
|
"path": "framework/press",
|
|
59
59
|
"deps": []
|
|
60
60
|
},
|
|
61
61
|
"@uniweb/runtime": {
|
|
62
|
-
"version": "0.8.
|
|
62
|
+
"version": "0.8.14",
|
|
63
63
|
"path": "framework/runtime",
|
|
64
64
|
"deps": [
|
|
65
65
|
"@uniweb/core",
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"deps": []
|
|
93
93
|
},
|
|
94
94
|
"@uniweb/unipress": {
|
|
95
|
-
"version": "0.4.
|
|
95
|
+
"version": "0.4.9",
|
|
96
96
|
"path": "framework/unipress",
|
|
97
97
|
"deps": [
|
|
98
98
|
"@uniweb/build",
|
package/src/index.js
CHANGED
|
@@ -1205,12 +1205,12 @@ ${colors.cyan}${colors.bright}uniweb login${colors.reset} ${colors.dim}— Log i
|
|
|
1205
1205
|
${colors.bright}Usage:${colors.reset}
|
|
1206
1206
|
uniweb login [options]
|
|
1207
1207
|
|
|
1208
|
-
Opens a browser to
|
|
1208
|
+
Opens a browser to www.uniweb.app for OAuth-style login, then captures
|
|
1209
1209
|
the token via a loopback callback. Falls back to a paste-token prompt
|
|
1210
1210
|
if the browser flow fails.
|
|
1211
1211
|
|
|
1212
1212
|
${colors.bright}Options:${colors.reset}
|
|
1213
|
-
--backend <url> Override the auth backend (default: https://
|
|
1213
|
+
--backend <url> Override the auth backend (default: https://www.uniweb.app)
|
|
1214
1214
|
|
|
1215
1215
|
In non-interactive mode (CI / no TTY / --non-interactive), this command
|
|
1216
1216
|
errors out — set the \`UNIWEB_TOKEN\` env var instead, or run \`login\`
|
package/src/utils/config.js
CHANGED
|
@@ -27,10 +27,8 @@ import { filterCmd } from './pm.js'
|
|
|
27
27
|
// REGISTRY hosts platform operations (publish, foundations, runtime, admin):
|
|
28
28
|
// moved to hosting.uniweb.app in the CDN migration (Phase 4c, 2026-05-04).
|
|
29
29
|
// BACKEND hosts the PHP user-facing surface (login, account, orgs, billing,
|
|
30
|
-
// publish-authorize)
|
|
31
|
-
|
|
32
|
-
// uniweb.app/api/* when v4 ships. Until then, it stays at hub.uniweb.app.
|
|
33
|
-
const PRODUCTION_BACKEND_URL = 'https://hub.uniweb.app'
|
|
30
|
+
// publish-authorize).
|
|
31
|
+
const PRODUCTION_BACKEND_URL = 'https://www.uniweb.app'
|
|
34
32
|
const PRODUCTION_REGISTRY_URL = 'https://hosting.uniweb.app'
|
|
35
33
|
|
|
36
34
|
/**
|