blume 0.7.0 → 1.0.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.
- package/CHANGELOG.md +666 -0
- package/LICENSE +21 -0
- package/README.md +107 -0
- package/dist/cli/index.js +1852 -380
- package/dist/cli/index.js.map +98 -91
- package/dist/types/ai/component-markdown.d.ts +34 -0
- package/dist/types/components/content/youtube.d.ts +18 -0
- package/dist/types/core/base-path.d.ts +9 -0
- package/dist/types/core/config-input.d.ts +47 -2
- package/dist/types/core/config.d.ts +3 -2
- package/dist/types/core/data.d.ts +7 -0
- package/dist/types/core/i18n-ui.d.ts +526 -132
- package/dist/types/core/schema.d.ts +293 -146
- package/dist/types/index.d.ts +1 -0
- package/dist/types/openapi/references.d.ts +60 -0
- package/dist/types/seo/x-handle.d.ts +12 -0
- package/docs/01-quickstart.mdx +5 -2
- package/docs/02-deployment.mdx +8 -8
- package/docs/03-faq.mdx +46 -16
- package/docs/advanced/api-reference.mdx +1 -1
- package/docs/advanced/changelog.mdx +1 -1
- package/docs/advanced/custom-pages.mdx +1 -1
- package/docs/advanced/skills.mdx +1 -1
- package/docs/configuration/ai.mdx +49 -10
- package/docs/configuration/customization.mdx +11 -0
- package/docs/configuration/export.mdx +1 -1
- package/docs/configuration/index.mdx +27 -3
- package/docs/configuration/seo.mdx +35 -5
- package/docs/content/components.mdx +2 -2
- package/docs/content/i18n.mdx +1 -1
- package/docs/content/navigation.mdx +3 -3
- package/docs/content/sources.mdx +1 -1
- package/docs/content/syntax.mdx +6 -4
- package/docs/index.mdx +2 -2
- package/docs/reference/cli.mdx +9 -7
- package/docs/reference/frontmatter.mdx +1 -1
- package/package.json +22 -4
- package/skills/blume/SKILL.md +5 -3
- package/skills/blume-update-docs/SKILL.md +3 -2
- package/src/ai/agent-readability.ts +9 -8
- package/src/ai/ask-context.ts +7 -2
- package/src/ai/ask-data.ts +3 -0
- package/src/ai/component-markdown.ts +461 -0
- package/src/ai/llms.ts +135 -26
- package/src/ai/markdown.ts +35 -6
- package/src/ai/mcp/data.ts +25 -4
- package/src/ai/mcp/discovery.ts +10 -3
- package/src/ai/mcp/server.ts +21 -7
- package/src/ai/mcp/tools.ts +1 -1
- package/src/ai/visibility.ts +74 -0
- package/src/astro/component-slots.ts +11 -1
- package/src/astro/generate.ts +77 -45
- package/src/astro/integration.ts +1 -1
- package/src/astro/markdown-negotiation.ts +1 -1
- package/src/astro/pages.ts +81 -19
- package/src/astro/templates.ts +150 -19
- package/src/blume-modules.d.ts +8 -0
- package/src/cli/commands/build.ts +120 -23
- package/src/cli/commands/check.ts +1 -1
- package/src/cli/commands/dev.ts +26 -5
- package/src/cli/commands/eject.ts +47 -19
- package/src/cli/commands/init.ts +120 -180
- package/src/cli/commands/preview.ts +4 -1
- package/src/cli/commands/validate.ts +43 -2
- package/src/cli/dev-lock.ts +8 -4
- package/src/cli/eject-scripts.ts +72 -0
- package/src/cli/env.ts +15 -5
- package/src/cli/init/questions.ts +158 -0
- package/src/cli/init/scaffold.ts +380 -0
- package/src/cli/internal-error.ts +9 -4
- package/src/cli/prepare.ts +3 -2
- package/src/components/Icon.astro +2 -1
- package/src/components/content/AccordionItem.astro +23 -4
- package/src/components/content/Badge.astro +3 -1
- package/src/components/content/Card.astro +4 -2
- package/src/components/content/Step.astro +10 -1
- package/src/components/content/Tabs.astro +15 -3
- package/src/components/content/Tile.astro +2 -1
- package/src/components/content/Tooltip.astro +3 -1
- package/src/components/content/Update.astro +9 -2
- package/src/components/content/auto-type-table.ts +7 -1
- package/src/components/content/base-href.ts +33 -0
- package/src/components/content/changelog-element.ts +9 -2
- package/src/components/content/mermaid-element.ts +7 -2
- package/src/components/islands/AskAI.astro +5 -2
- package/src/components/islands/ask-ai.tsx +86 -11
- package/src/components/islands/hooks.ts +28 -8
- package/src/components/layout/Banner.astro +10 -2
- package/src/components/layout/Breadcrumbs.astro +11 -2
- package/src/components/layout/Header.astro +13 -4
- package/src/components/layout/Logo.astro +11 -3
- package/src/components/layout/NavTree.astro +19 -5
- package/src/components/layout/PageActions.astro +25 -10
- package/src/components/layout/PageLayout.astro +85 -9
- package/src/components/layout/Pagination.astro +10 -4
- package/src/components/layout/ReferenceLayout.astro +20 -2
- package/src/components/layout/RootLayout.astro +142 -12
- package/src/components/layout/Search.astro +117 -27
- package/src/components/layout/search/algolia.ts +11 -2
- package/src/components/layout/search/endpoint.ts +11 -5
- package/src/components/layout/search/orama-cloud.ts +8 -2
- package/src/components/layout/search/types.ts +5 -1
- package/src/components/layout/search/typesense.ts +4 -1
- package/src/components/layout/toc-element.ts +1 -1
- package/src/components/openapi/ApiTagOperations.astro +2 -1
- package/src/components/openapi/Operation.astro +47 -40
- package/src/components/openapi/RequestPanel.astro +1 -1
- package/src/components/openapi/helpers.ts +71 -3
- package/src/components/openapi/panel.ts +1 -1
- package/src/core/base-path.ts +24 -0
- package/src/core/builtin-tags.ts +2 -0
- package/src/core/config-input.ts +48 -2
- package/src/core/config.ts +3 -2
- package/src/core/data.ts +4 -0
- package/src/core/frontmatter.ts +7 -0
- package/src/core/graph.ts +15 -5
- package/src/core/i18n-ui.ts +54 -0
- package/src/core/i18n.ts +16 -8
- package/src/core/last-modified.ts +13 -6
- package/src/core/links.ts +32 -8
- package/src/core/navigation.ts +29 -4
- package/src/core/package-json.ts +17 -2
- package/src/core/project-graph.ts +15 -6
- package/src/core/schema.ts +71 -2
- package/src/core/sources/assets.ts +6 -1
- package/src/core/sources/filesystem.ts +4 -0
- package/src/core/sources/mdx-remote.ts +23 -14
- package/src/core/sources/normalize.ts +152 -50
- package/src/core/sources/notion.ts +8 -8
- package/src/core/ui-packs/ar.ts +8 -0
- package/src/core/ui-packs/bg.ts +8 -0
- package/src/core/ui-packs/bn.ts +8 -0
- package/src/core/ui-packs/ca.ts +8 -0
- package/src/core/ui-packs/cs.ts +8 -0
- package/src/core/ui-packs/da.ts +8 -0
- package/src/core/ui-packs/de.ts +8 -0
- package/src/core/ui-packs/el.ts +8 -0
- package/src/core/ui-packs/es.ts +8 -0
- package/src/core/ui-packs/fa.ts +8 -0
- package/src/core/ui-packs/fi.ts +8 -0
- package/src/core/ui-packs/fr.ts +9 -1
- package/src/core/ui-packs/he.ts +8 -0
- package/src/core/ui-packs/hi.ts +8 -0
- package/src/core/ui-packs/hr.ts +8 -0
- package/src/core/ui-packs/hu.ts +8 -0
- package/src/core/ui-packs/id.ts +8 -0
- package/src/core/ui-packs/it.ts +8 -0
- package/src/core/ui-packs/ja.ts +8 -0
- package/src/core/ui-packs/ko.ts +8 -0
- package/src/core/ui-packs/nl.ts +8 -0
- package/src/core/ui-packs/no.ts +8 -0
- package/src/core/ui-packs/pl.ts +8 -0
- package/src/core/ui-packs/pt-br.ts +8 -0
- package/src/core/ui-packs/pt.ts +8 -0
- package/src/core/ui-packs/ro.ts +8 -0
- package/src/core/ui-packs/ru.ts +8 -0
- package/src/core/ui-packs/sk.ts +8 -0
- package/src/core/ui-packs/sr.ts +8 -0
- package/src/core/ui-packs/sv.ts +8 -0
- package/src/core/ui-packs/th.ts +8 -0
- package/src/core/ui-packs/tr.ts +8 -0
- package/src/core/ui-packs/uk.ts +8 -0
- package/src/core/ui-packs/vi.ts +8 -0
- package/src/core/ui-packs/zh-tw.ts +8 -0
- package/src/core/ui-packs/zh.ts +8 -0
- package/src/deploy/adapter-output.ts +18 -8
- package/src/deploy/redirects.ts +7 -2
- package/src/deploy/sitemap.ts +53 -11
- package/src/index.ts +5 -0
- package/src/markdown/base-links.ts +10 -8
- package/src/markdown/index.ts +15 -3
- package/src/markdown/inline-code.ts +7 -2
- package/src/markdown/package-commands.ts +10 -4
- package/src/og/card.ts +4 -2
- package/src/og/dimensions.ts +12 -0
- package/src/openapi/model.ts +12 -4
- package/src/openapi/parse.ts +21 -0
- package/src/openapi/references.ts +38 -8
- package/src/openapi/render-mdx.ts +62 -1
- package/src/openapi/source.ts +59 -10
- package/src/registry/eject.ts +184 -12
- package/src/registry/registry.ts +0 -3
- package/src/search/documents.ts +34 -2
- package/src/seo/jsonld.ts +20 -13
- package/src/seo/x-handle.ts +18 -0
|
@@ -11,6 +11,7 @@ import { ensureGitignore } from "../../core/gitignore.ts";
|
|
|
11
11
|
import type { BlumeProject } from "../../core/project-graph.ts";
|
|
12
12
|
import type { ResolvedConfig } from "../../core/schema.ts";
|
|
13
13
|
import { serverFeatures } from "../../core/server-features.ts";
|
|
14
|
+
import type { ProjectContext } from "../../core/types.ts";
|
|
14
15
|
import {
|
|
15
16
|
deployStaticDir,
|
|
16
17
|
surfaceAdapterOutput,
|
|
@@ -63,7 +64,10 @@ const validateBudgetFlags = (args: BudgetArgs): void => {
|
|
|
63
64
|
/**
|
|
64
65
|
* Emit platform redirect files for a static build (adapters wire redirects
|
|
65
66
|
* natively). Always writes the manifest; writes `_redirects`/`vercel.json` only
|
|
66
|
-
* when the user hasn't shipped one via public/.
|
|
67
|
+
* when the user hasn't shipped one via public/. Note that Vercel's
|
|
68
|
+
* git-integration builds read `vercel.json` from the repository root only —
|
|
69
|
+
* the copy emitted here takes effect when the dist folder itself is deployed
|
|
70
|
+
* directly via the Vercel CLI.
|
|
67
71
|
*/
|
|
68
72
|
const emitRedirectFiles = async (
|
|
69
73
|
config: ResolvedConfig,
|
|
@@ -189,11 +193,105 @@ const enforceBudget = async (
|
|
|
189
193
|
return passed ? "pass" : "fail";
|
|
190
194
|
};
|
|
191
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Run the optional bundle report (`--analyze`) and performance-budget gate
|
|
198
|
+
* against the directory whose `_astro/` client assets the deploy serves.
|
|
199
|
+
* Shared by real and isolated builds — an isolated CI run passing
|
|
200
|
+
* `--budget-js` must still fail on an exceeded budget rather than silently
|
|
201
|
+
* skipping the check. Exits non-zero when a budget is exceeded.
|
|
202
|
+
*/
|
|
203
|
+
export const runClientAssetChecks = async (
|
|
204
|
+
staticDir: string,
|
|
205
|
+
args: { analyze?: boolean } & BudgetArgs
|
|
206
|
+
): Promise<void> => {
|
|
207
|
+
if (args.analyze) {
|
|
208
|
+
await reportBundleSizes(staticDir);
|
|
209
|
+
}
|
|
210
|
+
if ((await enforceBudget(staticDir, args)) === "fail") {
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Root of an isolated build's output. The runtime-local `dist/`, except for a
|
|
217
|
+
* Vercel server build, whose deploy bundle lands at `<runtime>/.vercel/output`
|
|
218
|
+
* and is never surfaced to the project root.
|
|
219
|
+
*/
|
|
220
|
+
export const isolatedOutputDir = (
|
|
221
|
+
config: ResolvedConfig,
|
|
222
|
+
context: ProjectContext
|
|
223
|
+
): string => {
|
|
224
|
+
const { adapter, output } = config.deployment;
|
|
225
|
+
if (output === "server" && adapter === "vercel") {
|
|
226
|
+
return join(context.outDir, ".vercel", "output");
|
|
227
|
+
}
|
|
228
|
+
return context.distDir ?? join(context.outDir, "dist");
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Directory holding an isolated build's client `_astro/` assets. Mirrors
|
|
233
|
+
* `deployStaticDir`, except that an isolated build never surfaces the adapter
|
|
234
|
+
* bundle to the project root — a Vercel server build's static output stays at
|
|
235
|
+
* `<runtime>/.vercel/output/static`, where `deployStaticDir` would instead
|
|
236
|
+
* point at the project-root copy (a previous real build's assets, or nothing).
|
|
237
|
+
*/
|
|
238
|
+
export const isolatedStaticDir = (
|
|
239
|
+
config: ResolvedConfig,
|
|
240
|
+
context: ProjectContext
|
|
241
|
+
): string => {
|
|
242
|
+
const { adapter, output } = config.deployment;
|
|
243
|
+
const outputDir = isolatedOutputDir(config, context);
|
|
244
|
+
if (output === "server" && adapter === "vercel") {
|
|
245
|
+
return join(outputDir, "static");
|
|
246
|
+
}
|
|
247
|
+
if (output === "server" && adapter === "node") {
|
|
248
|
+
return join(outputDir, "client");
|
|
249
|
+
}
|
|
250
|
+
return outputDir;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Generate `llms.txt`/`llms-full.txt` into the dist dir. A user's own file in
|
|
255
|
+
* `public/` (copied into dist by Astro before this runs, like the sitemap and
|
|
256
|
+
* robots.txt) wins over the generated one — each file is checked and replaced
|
|
257
|
+
* independently, so a custom `llms.txt` still gets a generated `llms-full.txt`.
|
|
258
|
+
*/
|
|
259
|
+
const publishLlmsFiles = async (
|
|
260
|
+
project: BlumeProject,
|
|
261
|
+
distDir: string
|
|
262
|
+
): Promise<void> => {
|
|
263
|
+
const indexPath = join(distDir, "llms.txt");
|
|
264
|
+
const fullPath = join(distDir, "llms-full.txt");
|
|
265
|
+
const writeIndex = !existsSync(indexPath);
|
|
266
|
+
const writeFull = !existsSync(fullPath);
|
|
267
|
+
if (!(writeIndex || writeFull)) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const { index, full } = await buildLlmsFiles(project);
|
|
271
|
+
const writes: Promise<void>[] = [];
|
|
272
|
+
if (writeIndex) {
|
|
273
|
+
writes.push(writeFile(indexPath, index, "utf-8"));
|
|
274
|
+
}
|
|
275
|
+
if (writeFull) {
|
|
276
|
+
writes.push(writeFile(fullPath, full, "utf-8"));
|
|
277
|
+
}
|
|
278
|
+
await Promise.all(writes);
|
|
279
|
+
logger.success(
|
|
280
|
+
`Generated ${[
|
|
281
|
+
writeIndex ? "llms.txt" : null,
|
|
282
|
+
writeFull ? "llms-full.txt" : null,
|
|
283
|
+
]
|
|
284
|
+
.filter(Boolean)
|
|
285
|
+
.join(" and ")}`
|
|
286
|
+
);
|
|
287
|
+
};
|
|
288
|
+
|
|
192
289
|
/**
|
|
193
290
|
* Run every deploy post-step of a real (non-isolated) build: the search index +
|
|
194
291
|
* hosted-provider sync, llms.txt, sitemap/robots, redirect files, the summary
|
|
195
292
|
* box, and the optional bundle report / budget gate. Exits non-zero if a budget
|
|
196
|
-
* is exceeded. Isolated verify builds skip all of this
|
|
293
|
+
* is exceeded. Isolated verify builds skip all of this except the bundle
|
|
294
|
+
* report / budget gate, which they run against their own output.
|
|
197
295
|
*/
|
|
198
296
|
const publishBuildArtifacts = async (
|
|
199
297
|
project: BlumeProject,
|
|
@@ -214,13 +312,8 @@ const publishBuildArtifacts = async (
|
|
|
214
312
|
warn: (message) => logger.warn(message),
|
|
215
313
|
});
|
|
216
314
|
|
|
217
|
-
if (project.config.ai.llmsTxt) {
|
|
218
|
-
|
|
219
|
-
await Promise.all([
|
|
220
|
-
writeFile(join(distDir, "llms.txt"), index, "utf-8"),
|
|
221
|
-
writeFile(join(distDir, "llms-full.txt"), full, "utf-8"),
|
|
222
|
-
]);
|
|
223
|
-
logger.success("Generated llms.txt and llms-full.txt");
|
|
315
|
+
if (project.config.ai.llmsTxt.enabled) {
|
|
316
|
+
await publishLlmsFiles(project, distDir);
|
|
224
317
|
}
|
|
225
318
|
|
|
226
319
|
// A user's own public/ file (copied into dist by Astro) always wins.
|
|
@@ -253,6 +346,11 @@ const publishBuildArtifacts = async (
|
|
|
253
346
|
|
|
254
347
|
const { config } = project;
|
|
255
348
|
const features = serverFeatures(config);
|
|
349
|
+
// `buildSitemap` returns null both when the sitemap is disabled and when no
|
|
350
|
+
// `site` is configured — only the latter deserves the remediation hint.
|
|
351
|
+
const sitemapNote = config.seo.sitemap
|
|
352
|
+
? "no (set deployment.site)"
|
|
353
|
+
: "no (seo.sitemap is false)";
|
|
256
354
|
logger.box(
|
|
257
355
|
[
|
|
258
356
|
`Output ${config.deployment.output}`,
|
|
@@ -260,21 +358,15 @@ const publishBuildArtifacts = async (
|
|
|
260
358
|
`Site ${config.deployment.site ?? "not set"}`,
|
|
261
359
|
`Search ${config.search.provider}`,
|
|
262
360
|
`Redirects ${config.redirects.length}`,
|
|
263
|
-
`Sitemap ${sitemap ? "yes" :
|
|
361
|
+
`Sitemap ${sitemap ? "yes" : sitemapNote}`,
|
|
264
362
|
`Robots ${robots ? "yes" : "no"}`,
|
|
265
363
|
`Agent JSON ${agentReadability ? "yes" : "no"}`,
|
|
266
|
-
`LLM files ${config.ai.llmsTxt ? "yes" : "no"}`,
|
|
364
|
+
`LLM files ${config.ai.llmsTxt.enabled ? "yes" : "no"}`,
|
|
267
365
|
`Server features ${features.length > 0 ? features.join(", ") : "none"}`,
|
|
268
366
|
].join("\n")
|
|
269
367
|
);
|
|
270
368
|
|
|
271
|
-
|
|
272
|
-
await reportBundleSizes(distDir);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if ((await enforceBudget(distDir, args)) === "fail") {
|
|
276
|
-
process.exit(1);
|
|
277
|
-
}
|
|
369
|
+
await runClientAssetChecks(distDir, args);
|
|
278
370
|
|
|
279
371
|
logger.success(`Built to ${distDir}`);
|
|
280
372
|
};
|
|
@@ -330,7 +422,7 @@ export const buildCommand = defineCommand({
|
|
|
330
422
|
const runtimeDir = args.isolated
|
|
331
423
|
? ".blume-verify"
|
|
332
424
|
: process.env.BLUME_RUNTIME_DIR;
|
|
333
|
-
refuseIfDevRunning(root, "building", runtimeDir);
|
|
425
|
+
refuseIfDevRunning(root, "building", { isolatedHint: true, runtimeDir });
|
|
334
426
|
if (args.isolated) {
|
|
335
427
|
await ensureGitignore(root, [".blume-verify/"]);
|
|
336
428
|
}
|
|
@@ -369,15 +461,20 @@ export const buildCommand = defineCommand({
|
|
|
369
461
|
root: project.context.outDir,
|
|
370
462
|
});
|
|
371
463
|
|
|
372
|
-
const distDir = project.context.distDir ?? join(root, "dist");
|
|
373
|
-
|
|
374
464
|
// An isolated build is a throwaway verify: it only needs to confirm the site
|
|
375
465
|
// compiles and renders. Skip the network post-steps (search sync) and
|
|
376
466
|
// deploy artifacts (index/llms/sitemap/robots/redirects) that only matter
|
|
377
|
-
// for a real publish and would push to hosted providers.
|
|
467
|
+
// for a real publish and would push to hosted providers. The bundle report
|
|
468
|
+
// and budget gate still run, though — `blume build --isolated --budget-js
|
|
469
|
+
// 100` exiting 0 without measuring anything would be a silent false pass
|
|
470
|
+
// in CI.
|
|
378
471
|
if (runtimeDir) {
|
|
472
|
+
await runClientAssetChecks(
|
|
473
|
+
isolatedStaticDir(project.config, project.context),
|
|
474
|
+
args
|
|
475
|
+
);
|
|
379
476
|
logger.success(
|
|
380
|
-
`Isolated build OK — output at ${
|
|
477
|
+
`Isolated build OK — output at ${isolatedOutputDir(project.config, project.context)} (not published).`
|
|
381
478
|
);
|
|
382
479
|
return;
|
|
383
480
|
}
|
|
@@ -39,7 +39,7 @@ export const checkCommand = defineCommand({
|
|
|
39
39
|
const runtimeDir = args.isolated
|
|
40
40
|
? ".blume-verify"
|
|
41
41
|
: process.env.BLUME_RUNTIME_DIR;
|
|
42
|
-
refuseIfDevRunning(root, "checking", runtimeDir);
|
|
42
|
+
refuseIfDevRunning(root, "checking", { isolatedHint: true, runtimeDir });
|
|
43
43
|
if (args.isolated) {
|
|
44
44
|
await ensureGitignore(root, [".blume-verify/"]);
|
|
45
45
|
}
|
package/src/cli/commands/dev.ts
CHANGED
|
@@ -15,9 +15,21 @@ import {
|
|
|
15
15
|
DevLockHeldError,
|
|
16
16
|
updateDevLockPort,
|
|
17
17
|
} from "../dev-lock.ts";
|
|
18
|
-
import { logger } from "../log.ts";
|
|
18
|
+
import { logger, reportDiagnostics } from "../log.ts";
|
|
19
19
|
import { prepareProject } from "../prepare.ts";
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Resolve a `--host` flag value into what Astro/Vite's `server.host` expects.
|
|
23
|
+
* citty (0.1) has no mixed string/boolean arg type, so `host` is declared as a
|
|
24
|
+
* string and a bare `--host` parses as `""` — Node would bind all interfaces
|
|
25
|
+
* for `""`, but Vite's `resolveHostname` treats it as a literal hostname and
|
|
26
|
+
* prints malformed URLs like `http://:4321/`. Match Astro's own `--host`
|
|
27
|
+
* semantics instead: bare flag → `true` (bind all interfaces), `--host
|
|
28
|
+
* 10.0.0.1` → that address, absent → `false` (localhost only).
|
|
29
|
+
*/
|
|
30
|
+
export const normalizeHost = (host: string | undefined): boolean | string =>
|
|
31
|
+
host === "" ? true : (host ?? false);
|
|
32
|
+
|
|
21
33
|
/**
|
|
22
34
|
* A fingerprint of the route set: the sorted `path entryId` pairs. It changes
|
|
23
35
|
* when a page is added, removed, or renamed (a folder rename shifts many at
|
|
@@ -100,13 +112,13 @@ export const devCommand = defineCommand({
|
|
|
100
112
|
|
|
101
113
|
// A factory so `runRegenerate` can recreate the server on a structural
|
|
102
114
|
// (route-set) change: only a cold container re-globs Astro's content store,
|
|
103
|
-
// which its in-place config restart doesn't. `open` is
|
|
115
|
+
// which its in-place config restart doesn't. `open` is honored on first
|
|
104
116
|
// boot only — a restart must not reopen the browser.
|
|
105
117
|
const createServer = (listenPort: number | undefined, open: boolean) =>
|
|
106
118
|
dev({
|
|
107
119
|
logLevel: args.debug ? "debug" : "info",
|
|
108
120
|
root: project.context.outDir,
|
|
109
|
-
server: { host: args.host
|
|
121
|
+
server: { host: normalizeHost(args.host), open, port: listenPort },
|
|
110
122
|
});
|
|
111
123
|
|
|
112
124
|
let server = await createServer(explicitPort, args.open ?? false);
|
|
@@ -148,7 +160,6 @@ export const devCommand = defineCommand({
|
|
|
148
160
|
});
|
|
149
161
|
const nextSignature = routeSignature(next.manifest.routes);
|
|
150
162
|
const structural = nextSignature !== lastSignature;
|
|
151
|
-
lastSignature = nextSignature;
|
|
152
163
|
if (structural) {
|
|
153
164
|
await server.stop();
|
|
154
165
|
await generateRuntime(next);
|
|
@@ -156,7 +167,17 @@ export const devCommand = defineCommand({
|
|
|
156
167
|
} else {
|
|
157
168
|
await generateRuntime(next);
|
|
158
169
|
}
|
|
159
|
-
//
|
|
170
|
+
// Commit the signature only after the (re)generation succeeded. If the
|
|
171
|
+
// restart above throws mid-sequence, the signature stays stale so the
|
|
172
|
+
// next watch event retries the structural path — committing early would
|
|
173
|
+
// route it to the non-structural branch with the server still down.
|
|
174
|
+
lastSignature = nextSignature;
|
|
175
|
+
// Surface any content/config errors in the terminal AND the browser
|
|
176
|
+
// overlay. The terminal report must not be skipped: on a published
|
|
177
|
+
// install the CLI bundle holds its own copy of the integration module,
|
|
178
|
+
// separate from the Vite module graph that registers the overlay, so
|
|
179
|
+
// the overlay call below can be a no-op there.
|
|
180
|
+
reportDiagnostics(next.diagnostics, root);
|
|
160
181
|
showBlumeErrorOverlay(next.diagnostics);
|
|
161
182
|
} catch (error) {
|
|
162
183
|
logger.error(`Regeneration failed: ${(error as Error).message}`);
|
|
@@ -1,28 +1,32 @@
|
|
|
1
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
-
|
|
3
1
|
import { defineCommand } from "citty";
|
|
4
|
-
import {
|
|
2
|
+
import { relative } from "pathe";
|
|
5
3
|
|
|
4
|
+
import { loadConfig } from "../../core/config.ts";
|
|
6
5
|
import { eject } from "../../registry/eject.ts";
|
|
7
6
|
import { refuseIfDevRunning } from "../dev-lock.ts";
|
|
7
|
+
import {
|
|
8
|
+
droppedArtifactNotices,
|
|
9
|
+
updatePackageScripts,
|
|
10
|
+
} from "../eject-scripts.ts";
|
|
11
|
+
import { commandsFor, detectPackageManager } from "../init/scaffold.ts";
|
|
8
12
|
import { logger } from "../log.ts";
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Warn which `blume build` post-build artifacts the ejected app stops
|
|
16
|
+
* producing. Printed both at the confirmation (so the decision is informed)
|
|
17
|
+
* and after `--yes` (so a direct eject still sees it). No-op when the config
|
|
18
|
+
* activates none of them.
|
|
19
|
+
*/
|
|
20
|
+
const reportDroppedArtifacts = (notices: string[]): void => {
|
|
21
|
+
if (notices.length === 0) {
|
|
16
22
|
return;
|
|
17
23
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
25
|
-
await writeFile(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`, "utf-8");
|
|
24
|
+
logger.warn(
|
|
25
|
+
[
|
|
26
|
+
"The ejected build script runs plain `astro build`, which stops producing these `blume build` artifacts:",
|
|
27
|
+
...notices.map((notice) => ` - ${notice}`),
|
|
28
|
+
].join("\n")
|
|
29
|
+
);
|
|
26
30
|
};
|
|
27
31
|
|
|
28
32
|
export const ejectCommand = defineCommand({
|
|
@@ -37,23 +41,47 @@ export const ejectCommand = defineCommand({
|
|
|
37
41
|
const root = process.cwd();
|
|
38
42
|
refuseIfDevRunning(root, "ejecting");
|
|
39
43
|
|
|
44
|
+
// Config-aware drop list: only the artifacts this project actually
|
|
45
|
+
// produces are mentioned (e.g. the Pagefind index only for
|
|
46
|
+
// `search.provider: "pagefind"`).
|
|
47
|
+
let notices: string[] = [];
|
|
48
|
+
try {
|
|
49
|
+
const { config } = await loadConfig(root);
|
|
50
|
+
notices = droppedArtifactNotices(config);
|
|
51
|
+
} catch {
|
|
52
|
+
// A config that fails to load can't gate the notice; the eject itself
|
|
53
|
+
// surfaces the load error.
|
|
54
|
+
}
|
|
55
|
+
|
|
40
56
|
if (!args.yes) {
|
|
41
57
|
logger.warn(
|
|
42
58
|
"Eject is one-way: it writes astro.config.mjs, src/, and (if absent) tsconfig.json, rewrites your package.json scripts, and removes .blume. An existing tsconfig.json is left untouched."
|
|
43
59
|
);
|
|
60
|
+
reportDroppedArtifacts(notices);
|
|
44
61
|
logger.info("Re-run with --yes to proceed.");
|
|
45
62
|
return;
|
|
46
63
|
}
|
|
47
64
|
|
|
48
|
-
const files = await eject(root);
|
|
65
|
+
const { files, warnings } = await eject(root);
|
|
49
66
|
await updatePackageScripts(root);
|
|
50
67
|
|
|
68
|
+
// The same surface as the generated-runtime path (prepare.ts): one warn
|
|
69
|
+
// per generation warning, e.g. a Scalar reference spec that wasn't found.
|
|
70
|
+
for (const warning of warnings) {
|
|
71
|
+
logger.warn(warning);
|
|
72
|
+
}
|
|
73
|
+
|
|
51
74
|
logger.success(`Ejected ${files.length} file(s):`);
|
|
52
75
|
for (const file of files) {
|
|
53
76
|
process.stdout.write(` ${relative(root, file)}\n`);
|
|
54
77
|
}
|
|
78
|
+
reportDroppedArtifacts(notices);
|
|
79
|
+
// Print run commands matching the user's package manager, detected the
|
|
80
|
+
// same way as `blume init`'s next-steps hint.
|
|
81
|
+
const pm = detectPackageManager(process.env.npm_config_user_agent);
|
|
82
|
+
const { build, dev } = commandsFor(pm);
|
|
55
83
|
logger.box(
|
|
56
|
-
|
|
84
|
+
`Your project is now a standalone Astro app.\n\n ${dev}\n ${build}\n\nThe blume package remains importable.`
|
|
57
85
|
);
|
|
58
86
|
},
|
|
59
87
|
});
|