@ascribed/astro 0.3.2-next.22 → 0.3.2-next.23

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/dist/run.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,sCAAsC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAU9C;;;GAGG;AACH,MAAM,OAAO,GAAG,+DAA+D,CAAC;AAEhF;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,WAAmB;IAC7C,IAAI,WAAW,KAAK,EAAE;QAAE,OAAO,KAAK,CAAC;IACrC,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC7E,OAAO,OAAO,KAAK,IAAI,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC;AACtE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CACxB,OAAoC,EACpC,OAAe,EACf,MAAM,GAAG,KAAK;IAEd,OAAO,OAAO,KAAK,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK,IAAI,CAAC,OAAO,KAAK,KAAK,IAAI,MAAM,CAAC,CAAC,CAAC;AAClF,CAAC;AAED,kHAAkH;AAClH,MAAM,UAAU,SAAS,CAAC,MAAoC,EAAE,OAAe;IAC7E,OAAO,OAAO,KAAK,KAAK,IAAI,MAAM,KAAK,KAAK,CAAC;AAC/C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,OASxB;IACC,MAAM,IAAI,GAAG;QACX,OAAO;QACP,QAAQ;QACR,CAAC,OAAO,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;QACvC,SAAS;QACT,OAAO,CAAC,KAAK;QACb,UAAU;QACV,OAAO,CAAC,UAAU;QAClB,SAAS;QACT,OAAO;KACR,CAAC;IACF,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI;QAAE,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACrD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,QAAQ,CACN,OAAO,CAAC,MAAM,EACd,IAAI,EACJ,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,EACjD,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACxB,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBACnB,MAAM,MAAM,GAAG,GAAG,MAAM,GAAG,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC;gBAC3C,MAAM,CACJ,IAAI,KAAK,CACP,2BAA2B,OAAO,CAAC,KAAK,YAAY,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,MAAM,MAAM,EAAE,EAAE,EAC3G,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CACF,CAAC;gBACF,OAAO;YACT,CAAC;YACD,OAAO,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAClE,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["// Running `ascribe build` for the configured build: its site output, and\n// its JSON output while review is on.\nimport { execFile } from \"node:child_process\";\n\n/** What a successful build printed. */\nexport interface BuildResult {\n /** The checks' report, which is more than a summary line when there is anything to report. */\n diagnostics: string;\n /** The `built <build>/site: …` lines. */\n summary: string;\n}\n\n/**\n * A check's summary line, the report's last: `checked 14 files for build \\`site\\`, reported on 14:\n * 0 errors, 0 warnings, 0 advice`.\n */\nconst SUMMARY = /^checked [^:\\n]*: (\\d+) errors?, (\\d+) warnings?, \\d+ advice$/;\n\n/**\n * Whether a build's report is worth a warning: anything but a report whose\n * summary line counts no errors and no warnings, which is logged at info\n * level, advice and all.\n */\nexport function hasWarnings(diagnostics: string): boolean {\n if (diagnostics === \"\") return false;\n const summary = SUMMARY.exec(diagnostics.trimEnd().split(\"\\n\").at(-1) ?? \"\");\n return summary === null || summary[1] !== \"0\" || summary[2] !== \"0\";\n}\n\n/**\n * Whether an Astro command builds with source anchors, given the integration's\n * `anchors` option: `\"dev\"` means in `astro dev` only, so `astro build` has\n * them only when it is `true`. Review in the site preview (`review`) needs\n * them in `astro dev`.\n */\nexport function anchorsFor(\n anchors: boolean | \"dev\" | undefined,\n command: string,\n review = false,\n): boolean {\n return anchors === true || (command === \"dev\" && (anchors === \"dev\" || review));\n}\n\n/** Whether an Astro command has review in the site preview, given the `review` option (an entry's name is on). */\nexport function reviewFor(review: boolean | string | undefined, command: string): boolean {\n return command === \"dev\" && review !== false;\n}\n\n/**\n * Builds one build's outputs, the site output by default. Rejects, with the compiler's report as the\n * message, if the build has errors: the Astro build fails with them.\n */\nexport function runBuild(options: {\n binary: string;\n configPath: string;\n build: string;\n cwd: string;\n /** Write source anchors (`--anchors`). */\n anchors?: boolean;\n /** The outputs to write. Default: `[\"site\"]`. */\n outputs?: readonly (\"site\" | \"plain\" | \"json\")[];\n}): Promise<BuildResult> {\n const args = [\n \"build\",\n \"--emit\",\n (options.outputs ?? [\"site\"]).join(\",\"),\n \"--build\",\n options.build,\n \"--config\",\n options.configPath,\n \"--color\",\n \"never\",\n ];\n if (options.anchors === true) args.push(\"--anchors\");\n return new Promise((resolve, reject) => {\n execFile(\n options.binary,\n args,\n { cwd: options.cwd, maxBuffer: 64 * 1024 * 1024 },\n (error, stdout, stderr) => {\n if (error !== null) {\n const report = `${stdout}${stderr}`.trim();\n reject(\n new Error(\n `\\`ascribe build --build ${options.build}\\` failed${report === \"\" ? `: ${error.message}` : `:\\n${report}`}`,\n { cause: error },\n ),\n );\n return;\n }\n resolve({ diagnostics: stdout.trim(), summary: stderr.trim() });\n },\n );\n });\n}\n"]}
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,sCAAsC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAU9C;;;GAGG;AACH,MAAM,OAAO,GAAG,+DAA+D,CAAC;AAEhF;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,WAAmB;IAC7C,IAAI,WAAW,KAAK,EAAE;QAAE,OAAO,KAAK,CAAC;IACrC,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC7E,OAAO,OAAO,KAAK,IAAI,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC;AACtE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CACxB,OAAoC,EACpC,OAAe,EACf,MAAM,GAAG,KAAK;IAEd,OAAO,OAAO,KAAK,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK,IAAI,CAAC,OAAO,KAAK,KAAK,IAAI,MAAM,CAAC,CAAC,CAAC;AAClF,CAAC;AAED,kHAAkH;AAClH,MAAM,UAAU,SAAS,CAAC,MAAoC,EAAE,OAAe;IAC7E,OAAO,OAAO,KAAK,KAAK,IAAI,MAAM,KAAK,KAAK,CAAC;AAC/C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,OAUxB;IACC,MAAM,MAAM,GAAG,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;IACnF,MAAM,IAAI,GAAG;QACX,OAAO;QACP,QAAQ;QACR,CAAC,OAAO,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;QACvC,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAChD,UAAU;QACV,OAAO,CAAC,UAAU;QAClB,SAAS;QACT,OAAO;KACR,CAAC;IACF,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI;QAAE,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACrD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,QAAQ,CACN,OAAO,CAAC,MAAM,EACd,IAAI,EACJ,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,EACjD,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACxB,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBACnB,MAAM,MAAM,GAAG,GAAG,MAAM,GAAG,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC;gBAC3C,MAAM,CACJ,IAAI,KAAK,CACP,mBAAmB,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,MAAM,MAAM,EAAE,EAAE,EACzI,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CACF,CAAC;gBACF,OAAO;YACT,CAAC;YACD,OAAO,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAClE,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["// Running `ascribe build` for the configured build: its site output, and\n// its JSON output while review is on.\nimport { execFile } from \"node:child_process\";\n\n/** What a successful build printed. */\nexport interface BuildResult {\n /** The checks' report, which is more than a summary line when there is anything to report. */\n diagnostics: string;\n /** The `built <build>/site: …` lines. */\n summary: string;\n}\n\n/**\n * A check's summary line, the report's last: `checked 14 files for build \\`site\\`, reported on 14:\n * 0 errors, 0 warnings, 0 advice`.\n */\nconst SUMMARY = /^checked [^:\\n]*: (\\d+) errors?, (\\d+) warnings?, \\d+ advice$/;\n\n/**\n * Whether a build's report is worth a warning: anything but a report whose\n * summary line counts no errors and no warnings, which is logged at info\n * level, advice and all.\n */\nexport function hasWarnings(diagnostics: string): boolean {\n if (diagnostics === \"\") return false;\n const summary = SUMMARY.exec(diagnostics.trimEnd().split(\"\\n\").at(-1) ?? \"\");\n return summary === null || summary[1] !== \"0\" || summary[2] !== \"0\";\n}\n\n/**\n * Whether an Astro command builds with source anchors, given the integration's\n * `anchors` option: `\"dev\"` means in `astro dev` only, so `astro build` has\n * them only when it is `true`. Review in the site preview (`review`) needs\n * them in `astro dev`.\n */\nexport function anchorsFor(\n anchors: boolean | \"dev\" | undefined,\n command: string,\n review = false,\n): boolean {\n return anchors === true || (command === \"dev\" && (anchors === \"dev\" || review));\n}\n\n/** Whether an Astro command has review in the site preview, given the `review` option (an entry's name is on). */\nexport function reviewFor(review: boolean | string | undefined, command: string): boolean {\n return command === \"dev\" && review !== false;\n}\n\n/**\n * Builds one build's outputs, or several builds', the site output by default. Rejects, with the compiler's report as the\n * message, if the build has errors: the Astro build fails with them.\n */\nexport function runBuild(options: {\n binary: string;\n configPath: string;\n /** The build, or several, each a `--build`. */\n build: string | readonly string[];\n cwd: string;\n /** Write source anchors (`--anchors`). */\n anchors?: boolean;\n /** The outputs to write. Default: `[\"site\"]`. */\n outputs?: readonly (\"site\" | \"plain\" | \"json\")[];\n}): Promise<BuildResult> {\n const builds = typeof options.build === \"string\" ? [options.build] : options.build;\n const args = [\n \"build\",\n \"--emit\",\n (options.outputs ?? [\"site\"]).join(\",\"),\n ...builds.flatMap((build) => [\"--build\", build]),\n \"--config\",\n options.configPath,\n \"--color\",\n \"never\",\n ];\n if (options.anchors === true) args.push(\"--anchors\");\n return new Promise((resolve, reject) => {\n execFile(\n options.binary,\n args,\n { cwd: options.cwd, maxBuffer: 64 * 1024 * 1024 },\n (error, stdout, stderr) => {\n if (error !== null) {\n const report = `${stdout}${stderr}`.trim();\n reject(\n new Error(\n `\\`ascribe build ${builds.map((build) => `--build ${build}`).join(\" \")}\\` failed${report === \"\" ? `: ${error.message}` : `:\\n${report}`}`,\n { cause: error },\n ),\n );\n return;\n }\n resolve({ diagnostics: stdout.trim(), summary: stderr.trim() });\n },\n );\n });\n}\n"]}
@@ -0,0 +1,96 @@
1
+ /** A live line of a versioned build, as the file has it. */
2
+ interface LiveLine {
3
+ /** The line, as `[releases]` writes it: `3.4`. */
4
+ line: string;
5
+ /** `current` for the newest line, `live` for the others. */
6
+ status: "current" | "live";
7
+ /** The build that writes the line's output: the declared build, or `<build>@<line>`. */
8
+ build: string;
9
+ /** The path the line's pages are under, from the host's root, ending in `/`. */
10
+ base: string;
11
+ /** The last day the line is supported, `YYYY-MM-DD`, when the project says. */
12
+ ends?: string;
13
+ }
14
+ /** A line that has left the window, as the file has it. */
15
+ interface ArchivedLine {
16
+ line: string;
17
+ status: "archived";
18
+ /** Where the line can still be read, when anywhere. */
19
+ url?: string;
20
+ /** The day its support ended, `YYYY-MM-DD`, when the project says. */
21
+ ended?: string;
22
+ }
23
+ type Line = LiveLine | ArchivedLine;
24
+ /** A line with where its site output is. */
25
+ export interface LineOutput extends LiveLine {
26
+ /** The line's site output root: `<output-dir>/<build>/site`. */
27
+ siteRoot: string;
28
+ /** The line's plain output root, its outputs for agents: `<output-dir>/<build>/plain`. */
29
+ plainRoot: string;
30
+ }
31
+ /**
32
+ * The live lines of the build whose site output is at `siteRoot`, newest
33
+ * first, each with its site output, or `undefined` when the build isn't
34
+ * versioned. `siteRoot` is the declared build's, which is the current line's.
35
+ */
36
+ export declare function linesOf(siteRoot: string): LineOutput[] | undefined;
37
+ /** The lines other than the current one, newest first: empty when the build isn't versioned. */
38
+ export declare function olderLines(siteRoot: string): LineOutput[];
39
+ /** A line that publishes a page, and the page's route in it. */
40
+ export interface PageLine {
41
+ line: string;
42
+ current: boolean;
43
+ route: string;
44
+ }
45
+ /**
46
+ * The lines that publish the page whose source path is `source`, newest
47
+ * first, each with the page's route in it, in the versioned build whose
48
+ * current line's site output is at `siteRoot`. A line that doesn't have the
49
+ * page has the page that takes its place there, when one does: the page
50
+ * that replaces it, or that it replaces, nearest along the chain first.
51
+ * Empty when the build isn't versioned or no line has the page.
52
+ */
53
+ export declare function pageLines(siteRoot: string, source: string): PageLine[];
54
+ /** What a page of an older line says about the line, and where the current one is. */
55
+ export interface LineNotice {
56
+ /** The label of the target the build is versioned by: `Self-managed`. */
57
+ label: string;
58
+ /** The page's line: `3.4`. */
59
+ line: string;
60
+ /** The last day the line is supported, `YYYY-MM-DD`, when the project says. */
61
+ ends?: string;
62
+ /** The current line: `3.5`. */
63
+ current: string;
64
+ /** The page's route in the current line, or the current line's home's. */
65
+ route: string;
66
+ /** Whether `route` is the page, or the page that takes its place, rather than the home. */
67
+ samePage: boolean;
68
+ }
69
+ /**
70
+ * What the page whose source path is `source`, in the line `line`, says about
71
+ * that line, in the versioned build whose current line's site output is at
72
+ * `siteRoot`: `undefined` on the current line, and for a build that isn't
73
+ * versioned. `home` is the current line's home's route, the link when the
74
+ * current line has neither the page nor one in its place.
75
+ */
76
+ export declare function lineNotice(siteRoot: string, source: string, line: string, home: string): LineNotice | undefined;
77
+ /** A line of a versioned build, as a page's list of versions shows it. */
78
+ export interface ListedLine {
79
+ line: string;
80
+ /** `current`, `live`, or `archived`. */
81
+ status: Line["status"];
82
+ /**
83
+ * Where the line is linked to: the page's route in a live line that
84
+ * publishes it, or the page in its place; an archived line's address.
85
+ * Absent for a live line with neither, and an archived line with no address.
86
+ */
87
+ route?: string;
88
+ }
89
+ /**
90
+ * Every line of the versioned build whose current line's site output is at
91
+ * `siteRoot`, newest first, live then archived, each with where a list of
92
+ * versions on the page whose source path is `source` links it. Archived lines
93
+ * with no address are left out. Empty when the build isn't versioned.
94
+ */
95
+ export declare function listedLines(siteRoot: string, source: string): ListedLine[];
96
+ export {};
@@ -0,0 +1,160 @@
1
+ // A versioned build's lines, from `<output-dir>/_ascribe/versions.json`
2
+ // (the output layout contract's versions file): which lines a build has, the
3
+ // site output of each, the lines that have left it and where each can still be
4
+ // read, and the lines that publish each page, with its route in each. The compiler writes the file with every build of a project that
5
+ // has `[releases]`, whichever build was asked for, so the current line's
6
+ // build is enough to know the others.
7
+ import { readFileSync, statSync } from "node:fs";
8
+ import path from "node:path";
9
+ /** The version of the file this integration reads. */
10
+ const VERSION = 1;
11
+ /** A build's site output root (`<output-dir>/<build>/site`) split into the two it's made of. */
12
+ function outputOf(siteRoot) {
13
+ const buildDir = path.dirname(siteRoot);
14
+ return { outputRoot: path.dirname(buildDir), build: path.basename(buildDir) };
15
+ }
16
+ function versionsFile(outputRoot) {
17
+ return path.join(outputRoot, "_ascribe", "versions.json");
18
+ }
19
+ /** The file as last read, by its path, and when it was written. */
20
+ const read = new Map();
21
+ /**
22
+ * The versions file of the project whose output is at `outputRoot`, or
23
+ * `undefined` when it has none: a project with no `[releases]`. Read again
24
+ * when the file changes, as it does with each build in `astro dev`.
25
+ */
26
+ function readVersions(outputRoot) {
27
+ const file = versionsFile(outputRoot);
28
+ let written;
29
+ try {
30
+ written = statSync(file).mtimeMs;
31
+ }
32
+ catch {
33
+ return undefined;
34
+ }
35
+ const known = read.get(file);
36
+ if (known !== undefined && known.written === written)
37
+ return known.file;
38
+ const parsed = JSON.parse(readFileSync(file, "utf8"));
39
+ if (parsed.version !== VERSION) {
40
+ throw new Error(`@ascribed/astro: ${file} is version ${String(parsed.version)} of the versions file, and this version of the integration reads version ${VERSION}. Update @ascribed/astro and @ascribed/cli together.`);
41
+ }
42
+ read.set(file, { written, file: parsed });
43
+ return parsed;
44
+ }
45
+ /** The entry of the versioned build whose site output is at `siteRoot`. */
46
+ function versionedBuild(siteRoot) {
47
+ const { outputRoot, build } = outputOf(siteRoot);
48
+ return readVersions(outputRoot)?.builds[build];
49
+ }
50
+ function isLive(line) {
51
+ return line.status !== "archived";
52
+ }
53
+ /**
54
+ * The live lines of the build whose site output is at `siteRoot`, newest
55
+ * first, each with its site output, or `undefined` when the build isn't
56
+ * versioned. `siteRoot` is the declared build's, which is the current line's.
57
+ */
58
+ export function linesOf(siteRoot) {
59
+ const { outputRoot } = outputOf(siteRoot);
60
+ const versioned = versionedBuild(siteRoot);
61
+ if (versioned === undefined)
62
+ return undefined;
63
+ return versioned.lines.filter(isLive).map((line) => ({
64
+ ...line,
65
+ siteRoot: path.join(outputRoot, line.build, "site"),
66
+ plainRoot: path.join(outputRoot, line.build, "plain"),
67
+ }));
68
+ }
69
+ /** The lines other than the current one, newest first: empty when the build isn't versioned. */
70
+ export function olderLines(siteRoot) {
71
+ return (linesOf(siteRoot) ?? []).filter((line) => line.status !== "current");
72
+ }
73
+ /**
74
+ * The lines that publish the page whose source path is `source`, newest
75
+ * first, each with the page's route in it, in the versioned build whose
76
+ * current line's site output is at `siteRoot`. A line that doesn't have the
77
+ * page has the page that takes its place there, when one does: the page
78
+ * that replaces it, or that it replaces, nearest along the chain first.
79
+ * Empty when the build isn't versioned or no line has the page.
80
+ */
81
+ export function pageLines(siteRoot, source) {
82
+ const versioned = versionedBuild(siteRoot);
83
+ if (versioned?.pages[source] === undefined)
84
+ return [];
85
+ const pages = versioned.pages;
86
+ return versioned.lines.filter(isLive).flatMap((line) => {
87
+ const route = inLine(pages, source, line.line);
88
+ return route === undefined
89
+ ? []
90
+ : [{ line: line.line, current: line.status === "current", route }];
91
+ });
92
+ }
93
+ /**
94
+ * The route, in `line`, of the page `source` or of the page nearest it along
95
+ * its `replaces` and `replaced-by`, the one that replaces it first.
96
+ */
97
+ function inLine(pages, source, line) {
98
+ const seen = new Set([source]);
99
+ const queue = [source];
100
+ for (let at = queue.shift(); at !== undefined; at = queue.shift()) {
101
+ const page = pages[at];
102
+ const route = page?.lines[line];
103
+ if (route !== undefined)
104
+ return route;
105
+ for (const next of [page?.["replaced-by"], page?.replaces]) {
106
+ if (next !== undefined && !seen.has(next)) {
107
+ seen.add(next);
108
+ queue.push(next);
109
+ }
110
+ }
111
+ }
112
+ return undefined;
113
+ }
114
+ /**
115
+ * What the page whose source path is `source`, in the line `line`, says about
116
+ * that line, in the versioned build whose current line's site output is at
117
+ * `siteRoot`: `undefined` on the current line, and for a build that isn't
118
+ * versioned. `home` is the current line's home's route, the link when the
119
+ * current line has neither the page nor one in its place.
120
+ */
121
+ export function lineNotice(siteRoot, source, line, home) {
122
+ const versioned = versionedBuild(siteRoot);
123
+ if (versioned === undefined)
124
+ return undefined;
125
+ const live = versioned.lines.filter(isLive);
126
+ const own = live.find((each) => each.line === line);
127
+ const current = live.find((each) => each.status === "current");
128
+ if (own === undefined || current === undefined || own === current)
129
+ return undefined;
130
+ const route = inLine(versioned.pages, source, current.line);
131
+ return {
132
+ label: versioned.label ?? versioned.target,
133
+ line,
134
+ ...(own.ends === undefined ? {} : { ends: own.ends }),
135
+ current: current.line,
136
+ route: route ?? home,
137
+ samePage: route !== undefined,
138
+ };
139
+ }
140
+ /**
141
+ * Every line of the versioned build whose current line's site output is at
142
+ * `siteRoot`, newest first, live then archived, each with where a list of
143
+ * versions on the page whose source path is `source` links it. Archived lines
144
+ * with no address are left out. Empty when the build isn't versioned.
145
+ */
146
+ export function listedLines(siteRoot, source) {
147
+ const versioned = versionedBuild(siteRoot);
148
+ if (versioned === undefined)
149
+ return [];
150
+ return versioned.lines.flatMap((line) => {
151
+ if (!isLive(line)) {
152
+ return line.url === undefined
153
+ ? []
154
+ : [{ line: line.line, status: line.status, route: line.url }];
155
+ }
156
+ const route = inLine(versioned.pages, source, line.line);
157
+ return [{ line: line.line, status: line.status, ...(route === undefined ? {} : { route }) }];
158
+ });
159
+ }
160
+ //# sourceMappingURL=versions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"versions.js","sourceRoot":"","sources":["../src/versions.ts"],"names":[],"mappings":"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","sourcesContent":["// A versioned build's lines, from `<output-dir>/_ascribe/versions.json`\n// (the output layout contract's versions file): which lines a build has, the\n// site output of each, the lines that have left it and where each can still be\n// read, and the lines that publish each page, with its route in each. The compiler writes the file with every build of a project that\n// has `[releases]`, whichever build was asked for, so the current line's\n// build is enough to know the others.\nimport { readFileSync, statSync } from \"node:fs\";\nimport path from \"node:path\";\n\n/** The version of the file this integration reads. */\nconst VERSION = 1;\n\n/** A live line of a versioned build, as the file has it. */\ninterface LiveLine {\n /** The line, as `[releases]` writes it: `3.4`. */\n line: string;\n /** `current` for the newest line, `live` for the others. */\n status: \"current\" | \"live\";\n /** The build that writes the line's output: the declared build, or `<build>@<line>`. */\n build: string;\n /** The path the line's pages are under, from the host's root, ending in `/`. */\n base: string;\n /** The last day the line is supported, `YYYY-MM-DD`, when the project says. */\n ends?: string;\n}\n\n/** A line that has left the window, as the file has it. */\ninterface ArchivedLine {\n line: string;\n status: \"archived\";\n /** Where the line can still be read, when anywhere. */\n url?: string;\n /** The day its support ended, `YYYY-MM-DD`, when the project says. */\n ended?: string;\n}\n\ntype Line = LiveLine | ArchivedLine;\n\n/** A line with where its site output is. */\nexport interface LineOutput extends LiveLine {\n /** The line's site output root: `<output-dir>/<build>/site`. */\n siteRoot: string;\n /** The line's plain output root, its outputs for agents: `<output-dir>/<build>/plain`. */\n plainRoot: string;\n}\n\n/** A page's entry: its route in each line that has it, and the pages it replaces or is replaced by. */\ninterface VersionsPage {\n lines: Record<string, string>;\n replaces?: string;\n \"replaced-by\"?: string;\n}\n\n/** The file, as much as the integration reads. */\ninterface VersionsFile {\n version: number;\n builds: Record<\n string,\n {\n target: string;\n /** The target's label: `Self-managed`. Absent in a file an earlier Ascribe wrote. */\n label?: string;\n lines: Line[];\n pages: Record<string, VersionsPage>;\n }\n >;\n}\n\n/** A build's site output root (`<output-dir>/<build>/site`) split into the two it's made of. */\nfunction outputOf(siteRoot: string): { outputRoot: string; build: string } {\n const buildDir = path.dirname(siteRoot);\n return { outputRoot: path.dirname(buildDir), build: path.basename(buildDir) };\n}\n\nfunction versionsFile(outputRoot: string): string {\n return path.join(outputRoot, \"_ascribe\", \"versions.json\");\n}\n\n/** The file as last read, by its path, and when it was written. */\nconst read = new Map<string, { written: number; file: VersionsFile }>();\n\n/**\n * The versions file of the project whose output is at `outputRoot`, or\n * `undefined` when it has none: a project with no `[releases]`. Read again\n * when the file changes, as it does with each build in `astro dev`.\n */\nfunction readVersions(outputRoot: string): VersionsFile | undefined {\n const file = versionsFile(outputRoot);\n let written: number;\n try {\n written = statSync(file).mtimeMs;\n } catch {\n return undefined;\n }\n const known = read.get(file);\n if (known !== undefined && known.written === written) return known.file;\n const parsed = JSON.parse(readFileSync(file, \"utf8\")) as VersionsFile;\n if (parsed.version !== VERSION) {\n throw new Error(\n `@ascribed/astro: ${file} is version ${String(parsed.version)} of the versions file, and this version of the integration reads version ${VERSION}. Update @ascribed/astro and @ascribed/cli together.`,\n );\n }\n read.set(file, { written, file: parsed });\n return parsed;\n}\n\n/** The entry of the versioned build whose site output is at `siteRoot`. */\nfunction versionedBuild(siteRoot: string): VersionsFile[\"builds\"][string] | undefined {\n const { outputRoot, build } = outputOf(siteRoot);\n return readVersions(outputRoot)?.builds[build];\n}\n\nfunction isLive(line: Line): line is LiveLine {\n return line.status !== \"archived\";\n}\n\n/**\n * The live lines of the build whose site output is at `siteRoot`, newest\n * first, each with its site output, or `undefined` when the build isn't\n * versioned. `siteRoot` is the declared build's, which is the current line's.\n */\nexport function linesOf(siteRoot: string): LineOutput[] | undefined {\n const { outputRoot } = outputOf(siteRoot);\n const versioned = versionedBuild(siteRoot);\n if (versioned === undefined) return undefined;\n return versioned.lines.filter(isLive).map((line) => ({\n ...line,\n siteRoot: path.join(outputRoot, line.build, \"site\"),\n plainRoot: path.join(outputRoot, line.build, \"plain\"),\n }));\n}\n\n/** The lines other than the current one, newest first: empty when the build isn't versioned. */\nexport function olderLines(siteRoot: string): LineOutput[] {\n return (linesOf(siteRoot) ?? []).filter((line) => line.status !== \"current\");\n}\n\n/** A line that publishes a page, and the page's route in it. */\nexport interface PageLine {\n line: string;\n current: boolean;\n route: string;\n}\n\n/**\n * The lines that publish the page whose source path is `source`, newest\n * first, each with the page's route in it, in the versioned build whose\n * current line's site output is at `siteRoot`. A line that doesn't have the\n * page has the page that takes its place there, when one does: the page\n * that replaces it, or that it replaces, nearest along the chain first.\n * Empty when the build isn't versioned or no line has the page.\n */\nexport function pageLines(siteRoot: string, source: string): PageLine[] {\n const versioned = versionedBuild(siteRoot);\n if (versioned?.pages[source] === undefined) return [];\n const pages = versioned.pages;\n return versioned.lines.filter(isLive).flatMap((line) => {\n const route = inLine(pages, source, line.line);\n return route === undefined\n ? []\n : [{ line: line.line, current: line.status === \"current\", route }];\n });\n}\n\n/**\n * The route, in `line`, of the page `source` or of the page nearest it along\n * its `replaces` and `replaced-by`, the one that replaces it first.\n */\nfunction inLine(\n pages: Record<string, VersionsPage>,\n source: string,\n line: string,\n): string | undefined {\n const seen = new Set([source]);\n const queue = [source];\n for (let at = queue.shift(); at !== undefined; at = queue.shift()) {\n const page = pages[at];\n const route = page?.lines[line];\n if (route !== undefined) return route;\n for (const next of [page?.[\"replaced-by\"], page?.replaces]) {\n if (next !== undefined && !seen.has(next)) {\n seen.add(next);\n queue.push(next);\n }\n }\n }\n return undefined;\n}\n\n/** What a page of an older line says about the line, and where the current one is. */\nexport interface LineNotice {\n /** The label of the target the build is versioned by: `Self-managed`. */\n label: string;\n /** The page's line: `3.4`. */\n line: string;\n /** The last day the line is supported, `YYYY-MM-DD`, when the project says. */\n ends?: string;\n /** The current line: `3.5`. */\n current: string;\n /** The page's route in the current line, or the current line's home's. */\n route: string;\n /** Whether `route` is the page, or the page that takes its place, rather than the home. */\n samePage: boolean;\n}\n\n/**\n * What the page whose source path is `source`, in the line `line`, says about\n * that line, in the versioned build whose current line's site output is at\n * `siteRoot`: `undefined` on the current line, and for a build that isn't\n * versioned. `home` is the current line's home's route, the link when the\n * current line has neither the page nor one in its place.\n */\nexport function lineNotice(\n siteRoot: string,\n source: string,\n line: string,\n home: string,\n): LineNotice | undefined {\n const versioned = versionedBuild(siteRoot);\n if (versioned === undefined) return undefined;\n const live = versioned.lines.filter(isLive);\n const own = live.find((each) => each.line === line);\n const current = live.find((each) => each.status === \"current\");\n if (own === undefined || current === undefined || own === current) return undefined;\n const route = inLine(versioned.pages, source, current.line);\n return {\n label: versioned.label ?? versioned.target,\n line,\n ...(own.ends === undefined ? {} : { ends: own.ends }),\n current: current.line,\n route: route ?? home,\n samePage: route !== undefined,\n };\n}\n\n/** A line of a versioned build, as a page's list of versions shows it. */\nexport interface ListedLine {\n line: string;\n /** `current`, `live`, or `archived`. */\n status: Line[\"status\"];\n /**\n * Where the line is linked to: the page's route in a live line that\n * publishes it, or the page in its place; an archived line's address.\n * Absent for a live line with neither, and an archived line with no address.\n */\n route?: string;\n}\n\n/**\n * Every line of the versioned build whose current line's site output is at\n * `siteRoot`, newest first, live then archived, each with where a list of\n * versions on the page whose source path is `source` links it. Archived lines\n * with no address are left out. Empty when the build isn't versioned.\n */\nexport function listedLines(siteRoot: string, source: string): ListedLine[] {\n const versioned = versionedBuild(siteRoot);\n if (versioned === undefined) return [];\n return versioned.lines.flatMap((line): ListedLine[] => {\n if (!isLive(line)) {\n return line.url === undefined\n ? []\n : [{ line: line.line, status: line.status, route: line.url }];\n }\n const route = inLine(versioned.pages, source, line.line);\n return [{ line: line.line, status: line.status, ...(route === undefined ? {} : { route }) }];\n });\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ascribed/astro",
3
- "version": "0.3.2-next.22",
3
+ "version": "0.3.2-next.23",
4
4
  "description": "The Astro integration for Ascribe.",
5
5
  "type": "module",
6
6
  "license": "MPL-2.0",
@@ -22,7 +22,9 @@
22
22
  "src/Availability.astro",
23
23
  "src/Permalink.astro",
24
24
  "src/MissingPermalink.astro",
25
- "src/Head.astro"
25
+ "src/Head.astro",
26
+ "src/Versions.astro",
27
+ "src/VersionBanner.astro"
26
28
  ],
27
29
  "exports": {
28
30
  ".": {
@@ -41,6 +43,8 @@
41
43
  "./Availability.astro": "./src/Availability.astro",
42
44
  "./MissingPermalink.astro": "./src/MissingPermalink.astro",
43
45
  "./Head.astro": "./src/Head.astro",
46
+ "./Versions.astro": "./src/Versions.astro",
47
+ "./VersionBanner.astro": "./src/VersionBanner.astro",
44
48
  "./content": {
45
49
  "types": "./dist/content.d.ts",
46
50
  "default": "./dist/content.js"
@@ -54,9 +58,9 @@
54
58
  "node": ">=24"
55
59
  },
56
60
  "dependencies": {
57
- "@ascribed/cli": "0.3.2-next.22",
58
- "@ascribed/elements": "0.3.2-next.22",
59
- "@ascribed/review": "0.3.2-next.22",
61
+ "@ascribed/cli": "0.3.2-next.23",
62
+ "@ascribed/elements": "0.3.2-next.23",
63
+ "@ascribed/review": "0.3.2-next.23",
60
64
  "github-slugger": "^2.0.0",
61
65
  "property-information": "^7.2.0",
62
66
  "smol-toml": "^1.9.1"
@@ -92,7 +96,7 @@
92
96
  "publishConfig": {
93
97
  "access": "public"
94
98
  },
95
- "gitHead": "92ffb16209044f99d17de66ba6788a426b4b810e",
99
+ "gitHead": "27a51bc3d9f31138a355a76154e990fe790d1978",
96
100
  "scripts": {
97
101
  "build": "tsc -p tsconfig.build.json",
98
102
  "typecheck": "tsc --noEmit",
@@ -14,6 +14,7 @@ interface Props {
14
14
  dimension: string;
15
15
  states: readonly string[];
16
16
  versions?: readonly string[] | undefined;
17
+ planned?: boolean | undefined;
17
18
  text: string;
18
19
  }[]
19
20
  | undefined;
@@ -33,6 +34,7 @@ const { available } = Astro.props;
33
34
  dimension={item.dimension}
34
35
  states={item.states.join(" ")}
35
36
  versions={item.versions?.join(" ")}
37
+ planned={item.planned ? "" : undefined}
36
38
  >
37
39
  {item.text}
38
40
  </ascribe-availability-target>
@@ -0,0 +1,76 @@
1
+ ---
2
+ // What an older line of a versioned build is, and where the current one is,
3
+ // at the top of each of its pages, from `<output-dir>/_ascribe/versions.json`:
4
+ //
5
+ // Documentation for Self-managed 3.4. Support ends on 31 January 2027.
6
+ // See this page for 3.5.
7
+ //
8
+ // The link goes to the page in the current line, or the page that takes its
9
+ // place there, and to the current line's home when it has neither, in words
10
+ // that say so. The second sentence is left out when the line has no `ends`.
11
+ // A layout includes it given the page's collection entry:
12
+ //
13
+ // import VersionBanner from "@ascribed/astro/VersionBanner.astro";
14
+ // <VersionBanner entry={entry} />
15
+ //
16
+ // It renders nothing on the current line, or for a page of a build that isn't
17
+ // versioned. Each sentence's words are a property, so a site can translate
18
+ // them. It writes no styles: the site's own select it by
19
+ // `[data-version-banner]`, or the `class` given.
20
+ import { versionNotice } from "@ascribed/astro/content";
21
+
22
+ interface Props {
23
+ /** The page's collection entry. */
24
+ entry: { filePath?: string | undefined; data?: unknown };
25
+ /** Before the line's label and number. Default: "Documentation for". */
26
+ documentation?: string;
27
+ /** Before the last day the line is supported. Default: "Support ends on". */
28
+ supportEnds?: string;
29
+ /** The link to the page in the current line, before its number. Default: "See this page for". */
30
+ samePage?: string;
31
+ /** The link to the current line's home, before its number. Default: "See the documentation for". */
32
+ home?: string;
33
+ /** The locale the date is written in. Default: "en-GB", as in 31 January 2027. */
34
+ locale?: string;
35
+ /** A class for the banner. */
36
+ class?: string;
37
+ }
38
+
39
+ const {
40
+ entry,
41
+ documentation = "Documentation for",
42
+ supportEnds = "Support ends on",
43
+ samePage = "See this page for",
44
+ home = "See the documentation for",
45
+ locale = "en-GB",
46
+ class: className,
47
+ } = Astro.props;
48
+ const notice = versionNotice(entry);
49
+ const ends =
50
+ notice?.ends === undefined
51
+ ? undefined
52
+ : new Date(`${notice.ends}T00:00:00Z`).toLocaleDateString(locale, {
53
+ day: "numeric",
54
+ month: "long",
55
+ year: "numeric",
56
+ timeZone: "UTC",
57
+ });
58
+ ---
59
+
60
+ {
61
+ notice !== undefined && (
62
+ <aside class={className} data-version-banner data-line={notice.line}>
63
+ <p>
64
+ {documentation} {notice.label} {notice.line}.{" "}
65
+ {ends !== undefined && (
66
+ <>
67
+ {supportEnds} <time datetime={notice.ends}>{ends}</time>.{" "}
68
+ </>
69
+ )}
70
+ <a href={notice.route} data-current-line={notice.current}>
71
+ {notice.samePage ? samePage : home} {notice.current}.
72
+ </a>
73
+ </p>
74
+ </aside>
75
+ )
76
+ }
@@ -0,0 +1,69 @@
1
+ ---
2
+ // The lines of a versioned build, as a list, newest first, from
3
+ // `<output-dir>/_ascribe/versions.json`: each line's number, linking to the
4
+ // page in that line, with the page's own line marked `aria-current="page"`
5
+ // and the current line `data-current`. A line that doesn't have the page
6
+ // links to the page that takes its place there, when one says so with
7
+ // `replaces`, and is shown unlinked otherwise, with words saying so. The
8
+ // archived lines that have an address follow, each linking to it, with a
9
+ // word saying it's archived. A layout includes it given the page's collection
10
+ // entry:
11
+ //
12
+ // import Versions from "@ascribed/astro/Versions.astro";
13
+ // <Versions entry={entry} />
14
+ //
15
+ // It renders nothing for a page of a build that isn't versioned, or of a
16
+ // build with one line to list. It writes no styles: the site's own select it
17
+ // by `nav[aria-label]`, or the `class` given.
18
+ import { lineVersions } from "@ascribed/astro/content";
19
+
20
+ interface Props {
21
+ /** The page's collection entry. */
22
+ entry: { filePath?: string | undefined; data?: unknown };
23
+ /** The list's accessible name. Default: "Versions". */
24
+ label?: string;
25
+ /** After a line that doesn't have the page. Default: "(not in this version)". */
26
+ missing?: string;
27
+ /** After an archived line. Default: "(archived)". */
28
+ archived?: string;
29
+ /** A class for the `<nav>`. */
30
+ class?: string;
31
+ }
32
+
33
+ const {
34
+ entry,
35
+ label = "Versions",
36
+ missing = "(not in this version)",
37
+ archived = "(archived)",
38
+ class: className,
39
+ } = Astro.props;
40
+ const versions = lineVersions(entry);
41
+ ---
42
+
43
+ {
44
+ versions.length > 1 && (
45
+ <nav aria-label={label} class={className}>
46
+ <ul>
47
+ {versions.map((version) => (
48
+ <li>
49
+ {version.route === undefined ? (
50
+ <span data-line={version.line} data-missing>
51
+ {version.line} {missing}
52
+ </span>
53
+ ) : (
54
+ <a
55
+ href={version.route}
56
+ aria-current={version.own ? "page" : undefined}
57
+ data-line={version.line}
58
+ data-current={version.status === "current" ? "" : undefined}
59
+ data-archived={version.status === "archived" ? "" : undefined}
60
+ >
61
+ {version.status === "archived" ? `${version.line} ${archived}` : version.line}
62
+ </a>
63
+ )}
64
+ </li>
65
+ ))}
66
+ </ul>
67
+ </nav>
68
+ )
69
+ }
@@ -5,6 +5,10 @@ import Availability from "@ascribed/astro/Availability.astro";
5
5
  import Elements from "@ascribed/astro/Elements.astro";
6
6
  // Everything the head says about the page, from its record.
7
7
  import Head from "@ascribed/astro/Head.astro";
8
+ // With a versioned build, the lines that have the page, and on an older line
9
+ // what it is and where the current one is.
10
+ import Versions from "@ascribed/astro/Versions.astro";
11
+ import VersionBanner from "@ascribed/astro/VersionBanner.astro";
8
12
  import { sidebar } from "@ascribed/astro/content";
9
13
  import { nav } from "../nav.ts";
10
14
 
@@ -34,7 +38,8 @@ const titleHtml = (of: CollectionEntry<"docs">) =>
34
38
  const { available } = page(entry);
35
39
  // The sidebar: src/nav.ts's groups, each page with its address. The build
36
40
  // fails when the list leaves out a page, or names one that doesn't exist.
37
- const groups = sidebar(nav, await getCollection("docs"));
41
+ // With a versioned build, it shows the pages of the line this page is in.
42
+ const groups = sidebar(nav, await getCollection("docs"), { entry });
38
43
  ---
39
44
 
40
45
  <!doctype html>
@@ -68,8 +73,10 @@ const groups = sidebar(nav, await getCollection("docs"));
68
73
  }
69
74
  </nav>
70
75
  <main>
76
+ <VersionBanner entry={entry} />
71
77
  <h1 set:html={titleHtml(entry)} />
72
78
  <Availability available={available} />
79
+ <Versions entry={entry} />
73
80
  <nav aria-label="On this page">
74
81
  <ul>
75
82
  {