blume 0.7.0 → 0.8.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 +618 -0
- package/LICENSE +21 -0
- package/README.md +107 -0
- package/dist/cli/index.js +1487 -360
- package/dist/cli/index.js.map +91 -85
- 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 +36 -2
- package/dist/types/core/config.d.ts +3 -2
- package/dist/types/core/data.d.ts +2 -0
- package/dist/types/core/i18n-ui.d.ts +476 -132
- package/dist/types/core/schema.d.ts +216 -145
- package/dist/types/index.d.ts +1 -0
- package/dist/types/openapi/references.d.ts +60 -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/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/index.mdx +27 -3
- package/docs/configuration/seo.mdx +2 -2
- package/docs/content/components.mdx +1 -1
- 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 +4 -2
- package/docs/index.mdx +2 -2
- package/docs/reference/cli.mdx +8 -6
- package/package.json +14 -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/visibility.ts +74 -0
- package/src/astro/component-slots.ts +11 -1
- package/src/astro/generate.ts +76 -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 +99 -12
- package/src/blume-modules.d.ts +8 -0
- package/src/cli/commands/build.ts +99 -19
- 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/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 +56 -6
- package/src/components/islands/hooks.ts +28 -8
- package/src/components/layout/Banner.astro +10 -2
- package/src/components/layout/Header.astro +13 -4
- package/src/components/layout/Logo.astro +11 -3
- package/src/components/layout/NavTree.astro +17 -3
- package/src/components/layout/PageActions.astro +25 -10
- package/src/components/layout/PageLayout.astro +45 -8
- package/src/components/layout/ReferenceLayout.astro +8 -1
- package/src/components/layout/RootLayout.astro +67 -9
- package/src/components/layout/Search.astro +94 -22
- 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 +37 -2
- package/src/core/config.ts +3 -2
- package/src/core/data.ts +2 -0
- package/src/core/graph.ts +15 -5
- package/src/core/i18n-ui.ts +45 -0
- 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 +36 -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 +1 -0
- package/src/core/ui-packs/bg.ts +1 -0
- package/src/core/ui-packs/bn.ts +1 -0
- package/src/core/ui-packs/ca.ts +1 -0
- package/src/core/ui-packs/cs.ts +1 -0
- package/src/core/ui-packs/da.ts +1 -0
- package/src/core/ui-packs/de.ts +1 -0
- package/src/core/ui-packs/el.ts +1 -0
- package/src/core/ui-packs/es.ts +1 -0
- package/src/core/ui-packs/fa.ts +1 -0
- package/src/core/ui-packs/fi.ts +1 -0
- package/src/core/ui-packs/fr.ts +2 -1
- package/src/core/ui-packs/he.ts +1 -0
- package/src/core/ui-packs/hi.ts +1 -0
- package/src/core/ui-packs/hr.ts +1 -0
- package/src/core/ui-packs/hu.ts +1 -0
- package/src/core/ui-packs/id.ts +1 -0
- package/src/core/ui-packs/it.ts +1 -0
- package/src/core/ui-packs/ja.ts +1 -0
- package/src/core/ui-packs/ko.ts +1 -0
- package/src/core/ui-packs/nl.ts +1 -0
- package/src/core/ui-packs/no.ts +1 -0
- package/src/core/ui-packs/pl.ts +1 -0
- package/src/core/ui-packs/pt-br.ts +1 -0
- package/src/core/ui-packs/pt.ts +1 -0
- package/src/core/ui-packs/ro.ts +1 -0
- package/src/core/ui-packs/ru.ts +1 -0
- package/src/core/ui-packs/sk.ts +1 -0
- package/src/core/ui-packs/sr.ts +1 -0
- package/src/core/ui-packs/sv.ts +1 -0
- package/src/core/ui-packs/th.ts +1 -0
- package/src/core/ui-packs/tr.ts +1 -0
- package/src/core/ui-packs/uk.ts +1 -0
- package/src/core/ui-packs/vi.ts +1 -0
- package/src/core/ui-packs/zh-tw.ts +1 -0
- package/src/core/ui-packs/zh.ts +1 -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/openapi/model.ts +12 -4
- package/src/openapi/parse.ts +21 -0
- package/src/openapi/references.ts +38 -8
- 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 +13 -12
|
@@ -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,
|
|
@@ -189,11 +190,89 @@ const enforceBudget = async (
|
|
|
189
190
|
return passed ? "pass" : "fail";
|
|
190
191
|
};
|
|
191
192
|
|
|
193
|
+
/**
|
|
194
|
+
* Run the optional bundle report (`--analyze`) and performance-budget gate
|
|
195
|
+
* against the directory whose `_astro/` client assets the deploy serves.
|
|
196
|
+
* Shared by real and isolated builds — an isolated CI run passing
|
|
197
|
+
* `--budget-js` must still fail on an exceeded budget rather than silently
|
|
198
|
+
* skipping the check. Exits non-zero when a budget is exceeded.
|
|
199
|
+
*/
|
|
200
|
+
export const runClientAssetChecks = async (
|
|
201
|
+
staticDir: string,
|
|
202
|
+
args: { analyze?: boolean } & BudgetArgs
|
|
203
|
+
): Promise<void> => {
|
|
204
|
+
if (args.analyze) {
|
|
205
|
+
await reportBundleSizes(staticDir);
|
|
206
|
+
}
|
|
207
|
+
if ((await enforceBudget(staticDir, args)) === "fail") {
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Directory holding an isolated build's client `_astro/` assets. Mirrors
|
|
214
|
+
* `deployStaticDir`, except that an isolated build never surfaces the adapter
|
|
215
|
+
* bundle to the project root — a Vercel server build's static output stays at
|
|
216
|
+
* `<runtime>/.vercel/output/static`, where `deployStaticDir` would instead
|
|
217
|
+
* point at the project-root copy (a previous real build's assets, or nothing).
|
|
218
|
+
*/
|
|
219
|
+
export const isolatedStaticDir = (
|
|
220
|
+
config: ResolvedConfig,
|
|
221
|
+
context: ProjectContext
|
|
222
|
+
): string => {
|
|
223
|
+
const { adapter, output } = config.deployment;
|
|
224
|
+
if (output === "server" && adapter === "vercel") {
|
|
225
|
+
return join(context.outDir, ".vercel", "output", "static");
|
|
226
|
+
}
|
|
227
|
+
const dist = context.distDir ?? join(context.outDir, "dist");
|
|
228
|
+
if (output === "server" && adapter === "node") {
|
|
229
|
+
return join(dist, "client");
|
|
230
|
+
}
|
|
231
|
+
return dist;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Generate `llms.txt`/`llms-full.txt` into the dist dir. A user's own file in
|
|
236
|
+
* `public/` (copied into dist by Astro before this runs, like the sitemap and
|
|
237
|
+
* robots.txt) wins over the generated one — each file is checked and replaced
|
|
238
|
+
* independently, so a custom `llms.txt` still gets a generated `llms-full.txt`.
|
|
239
|
+
*/
|
|
240
|
+
const publishLlmsFiles = async (
|
|
241
|
+
project: BlumeProject,
|
|
242
|
+
distDir: string
|
|
243
|
+
): Promise<void> => {
|
|
244
|
+
const indexPath = join(distDir, "llms.txt");
|
|
245
|
+
const fullPath = join(distDir, "llms-full.txt");
|
|
246
|
+
const writeIndex = !existsSync(indexPath);
|
|
247
|
+
const writeFull = !existsSync(fullPath);
|
|
248
|
+
if (!(writeIndex || writeFull)) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const { index, full } = await buildLlmsFiles(project);
|
|
252
|
+
const writes: Promise<void>[] = [];
|
|
253
|
+
if (writeIndex) {
|
|
254
|
+
writes.push(writeFile(indexPath, index, "utf-8"));
|
|
255
|
+
}
|
|
256
|
+
if (writeFull) {
|
|
257
|
+
writes.push(writeFile(fullPath, full, "utf-8"));
|
|
258
|
+
}
|
|
259
|
+
await Promise.all(writes);
|
|
260
|
+
logger.success(
|
|
261
|
+
`Generated ${[
|
|
262
|
+
writeIndex ? "llms.txt" : null,
|
|
263
|
+
writeFull ? "llms-full.txt" : null,
|
|
264
|
+
]
|
|
265
|
+
.filter(Boolean)
|
|
266
|
+
.join(" and ")}`
|
|
267
|
+
);
|
|
268
|
+
};
|
|
269
|
+
|
|
192
270
|
/**
|
|
193
271
|
* Run every deploy post-step of a real (non-isolated) build: the search index +
|
|
194
272
|
* hosted-provider sync, llms.txt, sitemap/robots, redirect files, the summary
|
|
195
273
|
* box, and the optional bundle report / budget gate. Exits non-zero if a budget
|
|
196
|
-
* is exceeded. Isolated verify builds skip all of this
|
|
274
|
+
* is exceeded. Isolated verify builds skip all of this except the bundle
|
|
275
|
+
* report / budget gate, which they run against their own output.
|
|
197
276
|
*/
|
|
198
277
|
const publishBuildArtifacts = async (
|
|
199
278
|
project: BlumeProject,
|
|
@@ -214,13 +293,8 @@ const publishBuildArtifacts = async (
|
|
|
214
293
|
warn: (message) => logger.warn(message),
|
|
215
294
|
});
|
|
216
295
|
|
|
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");
|
|
296
|
+
if (project.config.ai.llmsTxt.enabled) {
|
|
297
|
+
await publishLlmsFiles(project, distDir);
|
|
224
298
|
}
|
|
225
299
|
|
|
226
300
|
// A user's own public/ file (copied into dist by Astro) always wins.
|
|
@@ -253,6 +327,11 @@ const publishBuildArtifacts = async (
|
|
|
253
327
|
|
|
254
328
|
const { config } = project;
|
|
255
329
|
const features = serverFeatures(config);
|
|
330
|
+
// `buildSitemap` returns null both when the sitemap is disabled and when no
|
|
331
|
+
// `site` is configured — only the latter deserves the remediation hint.
|
|
332
|
+
const sitemapNote = config.seo.sitemap
|
|
333
|
+
? "no (set deployment.site)"
|
|
334
|
+
: "no (seo.sitemap is false)";
|
|
256
335
|
logger.box(
|
|
257
336
|
[
|
|
258
337
|
`Output ${config.deployment.output}`,
|
|
@@ -260,21 +339,15 @@ const publishBuildArtifacts = async (
|
|
|
260
339
|
`Site ${config.deployment.site ?? "not set"}`,
|
|
261
340
|
`Search ${config.search.provider}`,
|
|
262
341
|
`Redirects ${config.redirects.length}`,
|
|
263
|
-
`Sitemap ${sitemap ? "yes" :
|
|
342
|
+
`Sitemap ${sitemap ? "yes" : sitemapNote}`,
|
|
264
343
|
`Robots ${robots ? "yes" : "no"}`,
|
|
265
344
|
`Agent JSON ${agentReadability ? "yes" : "no"}`,
|
|
266
|
-
`LLM files ${config.ai.llmsTxt ? "yes" : "no"}`,
|
|
345
|
+
`LLM files ${config.ai.llmsTxt.enabled ? "yes" : "no"}`,
|
|
267
346
|
`Server features ${features.length > 0 ? features.join(", ") : "none"}`,
|
|
268
347
|
].join("\n")
|
|
269
348
|
);
|
|
270
349
|
|
|
271
|
-
|
|
272
|
-
await reportBundleSizes(distDir);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if ((await enforceBudget(distDir, args)) === "fail") {
|
|
276
|
-
process.exit(1);
|
|
277
|
-
}
|
|
350
|
+
await runClientAssetChecks(distDir, args);
|
|
278
351
|
|
|
279
352
|
logger.success(`Built to ${distDir}`);
|
|
280
353
|
};
|
|
@@ -330,7 +403,7 @@ export const buildCommand = defineCommand({
|
|
|
330
403
|
const runtimeDir = args.isolated
|
|
331
404
|
? ".blume-verify"
|
|
332
405
|
: process.env.BLUME_RUNTIME_DIR;
|
|
333
|
-
refuseIfDevRunning(root, "building", runtimeDir);
|
|
406
|
+
refuseIfDevRunning(root, "building", { isolatedHint: true, runtimeDir });
|
|
334
407
|
if (args.isolated) {
|
|
335
408
|
await ensureGitignore(root, [".blume-verify/"]);
|
|
336
409
|
}
|
|
@@ -374,8 +447,15 @@ export const buildCommand = defineCommand({
|
|
|
374
447
|
// An isolated build is a throwaway verify: it only needs to confirm the site
|
|
375
448
|
// compiles and renders. Skip the network post-steps (search sync) and
|
|
376
449
|
// deploy artifacts (index/llms/sitemap/robots/redirects) that only matter
|
|
377
|
-
// for a real publish and would push to hosted providers.
|
|
450
|
+
// for a real publish and would push to hosted providers. The bundle report
|
|
451
|
+
// and budget gate still run, though — `blume build --isolated --budget-js
|
|
452
|
+
// 100` exiting 0 without measuring anything would be a silent false pass
|
|
453
|
+
// in CI.
|
|
378
454
|
if (runtimeDir) {
|
|
455
|
+
await runClientAssetChecks(
|
|
456
|
+
isolatedStaticDir(project.config, project.context),
|
|
457
|
+
args
|
|
458
|
+
);
|
|
379
459
|
logger.success(
|
|
380
460
|
`Isolated build OK — output at ${distDir} (not published).`
|
|
381
461
|
);
|
|
@@ -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
|
});
|