blume 1.6.5 → 1.7.0

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.
Files changed (179) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/bin/blume.mjs +3 -2
  3. package/dist/cli/chunk-0qhq7b8q.js +111 -0
  4. package/dist/cli/chunk-0qhq7b8q.js.map +11 -0
  5. package/dist/cli/chunk-18tjv4f7.js +96 -0
  6. package/dist/cli/chunk-18tjv4f7.js.map +10 -0
  7. package/dist/cli/chunk-27gtm2ym.js +69 -0
  8. package/dist/cli/chunk-27gtm2ym.js.map +11 -0
  9. package/dist/cli/chunk-2aj8ddew.js +72 -0
  10. package/dist/cli/chunk-2aj8ddew.js.map +10 -0
  11. package/dist/cli/chunk-3r94j3tc.js +221 -0
  12. package/dist/cli/chunk-3r94j3tc.js.map +10 -0
  13. package/dist/cli/chunk-4trphnvy.js +102 -0
  14. package/dist/cli/chunk-4trphnvy.js.map +11 -0
  15. package/dist/cli/chunk-4xyggvgf.js +21 -0
  16. package/dist/cli/chunk-4xyggvgf.js.map +10 -0
  17. package/dist/cli/chunk-5d4q7121.js +4064 -0
  18. package/dist/cli/chunk-5d4q7121.js.map +40 -0
  19. package/dist/cli/chunk-5hs6gb7n.js +32 -0
  20. package/dist/cli/chunk-5hs6gb7n.js.map +10 -0
  21. package/dist/cli/chunk-6kzzpsx8.js +26 -0
  22. package/dist/cli/chunk-6kzzpsx8.js.map +10 -0
  23. package/dist/cli/chunk-8gnpdsn1.js +952 -0
  24. package/dist/cli/chunk-8gnpdsn1.js.map +12 -0
  25. package/dist/cli/chunk-9qs6acpw.js +176 -0
  26. package/dist/cli/chunk-9qs6acpw.js.map +10 -0
  27. package/dist/cli/chunk-agy5rzxy.js +2453 -0
  28. package/dist/cli/chunk-agy5rzxy.js.map +15 -0
  29. package/dist/cli/chunk-bcy492zc.js +16 -0
  30. package/dist/cli/chunk-bcy492zc.js.map +10 -0
  31. package/dist/cli/chunk-btfr9yvw.js +41 -0
  32. package/dist/cli/chunk-btfr9yvw.js.map +10 -0
  33. package/dist/cli/chunk-cbjnx4s8.js +73 -0
  34. package/dist/cli/chunk-cbjnx4s8.js.map +10 -0
  35. package/dist/cli/chunk-cfw6x4rm.js +1967 -0
  36. package/dist/cli/chunk-cfw6x4rm.js.map +34 -0
  37. package/dist/cli/chunk-ckh3a410.js +277 -0
  38. package/dist/cli/chunk-ckh3a410.js.map +11 -0
  39. package/dist/cli/chunk-drke6t0h.js +259 -0
  40. package/dist/cli/chunk-drke6t0h.js.map +11 -0
  41. package/dist/cli/chunk-ev67ycx0.js +15 -0
  42. package/dist/cli/chunk-ev67ycx0.js.map +10 -0
  43. package/dist/cli/chunk-ey89bjj1.js +209 -0
  44. package/dist/cli/chunk-ey89bjj1.js.map +11 -0
  45. package/dist/cli/chunk-j6pxe0dt.js +69 -0
  46. package/dist/cli/chunk-j6pxe0dt.js.map +11 -0
  47. package/dist/cli/chunk-jk1zwka1.js +387 -0
  48. package/dist/cli/chunk-jk1zwka1.js.map +12 -0
  49. package/dist/cli/chunk-jtb45atp.js +467 -0
  50. package/dist/cli/chunk-jtb45atp.js.map +14 -0
  51. package/dist/cli/chunk-jxkxjsc1.js +76 -0
  52. package/dist/cli/chunk-jxkxjsc1.js.map +10 -0
  53. package/dist/cli/chunk-kwx90v78.js +81 -0
  54. package/dist/cli/chunk-kwx90v78.js.map +10 -0
  55. package/dist/cli/chunk-n0nyat6g.js +30 -0
  56. package/dist/cli/chunk-n0nyat6g.js.map +10 -0
  57. package/dist/cli/chunk-pxj10x8y.js +35 -0
  58. package/dist/cli/chunk-pxj10x8y.js.map +10 -0
  59. package/dist/cli/chunk-qq9nm3qd.js +1141 -0
  60. package/dist/cli/chunk-qq9nm3qd.js.map +19 -0
  61. package/dist/cli/chunk-s102bysw.js +5170 -0
  62. package/dist/cli/chunk-s102bysw.js.map +47 -0
  63. package/dist/cli/chunk-s5dsk8bj.js +769 -0
  64. package/dist/cli/chunk-s5dsk8bj.js.map +13 -0
  65. package/dist/cli/chunk-s5e5jt53.js +227 -0
  66. package/dist/cli/chunk-s5e5jt53.js.map +11 -0
  67. package/dist/cli/chunk-sbdqrjbb.js +81 -0
  68. package/dist/cli/chunk-sbdqrjbb.js.map +10 -0
  69. package/dist/cli/chunk-tnskyrej.js +117 -0
  70. package/dist/cli/chunk-tnskyrej.js.map +10 -0
  71. package/dist/cli/chunk-v2ymm99c.js +1016 -0
  72. package/dist/cli/chunk-v2ymm99c.js.map +13 -0
  73. package/dist/cli/chunk-v5mm027v.js +185 -0
  74. package/dist/cli/chunk-v5mm027v.js.map +11 -0
  75. package/dist/cli/chunk-vt8fgygt.js +23 -0
  76. package/dist/cli/chunk-vt8fgygt.js.map +10 -0
  77. package/dist/cli/chunk-vxv4x1n8.js +17 -0
  78. package/dist/cli/chunk-vxv4x1n8.js.map +10 -0
  79. package/dist/cli/chunk-wd27zjcz.js +60 -0
  80. package/dist/cli/chunk-wd27zjcz.js.map +10 -0
  81. package/dist/cli/chunk-x66c5yjn.js +23 -0
  82. package/dist/cli/chunk-x66c5yjn.js.map +10 -0
  83. package/dist/cli/chunk-xv91q4nm.js +5314 -0
  84. package/dist/cli/chunk-xv91q4nm.js.map +58 -0
  85. package/dist/cli/chunk-y3g15rvv.js +679 -0
  86. package/dist/cli/chunk-y3g15rvv.js.map +15 -0
  87. package/dist/cli/chunk-ye9zdkgv.js +136 -0
  88. package/dist/cli/chunk-ye9zdkgv.js.map +10 -0
  89. package/dist/cli/chunk-ynacq3ev.js +1062 -0
  90. package/dist/cli/chunk-ynacq3ev.js.map +25 -0
  91. package/dist/cli/chunk-zr3ygrq3.js +54 -0
  92. package/dist/cli/chunk-zr3ygrq3.js.map +10 -0
  93. package/dist/cli/index.js +55 -27597
  94. package/dist/cli/index.js.map +5 -243
  95. package/dist/types/ai/ask-context.d.ts +26 -0
  96. package/dist/types/components/layout/nav-utils.d.ts +33 -1
  97. package/dist/types/core/code-fences.d.ts +11 -0
  98. package/dist/types/core/package-root.d.ts +1 -1
  99. package/dist/types/core/schema.d.ts +70 -0
  100. package/dist/types/theme/fonts.d.ts +22 -22
  101. package/docs/02-deployment.mdx +22 -1
  102. package/docs/configuration/ask-ai.mdx +1 -1
  103. package/docs/configuration/customization.mdx +2 -9
  104. package/docs/content/navigation.mdx +2 -0
  105. package/docs/content/syntax.mdx +1 -1
  106. package/docs/discoverability/open-graph.mdx +4 -0
  107. package/docs/reference/cli.mdx +1 -1
  108. package/package.json +4 -2
  109. package/src/ai/api/handlers.ts +4 -7
  110. package/src/ai/api/paths.ts +8 -0
  111. package/src/ai/api/spec.ts +2 -1
  112. package/src/ai/ask-context.ts +378 -22
  113. package/src/astro/generate.ts +161 -28
  114. package/src/astro/include-hmr.ts +10 -13
  115. package/src/astro/include-refresh.ts +0 -0
  116. package/src/astro/index.ts +6 -1
  117. package/src/astro/integration.ts +280 -53
  118. package/src/astro/module-types.ts +83 -0
  119. package/src/astro/templates.ts +256 -108
  120. package/src/audit/image-size.ts +10 -8
  121. package/src/cli/command-meta.ts +77 -0
  122. package/src/cli/commands/add.ts +2 -4
  123. package/src/cli/commands/audit.ts +2 -4
  124. package/src/cli/commands/build.ts +70 -346
  125. package/src/cli/commands/check.ts +2 -4
  126. package/src/cli/commands/dev.ts +31 -42
  127. package/src/cli/commands/doctor.ts +2 -4
  128. package/src/cli/commands/eject.ts +3 -41
  129. package/src/cli/commands/eval.ts +2 -5
  130. package/src/cli/commands/init.ts +2 -4
  131. package/src/cli/commands/mcp-stdio.ts +2 -5
  132. package/src/cli/commands/preview.ts +3 -5
  133. package/src/cli/commands/sync.ts +2 -4
  134. package/src/cli/commands/translate.ts +2 -5
  135. package/src/cli/commands/validate.ts +2 -4
  136. package/src/cli/commands/version.ts +2 -4
  137. package/src/cli/eject-scripts.ts +0 -45
  138. package/src/cli/host-args.ts +16 -0
  139. package/src/cli/index.ts +84 -35
  140. package/src/cli/lazy-command.ts +47 -0
  141. package/src/components/Icon.astro +24 -0
  142. package/src/components/content/GithubInfo.astro +4 -1
  143. package/src/components/icon-sprite-middleware.ts +41 -0
  144. package/src/components/icon-sprite.ts +93 -0
  145. package/src/components/layout/IconSprite.astro +11 -0
  146. package/src/components/layout/NavTree.astro +156 -188
  147. package/src/components/layout/NavTreeCache.astro +45 -0
  148. package/src/components/layout/NavTreeScript.astro +256 -0
  149. package/src/components/layout/PageActions.astro +11 -5
  150. package/src/components/layout/PageLayout.astro +21 -3
  151. package/src/components/layout/ReferenceLayout.astro +21 -4
  152. package/src/components/layout/RootLayout.astro +44 -6
  153. package/src/components/layout/nav-cache.ts +49 -0
  154. package/src/components/layout/nav-utils.ts +69 -1
  155. package/src/components/layout/page-locale.ts +29 -0
  156. package/src/core/api-name.ts +18 -0
  157. package/src/core/code-fences.ts +48 -0
  158. package/src/core/content-assets.ts +3 -7
  159. package/src/core/includes.ts +3 -7
  160. package/src/core/package-root.ts +1 -1
  161. package/src/core/schema.ts +19 -0
  162. package/src/core/sources/normalize.ts +2 -37
  163. package/src/core/sources/obsidian.ts +3 -2
  164. package/src/core/svg-dimensions.ts +97 -0
  165. package/src/core/version-cut.ts +2 -2
  166. package/src/deploy/artifacts.ts +370 -0
  167. package/src/deploy/cloudflare-negotiation.ts +97 -32
  168. package/src/deploy/function-bundle.ts +66 -20
  169. package/src/deploy/sitemap.ts +6 -0
  170. package/src/deploy/vercel-negotiation.ts +8 -30
  171. package/src/markdown/language-icon.ts +64 -20
  172. package/src/markdown/mermaid.ts +11 -0
  173. package/src/og/cache.ts +236 -0
  174. package/src/og/card.ts +18 -16
  175. package/src/og/index.ts +8 -1
  176. package/src/openapi/render-mdx.ts +9 -5
  177. package/src/registry/eject.ts +23 -10
  178. package/src/theme/entry.ts +41 -7
  179. package/src/theme/fonts.ts +30 -23
@@ -0,0 +1,277 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ reportInternalError
4
+ } from "./chunk-btfr9yvw.js";
5
+ import {
6
+ writeTextAtomic
7
+ } from "./chunk-ev67ycx0.js";
8
+ import {
9
+ VERSION_LIKE,
10
+ isWithin,
11
+ localizeRoute,
12
+ nextFenceState,
13
+ scanProject,
14
+ versionizeRoute
15
+ } from "./chunk-s102bysw.js";
16
+ import {
17
+ VERSION_ID,
18
+ loadConfig
19
+ } from "./chunk-xv91q4nm.js";
20
+ import {
21
+ stripBasePath
22
+ } from "./chunk-27gtm2ym.js";
23
+ import"./chunk-4xyggvgf.js";
24
+ import"./chunk-6kzzpsx8.js";
25
+ import {
26
+ BlumeError,
27
+ logger
28
+ } from "./chunk-ey89bjj1.js";
29
+ import {
30
+ commandMeta
31
+ } from "./chunk-2aj8ddew.js";
32
+
33
+ // src/cli/commands/version.ts
34
+ import { defineCommand } from "citty";
35
+
36
+ // src/core/version-cut.ts
37
+ import { existsSync } from "node:fs";
38
+ import { cp, readdir, readFile, rm } from "node:fs/promises";
39
+ import { join, relative } from "pathe";
40
+ class CutError extends Error {
41
+ constructor(message) {
42
+ super(message);
43
+ this.name = "CutError";
44
+ }
45
+ }
46
+ var INLINE_CODE = /`[^`]*`/gu;
47
+ var ROOT_LINK = /(?<prefix>\]\(|href="|src=")(?<target>\/[^\s"')]*)/gu;
48
+ var buildRouteRewrites = (project, id) => {
49
+ const { basePath, i18n } = project.config;
50
+ const { contentRoot } = project.context;
51
+ const rewrites = new Map;
52
+ for (const page of project.graph.pages) {
53
+ const { sourcePath } = page;
54
+ if (page.version !== "" || page.collection === "staged" || !sourcePath || relative(contentRoot, sourcePath).startsWith("..")) {
55
+ continue;
56
+ }
57
+ const logical = versionizeRoute(page.versionKey, id);
58
+ rewrites.set(stripBasePath(basePath, page.route), i18n ? localizeRoute(logical, page.locale, i18n) : logical);
59
+ }
60
+ return rewrites;
61
+ };
62
+ var rewriteLine = (line, rewrites) => {
63
+ const masked = line.replaceAll(INLINE_CODE, (span) => " ".repeat(span.length));
64
+ let out = "";
65
+ let cursor = 0;
66
+ let count = 0;
67
+ for (const match of masked.matchAll(ROOT_LINK)) {
68
+ const target = match.groups?.target ?? "";
69
+ const hash = target.search(/[#?]/u);
70
+ const path = hash === -1 ? target : target.slice(0, hash);
71
+ const suffix = hash === -1 ? "" : target.slice(hash);
72
+ const bare = path !== "/" && path.endsWith("/") ? path.slice(0, -1) : path;
73
+ const replacement = rewrites.get(bare);
74
+ if (replacement === undefined) {
75
+ continue;
76
+ }
77
+ const offset = (match.index ?? 0) + (match.groups?.prefix?.length ?? 0);
78
+ out += line.slice(cursor, offset) + replacement + suffix;
79
+ cursor = offset + target.length;
80
+ count += 1;
81
+ }
82
+ return count === 0 ? line : out + line.slice(cursor);
83
+ };
84
+ var rewriteSnapshotLinks = (source, rewrites) => {
85
+ let fence = null;
86
+ let count = 0;
87
+ const lines = source.split(`
88
+ `).map((line) => {
89
+ const next = nextFenceState(line, fence);
90
+ const inFence = fence !== null || next !== null;
91
+ fence = next;
92
+ if (inFence) {
93
+ return line;
94
+ }
95
+ const rewrittenLine = rewriteLine(line, rewrites);
96
+ if (rewrittenLine !== line) {
97
+ count += 1;
98
+ }
99
+ return rewrittenLine;
100
+ });
101
+ return { count, text: lines.join(`
102
+ `) };
103
+ };
104
+ var snippetFor = (id, hasVersions) => hasVersions ? `Add to versions.archived in blume.config.ts (newest first):
105
+
106
+ { id: "${id}" },
107
+ ` : `Add to blume.config.ts:
108
+
109
+ versions: {
110
+ archived: [{ id: "${id}" }],
111
+ current: { label: "…" },
112
+ },
113
+ `;
114
+ var insertArchivedVersion = async (configPath, id) => {
115
+ let text;
116
+ try {
117
+ text = await readFile(configPath, "utf-8");
118
+ } catch {
119
+ return false;
120
+ }
121
+ const match = text.match(/(?<lead>archived:\s*\[)/u);
122
+ if (!match || match.index === undefined) {
123
+ return false;
124
+ }
125
+ const insertAt = match.index + (match.groups?.lead?.length ?? 0);
126
+ const lineStart = text.lastIndexOf(`
127
+ `, match.index) + 1;
128
+ const indent = text.slice(lineStart).match(/^\s*/u)?.[0] ?? "";
129
+ const rest = text.slice(insertAt);
130
+ const eol = /^\r?\n/u.exec(rest)?.[0];
131
+ let entry;
132
+ if (rest.trimStart().startsWith("]")) {
133
+ entry = `{ id: "${id}" }`;
134
+ } else if (eol) {
135
+ entry = `${eol}${indent} { id: "${id}" },`;
136
+ } else {
137
+ entry = `{ id: "${id}" }, `;
138
+ }
139
+ await writeTextAtomic(configPath, text.slice(0, insertAt) + entry + rest);
140
+ return true;
141
+ };
142
+ var cutVersion = async (root, id, options = {}) => {
143
+ if (!VERSION_ID.test(id)) {
144
+ throw new CutError(`Version ids must start with a letter (e.g. "v1.0") and contain only letters, digits, dots, hyphens, and underscores — got "${id}".`);
145
+ }
146
+ const project = await scanProject(root);
147
+ const errors = project.diagnostics.filter((diagnostic) => diagnostic.severity === "error");
148
+ if (errors.length > 0) {
149
+ throw new CutError(`The project has ${errors.length} error diagnostic(s) — fix them before cutting a version (run \`blume validate\`).`);
150
+ }
151
+ const { contentRoot } = project.context;
152
+ if (project.config.versions?.archived.some((version) => version.id === id)) {
153
+ throw new CutError(`Version "${id}" is already registered in versions.archived.`);
154
+ }
155
+ const dir = join(contentRoot, id);
156
+ if (existsSync(dir)) {
157
+ if (!options.force) {
158
+ throw new CutError(`${dir} already exists — pass --force to overwrite the snapshot.`);
159
+ }
160
+ await rm(dir, { force: true, recursive: true });
161
+ }
162
+ const excluded = new Set([
163
+ id,
164
+ "node_modules",
165
+ ...project.config.versions?.archived.map((version) => version.id) ?? []
166
+ ]);
167
+ const foreignRoots = project.sources.flatMap((source) => source.staged && source.contentRoot ? [source.contentRoot] : []).filter((tree) => isWithin(contentRoot, tree));
168
+ const isForeign = (path) => foreignRoots.some((tree) => isWithin(tree, path));
169
+ const entries = await readdir(contentRoot, { withFileTypes: true });
170
+ let copied = 0;
171
+ await Promise.all(entries.map(async (entry) => {
172
+ if (excluded.has(entry.name) || entry.name.startsWith(".") || entry.isDirectory() && VERSION_LIKE.test(entry.name)) {
173
+ return;
174
+ }
175
+ await cp(join(contentRoot, entry.name), join(dir, entry.name), {
176
+ filter: (source) => !isForeign(source),
177
+ recursive: true
178
+ });
179
+ }));
180
+ const rewrites = buildRouteRewrites(project, id);
181
+ const rewritten = [];
182
+ const walk = async (current) => {
183
+ const children = await readdir(current, { withFileTypes: true });
184
+ await Promise.all(children.map(async (child) => {
185
+ const abs = join(current, child.name);
186
+ if (child.isDirectory()) {
187
+ await walk(abs);
188
+ return;
189
+ }
190
+ copied += 1;
191
+ if (!/\.mdx?$/u.test(child.name)) {
192
+ return;
193
+ }
194
+ const source = await readFile(abs, "utf-8");
195
+ const { text, count } = rewriteSnapshotLinks(source, rewrites);
196
+ if (count > 0) {
197
+ await writeTextAtomic(abs, text);
198
+ rewritten.push({ count, file: relative(dir, abs) });
199
+ }
200
+ }));
201
+ };
202
+ await walk(dir);
203
+ const configPath = join(root, "blume.config.ts");
204
+ const configUpdated = project.config.versions !== undefined && await insertArchivedVersion(configPath, id);
205
+ return {
206
+ configSnippet: configUpdated ? null : snippetFor(id, project.config.versions !== undefined),
207
+ configUpdated,
208
+ copied,
209
+ dir,
210
+ rewritten
211
+ };
212
+ };
213
+
214
+ // src/cli/commands/version.ts
215
+ var versionCommand = defineCommand({
216
+ args: {
217
+ force: {
218
+ description: "Overwrite an existing snapshot directory.",
219
+ type: "boolean"
220
+ },
221
+ id: {
222
+ description: 'Version id to cut (e.g. "v1.0").',
223
+ required: false,
224
+ type: "positional"
225
+ }
226
+ },
227
+ meta: commandMeta.version,
228
+ async run({ args }) {
229
+ const root = process.cwd();
230
+ if (!args.id) {
231
+ const { config } = await loadConfig(root);
232
+ if (!config.versions) {
233
+ logger.info("Versioning is not configured. Cut the first version with `blume version <id>` (e.g. `blume version v1.0`).");
234
+ return;
235
+ }
236
+ const { current, archived } = config.versions;
237
+ process.stdout.write(` ${current.label} (current)${current.badge ? ` — ${current.badge}` : ""}
238
+ `);
239
+ for (const version of archived) {
240
+ process.stdout.write(` ${version.label ?? version.id} — ${version.id}/
241
+ `);
242
+ }
243
+ return;
244
+ }
245
+ try {
246
+ const result = await cutVersion(root, args.id, { force: args.force });
247
+ logger.success(`Snapshot ${result.dir} (${result.copied} file(s) copied)`);
248
+ const totalRewrites = result.rewritten.reduce((sum, entry) => sum + entry.count, 0);
249
+ if (totalRewrites > 0) {
250
+ logger.info(`Rewrote root-absolute links in ${result.rewritten.length} page(s) (${totalRewrites} line(s)).`);
251
+ }
252
+ if (result.configUpdated) {
253
+ logger.success(`Added "${args.id}" to versions.archived in blume.config.ts`);
254
+ } else if (result.configSnippet) {
255
+ logger.info(result.configSnippet);
256
+ }
257
+ logger.info("Archived versions are frozen — future edits belong in the live tree. Restart `blume dev` to pick up the snapshot.");
258
+ } catch (error) {
259
+ if (error instanceof CutError) {
260
+ logger.error(error.message);
261
+ process.exit(1);
262
+ }
263
+ if (error instanceof BlumeError) {
264
+ logger.error(error.diagnostic.message);
265
+ process.exit(1);
266
+ }
267
+ reportInternalError(error);
268
+ process.exit(1);
269
+ }
270
+ }
271
+ });
272
+ export {
273
+ versionCommand
274
+ };
275
+
276
+ //# debugId=D706C6D39089651F64756E2164756E21
277
+ //# sourceMappingURL=chunk-ckh3a410.js.map
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/cli/commands/version.ts", "../src/core/version-cut.ts"],
4
+ "sourcesContent": [
5
+ "import { defineCommand } from \"citty\";\n\nimport { loadConfig } from \"../../core/config.ts\";\nimport { BlumeError } from \"../../core/diagnostics.ts\";\nimport { CutError, cutVersion } from \"../../core/version-cut.ts\";\nimport { commandMeta } from \"../command-meta.ts\";\nimport { reportInternalError } from \"../internal-error.ts\";\nimport { logger } from \"../log.ts\";\n\nexport const versionCommand = defineCommand({\n args: {\n force: {\n description: \"Overwrite an existing snapshot directory.\",\n type: \"boolean\",\n },\n id: {\n description: 'Version id to cut (e.g. \"v1.0\").',\n required: false,\n type: \"positional\",\n },\n },\n meta: commandMeta.version,\n async run({ args }) {\n const root = process.cwd();\n\n if (!args.id) {\n const { config } = await loadConfig(root);\n if (!config.versions) {\n logger.info(\n \"Versioning is not configured. Cut the first version with `blume version <id>` (e.g. `blume version v1.0`).\"\n );\n return;\n }\n const { current, archived } = config.versions;\n process.stdout.write(\n ` ${current.label} (current)${current.badge ? ` — ${current.badge}` : \"\"}\\n`\n );\n for (const version of archived) {\n process.stdout.write(\n ` ${version.label ?? version.id} — ${version.id}/\\n`\n );\n }\n return;\n }\n\n try {\n const result = await cutVersion(root, args.id, { force: args.force });\n logger.success(\n `Snapshot ${result.dir} (${result.copied} file(s) copied)`\n );\n const totalRewrites = result.rewritten.reduce(\n (sum, entry) => sum + entry.count,\n 0\n );\n if (totalRewrites > 0) {\n logger.info(\n `Rewrote root-absolute links in ${result.rewritten.length} page(s) (${totalRewrites} line(s)).`\n );\n }\n if (result.configUpdated) {\n logger.success(\n `Added \"${args.id}\" to versions.archived in blume.config.ts`\n );\n } else if (result.configSnippet) {\n logger.info(result.configSnippet);\n }\n logger.info(\n \"Archived versions are frozen — future edits belong in the live tree. Restart `blume dev` to pick up the snapshot.\"\n );\n } catch (error) {\n if (error instanceof CutError) {\n logger.error(error.message);\n process.exit(1);\n }\n if (error instanceof BlumeError) {\n logger.error(error.diagnostic.message);\n process.exit(1);\n }\n reportInternalError(error);\n process.exit(1);\n }\n },\n});\n",
6
+ "import { existsSync } from \"node:fs\";\nimport { cp, readdir, readFile, rm } from \"node:fs/promises\";\n\nimport { join, relative } from \"pathe\";\n\nimport { stripBasePath } from \"./base-path.ts\";\nimport { nextFenceState } from \"./code-fences.ts\";\nimport type { FenceState } from \"./code-fences.ts\";\nimport { writeTextAtomic } from \"./fs-atomic.ts\";\nimport { localizeRoute } from \"./i18n.ts\";\nimport type { BlumeProject } from \"./project-graph.ts\";\nimport { isWithin, scanProject } from \"./project-graph.ts\";\nimport { VERSION_ID } from \"./schema.ts\";\nimport { VERSION_LIKE, versionizeRoute } from \"./versions.ts\";\n\n/** What `cutVersion` did, for the CLI to report. */\nexport interface CutResult {\n /** Files copied into the snapshot. */\n copied: number;\n /** Markdown pages whose root-absolute links were rewritten, with counts. */\n rewritten: { file: string; count: number }[];\n /** Whether `blume.config.ts` was updated in place. */\n configUpdated: boolean;\n /** Ready-to-paste config snippet when in-place update was not possible. */\n configSnippet: string | null;\n /** Absolute path of the created snapshot directory. */\n dir: string;\n}\n\n/** A `cutVersion` failure the CLI reports as a user error, not a crash. */\nexport class CutError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"CutError\";\n }\n}\n\n// Inline code spans are syntax being *shown*, not links — blank them before\n// matching so replacements can splice into the real line by index (the mask\n// preserves length). Mirrors the content-assets rewriter.\nconst INLINE_CODE = /`[^`]*`/gu;\n// A markdown link/image target or an HTML href/src attribute whose value is\n// root-absolute. Named groups carry the prefix (kept) and target (rewritten).\nconst ROOT_LINK = /(?<prefix>\\]\\(|href=\"|src=\")(?<target>\\/[^\\s\"')]*)/gu;\n\n/**\n * Build the link-rewrite table: every current-version route (basePath\n * stripped, since authors write root-absolute links as if mounted at root)\n * mapped to the same page's route inside the new snapshot. Only pages the\n * snapshot actually contains qualify — filesystem pages under the content\n * root. Spec-rendered references (`/api`, `/events`) and remote sources\n * aren't copied, so links to them keep pointing at the live pages instead of\n * a 404 inside the snapshot.\n */\nconst buildRouteRewrites = (\n project: BlumeProject,\n id: string\n): Map<string, string> => {\n const { basePath, i18n } = project.config;\n const { contentRoot } = project.context;\n const rewrites = new Map<string, string>();\n for (const page of project.graph.pages) {\n // `sourcePath` marks a page backed by a local file, which excludes\n // generated and remote pages; the content-root check below excludes local\n // files that live outside the tree a version snapshot copies. A staged\n // page (an Obsidian note) is served from its source, not from the copied\n // tree, so it keeps publishing as current even when its file sits under\n // the content root.\n const { sourcePath } = page;\n if (\n page.version !== \"\" ||\n page.collection === \"staged\" ||\n !sourcePath ||\n relative(contentRoot, sourcePath).startsWith(\"..\")\n ) {\n continue;\n }\n const logical = versionizeRoute(page.versionKey, id);\n rewrites.set(\n stripBasePath(basePath, page.route),\n i18n ? localizeRoute(logical, page.locale, i18n) : logical\n );\n }\n return rewrites;\n};\n\n/** Rewrite one line's root-absolute internal links via the rewrite table. */\nconst rewriteLine = (line: string, rewrites: Map<string, string>): string => {\n const masked = line.replaceAll(INLINE_CODE, (span) =>\n \" \".repeat(span.length)\n );\n let out = \"\";\n let cursor = 0;\n let count = 0;\n for (const match of masked.matchAll(ROOT_LINK)) {\n const target = match.groups?.target ?? \"\";\n // An anchor or query stays attached to the rewritten path.\n const hash = target.search(/[#?]/u);\n const path = hash === -1 ? target : target.slice(0, hash);\n const suffix = hash === -1 ? \"\" : target.slice(hash);\n const bare = path !== \"/\" && path.endsWith(\"/\") ? path.slice(0, -1) : path;\n const replacement = rewrites.get(bare);\n if (replacement === undefined) {\n continue;\n }\n const offset = (match.index ?? 0) + (match.groups?.prefix?.length ?? 0);\n out += line.slice(cursor, offset) + replacement + suffix;\n cursor = offset + target.length;\n count += 1;\n }\n return count === 0 ? line : out + line.slice(cursor);\n};\n\n/** A page's link-rewritten text and how many of its lines changed. */\nexport interface SnapshotRewrite {\n text: string;\n count: number;\n}\n\n/**\n * Rewrite a copied page's root-absolute internal links to their snapshot\n * equivalents, skipping fenced and inline code. Relative links need no\n * rewriting — the whole tree copies together, so they stay self-contained.\n */\nexport const rewriteSnapshotLinks = (\n source: string,\n rewrites: Map<string, string>\n): SnapshotRewrite => {\n let fence: FenceState = null;\n let count = 0;\n const lines = source.split(\"\\n\").map((line) => {\n const next = nextFenceState(line, fence);\n const inFence = fence !== null || next !== null;\n fence = next;\n if (inFence) {\n return line;\n }\n const rewrittenLine = rewriteLine(line, rewrites);\n if (rewrittenLine !== line) {\n count += 1;\n }\n return rewrittenLine;\n });\n return { count, text: lines.join(\"\\n\") };\n};\n\n/** The config entry to add, as a paste-ready snippet for the fallback path. */\nconst snippetFor = (id: string, hasVersions: boolean): string =>\n hasVersions\n ? `Add to versions.archived in blume.config.ts (newest first):\\n\\n { id: \"${id}\" },\\n`\n : `Add to blume.config.ts:\\n\\n versions: {\\n archived: [{ id: \"${id}\" }],\\n current: { label: \"…\" },\\n },\\n`;\n\n/**\n * Best-effort in-place config update: insert the new id at the head of an\n * existing \\`archived: [\\` array literal, preserving indentation. Anything\n * fancier (computed arrays, config spread across files) falls back to a\n * printed snippet — the config is user-authored TypeScript, and string-level\n * surgery beyond this simple shape risks corrupting it.\n */\nexport const insertArchivedVersion = async (\n configPath: string,\n id: string\n): Promise<boolean> => {\n let text: string;\n try {\n text = await readFile(configPath, \"utf-8\");\n } catch {\n return false;\n }\n const match = text.match(/(?<lead>archived:\\s*\\[)/u);\n if (!match || match.index === undefined) {\n return false;\n }\n const insertAt = match.index + (match.groups?.lead?.length ?? 0);\n // Indentation: one level deeper than the line holding `archived:`.\n const lineStart = text.lastIndexOf(\"\\n\", match.index) + 1;\n const indent = text.slice(lineStart).match(/^\\s*/u)?.[0] ?? \"\";\n const rest = text.slice(insertAt);\n // Match the array's authored shape: empty stays bare, an inline array gets\n // an inline entry, a multiline array gets its own indented line — on the\n // file's own line ending, so a CRLF config doesn't gain a lone LF.\n const eol = /^\\r?\\n/u.exec(rest)?.[0];\n let entry: string;\n if (rest.trimStart().startsWith(\"]\")) {\n entry = `{ id: \"${id}\" }`;\n } else if (eol) {\n entry = `${eol}${indent} { id: \"${id}\" },`;\n } else {\n entry = `{ id: \"${id}\" }, `;\n }\n await writeTextAtomic(configPath, text.slice(0, insertAt) + entry + rest);\n return true;\n};\n\n/**\n * Freeze the current docs as an archived version: copy the content tree into\n * `<contentRoot>/<id>/` (excluding existing snapshots), rewrite root-absolute\n * internal links so the copy is self-contained, and register the id in\n * `blume.config.ts` (or print the snippet to paste).\n */\nexport const cutVersion = async (\n root: string,\n id: string,\n options: { force?: boolean } = {}\n): Promise<CutResult> => {\n if (!VERSION_ID.test(id)) {\n throw new CutError(\n `Version ids must start with a letter (e.g. \"v1.0\") and contain only letters, digits, dots, hyphens, and underscores — got \"${id}\".`\n );\n }\n\n const project = await scanProject(root);\n const errors = project.diagnostics.filter(\n (diagnostic) => diagnostic.severity === \"error\"\n );\n if (errors.length > 0) {\n throw new CutError(\n `The project has ${errors.length} error diagnostic(s) — fix them before cutting a version (run \\`blume validate\\`).`\n );\n }\n // A missing content root already fails inside `scanProject` (the filesystem\n // source's own validation), so only the version-level checks remain here.\n const { contentRoot } = project.context;\n if (project.config.versions?.archived.some((version) => version.id === id)) {\n throw new CutError(\n `Version \"${id}\" is already registered in versions.archived.`\n );\n }\n\n const dir = join(contentRoot, id);\n if (existsSync(dir)) {\n if (!options.force) {\n throw new CutError(\n `${dir} already exists — pass --force to overwrite the snapshot.`\n );\n }\n await rm(dir, { force: true, recursive: true });\n }\n\n // Existing snapshots must not nest inside the new one: configured ids, plus\n // any version-shaped directory (`v1.0/`) that never made it into the config —\n // e.g. a prior cut whose config update fell back to a printed snippet. A\n // genuine content folder that merely looks like a version already draws the\n // rename-or-register diagnostic, so skipping it here is consistent.\n const excluded = new Set([\n id,\n \"node_modules\",\n ...(project.config.versions?.archived.map((version) => version.id) ?? []),\n ]);\n // Another local source's tree under the content root — an Obsidian vault the\n // filesystem source excludes — is that source's to publish. Copying it would\n // republish its raw notes (wikilinks and `%%comments%%` intact) as\n // filesystem pages of the snapshot, while the source itself keeps serving\n // them as current.\n const foreignRoots = project.sources\n .flatMap((source) =>\n source.staged && source.contentRoot ? [source.contentRoot] : []\n )\n .filter((tree) => isWithin(contentRoot, tree));\n const isForeign = (path: string): boolean =>\n foreignRoots.some((tree) => isWithin(tree, path));\n const entries = await readdir(contentRoot, { withFileTypes: true });\n let copied = 0;\n await Promise.all(\n entries.map(async (entry) => {\n if (\n excluded.has(entry.name) ||\n entry.name.startsWith(\".\") ||\n (entry.isDirectory() && VERSION_LIKE.test(entry.name))\n ) {\n return;\n }\n await cp(join(contentRoot, entry.name), join(dir, entry.name), {\n filter: (source) => !isForeign(source),\n recursive: true,\n });\n })\n );\n\n // Rewrite root-absolute internal links in every copied markdown page.\n const rewrites = buildRouteRewrites(project, id);\n const rewritten: { file: string; count: number }[] = [];\n const walk = async (current: string): Promise<void> => {\n const children = await readdir(current, { withFileTypes: true });\n await Promise.all(\n children.map(async (child) => {\n const abs = join(current, child.name);\n if (child.isDirectory()) {\n await walk(abs);\n return;\n }\n copied += 1;\n if (!/\\.mdx?$/u.test(child.name)) {\n return;\n }\n const source = await readFile(abs, \"utf-8\");\n const { text, count } = rewriteSnapshotLinks(source, rewrites);\n if (count > 0) {\n await writeTextAtomic(abs, text);\n rewritten.push({ count, file: relative(dir, abs) });\n }\n })\n );\n };\n await walk(dir);\n\n const configPath = join(root, \"blume.config.ts\");\n const configUpdated =\n project.config.versions !== undefined &&\n (await insertArchivedVersion(configPath, id));\n\n return {\n configSnippet: configUpdated\n ? null\n : snippetFor(id, project.config.versions !== undefined),\n configUpdated,\n copied,\n dir,\n rewritten,\n };\n};\n"
7
+ ],
8
+ "mappings": "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",
9
+ "debugId": "D706C6D39089651F64756E2164756E21",
10
+ "names": []
11
+ }
@@ -0,0 +1,259 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ ensureGitignore
4
+ } from "./chunk-5hs6gb7n.js";
5
+ import {
6
+ PACKAGE_MANAGERS,
7
+ TEMPLATES,
8
+ applyPlan,
9
+ buildPlan,
10
+ commandsFor,
11
+ detectPackageManager,
12
+ eject,
13
+ nextSteps,
14
+ titleize,
15
+ updatePackageScripts,
16
+ validateContentDir
17
+ } from "./chunk-s5dsk8bj.js";
18
+ import"./chunk-5d4q7121.js";
19
+ import"./chunk-ynacq3ev.js";
20
+ import"./chunk-pxj10x8y.js";
21
+ import"./chunk-sbdqrjbb.js";
22
+ import"./chunk-v5mm027v.js";
23
+ import"./chunk-ev67ycx0.js";
24
+ import"./chunk-vt8fgygt.js";
25
+ import"./chunk-3r94j3tc.js";
26
+ import"./chunk-agy5rzxy.js";
27
+ import"./chunk-j6pxe0dt.js";
28
+ import"./chunk-vxv4x1n8.js";
29
+ import"./chunk-wd27zjcz.js";
30
+ import"./chunk-s102bysw.js";
31
+ import"./chunk-xv91q4nm.js";
32
+ import"./chunk-27gtm2ym.js";
33
+ import"./chunk-4xyggvgf.js";
34
+ import"./chunk-6kzzpsx8.js";
35
+ import {
36
+ logger
37
+ } from "./chunk-ey89bjj1.js";
38
+ import {
39
+ commandMeta
40
+ } from "./chunk-2aj8ddew.js";
41
+
42
+ // src/cli/commands/init.ts
43
+ import * as clack from "@clack/prompts";
44
+ import { defineCommand } from "citty";
45
+ import { resolve as resolve2 } from "pathe";
46
+
47
+ // src/cli/init/questions.ts
48
+ import { basename, resolve } from "pathe";
49
+ var cancelled = (value) => typeof value === "symbol";
50
+ var collectAnswers = async (prompter, flags, defaults) => {
51
+ const directory = flags.directory ?? await prompter.text({
52
+ defaultValue: ".",
53
+ message: "Where should we create your project?",
54
+ placeholder: "./my-docs"
55
+ });
56
+ if (cancelled(directory)) {
57
+ return null;
58
+ }
59
+ const root = resolve(defaults.cwd, directory);
60
+ const title = await prompter.text({
61
+ initialValue: titleize(basename(root)),
62
+ message: "What's your docs site called?",
63
+ validate: (value) => value?.trim() ? undefined : "Give your docs site a name."
64
+ });
65
+ if (cancelled(title)) {
66
+ return null;
67
+ }
68
+ const template = flags.template ?? await prompter.select({
69
+ message: "Which template?",
70
+ options: [
71
+ { hint: "Markdown docs site", label: "docs", value: "docs" },
72
+ { hint: "OpenAPI reference at /api", label: "api", value: "api" },
73
+ { hint: "SDK docs with an install page", label: "sdk", value: "sdk" },
74
+ {
75
+ hint: "Docs plus a changelog tab",
76
+ label: "changelog",
77
+ value: "changelog"
78
+ }
79
+ ]
80
+ });
81
+ if (cancelled(template)) {
82
+ return null;
83
+ }
84
+ const picked = await prompter.multiselect({
85
+ initialValues: ["filesystem"],
86
+ message: "Where does your content live?",
87
+ options: [
88
+ { hint: "Local .mdx files", label: "filesystem", value: "filesystem" },
89
+ { hint: "An Obsidian vault", label: "obsidian", value: "obsidian" },
90
+ {
91
+ hint: "Changelog from GitHub Releases",
92
+ label: "github-releases",
93
+ value: "github-releases"
94
+ },
95
+ { hint: "A Notion database", label: "notion", value: "notion" },
96
+ { hint: "A Sanity dataset", label: "sanity", value: "sanity" },
97
+ {
98
+ hint: "MDX fetched from a GitHub repo",
99
+ label: "mdx-remote",
100
+ value: "mdx-remote"
101
+ }
102
+ ],
103
+ required: false
104
+ });
105
+ if (cancelled(picked)) {
106
+ return null;
107
+ }
108
+ const sources = picked.length === 0 ? ["filesystem"] : picked;
109
+ let contentDir = flags.contentDir ?? "docs";
110
+ if (flags.contentDir === undefined && sources.includes("filesystem")) {
111
+ const answer = await prompter.text({
112
+ defaultValue: "docs",
113
+ message: "Content directory?",
114
+ placeholder: "docs",
115
+ validate: (value) => validateContentDir(root, value || "docs")
116
+ });
117
+ if (cancelled(answer)) {
118
+ return null;
119
+ }
120
+ contentDir = answer;
121
+ }
122
+ return {
123
+ contentDir,
124
+ directory,
125
+ packageManager: flags.packageManager ?? detectPackageManager(defaults.userAgent),
126
+ sources,
127
+ template,
128
+ title
129
+ };
130
+ };
131
+
132
+ // src/cli/commands/init.ts
133
+ var ejectScaffold = async (root, answers) => {
134
+ const commands = commandsFor(answers.packageManager);
135
+ const cd = answers.directory === "." ? [] : [`cd ${answers.directory}`];
136
+ try {
137
+ await eject(root);
138
+ await updatePackageScripts(root);
139
+ logger.success("Ejected to a standalone Astro project.");
140
+ const steps = [...cd, commands.install, commands.dev];
141
+ logger.box(`Next steps:
142
+
143
+ ${steps.join(`
144
+ `)}
145
+ `);
146
+ } catch (error) {
147
+ logger.warn(`Scaffolded, but eject needs the project's dependencies installed to load blume.config.ts: ${error.message}`);
148
+ const steps = [
149
+ ...cd,
150
+ commands.install,
151
+ `${commands.exec} blume eject --yes`
152
+ ];
153
+ logger.box(`Next steps:
154
+
155
+ ${steps.join(`
156
+ `)}
157
+ `);
158
+ }
159
+ };
160
+ var initCommand = defineCommand({
161
+ args: {
162
+ "content-dir": {
163
+ description: "Content directory.",
164
+ type: "string"
165
+ },
166
+ dir: {
167
+ description: "Directory to scaffold into (default: current directory).",
168
+ required: false,
169
+ type: "positional"
170
+ },
171
+ eject: {
172
+ description: "Eject to a standalone Astro project after scaffolding.",
173
+ type: "boolean"
174
+ },
175
+ "package-manager": {
176
+ description: "Package manager for the next-steps hint (npm|pnpm|yarn|bun).",
177
+ type: "string"
178
+ },
179
+ template: {
180
+ description: "Starter template: docs | api | sdk | changelog.",
181
+ type: "string"
182
+ },
183
+ yes: {
184
+ description: "Skip prompts and scaffold with defaults.",
185
+ type: "boolean"
186
+ }
187
+ },
188
+ meta: commandMeta.init,
189
+ async run({ args }) {
190
+ const cwd = process.cwd();
191
+ const template = TEMPLATES.find((candidate) => candidate === args.template);
192
+ if (args.template !== undefined && template === undefined) {
193
+ logger.error(`Unknown template "${args.template}" (use ${TEMPLATES.join(" | ")}).`);
194
+ process.exit(1);
195
+ }
196
+ const pm = PACKAGE_MANAGERS.find((candidate) => candidate === args["package-manager"]);
197
+ if (args["package-manager"] !== undefined && pm === undefined) {
198
+ logger.error(`Unknown package manager "${args["package-manager"]}" (use ${PACKAGE_MANAGERS.join(" | ")}).`);
199
+ process.exit(1);
200
+ }
201
+ const interactive = !args.yes && process.stdin.isTTY === true && clack.isTTY(process.stdout) && !clack.isCI();
202
+ let answers;
203
+ if (interactive) {
204
+ clack.intro("blume init");
205
+ const collected = await collectAnswers(clack, {
206
+ contentDir: args["content-dir"],
207
+ directory: args.dir,
208
+ packageManager: pm,
209
+ template
210
+ }, { cwd, userAgent: process.env.npm_config_user_agent });
211
+ if (collected === null) {
212
+ clack.cancel("Cancelled — nothing was written.");
213
+ process.exit(0);
214
+ }
215
+ answers = collected;
216
+ } else {
217
+ answers = {
218
+ contentDir: args["content-dir"] ?? "docs",
219
+ directory: args.dir ?? ".",
220
+ packageManager: pm ?? detectPackageManager(process.env.npm_config_user_agent),
221
+ sources: ["filesystem"],
222
+ template: template ?? "docs",
223
+ title: "My Docs"
224
+ };
225
+ }
226
+ const root = resolve2(cwd, answers.directory);
227
+ if (validateContentDir(root, answers.contentDir) !== undefined) {
228
+ logger.error(`Invalid --content-dir "${answers.contentDir}" (must be a path inside the project).`);
229
+ process.exit(1);
230
+ }
231
+ const sink = interactive ? clack.log : logger;
232
+ const { createdPackage } = await applyPlan(buildPlan(root, answers), sink);
233
+ const ignored = await ensureGitignore(root, [
234
+ "node_modules/",
235
+ ".blume/",
236
+ "dist/"
237
+ ]);
238
+ if (ignored.length > 0) {
239
+ sink.success(`Added ${ignored.join(", ")} to .gitignore`);
240
+ }
241
+ if (args.eject) {
242
+ await ejectScaffold(root, answers);
243
+ return;
244
+ }
245
+ const steps = nextSteps(answers, createdPackage);
246
+ if (interactive) {
247
+ clack.note(steps.trimEnd());
248
+ clack.outro("You're all set.");
249
+ } else {
250
+ logger.box(steps);
251
+ }
252
+ }
253
+ });
254
+ export {
255
+ initCommand
256
+ };
257
+
258
+ //# debugId=E1A6E93028E72EA864756E2164756E21
259
+ //# sourceMappingURL=chunk-drke6t0h.js.map
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/cli/commands/init.ts", "../src/cli/init/questions.ts"],
4
+ "sourcesContent": [
5
+ "import * as clack from \"@clack/prompts\";\nimport { defineCommand } from \"citty\";\nimport { resolve } from \"pathe\";\n\nimport { ensureGitignore } from \"../../core/gitignore.ts\";\nimport { eject } from \"../../registry/eject.ts\";\nimport { commandMeta } from \"../command-meta.ts\";\nimport { updatePackageScripts } from \"../eject-scripts.ts\";\nimport { collectAnswers } from \"../init/questions.ts\";\nimport {\n applyPlan,\n buildPlan,\n commandsFor,\n detectPackageManager,\n nextSteps,\n PACKAGE_MANAGERS,\n TEMPLATES,\n validateContentDir,\n} from \"../init/scaffold.ts\";\nimport type { InitAnswers } from \"../init/scaffold.ts\";\nimport { logger } from \"../log.ts\";\n\n/**\n * Eject the freshly scaffolded project, or print the install-then-eject path.\n * Eject jiti-loads the scaffolded blume.config.ts, whose `import { defineConfig }\n * from \"blume\"` only resolves once dependencies are installed (or blume is\n * hoisted from an ancestor node_modules, as in a monorepo) — so on a fresh\n * scaffold the fallback below is the common path.\n */\nconst ejectScaffold = async (\n root: string,\n answers: InitAnswers\n): Promise<void> => {\n const commands = commandsFor(answers.packageManager);\n const cd = answers.directory === \".\" ? [] : [`cd ${answers.directory}`];\n try {\n await eject(root);\n // The scaffolded scripts point at the Blume CLI; the ejected app runs\n // Astro directly (mirroring the standalone `blume eject` command).\n await updatePackageScripts(root);\n logger.success(\"Ejected to a standalone Astro project.\");\n const steps = [...cd, commands.install, commands.dev];\n logger.box(`Next steps:\\n\\n ${steps.join(\"\\n \")}\\n`);\n } catch (error) {\n // SAFETY: eject and the script rewrite throw Error instances; only the\n // message is surfaced in the fallback hint.\n logger.warn(\n `Scaffolded, but eject needs the project's dependencies installed to load blume.config.ts: ${(error as Error).message}`\n );\n const steps = [\n ...cd,\n commands.install,\n `${commands.exec} blume eject --yes`,\n ];\n logger.box(`Next steps:\\n\\n ${steps.join(\"\\n \")}\\n`);\n }\n};\n\nexport const initCommand = defineCommand({\n args: {\n \"content-dir\": {\n description: \"Content directory.\",\n type: \"string\",\n },\n dir: {\n description: \"Directory to scaffold into (default: current directory).\",\n required: false,\n type: \"positional\",\n },\n eject: {\n description: \"Eject to a standalone Astro project after scaffolding.\",\n type: \"boolean\",\n },\n \"package-manager\": {\n description:\n \"Package manager for the next-steps hint (npm|pnpm|yarn|bun).\",\n type: \"string\",\n },\n template: {\n description: \"Starter template: docs | api | sdk | changelog.\",\n type: \"string\",\n },\n yes: {\n description: \"Skip prompts and scaffold with defaults.\",\n type: \"boolean\",\n },\n },\n meta: commandMeta.init,\n async run({ args }) {\n const cwd = process.cwd();\n\n const template = TEMPLATES.find((candidate) => candidate === args.template);\n if (args.template !== undefined && template === undefined) {\n logger.error(\n `Unknown template \"${args.template}\" (use ${TEMPLATES.join(\" | \")}).`\n );\n process.exit(1);\n }\n const pm = PACKAGE_MANAGERS.find(\n (candidate) => candidate === args[\"package-manager\"]\n );\n if (args[\"package-manager\"] !== undefined && pm === undefined) {\n logger.error(\n `Unknown package manager \"${args[\"package-manager\"]}\" (use ${PACKAGE_MANAGERS.join(\" | \")}).`\n );\n process.exit(1);\n }\n\n const interactive =\n !args.yes &&\n process.stdin.isTTY === true &&\n clack.isTTY(process.stdout) &&\n !clack.isCI();\n\n let answers: InitAnswers;\n if (interactive) {\n clack.intro(\"blume init\");\n const collected = await collectAnswers(\n clack,\n {\n contentDir: args[\"content-dir\"],\n directory: args.dir,\n packageManager: pm,\n template,\n },\n { cwd, userAgent: process.env.npm_config_user_agent }\n );\n if (collected === null) {\n clack.cancel(\"Cancelled — nothing was written.\");\n process.exit(0);\n }\n answers = collected;\n } else {\n answers = {\n contentDir: args[\"content-dir\"] ?? \"docs\",\n directory: args.dir ?? \".\",\n packageManager:\n pm ?? detectPackageManager(process.env.npm_config_user_agent),\n sources: [\"filesystem\"],\n template: template ?? \"docs\",\n title: \"My Docs\",\n };\n }\n\n const root = resolve(cwd, answers.directory);\n // Interactive runs validate this inline, but an explicit --content-dir\n // flag skips that prompt, so guard here in both modes.\n if (validateContentDir(root, answers.contentDir) !== undefined) {\n logger.error(\n `Invalid --content-dir \"${answers.contentDir}\" (must be a path inside the project).`\n );\n process.exit(1);\n }\n\n const sink = interactive ? clack.log : logger;\n const { createdPackage } = await applyPlan(buildPlan(root, answers), sink);\n\n // Keep installed dependencies, Blume's generated runtime (`.blume/`), and\n // build output (`dist/`) out of version control. Idempotent: creates\n // `.gitignore` when absent and skips entries already present\n // (trailing-slash agnostic).\n const ignored = await ensureGitignore(root, [\n \"node_modules/\",\n \".blume/\",\n \"dist/\",\n ]);\n if (ignored.length > 0) {\n sink.success(`Added ${ignored.join(\", \")} to .gitignore`);\n }\n\n if (args.eject) {\n await ejectScaffold(root, answers);\n return;\n }\n\n const steps = nextSteps(answers, createdPackage);\n if (interactive) {\n clack.note(steps.trimEnd());\n clack.outro(\"You're all set.\");\n } else {\n logger.box(steps);\n }\n },\n});\n",
6
+ "import { basename, resolve } from \"pathe\";\n\nimport {\n detectPackageManager,\n titleize,\n validateContentDir,\n} from \"./scaffold.ts\";\nimport type {\n InitAnswers,\n PackageManager,\n SourceKind,\n Template,\n} from \"./scaffold.ts\";\n\n/** One prompt option: value plus display label and optional hint. */\ninterface PromptOption<Value> {\n hint?: string;\n label: string;\n value: Value;\n}\n\n/**\n * The prompt surface `collectAnswers` needs — structurally satisfied by the\n * `@clack/prompts` module itself, and by plain fakes in tests. Every prompt\n * resolves to a cancel symbol when the user aborts.\n */\nexport interface Prompter {\n multiselect: (opts: {\n initialValues?: SourceKind[];\n message: string;\n options: PromptOption<SourceKind>[];\n required?: boolean;\n }) => Promise<SourceKind[] | symbol>;\n select: (opts: {\n message: string;\n options: PromptOption<Template>[];\n }) => Promise<Template | symbol>;\n text: (opts: {\n defaultValue?: string;\n initialValue?: string;\n message: string;\n placeholder?: string;\n validate?: (value: string | undefined) => string | undefined;\n }) => Promise<string | symbol>;\n}\n\n/** Flag values the user passed explicitly; each one skips its question. */\nexport interface InitFlags {\n contentDir?: string;\n directory?: string;\n packageManager?: PackageManager;\n template?: Template;\n}\n\nconst cancelled = (value: string | string[] | symbol): value is symbol =>\n typeof value === \"symbol\";\n\n/**\n * Run the interactive init flow, returning the collected answers or `null`\n * when the user cancels. All questions run before anything is written, so a\n * cancel never leaves a partial scaffold behind.\n */\nexport const collectAnswers = async (\n prompter: Prompter,\n flags: InitFlags,\n defaults: { cwd: string; userAgent?: string }\n): Promise<InitAnswers | null> => {\n const directory =\n flags.directory ??\n (await prompter.text({\n defaultValue: \".\",\n message: \"Where should we create your project?\",\n placeholder: \"./my-docs\",\n }));\n if (cancelled(directory)) {\n return null;\n }\n\n const root = resolve(defaults.cwd, directory);\n const title = await prompter.text({\n initialValue: titleize(basename(root)),\n message: \"What's your docs site called?\",\n validate: (value) =>\n value?.trim() ? undefined : \"Give your docs site a name.\",\n });\n if (cancelled(title)) {\n return null;\n }\n\n const template =\n flags.template ??\n (await prompter.select({\n message: \"Which template?\",\n options: [\n { hint: \"Markdown docs site\", label: \"docs\", value: \"docs\" },\n { hint: \"OpenAPI reference at /api\", label: \"api\", value: \"api\" },\n { hint: \"SDK docs with an install page\", label: \"sdk\", value: \"sdk\" },\n {\n hint: \"Docs plus a changelog tab\",\n label: \"changelog\",\n value: \"changelog\",\n },\n ],\n }));\n if (cancelled(template)) {\n return null;\n }\n\n const picked = await prompter.multiselect({\n initialValues: [\"filesystem\"],\n message: \"Where does your content live?\",\n options: [\n { hint: \"Local .mdx files\", label: \"filesystem\", value: \"filesystem\" },\n { hint: \"An Obsidian vault\", label: \"obsidian\", value: \"obsidian\" },\n {\n hint: \"Changelog from GitHub Releases\",\n label: \"github-releases\",\n value: \"github-releases\",\n },\n { hint: \"A Notion database\", label: \"notion\", value: \"notion\" },\n { hint: \"A Sanity dataset\", label: \"sanity\", value: \"sanity\" },\n {\n hint: \"MDX fetched from a GitHub repo\",\n label: \"mdx-remote\",\n value: \"mdx-remote\",\n },\n ],\n required: false,\n });\n if (cancelled(picked)) {\n return null;\n }\n // No selection desugars to the implicit local source, same as the schema.\n const sources: SourceKind[] = picked.length === 0 ? [\"filesystem\"] : picked;\n\n let contentDir = flags.contentDir ?? \"docs\";\n if (flags.contentDir === undefined && sources.includes(\"filesystem\")) {\n const answer = await prompter.text({\n defaultValue: \"docs\",\n message: \"Content directory?\",\n placeholder: \"docs\",\n validate: (value) => validateContentDir(root, value || \"docs\"),\n });\n if (cancelled(answer)) {\n return null;\n }\n contentDir = answer;\n }\n\n return {\n contentDir,\n directory,\n packageManager:\n flags.packageManager ?? detectPackageManager(defaults.userAgent),\n sources,\n template,\n title,\n };\n};\n"
7
+ ],
8
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AACA;AACA,oBAAS;;;ACFT;AAsDA,IAAM,YAAY,CAAC,UACjB,OAAO,UAAU;AAOZ,IAAM,iBAAiB,OAC5B,UACA,OACA,aACgC;AAAA,EAChC,MAAM,YACJ,MAAM,aACL,MAAM,SAAS,KAAK;AAAA,IACnB,cAAc;AAAA,IACd,SAAS;AAAA,IACT,aAAa;AAAA,EACf,CAAC;AAAA,EACH,IAAI,UAAU,SAAS,GAAG;AAAA,IACxB,OAAO;AAAA,EACT;AAAA,EAEA,MAAM,OAAO,QAAQ,SAAS,KAAK,SAAS;AAAA,EAC5C,MAAM,QAAQ,MAAM,SAAS,KAAK;AAAA,IAChC,cAAc,SAAS,SAAS,IAAI,CAAC;AAAA,IACrC,SAAS;AAAA,IACT,UAAU,CAAC,UACT,OAAO,KAAK,IAAI,YAAY;AAAA,EAChC,CAAC;AAAA,EACD,IAAI,UAAU,KAAK,GAAG;AAAA,IACpB,OAAO;AAAA,EACT;AAAA,EAEA,MAAM,WACJ,MAAM,YACL,MAAM,SAAS,OAAO;AAAA,IACrB,SAAS;AAAA,IACT,SAAS;AAAA,MACP,EAAE,MAAM,sBAAsB,OAAO,QAAQ,OAAO,OAAO;AAAA,MAC3D,EAAE,MAAM,6BAA6B,OAAO,OAAO,OAAO,MAAM;AAAA,MAChE,EAAE,MAAM,iCAAiC,OAAO,OAAO,OAAO,MAAM;AAAA,MACpE;AAAA,QACE,MAAM;AAAA,QACN,OAAO;AAAA,QACP,OAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF,CAAC;AAAA,EACH,IAAI,UAAU,QAAQ,GAAG;AAAA,IACvB,OAAO;AAAA,EACT;AAAA,EAEA,MAAM,SAAS,MAAM,SAAS,YAAY;AAAA,IACxC,eAAe,CAAC,YAAY;AAAA,IAC5B,SAAS;AAAA,IACT,SAAS;AAAA,MACP,EAAE,MAAM,oBAAoB,OAAO,cAAc,OAAO,aAAa;AAAA,MACrE,EAAE,MAAM,qBAAqB,OAAO,YAAY,OAAO,WAAW;AAAA,MAClE;AAAA,QACE,MAAM;AAAA,QACN,OAAO;AAAA,QACP,OAAO;AAAA,MACT;AAAA,MACA,EAAE,MAAM,qBAAqB,OAAO,UAAU,OAAO,SAAS;AAAA,MAC9D,EAAE,MAAM,oBAAoB,OAAO,UAAU,OAAO,SAAS;AAAA,MAC7D;AAAA,QACE,MAAM;AAAA,QACN,OAAO;AAAA,QACP,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,UAAU;AAAA,EACZ,CAAC;AAAA,EACD,IAAI,UAAU,MAAM,GAAG;AAAA,IACrB,OAAO;AAAA,EACT;AAAA,EAEA,MAAM,UAAwB,OAAO,WAAW,IAAI,CAAC,YAAY,IAAI;AAAA,EAErE,IAAI,aAAa,MAAM,cAAc;AAAA,EACrC,IAAI,MAAM,eAAe,aAAa,QAAQ,SAAS,YAAY,GAAG;AAAA,IACpE,MAAM,SAAS,MAAM,SAAS,KAAK;AAAA,MACjC,cAAc;AAAA,MACd,SAAS;AAAA,MACT,aAAa;AAAA,MACb,UAAU,CAAC,UAAU,mBAAmB,MAAM,SAAS,MAAM;AAAA,IAC/D,CAAC;AAAA,IACD,IAAI,UAAU,MAAM,GAAG;AAAA,MACrB,OAAO;AAAA,IACT;AAAA,IACA,aAAa;AAAA,EACf;AAAA,EAEA,OAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,gBACE,MAAM,kBAAkB,qBAAqB,SAAS,SAAS;AAAA,IACjE;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA;;;ADhIF,IAAM,gBAAgB,OACpB,MACA,YACkB;AAAA,EAClB,MAAM,WAAW,YAAY,QAAQ,cAAc;AAAA,EACnD,MAAM,KAAK,QAAQ,cAAc,MAAM,CAAC,IAAI,CAAC,MAAM,QAAQ,WAAW;AAAA,EACtE,IAAI;AAAA,IACF,MAAM,MAAM,IAAI;AAAA,IAGhB,MAAM,qBAAqB,IAAI;AAAA,IAC/B,OAAO,QAAQ,wCAAwC;AAAA,IACvD,MAAM,QAAQ,CAAC,GAAG,IAAI,SAAS,SAAS,SAAS,GAAG;AAAA,IACpD,OAAO,IAAI;AAAA;AAAA,IAAoB,MAAM,KAAK;AAAA,GAAM;AAAA,CAAK;AAAA,IACrD,OAAO,OAAO;AAAA,IAGd,OAAO,KACL,6FAA8F,MAAgB,SAChH;AAAA,IACA,MAAM,QAAQ;AAAA,MACZ,GAAG;AAAA,MACH,SAAS;AAAA,MACT,GAAG,SAAS;AAAA,IACd;AAAA,IACA,OAAO,IAAI;AAAA;AAAA,IAAoB,MAAM,KAAK;AAAA,GAAM;AAAA,CAAK;AAAA;AAAA;AAIlD,IAAM,cAAc,cAAc;AAAA,EACvC,MAAM;AAAA,IACJ,eAAe;AAAA,MACb,aAAa;AAAA,MACb,MAAM;AAAA,IACR;AAAA,IACA,KAAK;AAAA,MACH,aAAa;AAAA,MACb,UAAU;AAAA,MACV,MAAM;AAAA,IACR;AAAA,IACA,OAAO;AAAA,MACL,aAAa;AAAA,MACb,MAAM;AAAA,IACR;AAAA,IACA,mBAAmB;AAAA,MACjB,aACE;AAAA,MACF,MAAM;AAAA,IACR;AAAA,IACA,UAAU;AAAA,MACR,aAAa;AAAA,MACb,MAAM;AAAA,IACR;AAAA,IACA,KAAK;AAAA,MACH,aAAa;AAAA,MACb,MAAM;AAAA,IACR;AAAA,EACF;AAAA,EACA,MAAM,YAAY;AAAA,OACZ,IAAG,GAAG,QAAQ;AAAA,IAClB,MAAM,MAAM,QAAQ,IAAI;AAAA,IAExB,MAAM,WAAW,UAAU,KAAK,CAAC,cAAc,cAAc,KAAK,QAAQ;AAAA,IAC1E,IAAI,KAAK,aAAa,aAAa,aAAa,WAAW;AAAA,MACzD,OAAO,MACL,qBAAqB,KAAK,kBAAkB,UAAU,KAAK,KAAK,KAClE;AAAA,MACA,QAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,IACA,MAAM,KAAK,iBAAiB,KAC1B,CAAC,cAAc,cAAc,KAAK,kBACpC;AAAA,IACA,IAAI,KAAK,uBAAuB,aAAa,OAAO,WAAW;AAAA,MAC7D,OAAO,MACL,4BAA4B,KAAK,4BAA4B,iBAAiB,KAAK,KAAK,KAC1F;AAAA,MACA,QAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,IAEA,MAAM,cACJ,CAAC,KAAK,OACN,QAAQ,MAAM,UAAU,QAClB,YAAM,QAAQ,MAAM,KAC1B,CAAO,WAAK;AAAA,IAEd,IAAI;AAAA,IACJ,IAAI,aAAa;AAAA,MACT,YAAM,YAAY;AAAA,MACxB,MAAM,YAAY,MAAM,eACtB,OACA;AAAA,QACE,YAAY,KAAK;AAAA,QACjB,WAAW,KAAK;AAAA,QAChB,gBAAgB;AAAA,QAChB;AAAA,MACF,GACA,EAAE,KAAK,WAAW,QAAQ,IAAI,sBAAsB,CACtD;AAAA,MACA,IAAI,cAAc,MAAM;AAAA,QAChB,aAAO,kCAAkC;AAAA,QAC/C,QAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,MACA,UAAU;AAAA,IACZ,EAAO;AAAA,MACL,UAAU;AAAA,QACR,YAAY,KAAK,kBAAkB;AAAA,QACnC,WAAW,KAAK,OAAO;AAAA,QACvB,gBACE,MAAM,qBAAqB,QAAQ,IAAI,qBAAqB;AAAA,QAC9D,SAAS,CAAC,YAAY;AAAA,QACtB,UAAU,YAAY;AAAA,QACtB,OAAO;AAAA,MACT;AAAA;AAAA,IAGF,MAAM,OAAO,SAAQ,KAAK,QAAQ,SAAS;AAAA,IAG3C,IAAI,mBAAmB,MAAM,QAAQ,UAAU,MAAM,WAAW;AAAA,MAC9D,OAAO,MACL,0BAA0B,QAAQ,kDACpC;AAAA,MACA,QAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,IAEA,MAAM,OAAO,cAAoB,YAAM;AAAA,IACvC,QAAQ,mBAAmB,MAAM,UAAU,UAAU,MAAM,OAAO,GAAG,IAAI;AAAA,IAMzE,MAAM,UAAU,MAAM,gBAAgB,MAAM;AAAA,MAC1C;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,IACD,IAAI,QAAQ,SAAS,GAAG;AAAA,MACtB,KAAK,QAAQ,SAAS,QAAQ,KAAK,IAAI,iBAAiB;AAAA,IAC1D;AAAA,IAEA,IAAI,KAAK,OAAO;AAAA,MACd,MAAM,cAAc,MAAM,OAAO;AAAA,MACjC;AAAA,IACF;AAAA,IAEA,MAAM,QAAQ,UAAU,SAAS,cAAc;AAAA,IAC/C,IAAI,aAAa;AAAA,MACT,WAAK,MAAM,QAAQ,CAAC;AAAA,MACpB,YAAM,iBAAiB;AAAA,IAC/B,EAAO;AAAA,MACL,OAAO,IAAI,KAAK;AAAA;AAAA;AAGtB,CAAC;",
9
+ "debugId": "E1A6E93028E72EA864756E2164756E21",
10
+ "names": []
11
+ }
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/core/fs-atomic.ts
4
+ import { mkdir } from "node:fs/promises";
5
+ import { dirname } from "pathe";
6
+ import writeFileAtomic from "write-file-atomic";
7
+ var writeTextAtomic = async (path, text) => {
8
+ await mkdir(dirname(path), { recursive: true });
9
+ await writeFileAtomic(path, text, { encoding: "utf-8", fsync: false });
10
+ };
11
+
12
+ export { writeTextAtomic };
13
+
14
+ //# debugId=33DFD366D208CE0864756E2164756E21
15
+ //# sourceMappingURL=chunk-ev67ycx0.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/core/fs-atomic.ts"],
4
+ "sourcesContent": [
5
+ "import { mkdir } from \"node:fs/promises\";\n\nimport { dirname } from \"pathe\";\nimport writeFileAtomic from \"write-file-atomic\";\n\n/**\n * Write text to `path` atomically (unique temp file + rename) after ensuring\n * the parent directory exists, so a concurrent reader or file watcher never\n * observes a missing or half-written file. write-file-atomic's temp names are\n * unique per call — a pid-suffixed temp name is not, and two concurrent\n * writers to the same target in one process (translate lanes, staged-content\n * writes) would interleave through a shared temp file. `fsync` is off to\n * match the previous behavior: the point is watcher atomicity, not crash\n * durability, and a per-file fsync would slow dev regeneration.\n */\nexport const writeTextAtomic = async (\n path: string,\n text: string\n): Promise<void> => {\n await mkdir(dirname(path), { recursive: true });\n await writeFileAtomic(path, text, { encoding: \"utf-8\", fsync: false });\n};\n"
6
+ ],
7
+ "mappings": ";;;AAAA;AAEA;AACA;AAYO,IAAM,kBAAkB,OAC7B,MACA,SACkB;AAAA,EAClB,MAAM,MAAM,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAAA,EAC9C,MAAM,gBAAgB,MAAM,MAAM,EAAE,UAAU,SAAS,OAAO,MAAM,CAAC;AAAA;",
8
+ "debugId": "33DFD366D208CE0864756E2164756E21",
9
+ "names": []
10
+ }