@ascribed/astro 0.1.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.
@@ -0,0 +1,27 @@
1
+ // The content collection over a build's site output. Import it from
2
+ // `content.config.ts`, which Vite loads, so it can read what the integration
3
+ // resolved (`virtual:ascribe/site`) instead of finding the project itself.
4
+ //
5
+ // import { defineCollection } from "astro:content";
6
+ // import { ascribeCollection } from "@ascribed/astro/content";
7
+ // import { schema } from "../.ascribe/build/site/site/_ascribe/schema.ts";
8
+ // export const collections = { docs: defineCollection(ascribeCollection({ schema })) };
9
+ import { pathToFileURL } from "node:url";
10
+ import path from "node:path";
11
+ import { glob } from "astro/loaders";
12
+ import { siteRoot } from "virtual:ascribe/site";
13
+ /**
14
+ * The collection's loader and schema: the `glob` loader on the site output's
15
+ * pages, with the generated Zod schema (`_ascribe/schema.ts`'s `schema`).
16
+ *
17
+ * Entry ids are Astro's own (`guides/my-setup` for `Guides/My Setup.md`, `index`
18
+ * for the root page), which is what `AstroRouter` in `tessera-resolve`
19
+ * computes, so a page's route is the base path plus its id, and the root page is
20
+ * at the base path itself.
21
+ */
22
+ export function ascribeCollection(options) {
23
+ // `_ascribe/` holds the schema and published files, never pages.
24
+ const base = pathToFileURL(siteRoot + path.sep);
25
+ return { loader: glob({ pattern: ["**/*.md", "!_ascribe/**"], base }), schema: options.schema };
26
+ }
27
+ //# sourceMappingURL=content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.js","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,6EAA6E;AAC7E,2EAA2E;AAC3E,EAAE;AACF,sDAAsD;AACtD,iEAAiE;AACjE,6EAA6E;AAC7E,0FAA0F;AAC1F,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEhD;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAAI,OAAsB;IAIzD,iEAAiE;IACjE,MAAM,IAAI,GAAG,aAAa,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;IAChD,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC,SAAS,EAAE,cAAc,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;AAClG,CAAC"}
package/dist/dev.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ import type { AstroIntegration } from "astro";
2
+ import { type ProjectInfo } from "./project.js";
3
+ type ServerOptions = Parameters<NonNullable<AstroIntegration["hooks"]["astro:server:setup"]>>[0];
4
+ /**
5
+ * The assets a build copied from outside the content root, as absolute paths,
6
+ * read from the site output's manifest. Watching the content root doesn't
7
+ * cover them. A manifest that's missing or unreadable gives none: the next
8
+ * successful build writes one.
9
+ */
10
+ export declare function outsideAssets(project: ProjectInfo, build: string): string[];
11
+ /** Watch source files, serialize compiler runs, and refresh Astro after successful output. */
12
+ export declare function watchDev(options: {
13
+ server: ServerOptions["server"];
14
+ refreshContent: () => Promise<void>;
15
+ logger: ServerOptions["logger"];
16
+ project: ProjectInfo;
17
+ astro: {
18
+ base: string;
19
+ trailingSlash: string;
20
+ site: string | undefined;
21
+ };
22
+ build: string;
23
+ rebuild: () => Promise<void>;
24
+ }): void;
25
+ export {};
package/dist/dev.js ADDED
@@ -0,0 +1,135 @@
1
+ import { readFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { consumerMismatches, readProject } from "./project.js";
4
+ /**
5
+ * The assets a build copied from outside the content root, as absolute paths,
6
+ * read from the site output's manifest. Watching the content root doesn't
7
+ * cover them. A manifest that's missing or unreadable gives none: the next
8
+ * successful build writes one.
9
+ */
10
+ export function outsideAssets(project, build) {
11
+ const manifest = path.join(path.dirname(project.siteRoot(build)), "site.manifest.json");
12
+ let files;
13
+ try {
14
+ files = JSON.parse(readFileSync(manifest, "utf8")).files;
15
+ }
16
+ catch {
17
+ return [];
18
+ }
19
+ if (!Array.isArray(files))
20
+ return [];
21
+ const assets = new Set();
22
+ for (const entry of files) {
23
+ if (entry.kind !== "asset" || typeof entry.source !== "string")
24
+ continue;
25
+ const absolute = path.resolve(project.contentRoot, entry.source);
26
+ if (!absolute.startsWith(project.contentRoot + path.sep))
27
+ assets.add(absolute);
28
+ }
29
+ return [...assets].sort();
30
+ }
31
+ /** Watch source files, serialize compiler runs, and refresh Astro after successful output. */
32
+ export function watchDev(options) {
33
+ const { server, refreshContent, logger, rebuild } = options;
34
+ let project = options.project;
35
+ let timer;
36
+ let pending = Promise.resolve();
37
+ let dirty = false;
38
+ let running = false;
39
+ let failed = false;
40
+ let assets = new Set();
41
+ const watchAssets = () => {
42
+ assets = new Set(outsideAssets(project, options.build));
43
+ if (assets.size > 0)
44
+ server.watcher.add([...assets]);
45
+ };
46
+ const drain = async () => {
47
+ running = true;
48
+ const previousProject = project;
49
+ const previousSiteRoot = previousProject.siteRoot(options.build);
50
+ while (dirty) {
51
+ dirty = false;
52
+ try {
53
+ await rebuild();
54
+ failed = false;
55
+ }
56
+ catch (error) {
57
+ failed = true;
58
+ logger.error(error instanceof Error ? error.message : String(error));
59
+ }
60
+ }
61
+ if (!failed) {
62
+ try {
63
+ const reloaded = readProject(project.dir);
64
+ if (reloaded.siteRoot(options.build) !== previousSiteRoot) {
65
+ throw new Error("ascribe.toml changed output-dir; restart astro dev to reload the collection.");
66
+ }
67
+ const mismatches = consumerMismatches(reloaded, options.astro);
68
+ if (mismatches.length > 0) {
69
+ throw new Error(`@ascribed/astro: ascribe.toml and astro.config disagree:\n- ${mismatches.join("\n- ")}`);
70
+ }
71
+ project = reloaded;
72
+ server.watcher.add(project.contentRoot);
73
+ server.watcher.add(project.configPath);
74
+ watchAssets();
75
+ await refreshContent();
76
+ server.ws.send({ type: "full-reload" });
77
+ }
78
+ catch (error) {
79
+ failed = true;
80
+ logger.error(`could not refresh Ascribe content: ${error instanceof Error ? error.message : String(error)}`);
81
+ }
82
+ }
83
+ running = false;
84
+ };
85
+ const schedule = () => {
86
+ if (timer)
87
+ clearTimeout(timer);
88
+ timer = setTimeout(() => {
89
+ timer = undefined;
90
+ pending = pending.then(drain);
91
+ }, 50);
92
+ };
93
+ const onChange = (event, file) => {
94
+ const absolute = path.resolve(file);
95
+ if ((absolute === project.configPath || assets.has(absolute)) &&
96
+ ["add", "change", "unlink"].includes(event)) {
97
+ dirty = true;
98
+ schedule();
99
+ return;
100
+ }
101
+ if (!["add", "change", "unlink"].includes(event) ||
102
+ !absolute.startsWith(project.contentRoot + path.sep) ||
103
+ absolute.startsWith(project.outputRoot + path.sep))
104
+ return;
105
+ dirty = true;
106
+ schedule();
107
+ };
108
+ server.watcher.add(project.contentRoot);
109
+ server.watcher.add(project.configPath);
110
+ watchAssets();
111
+ server.watcher.on("all", onChange);
112
+ server.httpServer?.once("close", () => {
113
+ if (timer)
114
+ clearTimeout(timer);
115
+ server.watcher.off("all", onChange);
116
+ });
117
+ server.middlewares.use((_req, res, next) => {
118
+ if (!dirty && !running && !failed)
119
+ return next();
120
+ if (timer) {
121
+ clearTimeout(timer);
122
+ timer = undefined;
123
+ pending = pending.then(drain);
124
+ }
125
+ void pending.then(() => {
126
+ if (failed) {
127
+ res.statusCode = 503;
128
+ res.end("Ascribe build failed; fix the source and save to retry.");
129
+ }
130
+ else
131
+ next();
132
+ });
133
+ });
134
+ }
135
+ //# sourceMappingURL=dev.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev.js","sourceRoot":"","sources":["../src/dev.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ /** The directory of published files inside a site output root. */
3
+ export declare function publishedDir(siteRoot: string): string;
4
+ /** Copies the published files to `<outDir>/_ascribe/files/`. Returns how many top-level entries were there. */
5
+ export declare function copyPublishedFiles(siteRoot: string, outDir: URL): Promise<boolean>;
6
+ /** A dev-server middleware serving the published files at `<base>_ascribe/files/`. */
7
+ export declare function filesMiddleware(siteRoot: string, base: string): (req: IncomingMessage, res: ServerResponse, next: () => void) => void;
package/dist/files.js ADDED
@@ -0,0 +1,71 @@
1
+ // Serving `_ascribe/files/`: the files pages link to that aren't pages or
2
+ // images. Astro doesn't copy them, so the integration
3
+ // does: into the build's output directory, and in dev through a middleware.
4
+ // Both put them at `<base>_ascribe/files/`, the URL the site output links to.
5
+ import { createReadStream } from "node:fs";
6
+ import { cp, stat } from "node:fs/promises";
7
+ import path from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+ /** The directory of published files inside a site output root. */
10
+ export function publishedDir(siteRoot) {
11
+ return path.join(siteRoot, "_ascribe", "files");
12
+ }
13
+ /** Copies the published files to `<outDir>/_ascribe/files/`. Returns how many top-level entries were there. */
14
+ export async function copyPublishedFiles(siteRoot, outDir) {
15
+ const from = publishedDir(siteRoot);
16
+ if (!(await stat(from).then((s) => s.isDirectory(), () => false)))
17
+ return false;
18
+ await cp(from, path.join(fileURLToPath(outDir), "_ascribe", "files"), { recursive: true });
19
+ return true;
20
+ }
21
+ const TYPES = {
22
+ ".pdf": "application/pdf",
23
+ ".zip": "application/zip",
24
+ ".json": "application/json",
25
+ ".yaml": "text/yaml; charset=utf-8",
26
+ ".yml": "text/yaml; charset=utf-8",
27
+ ".txt": "text/plain; charset=utf-8",
28
+ ".csv": "text/csv; charset=utf-8",
29
+ ".md": "text/markdown; charset=utf-8",
30
+ ".html": "text/html; charset=utf-8",
31
+ ".css": "text/css; charset=utf-8",
32
+ ".js": "text/javascript; charset=utf-8",
33
+ ".png": "image/png",
34
+ ".jpg": "image/jpeg",
35
+ ".jpeg": "image/jpeg",
36
+ ".gif": "image/gif",
37
+ ".webp": "image/webp",
38
+ ".svg": "image/svg+xml",
39
+ };
40
+ /** A dev-server middleware serving the published files at `<base>_ascribe/files/`. */
41
+ export function filesMiddleware(siteRoot, base) {
42
+ const root = publishedDir(siteRoot);
43
+ // Only under the base path, as in the build. Vite strips the base from
44
+ // `req.url` before a plugin's middleware runs, but connect keeps the original in `originalUrl`.
45
+ const prefix = `${base}_ascribe/files/`;
46
+ return (req, res, next) => {
47
+ const pathname = (req.originalUrl ?? req.url ?? "").split(/[?#]/, 1)[0] ?? "";
48
+ if (!pathname.startsWith(prefix) || (req.method !== "GET" && req.method !== "HEAD"))
49
+ return next();
50
+ let relative;
51
+ try {
52
+ relative = decodeURIComponent(pathname.slice(prefix.length));
53
+ }
54
+ catch {
55
+ return next();
56
+ }
57
+ const file = path.resolve(root, relative);
58
+ if (file !== root && !file.startsWith(root + path.sep))
59
+ return next();
60
+ stat(file).then((info) => {
61
+ if (!info.isFile())
62
+ return next();
63
+ res.setHeader("Content-Type", TYPES[path.extname(file).toLowerCase()] ?? "application/octet-stream");
64
+ res.setHeader("Content-Length", info.size);
65
+ if (req.method === "HEAD")
66
+ return void res.end();
67
+ createReadStream(file).pipe(res);
68
+ }, () => next());
69
+ };
70
+ }
71
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.js","sourceRoot":"","sources":["../src/files.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { AstroIntegration } from "astro";
2
+ export { default as rehypeAscribeAttributes } from "./rehype.js";
3
+ export { satteriAscribeAttributes } from "./satteri.js";
4
+ /** Options of the integration. */
5
+ export interface AscribeOptions {
6
+ /** The build whose site output is the collection (a name in `ascribe.toml`). */
7
+ build: string;
8
+ /** The directory holding `ascribe.toml`, relative to the Astro root. Default: the root. */
9
+ project?: string;
10
+ /**
11
+ * The `ascribe` binary, relative to the Astro root. Default: `ASCRIBE_BIN`, then
12
+ * the platform binary installed with `@ascribed/cli`.
13
+ */
14
+ binary?: string;
15
+ }
16
+ /** Runs `ascribe build`, checks the site's routing, adds the markdown plugin, and serves published files. */
17
+ export default function ascribe(options: AscribeOptions): AstroIntegration;
package/dist/index.js ADDED
@@ -0,0 +1,137 @@
1
+ // @ascribed/astro: the Astro integration for Ascribe. Written against Astro
2
+ // 7.3.5.
3
+ //
4
+ // // astro.config.mjs
5
+ // import ascribe from "@ascribed/astro";
6
+ // export default defineConfig({
7
+ // base: "/docs", trailingSlash: "never", // as in ascribe.toml's [consumer]
8
+ // integrations: [ascribe({ build: "site" })],
9
+ // });
10
+ //
11
+ // // src/content.config.ts
12
+ // import { defineCollection } from "astro:content";
13
+ // import { ascribeCollection } from "@ascribed/astro/content";
14
+ // import { schema } from "../.ascribe/build/site/site/_ascribe/schema.ts";
15
+ // export const collections = { docs: defineCollection(ascribeCollection({ schema })) };
16
+ //
17
+ // // a layout's <head>
18
+ // import Elements from "@ascribed/astro/Elements.astro"; // <Elements />
19
+ import path from "node:path";
20
+ import { fileURLToPath } from "node:url";
21
+ import { findBinary } from "./binary.js";
22
+ import { watchDev } from "./dev.js";
23
+ import { copyPublishedFiles, filesMiddleware } from "./files.js";
24
+ import { consumerMismatches, readProject } from "./project.js";
25
+ import rehypeAscribeAttributes from "./rehype.js";
26
+ import { runBuild } from "./run.js";
27
+ import { satteriAscribeAttributes } from "./satteri.js";
28
+ export { default as rehypeAscribeAttributes } from "./rehype.js";
29
+ export { satteriAscribeAttributes } from "./satteri.js";
30
+ /** The virtual module `content.ts` reads the resolved site output root from. */
31
+ const SITE_MODULE = "virtual:ascribe/site";
32
+ /** Runs `ascribe build`, checks the site's routing, adds the markdown plugin, and serves published files. */
33
+ export default function ascribe(options) {
34
+ let siteRoot = "";
35
+ let devProject;
36
+ let devConfig;
37
+ let rebuild;
38
+ return {
39
+ name: "@ascribed/astro",
40
+ hooks: {
41
+ "astro:config:setup": async ({ config, command, logger, updateConfig }) => {
42
+ const root = fileURLToPath(config.root);
43
+ const project = readProject(path.resolve(root, options.project ?? "."));
44
+ // An unknown build is `ascribe build`'s to report: it knows the implicit `site` build.
45
+ siteRoot = project.siteRoot(options.build);
46
+ // `ascribe.toml`'s routing must be Astro's, or every link Ascribe writes is wrong.
47
+ const problems = consumerMismatches(project, {
48
+ base: config.base,
49
+ trailingSlash: config.trailingSlash,
50
+ site: config.site,
51
+ });
52
+ if (problems.length > 0) {
53
+ throw new Error(`@ascribed/astro: ascribe.toml and astro.config disagree:\n- ${problems.join("\n- ")}`);
54
+ }
55
+ // Where a markdown plugin goes depends on the processor Astro 7.3 runs.
56
+ const processor = config.markdown.processor;
57
+ const pluginLists = processor.options;
58
+ if (processor.name === "satteri" && pluginLists.hastPlugins !== undefined) {
59
+ pluginLists.hastPlugins.push(satteriAscribeAttributes());
60
+ }
61
+ else if (processor.name === "unified" && pluginLists.rehypePlugins !== undefined) {
62
+ pluginLists.rehypePlugins.push(rehypeAscribeAttributes);
63
+ }
64
+ else {
65
+ throw new Error(`@ascribed/astro: the markdown processor "${processor.name}" is not one it can add its plugin to. ` +
66
+ "Use Astro's default (satteri) or unified(), or add `rehypeAscribeAttributes` to your own.");
67
+ }
68
+ devConfig = {
69
+ base: config.base,
70
+ trailingSlash: config.trailingSlash,
71
+ site: config.site,
72
+ };
73
+ if (command !== "preview") {
74
+ const binary = findBinary({ binary: options.binary, root });
75
+ rebuild = async () => {
76
+ logger.info(`running ${path.basename(binary)} build for "${options.build}"`);
77
+ const result = await runBuild({
78
+ binary,
79
+ configPath: project.configPath,
80
+ build: options.build,
81
+ cwd: project.dir,
82
+ });
83
+ if (result.diagnostics !== "")
84
+ logger.warn(result.diagnostics);
85
+ if (result.summary !== "")
86
+ logger.info(result.summary);
87
+ };
88
+ await rebuild();
89
+ if (command === "dev")
90
+ devProject = project;
91
+ }
92
+ // Dev serving of `_ascribe/files/`; the build copies them in `astro:build:done`.
93
+ updateConfig({
94
+ vite: {
95
+ // `Elements.astro` is Astro source, so Vite must compile it, not load it as a Node module.
96
+ ssr: { noExternal: ["@ascribed/astro"] },
97
+ plugins: [
98
+ {
99
+ name: "@ascribed/astro:site",
100
+ resolveId: (id) => (id === SITE_MODULE ? `\0${SITE_MODULE}` : undefined),
101
+ load: (id) => id === `\0${SITE_MODULE}`
102
+ ? `export const siteRoot = ${JSON.stringify(siteRoot)};`
103
+ : undefined,
104
+ },
105
+ {
106
+ name: "@ascribed/astro:files",
107
+ configureServer(server) {
108
+ server.middlewares.use(filesMiddleware(siteRoot, config.base.endsWith("/") ? config.base : `${config.base}/`));
109
+ },
110
+ },
111
+ ],
112
+ },
113
+ });
114
+ },
115
+ "astro:server:setup": ({ server, refreshContent, logger }) => {
116
+ if (devProject && rebuild && devConfig) {
117
+ if (!refreshContent)
118
+ throw new Error("@ascribed/astro: this Astro version does not support refreshContent.");
119
+ watchDev({
120
+ server,
121
+ refreshContent: () => refreshContent({}),
122
+ logger,
123
+ project: devProject,
124
+ astro: devConfig,
125
+ build: options.build,
126
+ rebuild,
127
+ });
128
+ }
129
+ },
130
+ "astro:build:done": async ({ dir, logger }) => {
131
+ if (await copyPublishedFiles(siteRoot, dir))
132
+ logger.info("copied _ascribe/files/");
133
+ },
134
+ },
135
+ };
136
+ }
137
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,SAAS;AACT,EAAE;AACF,wBAAwB;AACxB,2CAA2C;AAC3C,kCAAkC;AAClC,oFAAoF;AACpF,kDAAkD;AAClD,QAAQ;AACR,EAAE;AACF,6BAA6B;AAC7B,sDAAsD;AACtD,iEAAiE;AACjE,6EAA6E;AAC7E,0FAA0F;AAC1F,EAAE;AACF,yBAAyB;AACzB,6EAA6E;AAC7E,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,uBAAuB,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAExD,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAExD,gFAAgF;AAChF,MAAM,WAAW,GAAG,sBAAsB,CAAC;AAe3C,6GAA6G;AAC7G,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,OAAuB;IACrD,IAAI,QAAQ,GAAG,EAAE,CAAC;IAClB,IAAI,UAAsD,CAAC;IAC3D,IAAI,SAMS,CAAC;IACd,IAAI,OAA0C,CAAC;IAC/C,OAAO;QACL,IAAI,EAAE,iBAAiB;QACvB,KAAK,EAAE;YACL,oBAAoB,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE;gBACxE,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBACxC,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,IAAI,GAAG,CAAC,CAAC,CAAC;gBACxE,uFAAuF;gBACvF,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;gBAE3C,mFAAmF;gBACnF,MAAM,QAAQ,GAAG,kBAAkB,CAAC,OAAO,EAAE;oBAC3C,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,aAAa,EAAE,MAAM,CAAC,aAAa;oBACnC,IAAI,EAAE,MAAM,CAAC,IAAI;iBAClB,CAAC,CAAC;gBACH,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACxB,MAAM,IAAI,KAAK,CACb,+DAA+D,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CACvF,CAAC;gBACJ,CAAC;gBAED,wEAAwE;gBACxE,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC;gBAC5C,MAAM,WAAW,GAAG,SAAS,CAAC,OAG7B,CAAC;gBACF,IAAI,SAAS,CAAC,IAAI,KAAK,SAAS,IAAI,WAAW,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;oBAC1E,WAAW,CAAC,WAAW,CAAC,IAAI,CAAC,wBAAwB,EAAE,CAAC,CAAC;gBAC3D,CAAC;qBAAM,IAAI,SAAS,CAAC,IAAI,KAAK,SAAS,IAAI,WAAW,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;oBACnF,WAAW,CAAC,aAAa,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;gBAC1D,CAAC;qBAAM,CAAC;oBACN,MAAM,IAAI,KAAK,CACb,4CAA4C,SAAS,CAAC,IAAI,yCAAyC;wBACjG,2FAA2F,CAC9F,CAAC;gBACJ,CAAC;gBAED,SAAS,GAAG;oBACV,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,aAAa,EAAE,MAAM,CAAC,aAAa;oBACnC,IAAI,EAAE,MAAM,CAAC,IAAI;iBAClB,CAAC;gBAEF,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;oBAC1B,MAAM,MAAM,GAAG,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;oBAC5D,OAAO,GAAG,KAAK,IAAI,EAAE;wBACnB,MAAM,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,eAAe,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC;wBAC7E,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC;4BAC5B,MAAM;4BACN,UAAU,EAAE,OAAO,CAAC,UAAU;4BAC9B,KAAK,EAAE,OAAO,CAAC,KAAK;4BACpB,GAAG,EAAE,OAAO,CAAC,GAAG;yBACjB,CAAC,CAAC;wBACH,IAAI,MAAM,CAAC,WAAW,KAAK,EAAE;4BAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;wBAC/D,IAAI,MAAM,CAAC,OAAO,KAAK,EAAE;4BAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;oBACzD,CAAC,CAAC;oBACF,MAAM,OAAO,EAAE,CAAC;oBAChB,IAAI,OAAO,KAAK,KAAK;wBAAE,UAAU,GAAG,OAAO,CAAC;gBAC9C,CAAC;gBAED,iFAAiF;gBACjF,YAAY,CAAC;oBACX,IAAI,EAAE;wBACJ,2FAA2F;wBAC3F,GAAG,EAAE,EAAE,UAAU,EAAE,CAAC,iBAAiB,CAAC,EAAE;wBACxC,OAAO,EAAE;4BACP;gCACE,IAAI,EAAE,sBAAsB;gCAC5B,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,WAAW,CAAC,CAAC,CAAC,KAAK,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;gCACxE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CACX,EAAE,KAAK,KAAK,WAAW,EAAE;oCACvB,CAAC,CAAC,2BAA2B,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG;oCACxD,CAAC,CAAC,SAAS;6BAChB;4BACD;gCACE,IAAI,EAAE,uBAAuB;gCAC7B,eAAe,CAAC,MAAM;oCACpB,MAAM,CAAC,WAAW,CAAC,GAAG,CACpB,eAAe,CACb,QAAQ,EACR,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,GAAG,CAC5D,CACF,CAAC;gCACJ,CAAC;6BACF;yBACF;qBACF;iBACF,CAAC,CAAC;YACL,CAAC;YACD,oBAAoB,EAAE,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE;gBAC3D,IAAI,UAAU,IAAI,OAAO,IAAI,SAAS,EAAE,CAAC;oBACvC,IAAI,CAAC,cAAc;wBACjB,MAAM,IAAI,KAAK,CAAC,sEAAsE,CAAC,CAAC;oBAC1F,QAAQ,CAAC;wBACP,MAAM;wBACN,cAAc,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,EAAE,CAAC;wBACxC,MAAM;wBACN,OAAO,EAAE,UAAU;wBACnB,KAAK,EAAE,SAAS;wBAChB,KAAK,EAAE,OAAO,CAAC,KAAK;wBACpB,OAAO;qBACR,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;YACD,kBAAkB,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE;gBAC5C,IAAI,MAAM,kBAAkB,CAAC,QAAQ,EAAE,GAAG,CAAC;oBAAE,MAAM,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;YACrF,CAAC;SACF;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,33 @@
1
+ /** The parts of an Ascribe project the integration reads. */
2
+ export interface ProjectInfo {
3
+ /** The directory holding `ascribe.toml`. */
4
+ dir: string;
5
+ /** The path of `ascribe.toml`. */
6
+ configPath: string;
7
+ /** Source files and assets watched during development. */
8
+ contentRoot: string;
9
+ /** Generated output, never treated as a source change. */
10
+ outputRoot: string;
11
+ /** `[consumer]`, with the profile's defaults. */
12
+ consumer: {
13
+ site: string | undefined;
14
+ basePath: string;
15
+ trailingSlash: "always" | "never";
16
+ };
17
+ /** The site output's root for a build: `<output-dir>/<build>/site`. */
18
+ siteRoot(build: string): string;
19
+ }
20
+ /** Reads `ascribe.toml` in `dir`. Throws a readable error if it can't be read. */
21
+ export declare function readProject(dir: string): ProjectInfo;
22
+ /** A base path with a leading and a trailing `/`, as Ascribe's router has it. */
23
+ export declare function normalizeBase(base: string): string;
24
+ /**
25
+ * The ways `ascribe.toml`'s `[consumer]` disagrees with Astro's configuration,
26
+ * as sentences. Ascribe writes every link with its own settings, so a site
27
+ * whose routes differ would have broken links.
28
+ */
29
+ export declare function consumerMismatches(project: ProjectInfo, astro: {
30
+ base: string;
31
+ trailingSlash: string;
32
+ site: string | undefined;
33
+ }): string[];
@@ -0,0 +1,79 @@
1
+ // What the integration needs from `ascribe.toml`: where a build's site output
2
+ // goes, and the consumer settings that must agree with `astro.config`.
3
+ // The compiler validates the whole file; this reads
4
+ // only what it compiled.
5
+ import { readFileSync } from "node:fs";
6
+ import path from "node:path";
7
+ import { parse } from "smol-toml";
8
+ /** Reads `ascribe.toml` in `dir`. Throws a readable error if it can't be read. */
9
+ export function readProject(dir) {
10
+ const configPath = path.join(dir, "ascribe.toml");
11
+ let table;
12
+ try {
13
+ table = parse(readFileSync(configPath, "utf8"));
14
+ }
15
+ catch (error) {
16
+ throw new Error(`@ascribed/astro can't read ${configPath}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
17
+ }
18
+ const project = section(table, "project");
19
+ const consumer = section(table, "consumer");
20
+ const outputDir = typeof project["output-dir"] === "string" ? project["output-dir"] : ".ascribe/build";
21
+ const contentDir = typeof project["content-root"] === "string" ? project["content-root"] : "docs";
22
+ const trailingSlash = consumer["trailing-slash"] === "never" ? "never" : "always";
23
+ return {
24
+ dir,
25
+ configPath,
26
+ contentRoot: path.resolve(dir, contentDir),
27
+ outputRoot: path.resolve(dir, outputDir),
28
+ consumer: {
29
+ site: typeof consumer["site"] === "string" ? consumer["site"] : undefined,
30
+ basePath: normalizeBase(typeof consumer["base-path"] === "string" ? consumer["base-path"] : "/"),
31
+ trailingSlash,
32
+ },
33
+ siteRoot: (build) => path.resolve(dir, outputDir, build, "site"),
34
+ };
35
+ }
36
+ function section(table, name) {
37
+ const value = table[name];
38
+ return typeof value === "object" && value !== null && !Array.isArray(value)
39
+ ? value
40
+ : {};
41
+ }
42
+ /** A base path with a leading and a trailing `/`, as Ascribe's router has it. */
43
+ export function normalizeBase(base) {
44
+ let result = base.trim();
45
+ if (!result.startsWith("/"))
46
+ result = `/${result}`;
47
+ if (!result.endsWith("/"))
48
+ result += "/";
49
+ return result;
50
+ }
51
+ /**
52
+ * The ways `ascribe.toml`'s `[consumer]` disagrees with Astro's configuration,
53
+ * as sentences. Ascribe writes every link with its own settings, so a site
54
+ * whose routes differ would have broken links.
55
+ */
56
+ export function consumerMismatches(project, astro) {
57
+ const problems = [];
58
+ const { basePath, trailingSlash, site } = project.consumer;
59
+ if (normalizeBase(astro.base) !== basePath) {
60
+ problems.push(`[consumer] base-path is "${basePath}", but Astro's \`base\` is "${astro.base}". Set them to the same path.`);
61
+ }
62
+ // Astro's "ignore" serves both forms, so Ascribe's links work either way.
63
+ if (astro.trailingSlash !== "ignore" && astro.trailingSlash !== trailingSlash) {
64
+ problems.push(`[consumer] trailing-slash is "${trailingSlash}", but Astro's \`trailingSlash\` is "${astro.trailingSlash}".`);
65
+ }
66
+ if (site !== undefined && astro.site !== undefined && origin(site) !== origin(astro.site)) {
67
+ problems.push(`[consumer] site is "${site}", but Astro's \`site\` is "${astro.site}".`);
68
+ }
69
+ return problems;
70
+ }
71
+ function origin(url) {
72
+ try {
73
+ return new URL(url).origin;
74
+ }
75
+ catch {
76
+ return url;
77
+ }
78
+ }
79
+ //# sourceMappingURL=project.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Root } from "hast";
2
+ /** Applies the site output's attribute markers (`ascribe-attributes`). */
3
+ export default function rehypeAscribeAttributes(): (tree: Root) => void;
package/dist/rehype.js ADDED
@@ -0,0 +1,46 @@
1
+ // The attribute marker as a rehype plugin, for Astro's `unified()` markdown
2
+ // processor (`@astrojs/markdown-remark`).
3
+ //
4
+ // Astro builds that processor as: remark-parse, remark-gfm, remark-smartypants,
5
+ // the user's remark plugins, remark-collect-images, remark-rehype (raw HTML
6
+ // allowed), syntax highlighting, the user's rehype plugins, `rehypeImages`,
7
+ // `rehypeHeadingIds`, and finally `rehype-raw`. A user rehype plugin therefore
8
+ // runs where markers are still `raw` nodes and before both of Astro's passes:
9
+ // `rehypeImages` copies an `<img>`'s properties into the image it optimizes,
10
+ // and `rehypeHeadingIds` keeps an id a heading already has and records it for
11
+ // the table of contents. A rehype plugin, not a remark one, so that both of
12
+ // Astro's processors can share `findEdits`.
13
+ import { findEdits, toProperty } from "./attributes.js";
14
+ /** Applies the site output's attribute markers (`ascribe-attributes`). */
15
+ export default function rehypeAscribeAttributes() {
16
+ return (tree) => {
17
+ const edits = findEdits(tree);
18
+ if (edits.length === 0)
19
+ return;
20
+ const removed = new Set();
21
+ for (const edit of edits) {
22
+ const properties = (edit.target.properties ??= {});
23
+ for (const attribute of edit.attributes) {
24
+ const [name, value] = toProperty(attribute);
25
+ properties[name] = value;
26
+ }
27
+ for (const node of edit.remove)
28
+ removed.add(node);
29
+ for (const { node, value } of edit.trim) {
30
+ if (value === "")
31
+ removed.add(node);
32
+ else
33
+ node.value = value;
34
+ }
35
+ }
36
+ dropNodes(tree, removed);
37
+ };
38
+ }
39
+ function dropNodes(node, removed) {
40
+ if (node.children === undefined)
41
+ return;
42
+ node.children = node.children.filter((child) => !removed.has(child));
43
+ for (const child of node.children)
44
+ dropNodes(child, removed);
45
+ }
46
+ //# sourceMappingURL=rehype.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rehype.js","sourceRoot":"","sources":["../src/rehype.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,0CAA0C;AAC1C,EAAE;AACF,gFAAgF;AAChF,4EAA4E;AAC5E,4EAA4E;AAC5E,+EAA+E;AAC/E,8EAA8E;AAC9E,6EAA6E;AAC7E,8EAA8E;AAC9E,4EAA4E;AAC5E,4CAA4C;AAG5C,OAAO,EAAE,SAAS,EAAE,UAAU,EAAiB,MAAM,iBAAiB,CAAC;AAEvE,0EAA0E;AAC1E,MAAM,CAAC,OAAO,UAAU,uBAAuB;IAC7C,OAAO,CAAC,IAAI,EAAE,EAAE;QACd,MAAM,KAAK,GAAG,SAAS,CAAC,IAAgB,CAAC,CAAC;QAC1C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC/B,MAAM,OAAO,GAAG,IAAI,GAAG,EAAY,CAAC;QACpC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,UAAU,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,KAAK,EAAE,CAAC,CAAC;YACnD,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;gBACxC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;gBAC5C,UAAU,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;YAC3B,CAAC;YACD,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,MAAM;gBAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAClD,KAAK,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;gBACxC,IAAI,KAAK,KAAK,EAAE;oBAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;;oBAC/B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;YAC1B,CAAC;QACH,CAAC;QACD,SAAS,CAAC,IAAgB,EAAE,OAAO,CAAC,CAAC;IACvC,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,IAAc,EAAE,OAAsB;IACvD,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS;QAAE,OAAO;IACxC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;IACrE,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ;QAAE,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;AAC/D,CAAC"}