uniweb 0.56.4 → 0.56.6

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uniweb",
3
- "version": "0.56.4",
3
+ "version": "0.56.6",
4
4
  "description": "Create structured Vite + React sites with content/code separation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,16 +41,17 @@
41
41
  "js-yaml": "^4.1.0",
42
42
  "prompts": "^2.4.2",
43
43
  "tar": "^7.0.0",
44
+ "@uniweb/content-writer": "^0.3.4",
44
45
  "@uniweb/core": "^0.29.4",
45
- "@uniweb/kit": "^0.19.2",
46
- "@uniweb/schemas": "^0.2.15",
47
- "@uniweb/runtime": "^0.26.4",
48
- "@uniweb/semantic-parser": "^1.4.0"
46
+ "@uniweb/kit": "^0.19.3",
47
+ "@uniweb/semantic-parser": "^1.4.1",
48
+ "@uniweb/schemas": "^0.3.1",
49
+ "@uniweb/runtime": "^0.26.4"
49
50
  },
50
51
  "peerDependencies": {
51
- "@uniweb/build": "^0.52.3",
52
- "@uniweb/content-reader": "^1.2.5",
53
- "@uniweb/semantic-parser": "^1.4.0"
52
+ "@uniweb/semantic-parser": "^1.4.1",
53
+ "@uniweb/build": "^0.52.5",
54
+ "@uniweb/content-reader": "^1.2.5"
54
55
  },
55
56
  "peerDependenciesMeta": {
56
57
  "@uniweb/build": {
@@ -341,7 +341,7 @@ Description paragraph.
341
341
  ![Image](./image.jpg)
342
342
  ```
343
343
 
344
- Heading levels set *structure* (pretitle, title, subtitle), not font size — the component controls visual sizing. The `#>` label line marks a pretitle explicitly (any number of leading `#`s spells the same label); a smaller ordinary heading directly above the title also becomes the pretitle.
344
+ Heading levels set *structure* (pretitle, title, subtitle), not font size — the component controls visual sizing. Write a pretitle as a `#>` label line any number of leading `#`s spells the same label, so match the heading you are labelling. A smaller ordinary heading directly above the title is *also* read as a pretitle, which is how files written before label lines keep working; prefer `#>`, because it says what it is wherever it lands rather than depending on what follows it.
345
345
 
346
346
  **A section with no `type:` renders through the foundation's default section type — a component named `Section`, unless the foundation's `main.js` sets `defaultSection` to something else.** This is what lets a folder of plain markdown with no frontmatter at all become pages: mounted documentation, an imported wiki, anything written before it met this framework. If such content renders blank, the foundation has no `Section` — that, not the markdown, is what to fix.
347
347
 
@@ -356,8 +356,9 @@ The semantic parser produces a flat, guaranteed structure. No null checks needed
356
356
  ```js
357
357
  content = {
358
358
  title: '', // Main heading (string or string[] for multi-line)
359
- pretitle: '', // `#>` label line(s), or smaller headings stacked above
360
- // the title (string or string[])
359
+ pretitle: '', // `#>` label line(s) also filled by smaller headings
360
+ // stacked above the title, for older content
361
+ // (string or string[])
361
362
  subtitle: '', // Line(s) one step below the title — each further
362
363
  // one-step descent is another line (string or string[])
363
364
  paragraphs: [], // Text blocks
@@ -401,7 +402,7 @@ Lightning quick. │ content.items[0].paragraphs[0] = "Lightning
401
402
  Enterprise-grade security. │ content.items[1].paragraphs[0] = "Enterprise-grade…"
402
403
  ```
403
404
 
404
- The staircase rule produces this — each heading relates to the one before it: the same size adds another line to the same part; **one step smaller** joins the headline as the next part down (the subtitle, then further subtitle lines); **two steps smaller** starts an item; and once body content has begun, *any* heading starts an item. `#>` label lines, and smaller headings stacked above the title, become `pretitle`.
405
+ The staircase rule produces this — each heading relates to the one before it: the same size adds another line to the same part; **one step smaller** joins the headline as the next part down (the subtitle, then further subtitle lines); **two steps smaller** starts an item; and once body content has begun, *any* heading starts an item. `#>` label lines become `pretitle`; so do smaller headings stacked above the title, which is the older spelling.
405
406
 
406
407
  ### Items have the full content shape
407
408
 
@@ -35,6 +35,7 @@ import {
35
35
  updateRootScripts
36
36
  } from '../utils/config.js'
37
37
  import { discoverFoundations, discoverSites } from '../utils/discover.js'
38
+ import { generateStarter, reportStarter, declarationFor } from './starter.js'
38
39
  import {
39
40
  validatePackageName,
40
41
  getExistingPackageNames,
@@ -73,17 +74,30 @@ const colors = {
73
74
  red: '\x1b[31m'
74
75
  }
75
76
 
77
+ // Porcelain (`--json`) mode: stdout carries ONLY the JSON, so every human line
78
+ // diverts to stderr — the convention `register.js` already follows and
79
+ // `families.js` states ("stdout carries JSON and nothing else, so it pipes").
80
+ //
81
+ // ⛔ It is set per run rather than only when true, because a module-level flag
82
+ // that is only ever turned ON leaks into the next call in the same process —
83
+ // which is every call in the test suite.
84
+ let jsonMode = false
85
+ export function setAddJsonMode(on) {
86
+ jsonMode = Boolean(on)
87
+ }
88
+
76
89
  function log(message) {
77
- console.log(message)
90
+ if (jsonMode) console.error(message)
91
+ else console.log(message)
78
92
  }
79
93
  function success(message) {
80
- console.log(`${colors.green}✓${colors.reset} ${message}`)
94
+ log(`${colors.green}✓${colors.reset} ${message}`)
81
95
  }
82
96
  function error(message) {
83
97
  console.error(`${colors.red}✗${colors.reset} ${message}`)
84
98
  }
85
99
  function info(message) {
86
- console.log(`${colors.dim}${message}${colors.reset}`)
100
+ log(`${colors.dim}${message}${colors.reset}`)
87
101
  }
88
102
 
89
103
  /**
@@ -106,12 +120,19 @@ function parseArgs(args) {
106
120
  // scaffold PR-preview workflows.
107
121
  target: null,
108
122
  projectName: null,
109
- previews: true
123
+ previews: true,
124
+ // `add section` only: generate starter content for the section type from
125
+ // its `content:` declaration, and which preset's params to frontmatter it
126
+ // with. `--write` sends the markdown to a file instead of stdout.
127
+ starter: false,
128
+ preset: null,
129
+ write: null,
130
+ json: false
110
131
  }
111
132
 
112
133
  // Booleans (no value) consumed up-front so the value-flag loop below
113
134
  // doesn't accidentally swallow the next positional.
114
- const BOOLEAN_FLAGS = new Set(['--force', '--no-previews'])
135
+ const BOOLEAN_FLAGS = new Set(['--force', '--no-previews', '--starter', '--json'])
115
136
 
116
137
  // Value flags, mapped to their result key. Both spellings are accepted:
117
138
  // `--host github-pages` and `--host=github-pages`.
@@ -131,7 +152,9 @@ function parseArgs(args) {
131
152
  '--host': 'host',
132
153
  '--domain': 'domain',
133
154
  '--target': 'target',
134
- '--project-name': 'projectName'
155
+ '--project-name': 'projectName',
156
+ '--preset': 'preset',
157
+ '--write': 'write'
135
158
  }
136
159
 
137
160
  /** Split `--flag=value` into [flag, value]; `--flag` into [flag, null]. */
@@ -169,6 +192,10 @@ function parseArgs(args) {
169
192
  result.force = true
170
193
  } else if (flag === '--no-previews') {
171
194
  result.previews = false
195
+ } else if (flag === '--starter') {
196
+ result.starter = true
197
+ } else if (flag === '--json') {
198
+ result.json = true
172
199
  }
173
200
  }
174
201
 
@@ -1074,6 +1101,7 @@ async function wireExtensionToSite(
1074
1101
  * Add a section type to a foundation
1075
1102
  */
1076
1103
  async function addSection(rootDir, opts) {
1104
+ setAddJsonMode(opts.json)
1077
1105
  let name = opts.name
1078
1106
 
1079
1107
  // Interactive name prompt when not provided
@@ -1169,10 +1197,31 @@ async function addSection(rootDir, opts) {
1169
1197
  const sectionDir = join(sectionsDir, name)
1170
1198
  const relSectionPath = relative(foundationDir, sectionDir)
1171
1199
 
1200
+ // ⭐ `--starter` ON AN EXISTING SECTION IS NOT AN ERROR. The flag asks one
1201
+ // question — *what content would an author start this section with?* — and a
1202
+ // section type that already exists is the case where it has a real `content:`
1203
+ // declaration to answer from. Refusing here would make the flag testable only
1204
+ // against stubs, which is the one case where the answer is least interesting.
1205
+ // Nothing is scaffolded and nothing is overwritten on this path.
1172
1206
  if (existsSync(sectionDir)) {
1207
+ if (opts.starter) {
1208
+ const { markdown, result } = await generateStarter({
1209
+ name,
1210
+ sectionDir,
1211
+ preset: opts.preset,
1212
+ json: opts.json,
1213
+ write: opts.write,
1214
+ })
1215
+ if (!opts.json) {
1216
+ if (!opts.write) log('\n' + markdown.trimEnd())
1217
+ reportStarter(result, { write: opts.write })
1218
+ }
1219
+ return
1220
+ }
1173
1221
  error(
1174
1222
  `Section '${name}' already exists at ${foundation.path}/${relSectionPath}/`
1175
1223
  )
1224
+ log(` ${colors.dim}--starter generates starter content for it without touching the files.${colors.reset}`)
1176
1225
  process.exit(1)
1177
1226
  }
1178
1227
 
@@ -1207,9 +1256,21 @@ export default function ${name}({ content, params }) {
1207
1256
  }
1208
1257
  `
1209
1258
 
1259
+ // With `--starter`, the scaffold gets a `content:` declaration derived from
1260
+ // the family the name resolves to — so the three pieces agree: a declaration,
1261
+ // a component that reads it, and content that fills it. Without the flag the
1262
+ // stub declares nothing, exactly as before.
1263
+ let starterDeclaration = ''
1264
+ let starterResult = null
1265
+ if (opts.starter) {
1266
+ const preview = await generateStarter({ name, sectionDir: sectionDir })
1267
+ starterResult = preview.result
1268
+ starterDeclaration = await declarationFor(preview.result)
1269
+ }
1270
+
1210
1271
  const metaContent = `export default {
1211
1272
  title: '${name}',
1212
- description: '',
1273
+ description: '',${starterDeclaration}
1213
1274
  params: {},
1214
1275
  }
1215
1276
  `
@@ -1230,6 +1291,37 @@ export default function ${name}({ content, params }) {
1230
1291
  `${colors.dim}The dev server will pick it up automatically.${colors.reset}`
1231
1292
  )
1232
1293
  }
1294
+
1295
+ if (starterResult) {
1296
+ // Regenerate against the declaration just written, so what is printed is
1297
+ // what this section's own `meta.js` now asks for rather than the family's
1298
+ // guess — the two agree here, and saying it from the file keeps them so.
1299
+ const { markdown, result } = await generateStarter({
1300
+ name,
1301
+ sectionDir,
1302
+ preset: opts.preset,
1303
+ json: opts.json,
1304
+ write: opts.write,
1305
+ })
1306
+ if (!opts.json) {
1307
+ log('')
1308
+ log(`${colors.dim}Starter content for a page section:${colors.reset}`)
1309
+ if (!opts.write) log('\n' + markdown.trimEnd())
1310
+ reportStarter(result, { write: opts.write })
1311
+ // The stub component reads three elements. The declaration above may name
1312
+ // more, because it comes from the family rather than from the stub — say
1313
+ // so, rather than leave a developer wondering why half the content they
1314
+ // were just handed renders as nothing.
1315
+ const stubReads = new Set(['title', 'paragraphs', 'links'])
1316
+ const unread = Object.keys(result.content).filter((slot) => !stubReads.has(slot))
1317
+ if (unread.length) {
1318
+ log(
1319
+ ` ${colors.dim}index.jsx reads title, paragraphs and links — extend it for: ${unread.join(', ')}${colors.reset}`
1320
+ )
1321
+ log('')
1322
+ }
1323
+ }
1324
+ }
1233
1325
  }
1234
1326
 
1235
1327
  /**
@@ -1730,7 +1822,20 @@ function parseNodeMajor(engines) {
1730
1822
  }
1731
1823
 
1732
1824
  /**
1733
- * Show help for the add command
1825
+ * Show help for the add command.
1826
+ *
1827
+ * ⚠️ CURRENTLY UNREACHABLE, and the second copy of this text is the cost.
1828
+ * `src/index.js` short-circuits ANY `--help` in the args before dispatching
1829
+ * (its comment says why — `deploy --help` used to open a browser), and `add`
1830
+ * has a dedicated block in that file's HELP map, so `printCommandHelp('add')`
1831
+ * always wins and the `args[0] === '--help'` guard above never fires.
1832
+ *
1833
+ * ⛔ Which means a flag documented here and not there is documented nowhere.
1834
+ * Measured 2026-09-16, when `--starter` was added to both: this copy's
1835
+ * unescaped backticks around \`content:\` closed the template literal and broke
1836
+ * the WHOLE module — every `uniweb add` subcommand failed with "missing ) after
1837
+ * argument list" — and `uniweb add --help` printed correctly throughout,
1838
+ * because it never reaches this function.
1734
1839
  */
1735
1840
  function showAddHelp() {
1736
1841
  log(`
@@ -1762,6 +1867,13 @@ ${colors.bright}Extension Options:${colors.reset}
1762
1867
 
1763
1868
  ${colors.bright}Section Options:${colors.reset}
1764
1869
  --foundation <n> Foundation to add section to (prompted if multiple exist)
1870
+ --starter Generate starter content from the section's \`content:\`
1871
+ declaration — what an author would begin editing. Works on
1872
+ a section that already exists (nothing is written), and on
1873
+ a new one (the scaffold gets a matching declaration)
1874
+ --preset <name> Frontmatter the starter content with this preset's params
1875
+ --write <file> Write the starter markdown to a file instead of printing it
1876
+ --json Emit the content structure and ProseMirror doc instead
1765
1877
 
1766
1878
  ${colors.bright}CI Options:${colors.reset}
1767
1879
  --host <name> github-pages | cloudflare-pages | netlify | vercel
@@ -1790,6 +1902,10 @@ ${colors.bright}Examples:${colors.reset}
1790
1902
  uniweb add extension effects --site site # Create ./extensions/effects/
1791
1903
  uniweb add section Hero # Create Hero section type
1792
1904
  uniweb add section Hero --foundation ui # Target specific foundation
1905
+ uniweb add section Hero --starter # Starter content for an existing Hero
1906
+ uniweb add section Pricing --starter # Scaffold Pricing + content that fills it
1907
+ uniweb add section Hero --starter --preset split # Frontmatter it with the 'split' preset
1908
+ uniweb add section Hero --starter --json # The structure + ProseMirror, for a script
1793
1909
  uniweb add foundation --project docs # Create ./docs/foundation/ (co-located)
1794
1910
  uniweb add site --project docs # Create ./docs/site/ (co-located)
1795
1911
  uniweb add ci # Pick a host, add a deploy workflow
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Starter content for a section type — `uniweb add section <Name> --starter`.
3
+ *
4
+ * ⭐ IT IS DERIVED FROM `content:`, NOT AUTHORED. A developer declares what
5
+ * their component expects; this turns that declaration into something an author
6
+ * can edit instead of an empty box. There is no `starter:` key in `meta.js` and
7
+ * there should not be [Diego, 2026-09-16] — authored sample copy drifts against
8
+ * the declaration it is meant to match, and it can never be localized, because a
9
+ * developer's string is the foundation's own words and is shown verbatim in
10
+ * every UI language.
11
+ *
12
+ * ⚖️ THE GENERATOR IS NOT HERE. It is `@uniweb/schemas/starter`, because the
13
+ * visual editor is its other caller and must reach it in a browser — this file
14
+ * is the CLI's rendering of the same answer. Anything that decides WHAT the
15
+ * content is belongs there; what is left here is reading a `meta.js` off disk
16
+ * and serializing.
17
+ */
18
+
19
+ import { existsSync } from 'node:fs'
20
+ import { readFile, writeFile } from 'node:fs/promises'
21
+ import { join, dirname } from 'node:path'
22
+ import { pathToFileURL } from 'node:url'
23
+ import { createHash } from 'node:crypto'
24
+
25
+ const colors = {
26
+ reset: '\x1b[0m',
27
+ bright: '\x1b[1m',
28
+ dim: '\x1b[2m',
29
+ cyan: '\x1b[36m',
30
+ yellow: '\x1b[33m',
31
+ }
32
+
33
+ /**
34
+ * Load a section's `meta.js` as it is on disk NOW.
35
+ *
36
+ * ⛔ The URL carries the file's content hash. Node caches an ES module by URL
37
+ * for the life of the process, and `@uniweb/build`'s own loader hit exactly this
38
+ * — a dev server regenerating after a `meta.js` edit re-imported the file it had
39
+ * started with. Same rule here, same reason.
40
+ */
41
+ async function loadMeta(metaPath) {
42
+ const version = createHash('sha1').update(await readFile(metaPath)).digest('hex').slice(0, 16)
43
+ const mod = await import(`${pathToFileURL(metaPath).href}?content=${version}`)
44
+ return mod.default
45
+ }
46
+
47
+ /**
48
+ * The three framework functions this composes, imported at call time.
49
+ *
50
+ * ⚖️ Dynamic, matching `inspect.js`: these resolve through the project's own
51
+ * workspace, and a clear message beats a stack trace when one is missing.
52
+ */
53
+ async function loadPipeline() {
54
+ try {
55
+ const [schemas, content, parser, writer] = await Promise.all([
56
+ import('@uniweb/schemas/starter'),
57
+ import('@uniweb/schemas/content'),
58
+ import('@uniweb/semantic-parser'),
59
+ import('@uniweb/content-writer'),
60
+ ])
61
+ return {
62
+ starterContent: schemas.starterContent,
63
+ describeContent: content.describeContent,
64
+ buildDoc: parser.buildDoc,
65
+ serializeSection: writer.serializeSection,
66
+ }
67
+ } catch (err) {
68
+ throw new Error(
69
+ `Starter content needs @uniweb/schemas, @uniweb/semantic-parser and @uniweb/content-writer.\n` +
70
+ ` ${err.message}\n` +
71
+ ` Run your package manager's install in this workspace and try again.`,
72
+ )
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Generate starter content for one section type and render it.
78
+ *
79
+ * @param {object} args
80
+ * @param {string} args.name - the section type (PascalCase).
81
+ * @param {string} args.sectionDir - the section's directory in the foundation.
82
+ * @param {string} [args.preset] - a declared preset whose params become the frontmatter.
83
+ * @param {boolean} [args.json] - emit the structure and the ProseMirror doc instead of markdown.
84
+ * @param {string} [args.write] - write the markdown to this path instead of printing it.
85
+ * @returns {Promise<{markdown: string, result: object}>}
86
+ */
87
+ export async function generateStarter({ name, sectionDir, preset, json, write }) {
88
+ const { starterContent, describeContent, buildDoc, serializeSection } = await loadPipeline()
89
+
90
+ const metaPath = join(sectionDir, 'meta.js')
91
+ const meta = existsSync(metaPath) ? await loadMeta(metaPath) : {}
92
+ const result = starterContent({ name, ...meta }, { preset })
93
+
94
+ const doc = buildDoc(result.content)
95
+ const markdown = doc ? serializeSection(result.params, doc) : ''
96
+
97
+ if (json) {
98
+ process.stdout.write(
99
+ JSON.stringify(
100
+ {
101
+ section: name,
102
+ family: result.family,
103
+ elementsInferred: result.elementsInferred,
104
+ unfilled: result.unfilled,
105
+ // What the component SAYS it expects, parsed — the same structure an
106
+ // editor renders a "what does this section want?" panel from. Carried
107
+ // here so the declaration and what was generated from it can be read
108
+ // side by side, without the app.
109
+ expects: describeContent({ name, ...meta }),
110
+ params: result.params,
111
+ content: result.content,
112
+ doc,
113
+ },
114
+ null,
115
+ 2,
116
+ ) + '\n',
117
+ )
118
+ return { markdown, result }
119
+ }
120
+
121
+ if (write) {
122
+ await writeFile(write, markdown, 'utf-8')
123
+ }
124
+
125
+ return { markdown, result }
126
+ }
127
+
128
+ /**
129
+ * The human-facing report that follows generation.
130
+ *
131
+ * ⭐ It SAYS when the element list was ours. A component declaring no `content:`
132
+ * gets its family's canonical set, and a developer reading generated content
133
+ * they never specified should be told why — otherwise the natural conclusion is
134
+ * that the generator invented a declaration on their behalf.
135
+ */
136
+ export function reportStarter(result, { write } = {}) {
137
+ const c = colors
138
+ const fam = result.family.id
139
+ ? `${c.cyan}${result.family.id}${c.reset} ${c.dim}(${result.family.source})${c.reset}`
140
+ : `${c.dim}no family — generic copy${c.reset}`
141
+
142
+ console.log('')
143
+ console.log(` ${c.dim}family:${c.reset} ${fam}`)
144
+ console.log(` ${c.dim}slots:${c.reset} ${Object.keys(result.content).join(', ') || '—'}`)
145
+
146
+ if (result.elementsInferred) {
147
+ console.log('')
148
+ console.log(
149
+ ` ${c.yellow}!${c.reset} This section declares no ${c.bright}content:${c.reset} — the elements above came from its family.`,
150
+ )
151
+ console.log(
152
+ ` ${c.dim}Declare what the component expects and the starter content follows it instead.${c.reset}`,
153
+ )
154
+ }
155
+
156
+ if (result.unfilled.length) {
157
+ console.log('')
158
+ console.log(` ${c.yellow}!${c.reset} Not filled: ${result.unfilled.join(', ')}`)
159
+ console.log(
160
+ ` ${c.dim}\`background\` is frontmatter, not content. A video needs an address we cannot invent.${c.reset}`,
161
+ )
162
+ console.log(
163
+ ` ${c.dim}A \`data\` block needs a schema: a @/ ref resolves at build, and an empty {} declares no shape.${c.reset}`,
164
+ )
165
+ }
166
+
167
+ if (write) {
168
+ console.log('')
169
+ console.log(` ${c.cyan}→${c.reset} written to ${write}`)
170
+ }
171
+ console.log('')
172
+ }
173
+
174
+ /**
175
+ * A `content:` declaration for a section being scaffolded, derived from the
176
+ * family the name resolves to. Written into the new `meta.js` so the scaffold is
177
+ * coherent: a declaration, a component, and content that fills it.
178
+ */
179
+ export async function declarationFor(result) {
180
+ const LABELS = {
181
+ title: 'Headline',
182
+ pretitle: 'Small label above the headline',
183
+ subtitle: 'Secondary headline',
184
+ paragraphs: 'Body copy [1-2]',
185
+ links: 'Calls to action [0-2]',
186
+ lists: 'Bullet points [0-1]',
187
+ items: 'One per entry [3-6]',
188
+ images: 'Image [1]',
189
+ icons: 'Icon [1]',
190
+ videos: 'Video [1]',
191
+ snippets: 'Code sample [1]',
192
+ data: 'The data block the author writes',
193
+ }
194
+ // ⛔ The slot → declaration spelling comes from @uniweb/schemas, not from a
195
+ // copy here. `image` declares and `images` delivers; that mapping is the
196
+ // generator's, and a second copy rots the moment a row is added there.
197
+ const { declarationKey } = await import('@uniweb/schemas/starter')
198
+ const lines = Object.keys(result.content).map(
199
+ (slot) => ` ${declarationKey(slot)}: '${LABELS[slot] || slot}',`,
200
+ )
201
+ return lines.length ? `\n content: {\n${lines.join('\n')}\n },\n` : ''
202
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-09-16T04:55:41.770Z",
3
+ "generatedAt": "2026-09-16T20:35:26.755Z",
4
4
  "packages": {
5
5
  "@uniweb/api": {
6
6
  "version": "0.3.6",
@@ -10,7 +10,7 @@
10
10
  ]
11
11
  },
12
12
  "@uniweb/build": {
13
- "version": "0.52.3",
13
+ "version": "0.52.5",
14
14
  "path": "framework/build",
15
15
  "deps": [
16
16
  "@uniweb/content-reader",
@@ -54,7 +54,7 @@
54
54
  ]
55
55
  },
56
56
  "@uniweb/kit": {
57
- "version": "0.19.2",
57
+ "version": "0.19.3",
58
58
  "path": "framework/kit",
59
59
  "deps": [
60
60
  "@uniweb/core",
@@ -95,7 +95,7 @@
95
95
  "deps": []
96
96
  },
97
97
  "@uniweb/schemas": {
98
- "version": "0.2.15",
98
+ "version": "0.3.1",
99
99
  "path": "framework/schemas",
100
100
  "deps": []
101
101
  },
@@ -105,12 +105,12 @@
105
105
  "deps": []
106
106
  },
107
107
  "@uniweb/semantic-parser": {
108
- "version": "1.4.0",
108
+ "version": "1.4.1",
109
109
  "path": "framework/semantic-parser",
110
110
  "deps": []
111
111
  },
112
112
  "@uniweb/templates": {
113
- "version": "0.14.4",
113
+ "version": "0.14.5",
114
114
  "path": "framework/templates",
115
115
  "deps": []
116
116
  },
package/src/index.js CHANGED
@@ -1505,7 +1505,7 @@ ${colors.bright}Subcommands:${colors.reset}
1505
1505
  add foundation [name] Add a foundation (--from, --path, --project)
1506
1506
  add site [name] Add a site (--from, --foundation, --path, --project)
1507
1507
  add extension <name> Add an extension (--from, --site, --path)
1508
- add section <name> Add a section type to a foundation (--foundation)
1508
+ add section <name> Add a section type to a foundation (--foundation, --starter)
1509
1509
  add ci Add a CI workflow so every push deploys (--host, --target)
1510
1510
 
1511
1511
  ${colors.bright}Common options:${colors.reset}
@@ -1514,6 +1514,15 @@ ${colors.bright}Common options:${colors.reset}
1514
1514
  --foundation <name> Wire site/extension to this foundation (CI-friendly)
1515
1515
  --site <name> Wire extension to this site (CI-friendly)
1516
1516
  --non-interactive Fail with usage info instead of prompting
1517
+
1518
+ ${colors.bright}Starter content (add section):${colors.reset}
1519
+ --starter Generate starter content from the section's
1520
+ \`content:\` declaration — what an author would begin
1521
+ editing. Works on an existing section (writes nothing)
1522
+ and on a new one (the scaffold gets a declaration)
1523
+ --preset <name> Frontmatter it with this preset's params
1524
+ --write <file> Write the markdown to a file instead of printing
1525
+ --json Emit the structure + ProseMirror doc instead
1517
1526
  `,
1518
1527
  export: `
1519
1528
  ${colors.cyan}${colors.bright}uniweb export${colors.reset} ${colors.dim}— Export a self-contained site for third-party hosting${colors.reset}
@@ -1888,7 +1897,7 @@ ${colors.bright}Add Subcommands:${colors.reset}
1888
1897
  add foundation [name] Add a foundation (--from, --path, --project)
1889
1898
  add site [name] Add a site (--from, --foundation, --path, --project)
1890
1899
  add extension <name> Add an extension (--from, --site, --path)
1891
- add section <name> Add a section type to a foundation (--foundation)
1900
+ add section <name> Add a section type to a foundation (--foundation, --starter)
1892
1901
  add ci Add a CI workflow so every push deploys (--host, --target)
1893
1902
 
1894
1903
  ${colors.bright}Global Options:${colors.reset}
@@ -122,7 +122,16 @@ export async function scaffoldSite(targetDir, context, options = {}) {
122
122
  registerVersions(getVersionsForTemplates())
123
123
 
124
124
  const templatePath = join(TEMPLATES_DIR, 'site')
125
- await copyTemplateDirectory(templatePath, targetDir, context, {
125
+ // A ref is written as a YAML scalar, not pasted as text: a scoped ref
126
+ // (`@acme/base@1.0.0`, `@acme/marketing`) starts with `@`, which plain YAML
127
+ // reserves, so the file would not parse — and every reader that swallows
128
+ // the parse error then reports the site as having no `$uuid`. js-yaml quotes
129
+ // only when needed, so `src` stays `src`. Rendered with `{{{ }}}`, because
130
+ // Handlebars' HTML escaping would also rewrite a URL's `&` and `=`.
131
+ const siteContext = context.foundationRef
132
+ ? { ...context, foundationRefYaml: yaml.dump(context.foundationRef, { lineWidth: -1 }).trim() }
133
+ : context
134
+ await copyTemplateDirectory(templatePath, targetDir, siteContext, {
126
135
  onProgress: options.onProgress,
127
136
  onWarning: options.onWarning
128
137
  })
@@ -4,7 +4,7 @@
4
4
  name: {{projectName}}
5
5
 
6
6
  {{#if foundationRef}}
7
- foundation: {{foundationRef}}
7
+ foundation: {{{foundationRefYaml}}}
8
8
 
9
9
  {{/if}}
10
10
  index: home