@uniweb/build 0.25.1 → 0.25.3
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/src/schema.js +18 -0
- package/src/site/collection-processor.js +41 -5
- package/src/site/deploy-config-writer.js +24 -3
- package/src/uwx/index.js +1 -0
- package/src/uwx/site.js +39 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.3",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -59,17 +59,17 @@
|
|
|
59
59
|
"js-yaml": "^4.1.0",
|
|
60
60
|
"sharp": "^0.35.3",
|
|
61
61
|
"yaml": "^2.5.0",
|
|
62
|
-
"@uniweb/
|
|
62
|
+
"@uniweb/semantic-parser": "^1.3.0",
|
|
63
63
|
"@uniweb/theming": "^0.1.15",
|
|
64
|
-
"@uniweb/projections": "^0.3.4",
|
|
65
64
|
"@uniweb/content-writer": "^0.3.4",
|
|
66
|
-
"@uniweb/
|
|
65
|
+
"@uniweb/projections": "^0.3.4",
|
|
66
|
+
"@uniweb/schemas": "^0.2.10"
|
|
67
67
|
},
|
|
68
68
|
"optionalDependencies": {
|
|
69
69
|
"@uniweb/semantic-parser": "^1.3.0",
|
|
70
|
+
"@uniweb/content-reader": "^1.2.4",
|
|
70
71
|
"@uniweb/schemas": "^0.2.10",
|
|
71
|
-
"@uniweb/runtime": "^0.12.9"
|
|
72
|
-
"@uniweb/content-reader": "^1.2.4"
|
|
72
|
+
"@uniweb/runtime": "^0.12.9"
|
|
73
73
|
},
|
|
74
74
|
"peerDependencies": {
|
|
75
75
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"@tailwindcss/vite": "^4.0.0",
|
|
79
79
|
"@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
|
|
80
80
|
"vite-plugin-svgr": "^4.0.0",
|
|
81
|
-
"@uniweb/core": "^0.11.
|
|
81
|
+
"@uniweb/core": "^0.11.3"
|
|
82
82
|
},
|
|
83
83
|
"peerDependenciesMeta": {
|
|
84
84
|
"vite": {
|
package/src/schema.js
CHANGED
|
@@ -156,6 +156,24 @@ const DEFAULT_ONLY_CAPABILITIES = [
|
|
|
156
156
|
* every `[#id]` rendered as its own literal text — indistinguishable from a
|
|
157
157
|
* foundation that had never opted in. Found only by reading generate-entry.js.
|
|
158
158
|
*
|
|
159
|
+
* ⚠️ THAT DIAGNOSIS WAS CORRECT AND INCOMPLETE, and the rest took three weeks
|
|
160
|
+
* to find (2026-08-23). The same commit that moved those keys onto the default
|
|
161
|
+
* export also added `import … from '@uniweb/kit/xref'`, whose graph reaches
|
|
162
|
+
* `Ref.jsx`. `loadFoundationConfig` could not parse that, so the config never
|
|
163
|
+
* loaded at all — the identical symptom, from an unrelated cause, arriving on
|
|
164
|
+
* the same day the correct fix was applied. The fix looked inert because it
|
|
165
|
+
* was: where the keys sat could not matter while nothing was read.
|
|
166
|
+
*
|
|
167
|
+
* ⛔ AND IT DISABLED THIS WARNING. `warnMisplacedCapabilities` runs AFTER the
|
|
168
|
+
* import; a config that throws never reaches it. The guard added to catch that
|
|
169
|
+
* incident was, from that day, unable to fire on the project it was written
|
|
170
|
+
* for. A check downstream of the thing most likely to fail is not a check.
|
|
171
|
+
*
|
|
172
|
+
* To tell the two apart: a misplaced named export loses only that capability.
|
|
173
|
+
* A config that fails to load loses everything the file declares — `vars`
|
|
174
|
+
* included, so the site also renders with no theme variables at all. If the
|
|
175
|
+
* theme is gone too, stop looking at export placement.
|
|
176
|
+
*
|
|
159
177
|
* A warning rather than an error: a foundation may legitimately export a name
|
|
160
178
|
* that collides for its own use, and failing someone's build over a naming
|
|
161
179
|
* coincidence is worse than telling them what we ignored.
|
|
@@ -134,12 +134,38 @@ function parseCollectionConfig(name, config) {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
/**
|
|
137
|
-
* Parse YAML frontmatter from markdown content
|
|
137
|
+
* Parse YAML frontmatter from markdown content.
|
|
138
|
+
*
|
|
139
|
+
* Two cases, and keeping them apart is the whole point:
|
|
140
|
+
*
|
|
141
|
+
* NO frontmatter — the file does not open with `---`, or never closes the
|
|
142
|
+
* block. Legitimate: a record can be pure body. Returns {}.
|
|
143
|
+
*
|
|
144
|
+
* DECLARED frontmatter that does not parse — an error, because every field
|
|
145
|
+
* is gone at once. Not just the one with the typo: title, slug, date, image,
|
|
146
|
+
* category, all of it. The record still builds, still ships, and lands at a
|
|
147
|
+
* filename-derived slug with no title and no cover.
|
|
148
|
+
*
|
|
149
|
+
* ⛔ THIS USED TO WARN AND CONTINUE, and the warning could not be found.
|
|
150
|
+
* Measured 2026-08-24 on a real post: an unquoted colon inside a description
|
|
151
|
+
* ("...on a website framework: everything hard about docs...") voided six
|
|
152
|
+
* fields and moved the page from /blog/docs-sites to /blog/11_docs_sites. The
|
|
153
|
+
* only trace was
|
|
154
|
+
*
|
|
155
|
+
* [collection-processor] YAML parse error: bad indentation of a mapping entry (4:72)
|
|
156
|
+
*
|
|
157
|
+
* on line 16 of 857 lines of build output, naming no file, nine lines above
|
|
158
|
+
* "Processed articles: 6 items" — a success line that reads as everything
|
|
159
|
+
* being fine. The build exited 0 and the broken record shipped.
|
|
160
|
+
*
|
|
161
|
+
* A parse error now names the file and says what it costs, because "which of
|
|
162
|
+
* my 200 records is (4:72) in?" is the question the old message left you with.
|
|
138
163
|
*
|
|
139
164
|
* @param {string} raw - Raw file content
|
|
165
|
+
* @param {string} [filepath] - Path to the file, for the error message
|
|
140
166
|
* @returns {{ frontmatter: Object, body: string }}
|
|
141
167
|
*/
|
|
142
|
-
function parseFrontmatter(raw) {
|
|
168
|
+
function parseFrontmatter(raw, filepath) {
|
|
143
169
|
if (!raw.trim().startsWith('---')) {
|
|
144
170
|
return { frontmatter: {}, body: raw }
|
|
145
171
|
}
|
|
@@ -154,8 +180,18 @@ function parseFrontmatter(raw) {
|
|
|
154
180
|
const body = parts.slice(2).join('---\n')
|
|
155
181
|
return { frontmatter, body }
|
|
156
182
|
} catch (err) {
|
|
157
|
-
|
|
158
|
-
|
|
183
|
+
const where = filepath ? `${filepath}: ` : ''
|
|
184
|
+
throw new Error(
|
|
185
|
+
`${where}frontmatter is not valid YAML — ${err.message}\n` +
|
|
186
|
+
` The file opens with \`---\`, so it is declaring frontmatter. Since the block does not\n` +
|
|
187
|
+
` parse, EVERY field in it is lost — title, slug, date, image, category — and the record\n` +
|
|
188
|
+
` would build as an untitled entry at a slug derived from its filename.\n` +
|
|
189
|
+
` A common cause is an unquoted value containing a colon followed by a space:\n` +
|
|
190
|
+
` description: Building on a framework: everything hard is a website problem\n` +
|
|
191
|
+
` Quote the value and it parses:\n` +
|
|
192
|
+
` description: "Building on a framework: everything hard is a website problem"`,
|
|
193
|
+
{ cause: err },
|
|
194
|
+
)
|
|
159
195
|
}
|
|
160
196
|
}
|
|
161
197
|
|
|
@@ -508,7 +544,7 @@ async function processContentItem(dir, filename, config, siteRoot, basePath) {
|
|
|
508
544
|
const slug = basename(filename, extname(filename))
|
|
509
545
|
|
|
510
546
|
// Parse frontmatter and body
|
|
511
|
-
const { frontmatter, body } = parseFrontmatter(raw)
|
|
547
|
+
const { frontmatter, body } = parseFrontmatter(raw, filepath)
|
|
512
548
|
|
|
513
549
|
// Skip unpublished items by default
|
|
514
550
|
if (frontmatter.published === false) {
|
|
@@ -26,10 +26,31 @@ import { existsSync } from 'node:fs'
|
|
|
26
26
|
import { join } from 'node:path'
|
|
27
27
|
import { Document, parseDocument, isMap } from 'yaml'
|
|
28
28
|
|
|
29
|
+
// The header every generated deploy.yml carries. It has to answer the question
|
|
30
|
+
// a reader has while looking AT the file — who wrote this, and may I edit it —
|
|
31
|
+
// because that is where the question gets asked, not in the docs.
|
|
32
|
+
//
|
|
33
|
+
// The previous wording opened "operational config ... edit `targets:` freely",
|
|
34
|
+
// which reads as a file you are expected to author with one auto-managed block
|
|
35
|
+
// inside it. It cost a real reader an afternoon: they went looking for what to
|
|
36
|
+
// write under `targets:` for a site that had never deployed, when the answer is
|
|
37
|
+
// that the first deploy writes it.
|
|
29
38
|
const SCAFFOLD_HEADER = [
|
|
30
|
-
' deploy.yml —
|
|
31
|
-
'
|
|
32
|
-
'
|
|
39
|
+
' deploy.yml — written by `uniweb deploy` / `uniweb publish`.',
|
|
40
|
+
'',
|
|
41
|
+
' You do not create this file. The first successful deploy does, recording the',
|
|
42
|
+
' target you picked and what happened. Later deploys rewrite only `lastDeploy:`',
|
|
43
|
+
' and leave the rest — including your comments — alone.',
|
|
44
|
+
'',
|
|
45
|
+
' Safe to commit; no credentials live here. Host credentials come from the',
|
|
46
|
+
' environment.',
|
|
47
|
+
'',
|
|
48
|
+
' default: which target is used when none is named',
|
|
49
|
+
' targets: where this site ships. Edit to change the destination, or add',
|
|
50
|
+
' a target and pick it with `--target <name>`',
|
|
51
|
+
' autoSave: `lastDeploy` to keep the record below, `off` to stop writing it',
|
|
52
|
+
' lastDeploy: what the last deploy did. A record, not a setting — nothing',
|
|
53
|
+
' reads it back, so a stale one is safe to delete',
|
|
33
54
|
].join('\n')
|
|
34
55
|
|
|
35
56
|
/**
|
package/src/uwx/index.js
CHANGED
package/src/uwx/site.js
CHANGED
|
@@ -947,3 +947,42 @@ export function writeSiteEntityUuid(siteRoot, uuid) {
|
|
|
947
947
|
export function writeSiteOrg(siteRoot, handle) {
|
|
948
948
|
return upsertYamlScalar(join(siteRoot, 'site.yml'), '$org', handle)
|
|
949
949
|
}
|
|
950
|
+
|
|
951
|
+
/**
|
|
952
|
+
* Record the backend this project SYNCS WITH (`site.yml::$backend`), beside `$uuid`/`$org`.
|
|
953
|
+
*
|
|
954
|
+
* ⭐ It is not a tag on the site uuid — it is the project's **sync scope**. Four surfaces
|
|
955
|
+
* hold backend-minted identity, and this one fact is what makes all of them meaningful:
|
|
956
|
+
* `site.yml::$uuid`, every collection record's `$uuid` in its own source file,
|
|
957
|
+
* `assets.json`, and `.uniweb/sync-cache.json`. Change the backend and every one of them
|
|
958
|
+
* is foreign at once — which is why a mismatch is a stop, not a fallback
|
|
959
|
+
* (`assertSiteBackendScope` in the CLI).
|
|
960
|
+
*
|
|
961
|
+
* ⛔ **Written ONLY for a non-default backend.** The 98% case stays out of the file, and an
|
|
962
|
+
* absent value reads as the default — correct both for a project written before this key
|
|
963
|
+
* existed and for one synced against the default. Callers decide; this writer does not know
|
|
964
|
+
* the CLI's default. See `recordSiteBackend` (cli/src/backend/site-sync.js), which is where
|
|
965
|
+
* the "is it the default?" test lives.
|
|
966
|
+
*
|
|
967
|
+
* ⚠️ **The name is load-bearing. Five alternatives were considered and rejected (2026-08-24);
|
|
968
|
+
* do not re-propose one.** `origin` is wrong twice over — a site HAS an origin (its own
|
|
969
|
+
* served domain), and this lane is git-modeled, where `origin` names a remote rather than a
|
|
970
|
+
* URL. `remote` implies a multiplicity this design closes. `hosting` and `platform` name the
|
|
971
|
+
* hosting edge, which this package has no client for. `host` collides with
|
|
972
|
+
* `deploy.yml::targets.<n>.host` (the deploy adapter). A `uuid: <id>@<backend>` suffix was
|
|
973
|
+
* rejected too: it would make "this never reaches the backend" a discipline at every read
|
|
974
|
+
* site instead of a property, and `siteProjectToDocument` assigns `$uuid` straight onto the
|
|
975
|
+
* wire document.
|
|
976
|
+
*
|
|
977
|
+
* Safe as a plain YAML scalar: a URL's `:` is followed by `/` or a digit, never a space, so
|
|
978
|
+
* it needs no quoting (verified against js-yaml for apex, `localhost:8080` and host:port+path
|
|
979
|
+
* forms, 2026-08-24). This is the same class of hazard as `$org`'s leading `@`, which is
|
|
980
|
+
* stripped for exactly that reason — but it lands on the safe side.
|
|
981
|
+
*
|
|
982
|
+
* @param {string} siteRoot
|
|
983
|
+
* @param {string} origin - a bare origin, no trailing slash
|
|
984
|
+
* @returns {boolean} true if site.yml changed
|
|
985
|
+
*/
|
|
986
|
+
export function writeSiteBackend(siteRoot, origin) {
|
|
987
|
+
return upsertYamlScalar(join(siteRoot, 'site.yml'), '$backend', origin)
|
|
988
|
+
}
|