blume 1.0.4 → 1.1.1
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 +80 -0
- package/dist/cli/index.js +13404 -10228
- package/dist/cli/index.js.map +94 -63
- package/dist/types/ai/component-markdown.d.ts +12 -1
- package/dist/types/core/config-input.d.ts +73 -4
- package/dist/types/core/data.d.ts +9 -0
- package/dist/types/core/deployment-env.d.ts +6 -0
- package/dist/types/core/diagnostics.d.ts +23 -0
- package/dist/types/core/i18n-ui.d.ts +8 -8
- package/dist/types/core/schema.d.ts +144 -22
- package/dist/types/core/sources/types.d.ts +3 -1
- package/dist/types/core/standard-schema.d.ts +41 -0
- package/dist/types/core/types.d.ts +20 -0
- package/dist/types/og/card.d.ts +63 -0
- package/dist/types/og/dimensions.d.ts +12 -0
- package/docs/01-quickstart.mdx +1 -1
- package/docs/02-deployment.mdx +9 -1
- package/docs/advanced/api-reference.mdx +11 -0
- package/docs/advanced/changelog.mdx +2 -2
- package/docs/advanced/skills.mdx +1 -1
- package/docs/configuration/ai.mdx +3 -3
- package/docs/configuration/customization.mdx +1 -1
- package/docs/configuration/export.mdx +1 -1
- package/docs/configuration/index.mdx +21 -1
- package/docs/configuration/search.mdx +28 -1
- package/docs/configuration/seo.mdx +37 -2
- package/docs/configuration/theming.mdx +1 -1
- package/docs/content/components.mdx +14 -0
- package/docs/content/index.mdx +1 -1
- package/docs/content/meta.mdx +1 -1
- package/docs/content/navigation.mdx +5 -1
- package/docs/content/sources.mdx +1 -1
- package/docs/reference/cli.mdx +80 -2
- package/docs/reference/frontmatter.mdx +31 -1
- package/package.json +4 -3
- package/skills/blume-migrate/SKILL.md +1 -1
- package/skills/blume-migrate/references/mintlify.md +3 -2
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +16 -4
- package/src/ai/component-markdown.ts +39 -11
- package/src/ai/llms.ts +19 -2
- package/src/ai/markdown.ts +5 -1
- package/src/astro/adapter-root.ts +70 -0
- package/src/astro/generate.ts +124 -50
- package/src/astro/index.ts +1 -0
- package/src/astro/pages.ts +39 -8
- package/src/astro/templates.ts +93 -28
- package/src/audit/agent.ts +114 -0
- package/src/audit/catalog.ts +826 -0
- package/src/audit/checks/assets.ts +177 -0
- package/src/audit/checks/content.ts +231 -0
- package/src/audit/checks/duplicates.ts +131 -0
- package/src/audit/checks/i18n.ts +246 -0
- package/src/audit/checks/indexability.ts +213 -0
- package/src/audit/checks/links.ts +223 -0
- package/src/audit/checks/llms.ts +138 -0
- package/src/audit/checks/network.ts +272 -0
- package/src/audit/checks/og-image.ts +113 -0
- package/src/audit/checks/redirects.ts +87 -0
- package/src/audit/checks/robots.ts +114 -0
- package/src/audit/checks/sitemap.ts +229 -0
- package/src/audit/checks/social.ts +238 -0
- package/src/audit/crawl.ts +259 -0
- package/src/audit/graph.ts +74 -0
- package/src/audit/html.ts +54 -0
- package/src/audit/image-size.ts +63 -0
- package/src/audit/locate.ts +33 -0
- package/src/audit/redirects.ts +74 -0
- package/src/audit/report.ts +278 -0
- package/src/audit/run.ts +198 -0
- package/src/audit/snapshot.ts +189 -0
- package/src/audit/types.ts +214 -0
- package/src/audit/url.ts +103 -0
- package/src/cli/commands/audit.ts +205 -0
- package/src/cli/commands/build.ts +64 -13
- package/src/cli/index.ts +2 -0
- package/src/cli/prepare.ts +10 -2
- package/src/components/content/Tabs.astro +98 -15
- package/src/components/layout/Breadcrumbs.astro +1 -1
- package/src/components/layout/Header.astro +1 -0
- package/src/components/layout/PageFeedback.astro +1 -1
- package/src/components/layout/PageLayout.astro +5 -1
- package/src/components/layout/Pagination.astro +1 -1
- package/src/components/layout/RootLayout.astro +5 -3
- package/src/components/layout/Search.astro +35 -6
- package/src/components/layout/TableOfContents.astro +1 -1
- package/src/components/openapi/Authorization.astro +80 -0
- package/src/components/openapi/Operation.astro +19 -1
- package/src/components/openapi/ParametersTable.astro +1 -1
- package/src/components/openapi/security.ts +201 -0
- package/src/components/openapi/snippets.ts +42 -13
- package/src/core/config-input.ts +78 -4
- package/src/core/data.ts +9 -1
- package/src/core/deployment-env.ts +9 -0
- package/src/core/diagnostics.ts +61 -12
- package/src/core/graph.ts +23 -4
- package/src/core/links.ts +2 -91
- package/src/core/nav-diagnostics.ts +48 -4
- package/src/core/navigation.ts +169 -14
- package/src/core/probe.ts +136 -0
- package/src/core/project-graph.ts +54 -20
- package/src/core/schema.ts +93 -3
- package/src/core/sources/github-releases.ts +65 -2
- package/src/core/sources/normalize.ts +198 -25
- package/src/core/sources/types.ts +3 -1
- package/src/core/standard-schema.ts +54 -0
- package/src/core/types.ts +20 -0
- package/src/deploy/adapter-output.ts +27 -15
- package/src/deploy/headers.ts +66 -0
- package/src/deploy/redirects.ts +49 -9
- package/src/markdown/index.ts +1 -0
- package/src/markdown/twoslash.ts +60 -0
- package/src/og/card.ts +98 -33
- package/src/og/index.ts +1 -1
- package/src/registry/eject.ts +3 -1
- package/src/search/popular.ts +33 -0
- package/src/theme/entry.ts +6 -1
- /package/docs/{03-faq.mdx → 07-faq.mdx} +0 -0
package/src/astro/templates.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type { ResolvedConfig } from "../core/schema.ts";
|
|
|
10
10
|
import { BLUME_IGNORE_DIRS } from "../core/sources/watch.ts";
|
|
11
11
|
import { trimChar } from "../core/trim.ts";
|
|
12
12
|
import type { ProjectContext } from "../core/types.ts";
|
|
13
|
-
import {
|
|
13
|
+
import { applyBaseToAstroRedirects } from "../deploy/redirects.ts";
|
|
14
14
|
import { hasScalarReferences } from "../openapi/references.ts";
|
|
15
15
|
import { searchProviderMeta } from "../search/providers.ts";
|
|
16
16
|
import { buildFontEntries } from "../theme/fonts.ts";
|
|
@@ -166,11 +166,16 @@ export const runtimeDependencies = (options: {
|
|
|
166
166
|
* static-prerender Vite environments.
|
|
167
167
|
*
|
|
168
168
|
* Two reasons a dep lands here:
|
|
169
|
-
* -
|
|
170
|
-
* platform-specific `.node` binding via
|
|
171
|
-
* Bundling it relocates `import.meta.url`
|
|
172
|
-
* ("Cannot find native binding") on other
|
|
173
|
-
* runner), so it must resolve from
|
|
169
|
+
* - `takumi-js` (OG image rendering) loads `@takumi-rs/core`, a native NAPI
|
|
170
|
+
* addon that finds its platform-specific `.node` binding via
|
|
171
|
+
* `createRequire(import.meta.url)`. Bundling it relocates `import.meta.url`
|
|
172
|
+
* and breaks the binding lookup ("Cannot find native binding") on other
|
|
173
|
+
* platforms (e.g. the Linux CI runner), so it must resolve from
|
|
174
|
+
* `node_modules` at runtime instead. The prerender env matches these by
|
|
175
|
+
* exact specifier, so every entry point Blume imports has to be listed:
|
|
176
|
+
* the bare `takumi-js` (render) plus `takumi-js/helpers` (the `googleFonts`
|
|
177
|
+
* OG-font loader). The `@takumi-rs/*` packages are listed too so the native
|
|
178
|
+
* backend is never pulled into a chunk down any transitive path.
|
|
174
179
|
* - The rest are pure-JS packages kept external so an isolated linker (Bun's
|
|
175
180
|
* `isolated` mode, pnpm) doesn't bundle their symlinked store copies. When
|
|
176
181
|
* Vite bundles such a package but leaves its own `node_modules` child
|
|
@@ -190,10 +195,13 @@ const RENDER_EXTERNAL_DEPS = [
|
|
|
190
195
|
"@shikijs/transformers",
|
|
191
196
|
"@takumi-rs/core",
|
|
192
197
|
"@takumi-rs/helpers",
|
|
198
|
+
"@takumi-rs/wasm",
|
|
193
199
|
"github-slugger",
|
|
194
200
|
"katex",
|
|
195
201
|
"shiki",
|
|
196
202
|
"simple-icons",
|
|
203
|
+
"takumi-js",
|
|
204
|
+
"takumi-js/helpers",
|
|
197
205
|
"zod",
|
|
198
206
|
];
|
|
199
207
|
|
|
@@ -212,6 +220,21 @@ const renderUserAliases = (
|
|
|
212
220
|
const astroOutDir = (context: ProjectContext): string =>
|
|
213
221
|
context.distDir ?? `${context.root}/dist`;
|
|
214
222
|
|
|
223
|
+
/**
|
|
224
|
+
* The root a deploy adapter is shown, in place of the `.blume` runtime Astro
|
|
225
|
+
* actually roots at. Adapters assume `outDir` is `<root>/dist` and resolve their
|
|
226
|
+
* own output (and Vercel's dependency trace) against `root`, so the root implied
|
|
227
|
+
* by Blume's `outDir` is the one that keeps that assumption true. See
|
|
228
|
+
* {@link withAdapterRoot}.
|
|
229
|
+
*
|
|
230
|
+
* For a normal build that is the project root (`<project>/dist` -> `<project>`).
|
|
231
|
+
* For a relocated runtime (`blume build --isolated`) it is the runtime dir
|
|
232
|
+
* itself (`<runtime>/dist` -> `<runtime>`), keeping a verify build's adapter
|
|
233
|
+
* output self-contained instead of overwriting the real `.vercel/output`.
|
|
234
|
+
*/
|
|
235
|
+
const adapterRoot = (context: ProjectContext): string =>
|
|
236
|
+
dirname(astroOutDir(context));
|
|
237
|
+
|
|
215
238
|
/**
|
|
216
239
|
* Excludes Vite's pre-bundled dep cache from @vitejs/plugin-react. Astro's
|
|
217
240
|
* react() replaces the plugin's default `/node_modules/` exclude with just
|
|
@@ -296,8 +319,17 @@ export const astroConfigTemplate = (options: {
|
|
|
296
319
|
}
|
|
297
320
|
return ADAPTER_OPTIONS[deployment.adapter] ?? "";
|
|
298
321
|
})();
|
|
322
|
+
// Vercel resolves its Build Output tree and its `@vercel/nft` dependency
|
|
323
|
+
// trace against the Astro root, which for Blume is the hidden `.blume`
|
|
324
|
+
// runtime — leaving the traced function without its chunks or node_modules.
|
|
325
|
+
// The other adapters emit into `outDir` (cloudflare, node) or are surfaced
|
|
326
|
+
// afterwards (netlify), so none of them read `root` this way.
|
|
327
|
+
const adapterExpr =
|
|
328
|
+
deployment.adapter === "vercel"
|
|
329
|
+
? `withAdapterRoot(adapter(${adapterArgs}), ${JSON.stringify(adapterRoot(context))})`
|
|
330
|
+
: `adapter(${adapterArgs})`;
|
|
299
331
|
const adapterOption =
|
|
300
|
-
server && deployment.adapter ? `\n adapter:
|
|
332
|
+
server && deployment.adapter ? `\n adapter: ${adapterExpr},` : "";
|
|
301
333
|
|
|
302
334
|
const siteOption = deployment.site
|
|
303
335
|
? `\n site: ${JSON.stringify(deployment.site)},`
|
|
@@ -320,10 +352,12 @@ export const astroConfigTemplate = (options: {
|
|
|
320
352
|
: "";
|
|
321
353
|
|
|
322
354
|
// Base the redirect paths the same way routes are based, so a redirect lands
|
|
323
|
-
// under `basePath` too. Astro layers its own `base` (deployment.base)
|
|
324
|
-
|
|
355
|
+
// under `basePath` too. Astro layers its own `base` (deployment.base) onto
|
|
356
|
+
// `from` when matching, but never onto `to` — see applyBaseToAstroRedirects.
|
|
357
|
+
const basedRedirects = applyBaseToAstroRedirects(
|
|
325
358
|
config.redirects,
|
|
326
|
-
config.basePath
|
|
359
|
+
config.basePath,
|
|
360
|
+
deployment.base ?? ""
|
|
327
361
|
);
|
|
328
362
|
const redirectsOption =
|
|
329
363
|
basedRedirects.length > 0
|
|
@@ -365,14 +399,21 @@ export const astroConfigTemplate = (options: {
|
|
|
365
399
|
const svelteImport = needsSvelte
|
|
366
400
|
? `import svelte from "@astrojs/svelte";\n`
|
|
367
401
|
: "";
|
|
368
|
-
const
|
|
402
|
+
const blumeImports = [
|
|
403
|
+
"blumeIntegration",
|
|
404
|
+
"prerenderDepsPlugin",
|
|
405
|
+
"serverAppResolvePlugin",
|
|
406
|
+
...(adapterOption.includes("withAdapterRoot") ? ["withAdapterRoot"] : []),
|
|
407
|
+
];
|
|
408
|
+
const blumeImport = `import { ${blumeImports.join(", ")} } from "blume/astro";\n`;
|
|
369
409
|
|
|
370
410
|
// Twoslash runs first, before the always-on transformers, but only on fences
|
|
371
411
|
// with the `twoslash` meta (explicitTrigger) — so it's opt-in per block with
|
|
372
412
|
// no config flag; the TypeScript compiler only spins up when a block uses it.
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
413
|
+
// Blume's preconfigured transformer compiles with the package's own pinned
|
|
414
|
+
// classic TypeScript, so the user's project can be on any version (see
|
|
415
|
+
// markdown/twoslash.ts).
|
|
416
|
+
const twoslashTransformer = "blumeTwoslashTransformer(), ";
|
|
376
417
|
|
|
377
418
|
// Content links are rewritten to their real served URL: the `deployment.base`
|
|
378
419
|
// subdirectory (Astro doesn't rewrite `<a href>`) layered over the site-wide
|
|
@@ -409,8 +450,8 @@ export const astroConfigTemplate = (options: {
|
|
|
409
450
|
${defineConfigImport}
|
|
410
451
|
import mdx from "@astrojs/mdx";
|
|
411
452
|
import tailwindcss from "@tailwindcss/vite";
|
|
412
|
-
import { blumeMarkdownProcessor, blumeMdxProcessor, blumeShikiTransformers } from "blume/markdown";
|
|
413
|
-
${
|
|
453
|
+
import { blumeMarkdownProcessor, blumeMdxProcessor, blumeShikiTransformers, blumeTwoslashTransformer } from "blume/markdown";
|
|
454
|
+
${reactImport}${vueImport}${svelteImport}${blumeImport}${adapterImport}
|
|
414
455
|
export default defineConfig({
|
|
415
456
|
root: ${JSON.stringify(context.outDir)},
|
|
416
457
|
srcDir: ${JSON.stringify(`${context.outDir}/src`)},
|
|
@@ -1037,7 +1078,9 @@ import data from "blume:data";
|
|
|
1037
1078
|
export const prerender = true;
|
|
1038
1079
|
|
|
1039
1080
|
// Custom (non-content) pages opted into a generated card, baked in at build.
|
|
1040
|
-
|
|
1081
|
+
// The annotation keeps the empty-array case from being an implicit any[]
|
|
1082
|
+
// (ts(7034)) under a strict tsconfig.
|
|
1083
|
+
const customRoutes: { slug: string; title: string }[] = ${JSON.stringify(customRoutes)};
|
|
1041
1084
|
|
|
1042
1085
|
export function getStaticPaths() {
|
|
1043
1086
|
const seen = new Set<string>();
|
|
@@ -1080,6 +1123,7 @@ export async function GET({ props }: { props: { title: string } }) {
|
|
|
1080
1123
|
accent: data.config.og.palette?.accent ?? data.config.theme.accent.light,
|
|
1081
1124
|
brand: data.config.title,
|
|
1082
1125
|
description: data.config.description,
|
|
1126
|
+
fonts: data.config.og.fonts,
|
|
1083
1127
|
logo: data.config.og.logo,
|
|
1084
1128
|
palette: data.config.og.palette,
|
|
1085
1129
|
repo: repoSlug,
|
|
@@ -1475,6 +1519,18 @@ const toTime = (value: string | null | undefined) => {
|
|
|
1475
1519
|
return Number.isNaN(date.getTime()) ? 0 : date.getTime();
|
|
1476
1520
|
};
|
|
1477
1521
|
|
|
1522
|
+
// The changelog is an unlocalized route, so its chrome renders in the default
|
|
1523
|
+
// locale's dictionary and direction (\`data.ui\` is the default locale's resolved
|
|
1524
|
+
// dictionary), mirroring the catch-all's locale wiring.
|
|
1525
|
+
const i18n = data.config.i18n;
|
|
1526
|
+
const localeMeta = i18n
|
|
1527
|
+
? i18n.locales.find((l) => l.code === i18n.defaultLocale)
|
|
1528
|
+
: null;
|
|
1529
|
+
const dir = localeMeta?.dir ?? "ltr";
|
|
1530
|
+
const htmlLang = i18n ? i18n.defaultLocale : "en";
|
|
1531
|
+
|
|
1532
|
+
// Formatted in the same locale as the chrome, and in UTC, to match the
|
|
1533
|
+
// per-page "last updated" stamp.
|
|
1478
1534
|
const formatDate = (value: string | null | undefined) => {
|
|
1479
1535
|
if (!value) {
|
|
1480
1536
|
return;
|
|
@@ -1482,7 +1538,7 @@ const formatDate = (value: string | null | undefined) => {
|
|
|
1482
1538
|
const date = new Date(value);
|
|
1483
1539
|
return Number.isNaN(date.getTime())
|
|
1484
1540
|
? undefined
|
|
1485
|
-
: new Intl.DateTimeFormat(
|
|
1541
|
+
: new Intl.DateTimeFormat(htmlLang, {
|
|
1486
1542
|
dateStyle: "long",
|
|
1487
1543
|
timeZone: "UTC",
|
|
1488
1544
|
}).format(date);
|
|
@@ -1579,16 +1635,6 @@ const base = data.config.site ? data.config.site.replace(/\\/$/, "") : null;
|
|
|
1579
1635
|
const basedRoute = withBase("/changelog");
|
|
1580
1636
|
const canonical = base ? base + basedRoute : null;
|
|
1581
1637
|
|
|
1582
|
-
// The changelog is an unlocalized route, so its chrome renders in the default
|
|
1583
|
-
// locale's dictionary and direction (\`data.ui\` is the default locale's resolved
|
|
1584
|
-
// dictionary), mirroring the catch-all's locale wiring.
|
|
1585
|
-
const i18n = data.config.i18n;
|
|
1586
|
-
const localeMeta = i18n
|
|
1587
|
-
? i18n.locales.find((l) => l.code === i18n.defaultLocale)
|
|
1588
|
-
: null;
|
|
1589
|
-
const dir = localeMeta?.dir ?? "ltr";
|
|
1590
|
-
const htmlLang = i18n ? i18n.defaultLocale : "en";
|
|
1591
|
-
|
|
1592
1638
|
// The page chrome (h1, title, description) comes from the same translatable
|
|
1593
1639
|
// \`changelog\` group as the reveal button; optional chaining tolerates a
|
|
1594
1640
|
// not-yet-regenerated data snapshot from before these keys existed.
|
|
@@ -1760,6 +1806,23 @@ const islandDirective = (spec: IslandSpec): string =>
|
|
|
1760
1806
|
? `client:only="${spec.framework}"`
|
|
1761
1807
|
: `client:${spec.client}`;
|
|
1762
1808
|
|
|
1809
|
+
/**
|
|
1810
|
+
* Frontmatter `Props` alias mirroring the wrapped component's own props, so
|
|
1811
|
+
* `{...Astro.props}` satisfies required props under `astro check` (the spread
|
|
1812
|
+
* of an untyped `Astro.props` contributes nothing to the JSX props type).
|
|
1813
|
+
* `infer P extends object` rather than `Record<string, unknown>` because
|
|
1814
|
+
* interfaces have no implicit index signature and would miss the narrower
|
|
1815
|
+
* constraint. Non-function component types (Vue/Svelte ambient modules) fall
|
|
1816
|
+
* back to an open record, keeping the untyped permissiveness they had.
|
|
1817
|
+
*/
|
|
1818
|
+
const wrapperPropsType = (name: string): string =>
|
|
1819
|
+
`type Props = typeof ${name} extends (
|
|
1820
|
+
props: infer P extends object,
|
|
1821
|
+
...rest: never[]
|
|
1822
|
+
) => unknown
|
|
1823
|
+
? P
|
|
1824
|
+
: Record<string, unknown>;`;
|
|
1825
|
+
|
|
1763
1826
|
/**
|
|
1764
1827
|
* Generate `.blume/src/generated/islands/<Name>.astro` — a wrapper that renders
|
|
1765
1828
|
* a convention island with its hydration directive applied. Astro client
|
|
@@ -1770,6 +1833,7 @@ export const islandWrapperTemplate = (spec: IslandSpec): string =>
|
|
|
1770
1833
|
`---
|
|
1771
1834
|
// Generated by Blume. Do not edit.
|
|
1772
1835
|
import Island from ${JSON.stringify(spec.file)};
|
|
1836
|
+
${wrapperPropsType("Island")}
|
|
1773
1837
|
---
|
|
1774
1838
|
<Island ${islandDirective(spec)} {...Astro.props}><slot /></Island>
|
|
1775
1839
|
`;
|
|
@@ -1832,6 +1896,7 @@ export const exampleWrapperTemplate = (spec: ExampleSpec): string =>
|
|
|
1832
1896
|
`---
|
|
1833
1897
|
// Generated by Blume. Do not edit.
|
|
1834
1898
|
import Example from ${JSON.stringify(spec.file)};
|
|
1899
|
+
${wrapperPropsType("Example")}
|
|
1835
1900
|
---
|
|
1836
1901
|
<Example ${exampleDirective(spec)}{...Astro.props}><slot /></Example>
|
|
1837
1902
|
`;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { mkdtemp, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
|
|
5
|
+
import { join } from "pathe";
|
|
6
|
+
|
|
7
|
+
import { reportJson } from "./report.ts";
|
|
8
|
+
import type { AuditResult } from "./run.ts";
|
|
9
|
+
|
|
10
|
+
/** A coding agent CLI the audit can hand its findings to (`--claude`, `--codex`). */
|
|
11
|
+
export interface AgentCli {
|
|
12
|
+
/** The executable to look up on PATH. */
|
|
13
|
+
bin: string;
|
|
14
|
+
/** How to install it, shown when the executable is missing. */
|
|
15
|
+
install: string;
|
|
16
|
+
/** Display name for messages. */
|
|
17
|
+
name: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type AgentKind = "claude" | "codex";
|
|
21
|
+
|
|
22
|
+
export const AGENTS: Record<AgentKind, AgentCli> = {
|
|
23
|
+
claude: {
|
|
24
|
+
bin: "claude",
|
|
25
|
+
install: "npm install -g @anthropic-ai/claude-code",
|
|
26
|
+
name: "Claude Code",
|
|
27
|
+
},
|
|
28
|
+
codex: {
|
|
29
|
+
bin: "codex",
|
|
30
|
+
install: "npm install -g @openai/codex",
|
|
31
|
+
name: "Codex",
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Write the full JSON report where the agent can read it. A file rather than
|
|
37
|
+
* inline prompt text: a large site's report can exceed the platform's argv
|
|
38
|
+
* limit, and the JSON already carries every finding untruncated — the terminal
|
|
39
|
+
* report previews three pages per check, the file never does.
|
|
40
|
+
*/
|
|
41
|
+
export const writeAgentReport = async (
|
|
42
|
+
result: AuditResult,
|
|
43
|
+
root: string
|
|
44
|
+
): Promise<string> => {
|
|
45
|
+
const dir = await mkdtemp(join(tmpdir(), "blume-audit-"));
|
|
46
|
+
const path = join(dir, "report.json");
|
|
47
|
+
await writeFile(path, reportJson(result, root));
|
|
48
|
+
return path;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** The handoff prompt: where the report is, how to read it, and the ground rules. */
|
|
52
|
+
export const fixPrompt = (reportPath: string): string =>
|
|
53
|
+
`Fix the issues found by \`blume audit\` in this project.
|
|
54
|
+
|
|
55
|
+
The full audit report is at ${reportPath}. It is JSON: each entry in \`diagnostics\` is one finding, with the check \`code\`, a \`message\` explaining what is wrong, the affected page \`url\`, the source \`file\` to edit (relative to the current directory, with a \`line\` when the finding points at a specific front matter key), and a \`suggestion\` describing the fix.
|
|
56
|
+
|
|
57
|
+
Work through every finding:
|
|
58
|
+
1. Read the report and group the findings by \`file\`.
|
|
59
|
+
2. Apply each finding's \`suggestion\` by editing the named source file — most fixes are front matter edits at the cited line.
|
|
60
|
+
3. Never fix a finding by deleting a page, removing content, or hiding it from the audit; if a finding genuinely needs a human decision, leave it and say so in your summary.
|
|
61
|
+
|
|
62
|
+
When you are done, run \`blume build\` and then \`blume audit\` to verify, and repeat until the audit reports no issues.`;
|
|
63
|
+
|
|
64
|
+
const spawnAgent = (
|
|
65
|
+
command: string,
|
|
66
|
+
args: string[],
|
|
67
|
+
shell: boolean
|
|
68
|
+
): Promise<number> =>
|
|
69
|
+
// oxlint-disable-next-line promise/avoid-new -- adapt spawn's event callbacks
|
|
70
|
+
new Promise((resolve, reject) => {
|
|
71
|
+
const child = spawn(command, args, { shell, stdio: "inherit" });
|
|
72
|
+
child.once("error", reject);
|
|
73
|
+
child.once("close", (code) => resolve(code ?? 1));
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* cmd.exe reports a missing executable through this exit code instead of a
|
|
78
|
+
* spawn error, so a shell launch can't rely on the `error` event for the
|
|
79
|
+
* "not installed" diagnosis.
|
|
80
|
+
*/
|
|
81
|
+
export const WINDOWS_COMMAND_NOT_FOUND = 9009;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Run the agent CLI interactively with the handoff prompt, inheriting the
|
|
85
|
+
* terminal so the user watches and steers the fixes rather than granting a
|
|
86
|
+
* headless process blanket write access. Resolves with the agent's exit code;
|
|
87
|
+
* rejects when the executable isn't on PATH.
|
|
88
|
+
*
|
|
89
|
+
* On Windows, npm installs agent CLIs as `.cmd` shims, which Node refuses to
|
|
90
|
+
* spawn without a shell — and cmd.exe cannot carry the multi-line prompt as an
|
|
91
|
+
* argument (a newline ends the command). So there the prompt is written to a
|
|
92
|
+
* file next to the report and handed over via a one-line pointer that survives
|
|
93
|
+
* cmd.exe quoting; a missing executable surfaces as
|
|
94
|
+
* {@link WINDOWS_COMMAND_NOT_FOUND} rather than a rejection.
|
|
95
|
+
*/
|
|
96
|
+
export const launchAgent = async (
|
|
97
|
+
bin: string,
|
|
98
|
+
prompt: string,
|
|
99
|
+
platform: NodeJS.Platform = process.platform
|
|
100
|
+
): Promise<number> => {
|
|
101
|
+
if (platform !== "win32") {
|
|
102
|
+
return await spawnAgent(bin, [prompt], false);
|
|
103
|
+
}
|
|
104
|
+
const dir = await mkdtemp(join(tmpdir(), "blume-audit-"));
|
|
105
|
+
const promptPath = join(dir, "prompt.md");
|
|
106
|
+
await writeFile(promptPath, prompt);
|
|
107
|
+
// Double quotes are the one grouping cmd.exe respects; neither the temp
|
|
108
|
+
// path nor the fixed pointer text can contain one.
|
|
109
|
+
return await spawnAgent(
|
|
110
|
+
`"${bin}" "Read ${promptPath} and follow its instructions exactly."`,
|
|
111
|
+
[],
|
|
112
|
+
true
|
|
113
|
+
);
|
|
114
|
+
};
|