@ascribed/astro 0.3.2-next.22 → 0.3.2
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/README.md +3 -0
- package/dist/content.d.ts +63 -1
- package/dist/content.js +183 -18
- package/dist/content.js.map +1 -1
- package/dist/files.d.ts +2 -0
- package/dist/files.js +7 -0
- package/dist/files.js.map +1 -1
- package/dist/index.js +72 -12
- package/dist/index.js.map +1 -1
- package/dist/names.d.ts +2 -0
- package/dist/names.js +2 -0
- package/dist/names.js.map +1 -1
- package/dist/nav.d.ts +9 -1
- package/dist/nav.js +13 -3
- package/dist/nav.js.map +1 -1
- package/dist/records.d.ts +8 -6
- package/dist/records.js +19 -4
- package/dist/records.js.map +1 -1
- package/dist/review/diff.d.ts +15 -4
- package/dist/review/diff.js +15 -7
- package/dist/review/diff.js.map +1 -1
- package/dist/review/routes.d.ts +2 -0
- package/dist/review/routes.js.map +1 -1
- package/dist/review/server.js +17 -1
- package/dist/review/server.js.map +1 -1
- package/dist/run.d.ts +3 -2
- package/dist/run.js +4 -4
- package/dist/run.js.map +1 -1
- package/dist/versions.d.ts +96 -0
- package/dist/versions.js +160 -0
- package/dist/versions.js.map +1 -0
- package/package.json +9 -6
- package/src/Availability.astro +2 -0
- package/src/VersionBanner.astro +76 -0
- package/src/Versions.astro +69 -0
- package/starter/src/layouts/Docs.astro +8 -1
package/README.md
CHANGED
|
@@ -67,6 +67,8 @@ const { entry } = Astro.props;
|
|
|
67
67
|
|
|
68
68
|
Each build writes a record of every page, `_ascribe/pages.json`, and the integration writes from every project's records what search engines read: `Head.astro` gives a page its `<title>`, description, canonical link, `noindex` when it's `unlisted`, and `nosnippet` when it's `unquoted`, a link to its Markdown version, the Open Graph tags a link preview reads, with the site's name and a social card given as properties, and its breadcrumbs as structured data, and `astro dev` warns about a tag a layout writes again; and the site gets one `sitemap.xml`, at Astro's `base`, and one `robots.txt`, at the host's root, with each project's `[crawlers] lines` and a group for each kind of crawler it disallows, such as `training = "disallow"`, in `astro build` and `astro dev`; projects that disagree on a kind fail the build. A page with no record, such as an Astro page of the site's own, gets nothing from `Head.astro` and is in the sitemap as a page Astro built. A site with its own `public/robots.txt` or `public/sitemap.xml` keeps it, and a site whose `base` isn't `/` gets no `robots.txt`: the build prints the lines its host's needs. With no `site`, there's no sitemap.
|
|
69
69
|
|
|
70
|
+
A versioned build (`versions` in `ascribe.toml`) is one collection with every live line's pages: the current line's at the base path, and an older line's under its number, `/docs/3.4/`, with the line in front of their entry ids. Each entry's data has `line` and `current`. `Versions.astro`, given a page's entry, lists the lines, each that publishes the page a link, with the page's own marked; `VersionBanner.astro` says on an older line's page which version it is and links to the current line's; `sidebar(nav, entries, { entry })` shows the pages of the entry's line. `astro dev` rebuilds every line. A permalink forwards to the current line, and each line's `llms.txt` and Markdown are published at its base path.
|
|
71
|
+
|
|
70
72
|
A page's Markdown version has no head, so its rules are response headers, which each build lists in `_ascribe/headers.json`: `X-Robots-Tag` when the page isn't listed, and a canonical `Link` to the page. `astro dev` sends them. With `[consumer] headers = "netlify"`, `astro build` writes them into the site's `_headers`; a site with its own `public/_headers` keeps it, and the build fails with the lines it lacks.
|
|
71
73
|
|
|
72
74
|
`npx @ascribed/astro init` writes those files into an Astro project once, as yours from then on: the collection (`src/content.config.ts`), the route (`src/pages/[...slug].astro`), a layout that loads the elements (`src/layouts/Docs.astro`), and the sidebar's list (`src/nav.ts`). It never writes over a file. `--project <dir>` names the folder holding `ascribe.toml`, and `--build <name>` the build, when there are several. The layout's sidebar comes from `sidebar(nav, entries)` in `@ascribed/astro/content`, which gives each listed page its entry and address, and fails the build when the list leaves out a page or names one that isn't there.
|
|
@@ -96,6 +98,7 @@ The [Astro guide](https://ascribed-dev.com/go/astro-guide/) walks through a site
|
|
|
96
98
|
| `src/init.ts`, `src/cli.ts`, `starter/` | `npx @ascribed/astro init`, the package's command (`ascribe-astro`), and the files it writes. `examples/astro-site`'s are what it writes for that project; `test/init.test.ts` holds that, and rewrites them with `ASCRIBE_BLESS=1` |
|
|
97
99
|
| `src/Elements.astro` | Loads `@ascribed/elements` (stylesheet and script) |
|
|
98
100
|
| `src/Availability.astro` | A page's availability as the element library's badge |
|
|
101
|
+
| `src/Versions.astro`, `src/VersionBanner.astro`, `src/versions.ts` | A versioned build's lines, from `<output-dir>/_ascribe/versions.json`, the list of the lines that have a page, and an older line's banner |
|
|
99
102
|
| `src/code-titles.ts` | The Shiki transformer that shows code block titles and tested lines |
|
|
100
103
|
| `src/Permalink.astro`, `src/permalinks.ts` | The route at each permalink's address, and its pages, from the site output's `_ascribe/permalinks.json` |
|
|
101
104
|
| `src/MissingPermalink.astro` | The explanation on a site's 404 page for an address under the permalinks' path that matches nothing |
|
package/dist/content.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { glob } from "astro/loaders";
|
|
2
2
|
import { type PageRecord } from "./records.js";
|
|
3
|
+
import { type LineNotice, type ListedLine, type PageLine } from "./versions.js";
|
|
3
4
|
import { type NavEntry, type NavGroup, type SidebarGroup } from "./nav.js";
|
|
4
5
|
export type { NavGroup, SidebarGroup, SidebarPage } from "./nav.js";
|
|
5
6
|
export type { Listing, ListingFrom, PageRecord, Step } from "./records.js";
|
|
7
|
+
export type { LineNotice } from "./versions.js";
|
|
6
8
|
export { headTags, scriptJson, type Card, type HeadOptions, type HeadTag } from "./head.js";
|
|
7
9
|
/**
|
|
8
10
|
* The collection's loader and schema: the `glob` loader on the site output's
|
|
@@ -14,6 +16,14 @@ export { headTags, scriptJson, type Card, type HeadOptions, type HeadTag } from
|
|
|
14
16
|
* for the root page), which is what `AstroRouter` in `ascribe-resolve`
|
|
15
17
|
* computes, so a page's route is the base path plus its id, and the root page is
|
|
16
18
|
* at the base path itself.
|
|
19
|
+
*
|
|
20
|
+
* A versioned build's collection has every live line's pages: the current
|
|
21
|
+
* line's with the ids above, and an older line's with the line in front
|
|
22
|
+
* (`3.4/guides/my-setup`, and `3.4` for its root page), so a page's route is
|
|
23
|
+
* still the base path plus its id. Each line's pages are `glob`'s over that
|
|
24
|
+
* line's site output, and each entry's data has `line`, the line it's from,
|
|
25
|
+
* and `current`, whether that's the current line. The schema doesn't type
|
|
26
|
+
* them: `Versions.astro` and `sidebar` read them.
|
|
17
27
|
*/
|
|
18
28
|
export declare function ascribeCollection<S>(options: {
|
|
19
29
|
schema: S;
|
|
@@ -30,17 +40,69 @@ export declare function ascribeCollection<S>(options: {
|
|
|
30
40
|
* the entries were loaded from. Throws, naming each, when `nav` leaves out a page
|
|
31
41
|
* of the collection, names a file that isn't one, or names a page twice, so the
|
|
32
42
|
* build fails until the list is complete.
|
|
43
|
+
*
|
|
44
|
+
* With a versioned build, `entry`, the page the sidebar is on, says which
|
|
45
|
+
* line's pages it shows: each page of the list that line has, so an older
|
|
46
|
+
* line's sidebar stays in that line. The list is checked against every
|
|
47
|
+
* line's pages. Without `entry`, it shows the current line's.
|
|
33
48
|
*/
|
|
34
49
|
export declare function sidebar<E extends NavEntry>(nav: readonly NavGroup[], entries: readonly E[], options?: {
|
|
35
50
|
name?: string;
|
|
51
|
+
entry?: {
|
|
52
|
+
data?: unknown;
|
|
53
|
+
};
|
|
36
54
|
}): SidebarGroup<E>[];
|
|
37
55
|
/**
|
|
38
56
|
* A collection entry's page record, from its build's `_ascribe/pages.json`:
|
|
39
57
|
* its route, title, description, canonical address, and listing, as every
|
|
40
58
|
* output Ascribe writes has them. `undefined` for an entry that isn't from an
|
|
41
|
-
* Ascribe collection, or whose page has no record.
|
|
59
|
+
* Ascribe collection, or whose page has no record. An entry of an older line
|
|
60
|
+
* has that line's record.
|
|
42
61
|
*/
|
|
43
62
|
export declare function pageRecord(entry: {
|
|
44
63
|
filePath?: string | undefined;
|
|
45
64
|
data?: unknown;
|
|
46
65
|
}): PageRecord | undefined;
|
|
66
|
+
/** A line that publishes a page: its route there, and whether it's the line the page is from. */
|
|
67
|
+
export interface PageVersion extends PageLine {
|
|
68
|
+
/** Whether this is the line of the entry asked about. */
|
|
69
|
+
own: boolean;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The lines of a versioned build that publish a collection entry's page,
|
|
73
|
+
* newest first, each with the page's route in it and whether it's the
|
|
74
|
+
* entry's own line, from `<output-dir>/_ascribe/versions.json`. A line that
|
|
75
|
+
* doesn't have the page isn't listed. Empty for an entry of a build that
|
|
76
|
+
* isn't versioned. `Versions.astro` writes them as a list of links.
|
|
77
|
+
*/
|
|
78
|
+
export declare function pageVersions(entry: {
|
|
79
|
+
filePath?: string | undefined;
|
|
80
|
+
data?: unknown;
|
|
81
|
+
}): PageVersion[];
|
|
82
|
+
/** A line of a versioned build, as a page's list of versions shows it. */
|
|
83
|
+
export interface LineVersion extends ListedLine {
|
|
84
|
+
/** Whether this is the line of the entry asked about. */
|
|
85
|
+
own: boolean;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Every line of a versioned build, for the list of versions on a collection
|
|
89
|
+
* entry's page, newest first: each live line, with the page's route in it,
|
|
90
|
+
* or the page in its place, and no route when it has neither; then each
|
|
91
|
+
* archived line that has an address, with that address. Empty for an entry
|
|
92
|
+
* of a build that isn't versioned. `Versions.astro` writes them.
|
|
93
|
+
*/
|
|
94
|
+
export declare function lineVersions(entry: {
|
|
95
|
+
filePath?: string | undefined;
|
|
96
|
+
data?: unknown;
|
|
97
|
+
}): LineVersion[];
|
|
98
|
+
/**
|
|
99
|
+
* What a collection entry's page of an older line says about the line: its
|
|
100
|
+
* label and number, the last day it's supported, and the page's route in the
|
|
101
|
+
* current line, or the page that takes its place there, or else the current
|
|
102
|
+
* line's home. `undefined` on the current line, and for an entry of a build
|
|
103
|
+
* that isn't versioned. `VersionBanner.astro` writes it.
|
|
104
|
+
*/
|
|
105
|
+
export declare function versionNotice(entry: {
|
|
106
|
+
filePath?: string | undefined;
|
|
107
|
+
data?: unknown;
|
|
108
|
+
}): LineNotice | undefined;
|
package/dist/content.js
CHANGED
|
@@ -23,6 +23,7 @@ import path from "node:path";
|
|
|
23
23
|
import { glob } from "astro/loaders";
|
|
24
24
|
import { astroRoot, collections, routes, siteRoot } from "virtual:ascribe/site";
|
|
25
25
|
import { recordFor } from "./records.js";
|
|
26
|
+
import { lineNotice, linesOf, listedLines, pageLines, } from "./versions.js";
|
|
26
27
|
import { pageRoute, sidebarGroups, } from "./nav.js";
|
|
27
28
|
export { headTags, scriptJson } from "./head.js";
|
|
28
29
|
/**
|
|
@@ -35,11 +36,79 @@ export { headTags, scriptJson } from "./head.js";
|
|
|
35
36
|
* for the root page), which is what `AstroRouter` in `ascribe-resolve`
|
|
36
37
|
* computes, so a page's route is the base path plus its id, and the root page is
|
|
37
38
|
* at the base path itself.
|
|
39
|
+
*
|
|
40
|
+
* A versioned build's collection has every live line's pages: the current
|
|
41
|
+
* line's with the ids above, and an older line's with the line in front
|
|
42
|
+
* (`3.4/guides/my-setup`, and `3.4` for its root page), so a page's route is
|
|
43
|
+
* still the base path plus its id. Each line's pages are `glob`'s over that
|
|
44
|
+
* line's site output, and each entry's data has `line`, the line it's from,
|
|
45
|
+
* and `current`, whether that's the current line. The schema doesn't type
|
|
46
|
+
* them: `Versions.astro` and `sidebar` read them.
|
|
38
47
|
*/
|
|
39
48
|
export function ascribeCollection(options) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
49
|
+
const root = rootFor(options.name);
|
|
50
|
+
// Whether the build is versioned is known once it's built, so it's read when the collection loads.
|
|
51
|
+
const loader = {
|
|
52
|
+
name: "@ascribed/astro",
|
|
53
|
+
load: async (context) => {
|
|
54
|
+
const lines = linesOf(root);
|
|
55
|
+
if (lines === undefined)
|
|
56
|
+
return pagesOf(root).load(context);
|
|
57
|
+
const older = lines.filter((line) => line.status !== "current").map((line) => line.line);
|
|
58
|
+
for (const line of lines) {
|
|
59
|
+
const current = line.status === "current";
|
|
60
|
+
await pagesOf(line.siteRoot).load({
|
|
61
|
+
...context,
|
|
62
|
+
store: lineStore(context.store, current ? undefined : line.line, older),
|
|
63
|
+
parseData: async (props) => {
|
|
64
|
+
const data = await context.parseData(props);
|
|
65
|
+
for (const key of ["line", "current"]) {
|
|
66
|
+
if (key in data)
|
|
67
|
+
throw new Error(`@ascribed/astro: ${props.filePath ?? props.id} has a frontmatter field \`${key}\`, and a versioned build's entries have \`${key}\` of their own. Rename the field.`);
|
|
68
|
+
}
|
|
69
|
+
return { ...data, line: line.line, current };
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
return { loader, schema: options.schema };
|
|
76
|
+
}
|
|
77
|
+
/** `glob` over one site output's pages. `_ascribe/` holds the schema and published files, never pages. */
|
|
78
|
+
function pagesOf(siteRoot) {
|
|
79
|
+
return glob({ pattern: ["**/*.md", "!_ascribe/**"], base: pathToFileURL(siteRoot + path.sep) });
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The collection's store as one line's `glob` sees it: its own entries, by
|
|
83
|
+
* the ids `glob` gives them, which the store keeps with the line in front.
|
|
84
|
+
* `line` is `undefined` for the current line, whose ids are `glob`'s, and
|
|
85
|
+
* whose entries are every one not under an older line, so an entry of a
|
|
86
|
+
* line that's no longer live is removed with the current line's stale ones.
|
|
87
|
+
*/
|
|
88
|
+
function lineStore(store, line, older) {
|
|
89
|
+
const under = (id, prefix) => id === prefix || id.startsWith(`${prefix}/`);
|
|
90
|
+
const outer = (id) => (line === undefined ? id : id === "index" ? line : `${line}/${id}`);
|
|
91
|
+
const inner = (id) => line === undefined ? id : id === line ? "index" : id.slice(line.length + 1);
|
|
92
|
+
const mine = (id) => line === undefined ? !older.some((prefix) => under(id, prefix)) : under(id, line);
|
|
93
|
+
const scoped = {
|
|
94
|
+
get: (id) => store.get(outer(id)),
|
|
95
|
+
set: (entry) => store.set({ ...entry, id: outer(entry.id) }),
|
|
96
|
+
delete: (id) => store.delete(outer(id)),
|
|
97
|
+
has: (id) => store.has(outer(id)),
|
|
98
|
+
keys: () => store.keys().filter(mine).map(inner),
|
|
99
|
+
values: () => store.entries().flatMap(([id, entry]) => (mine(id) ? [entry] : [])),
|
|
100
|
+
entries: () => store.entries().flatMap(([id, entry]) => (mine(id) ? [[inner(id), entry]] : [])),
|
|
101
|
+
clear: () => {
|
|
102
|
+
for (const id of store.keys().filter(mine))
|
|
103
|
+
store.delete(id);
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
// What `glob` uses beyond these (`addModuleImport`, `addAssetImports`) is by file, not by id.
|
|
107
|
+
return new Proxy(store, {
|
|
108
|
+
get: (target, key, receiver) => key in scoped
|
|
109
|
+
? scoped[key]
|
|
110
|
+
: Reflect.get(target, key, receiver),
|
|
111
|
+
});
|
|
43
112
|
}
|
|
44
113
|
/**
|
|
45
114
|
* A layout's sidebar: `nav`'s groups, each page with its collection entry and
|
|
@@ -49,42 +118,138 @@ export function ascribeCollection(options) {
|
|
|
49
118
|
* the entries were loaded from. Throws, naming each, when `nav` leaves out a page
|
|
50
119
|
* of the collection, names a file that isn't one, or names a page twice, so the
|
|
51
120
|
* build fails until the list is complete.
|
|
121
|
+
*
|
|
122
|
+
* With a versioned build, `entry`, the page the sidebar is on, says which
|
|
123
|
+
* line's pages it shows: each page of the list that line has, so an older
|
|
124
|
+
* line's sidebar stays in that line. The list is checked against every
|
|
125
|
+
* line's pages. Without `entry`, it shows the current line's.
|
|
52
126
|
*/
|
|
53
127
|
export function sidebar(nav, entries, options = {}) {
|
|
54
128
|
const root = sidebarRoot(entries, options.name);
|
|
55
129
|
const at = routes.find((route) => route.siteRoot === root);
|
|
56
130
|
const basePath = at?.basePath ?? "/";
|
|
57
131
|
const trailingSlash = at?.trailingSlash ?? "always";
|
|
58
|
-
|
|
132
|
+
const route = (id) => pageRoute(id, basePath, trailingSlash);
|
|
133
|
+
const lines = linesOf(root);
|
|
134
|
+
if (lines === undefined)
|
|
135
|
+
return sidebarGroups(nav, entries, route, (e) => sourceFile(root, e));
|
|
136
|
+
const current = lines.find((line) => line.status === "current")?.line;
|
|
137
|
+
const shown = lineOf(options.entry) ?? current;
|
|
138
|
+
const lineRoot = (entry) => lines.find((line) => line.line === lineOf(entry))?.siteRoot ?? root;
|
|
139
|
+
return sidebarGroups(nav, entries, route, (entry) => sourceFile(lineRoot(entry), entry), {
|
|
140
|
+
// An older line's ids have the line in front.
|
|
141
|
+
id: (entry) => {
|
|
142
|
+
const line = lineOf(entry);
|
|
143
|
+
if (line === undefined || line === current)
|
|
144
|
+
return entry.id;
|
|
145
|
+
return entry.id === line ? "index" : entry.id.slice(line.length + 1);
|
|
146
|
+
},
|
|
147
|
+
shown: (entry) => lineOf(entry) === shown,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/** The line an entry of a versioned build's collection is from, or `undefined`. */
|
|
151
|
+
function lineOf(entry) {
|
|
152
|
+
const data = entry?.data;
|
|
153
|
+
const line = data?.line;
|
|
154
|
+
return typeof line === "string" ? line : undefined;
|
|
59
155
|
}
|
|
60
156
|
/**
|
|
61
157
|
* A collection entry's page record, from its build's `_ascribe/pages.json`:
|
|
62
158
|
* its route, title, description, canonical address, and listing, as every
|
|
63
159
|
* output Ascribe writes has them. `undefined` for an entry that isn't from an
|
|
64
|
-
* Ascribe collection, or whose page has no record.
|
|
160
|
+
* Ascribe collection, or whose page has no record. An entry of an older line
|
|
161
|
+
* has that line's record.
|
|
65
162
|
*/
|
|
66
163
|
export function pageRecord(entry) {
|
|
67
|
-
|
|
164
|
+
const found = entryRoot(entry);
|
|
165
|
+
if (found === undefined)
|
|
166
|
+
return undefined;
|
|
167
|
+
return recordFor(found.lineRoot, found.source);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* The lines of a versioned build that publish a collection entry's page,
|
|
171
|
+
* newest first, each with the page's route in it and whether it's the
|
|
172
|
+
* entry's own line, from `<output-dir>/_ascribe/versions.json`. A line that
|
|
173
|
+
* doesn't have the page isn't listed. Empty for an entry of a build that
|
|
174
|
+
* isn't versioned. `Versions.astro` writes them as a list of links.
|
|
175
|
+
*/
|
|
176
|
+
export function pageVersions(entry) {
|
|
177
|
+
const found = entryRoot(entry);
|
|
178
|
+
if (found === undefined)
|
|
179
|
+
return [];
|
|
180
|
+
const own = lineOf(entry);
|
|
181
|
+
return pageLines(found.root, found.source).map((line) => ({
|
|
182
|
+
...line,
|
|
183
|
+
own: own === undefined ? line.current : line.line === own,
|
|
184
|
+
}));
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Every line of a versioned build, for the list of versions on a collection
|
|
188
|
+
* entry's page, newest first: each live line, with the page's route in it,
|
|
189
|
+
* or the page in its place, and no route when it has neither; then each
|
|
190
|
+
* archived line that has an address, with that address. Empty for an entry
|
|
191
|
+
* of a build that isn't versioned. `Versions.astro` writes them.
|
|
192
|
+
*/
|
|
193
|
+
export function lineVersions(entry) {
|
|
194
|
+
const found = entryRoot(entry);
|
|
195
|
+
if (found === undefined)
|
|
196
|
+
return [];
|
|
197
|
+
const own = lineOf(entry);
|
|
198
|
+
return listedLines(found.root, found.source).map((line) => ({
|
|
199
|
+
...line,
|
|
200
|
+
own: own === undefined ? line.status === "current" : line.line === own,
|
|
201
|
+
}));
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* What a collection entry's page of an older line says about the line: its
|
|
205
|
+
* label and number, the last day it's supported, and the page's route in the
|
|
206
|
+
* current line, or the page that takes its place there, or else the current
|
|
207
|
+
* line's home. `undefined` on the current line, and for an entry of a build
|
|
208
|
+
* that isn't versioned. `VersionBanner.astro` writes it.
|
|
209
|
+
*/
|
|
210
|
+
export function versionNotice(entry) {
|
|
211
|
+
const found = entryRoot(entry);
|
|
212
|
+
const line = lineOf(entry);
|
|
213
|
+
if (found === undefined || line === undefined)
|
|
68
214
|
return undefined;
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
215
|
+
const at = routes.find((route) => route.siteRoot === found.root);
|
|
216
|
+
const home = pageRoute("index", at?.basePath ?? "/", at?.trailingSlash ?? "always");
|
|
217
|
+
return lineNotice(found.root, found.source, line, home);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* The site output root of the entry's build (the current line's, for a
|
|
221
|
+
* versioned build), the root of the line it's from, and its page's source
|
|
222
|
+
* path, or `undefined` for an entry from none of the integration's
|
|
223
|
+
* collections.
|
|
224
|
+
*/
|
|
225
|
+
function entryRoot(entry) {
|
|
226
|
+
if (entry.filePath === undefined)
|
|
73
227
|
return undefined;
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
228
|
+
const file = entry.filePath;
|
|
229
|
+
for (const root of declaredRoots()) {
|
|
230
|
+
const lineRoot = [root, ...(linesOf(root) ?? []).map((line) => line.siteRoot)].find((candidate) => inside(candidate, file));
|
|
231
|
+
if (lineRoot === undefined)
|
|
232
|
+
continue;
|
|
233
|
+
const data = entry.data;
|
|
234
|
+
const source = typeof data?.source === "string"
|
|
235
|
+
? data.source
|
|
236
|
+
: path.relative(lineRoot, path.resolve(astroRoot, file)).split(path.sep).join("/");
|
|
237
|
+
return { root, lineRoot, source };
|
|
238
|
+
}
|
|
239
|
+
return undefined;
|
|
240
|
+
}
|
|
241
|
+
/** The site output roots of the integration's entries. */
|
|
242
|
+
function declaredRoots() {
|
|
243
|
+
return [...Object.values(collections), ...(siteRoot === "" ? [] : [siteRoot])];
|
|
79
244
|
}
|
|
80
245
|
/**
|
|
81
246
|
* The site output root of the collection a sidebar lists: the entry `name`
|
|
82
247
|
* names, or, with several and no name, the one the entries were loaded from.
|
|
83
248
|
*/
|
|
84
249
|
function sidebarRoot(entries, name) {
|
|
85
|
-
const
|
|
86
|
-
if (name === undefined && Object.keys(collections).length > 1 &&
|
|
87
|
-
const from =
|
|
250
|
+
const first = entries[0];
|
|
251
|
+
if (name === undefined && Object.keys(collections).length > 1 && first !== undefined) {
|
|
252
|
+
const from = entryRoot(first)?.root;
|
|
88
253
|
if (from !== undefined)
|
|
89
254
|
return from;
|
|
90
255
|
}
|
package/dist/content.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content.js","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"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","sourcesContent":["// The content collection over a build's site output. Import it from\n// `content.config.ts`, which Vite loads, so it can read what the integration\n// resolved (`virtual:ascribe/site`) instead of finding the project itself.\n//\n// import { defineCollection } from \"astro:content\";\n// import { ascribeCollection } from \"@ascribed/astro/content\";\n// import { schema } from \"../.ascribe/build/site/site/_ascribe/schema.ts\";\n// export const collections = { docs: defineCollection(ascribeCollection({ schema })) };\n//\n// With several projects, each collection names its entry:\n//\n// import { schema as docs } from \"../.astro/integrations/_ascribed_astro/docs/schema.ts\";\n// export const collections = {\n// docs: defineCollection(ascribeCollection({ schema: docs, name: \"docs\" })),\n// };\n//\n// A layout's sidebar, from the site's list of pages (`src/nav.ts`, which\n// `npx @ascribed/astro init` writes), checked against the collection:\n//\n// const groups = sidebar(nav, await getCollection(\"docs\"));\nimport { pathToFileURL } from \"node:url\";\nimport path from \"node:path\";\nimport { glob } from \"astro/loaders\";\nimport { astroRoot, collections, routes, siteRoot } from \"virtual:ascribe/site\";\nimport { recordFor, type PageRecord } from \"./records.js\";\nimport {\n pageRoute,\n sidebarGroups,\n type NavEntry,\n type NavGroup,\n type SidebarGroup,\n} from \"./nav.js\";\n\nexport type { NavGroup, SidebarGroup, SidebarPage } from \"./nav.js\";\nexport type { Listing, ListingFrom, PageRecord, Step } from \"./records.js\";\nexport { headTags, scriptJson, type Card, type HeadOptions, type HeadTag } from \"./head.js\";\n\n/**\n * The collection's loader and schema: the `glob` loader on the site output's\n * pages, with the generated Zod schema (`_ascribe/schema.ts`'s `schema`).\n * With several projects (`ascribe({ projects })`), `name` says which entry's\n * site output; it can be left out when there's one entry.\n *\n * Entry ids are Astro's own (`guides/my-setup` for `Guides/My Setup.md`, `index`\n * for the root page), which is what `AstroRouter` in `ascribe-resolve`\n * computes, so a page's route is the base path plus its id, and the root page is\n * at the base path itself.\n */\nexport function ascribeCollection<S>(options: { schema: S; name?: string }): {\n loader: ReturnType<typeof glob>;\n schema: S;\n} {\n // `_ascribe/` holds the schema and published files, never pages.\n const base = pathToFileURL(rootFor(options.name) + path.sep);\n return { loader: glob({ pattern: [\"**/*.md\", \"!_ascribe/**\"], base }), schema: options.schema };\n}\n\n/**\n * A layout's sidebar: `nav`'s groups, each page with its collection entry and\n * its address, which is the one Ascribe's links use. `entries` is the\n * collection (`await getCollection(\"docs\")`), and `name` says which project's,\n * as for `ascribeCollection`; with several projects and no `name`, it's the one\n * the entries were loaded from. Throws, naming each, when `nav` leaves out a page\n * of the collection, names a file that isn't one, or names a page twice, so the\n * build fails until the list is complete.\n */\nexport function sidebar<E extends NavEntry>(\n nav: readonly NavGroup[],\n entries: readonly E[],\n options: { name?: string } = {},\n): SidebarGroup<E>[] {\n const root = sidebarRoot(entries, options.name);\n const at = routes.find((route) => route.siteRoot === root);\n const basePath = at?.basePath ?? \"/\";\n const trailingSlash = at?.trailingSlash ?? \"always\";\n return sidebarGroups(\n nav,\n entries,\n (id) => pageRoute(id, basePath, trailingSlash),\n (entry) => sourceFile(root, entry),\n );\n}\n\n/**\n * A collection entry's page record, from its build's `_ascribe/pages.json`:\n * its route, title, description, canonical address, and listing, as every\n * output Ascribe writes has them. `undefined` for an entry that isn't from an\n * Ascribe collection, or whose page has no record.\n */\nexport function pageRecord(entry: {\n filePath?: string | undefined;\n data?: unknown;\n}): PageRecord | undefined {\n if (entry.filePath === undefined) return undefined;\n const file = path.resolve(astroRoot, entry.filePath);\n const roots = [...Object.values(collections), ...(siteRoot === \"\" ? [] : [siteRoot])];\n const root = roots.find((candidate) => inside(candidate, file));\n if (root === undefined) return undefined;\n const data = entry.data as { source?: unknown } | undefined;\n const source =\n typeof data?.source === \"string\"\n ? data.source\n : path.relative(root, file).split(path.sep).join(\"/\");\n return recordFor(root, source);\n}\n\n/**\n * The site output root of the collection a sidebar lists: the entry `name`\n * names, or, with several and no name, the one the entries were loaded from.\n */\nfunction sidebarRoot(entries: readonly NavEntry[], name: string | undefined): string {\n const file = entries[0]?.filePath;\n if (name === undefined && Object.keys(collections).length > 1 && file !== undefined) {\n const from = Object.values(collections).find((root) => inside(root, file));\n if (from !== undefined) return from;\n }\n return rootFor(name);\n}\n\n/** Whether `file`, from the Astro root as an entry's `filePath` is, is under `root`. */\nfunction inside(root: string, file: string): boolean {\n const relative = path.relative(root, path.resolve(astroRoot, file));\n return !relative.startsWith(\"..\") && !path.isAbsolute(relative);\n}\n\n/** An entry's source file, by its path in the site output, which mirrors the content root. */\nfunction sourceFile(root: string, entry: NavEntry): string {\n if (entry.filePath === undefined) return `the page ${entry.id}`;\n return inside(root, entry.filePath)\n ? path.relative(root, path.resolve(astroRoot, entry.filePath)).split(path.sep).join(\"/\")\n : entry.filePath;\n}\n\n/** The site output root of the entry `name` names. */\nfunction rootFor(name: string | undefined): string {\n const names = Object.keys(collections);\n if (names.length === 0) {\n if (name !== undefined) {\n throw new Error(\n `@ascribed/astro: ascribeCollection was given the name \"${name}\", but ascribe() was given one project, which has no name. Leave out \\`name\\`, or list the project in \\`projects\\`.`,\n );\n }\n return siteRoot;\n }\n if (name === undefined) {\n if (names.length === 1) return collections[names[0] as string] as string;\n throw new Error(\n `@ascribed/astro: ascribeCollection needs a \\`name\\` when ascribe() has several projects: one of ${names.map((n) => `\"${n}\"`).join(\", \")}.`,\n );\n }\n const root = collections[name];\n if (root === undefined) {\n throw new Error(\n `@ascribed/astro: ascribeCollection was given the name \"${name}\", and ascribe()'s projects are ${names.map((n) => `\"${n}\"`).join(\", \")}.`,\n );\n }\n return root;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"content.js","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"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","sourcesContent":["// The content collection over a build's site output. Import it from\n// `content.config.ts`, which Vite loads, so it can read what the integration\n// resolved (`virtual:ascribe/site`) instead of finding the project itself.\n//\n// import { defineCollection } from \"astro:content\";\n// import { ascribeCollection } from \"@ascribed/astro/content\";\n// import { schema } from \"../.ascribe/build/site/site/_ascribe/schema.ts\";\n// export const collections = { docs: defineCollection(ascribeCollection({ schema })) };\n//\n// With several projects, each collection names its entry:\n//\n// import { schema as docs } from \"../.astro/integrations/_ascribed_astro/docs/schema.ts\";\n// export const collections = {\n// docs: defineCollection(ascribeCollection({ schema: docs, name: \"docs\" })),\n// };\n//\n// A layout's sidebar, from the site's list of pages (`src/nav.ts`, which\n// `npx @ascribed/astro init` writes), checked against the collection:\n//\n// const groups = sidebar(nav, await getCollection(\"docs\"));\nimport { pathToFileURL } from \"node:url\";\nimport path from \"node:path\";\nimport { glob, type LoaderContext } from \"astro/loaders\";\nimport { astroRoot, collections, routes, siteRoot } from \"virtual:ascribe/site\";\nimport { recordFor, type PageRecord } from \"./records.js\";\nimport {\n lineNotice,\n linesOf,\n listedLines,\n pageLines,\n type LineNotice,\n type ListedLine,\n type PageLine,\n} from \"./versions.js\";\nimport {\n pageRoute,\n sidebarGroups,\n type NavEntry,\n type NavGroup,\n type SidebarGroup,\n} from \"./nav.js\";\n\nexport type { NavGroup, SidebarGroup, SidebarPage } from \"./nav.js\";\nexport type { Listing, ListingFrom, PageRecord, Step } from \"./records.js\";\nexport type { LineNotice } from \"./versions.js\";\nexport { headTags, scriptJson, type Card, type HeadOptions, type HeadTag } from \"./head.js\";\n\n/**\n * The collection's loader and schema: the `glob` loader on the site output's\n * pages, with the generated Zod schema (`_ascribe/schema.ts`'s `schema`).\n * With several projects (`ascribe({ projects })`), `name` says which entry's\n * site output; it can be left out when there's one entry.\n *\n * Entry ids are Astro's own (`guides/my-setup` for `Guides/My Setup.md`, `index`\n * for the root page), which is what `AstroRouter` in `ascribe-resolve`\n * computes, so a page's route is the base path plus its id, and the root page is\n * at the base path itself.\n *\n * A versioned build's collection has every live line's pages: the current\n * line's with the ids above, and an older line's with the line in front\n * (`3.4/guides/my-setup`, and `3.4` for its root page), so a page's route is\n * still the base path plus its id. Each line's pages are `glob`'s over that\n * line's site output, and each entry's data has `line`, the line it's from,\n * and `current`, whether that's the current line. The schema doesn't type\n * them: `Versions.astro` and `sidebar` read them.\n */\nexport function ascribeCollection<S>(options: { schema: S; name?: string }): {\n loader: ReturnType<typeof glob>;\n schema: S;\n} {\n const root = rootFor(options.name);\n // Whether the build is versioned is known once it's built, so it's read when the collection loads.\n const loader: ReturnType<typeof glob> = {\n name: \"@ascribed/astro\",\n load: async (context) => {\n const lines = linesOf(root);\n if (lines === undefined) return pagesOf(root).load(context);\n const older = lines.filter((line) => line.status !== \"current\").map((line) => line.line);\n for (const line of lines) {\n const current = line.status === \"current\";\n await pagesOf(line.siteRoot).load({\n ...context,\n store: lineStore(context.store, current ? undefined : line.line, older),\n parseData: async (props) => {\n const data: Record<string, unknown> = await context.parseData(props);\n for (const key of [\"line\", \"current\"]) {\n if (key in data)\n throw new Error(\n `@ascribed/astro: ${props.filePath ?? props.id} has a frontmatter field \\`${key}\\`, and a versioned build's entries have \\`${key}\\` of their own. Rename the field.`,\n );\n }\n return { ...data, line: line.line, current } as unknown as typeof props.data;\n },\n });\n }\n },\n };\n return { loader, schema: options.schema };\n}\n\n/** `glob` over one site output's pages. `_ascribe/` holds the schema and published files, never pages. */\nfunction pagesOf(siteRoot: string): ReturnType<typeof glob> {\n return glob({ pattern: [\"**/*.md\", \"!_ascribe/**\"], base: pathToFileURL(siteRoot + path.sep) });\n}\n\n/**\n * The collection's store as one line's `glob` sees it: its own entries, by\n * the ids `glob` gives them, which the store keeps with the line in front.\n * `line` is `undefined` for the current line, whose ids are `glob`'s, and\n * whose entries are every one not under an older line, so an entry of a\n * line that's no longer live is removed with the current line's stale ones.\n */\nfunction lineStore(\n store: LoaderContext[\"store\"],\n line: string | undefined,\n older: readonly string[],\n): LoaderContext[\"store\"] {\n const under = (id: string, prefix: string) => id === prefix || id.startsWith(`${prefix}/`);\n const outer = (id: string) => (line === undefined ? id : id === \"index\" ? line : `${line}/${id}`);\n const inner = (id: string) =>\n line === undefined ? id : id === line ? \"index\" : id.slice(line.length + 1);\n const mine = (id: string) =>\n line === undefined ? !older.some((prefix) => under(id, prefix)) : under(id, line);\n const scoped: Partial<LoaderContext[\"store\"]> = {\n get: (id) => store.get(outer(id)),\n set: (entry) => store.set({ ...entry, id: outer(entry.id) }),\n delete: (id) => store.delete(outer(id)),\n has: (id) => store.has(outer(id)),\n keys: () => store.keys().filter(mine).map(inner),\n values: () => store.entries().flatMap(([id, entry]) => (mine(id) ? [entry] : [])),\n entries: () =>\n store.entries().flatMap(([id, entry]) => (mine(id) ? [[inner(id), entry] as const] : [])),\n clear: () => {\n for (const id of store.keys().filter(mine)) store.delete(id);\n },\n };\n // What `glob` uses beyond these (`addModuleImport`, `addAssetImports`) is by file, not by id.\n return new Proxy(store, {\n get: (target, key, receiver) =>\n key in scoped\n ? scoped[key as keyof typeof scoped]\n : (Reflect.get(target, key, receiver) as unknown),\n });\n}\n\n/**\n * A layout's sidebar: `nav`'s groups, each page with its collection entry and\n * its address, which is the one Ascribe's links use. `entries` is the\n * collection (`await getCollection(\"docs\")`), and `name` says which project's,\n * as for `ascribeCollection`; with several projects and no `name`, it's the one\n * the entries were loaded from. Throws, naming each, when `nav` leaves out a page\n * of the collection, names a file that isn't one, or names a page twice, so the\n * build fails until the list is complete.\n *\n * With a versioned build, `entry`, the page the sidebar is on, says which\n * line's pages it shows: each page of the list that line has, so an older\n * line's sidebar stays in that line. The list is checked against every\n * line's pages. Without `entry`, it shows the current line's.\n */\nexport function sidebar<E extends NavEntry>(\n nav: readonly NavGroup[],\n entries: readonly E[],\n options: { name?: string; entry?: { data?: unknown } } = {},\n): SidebarGroup<E>[] {\n const root = sidebarRoot(entries, options.name);\n const at = routes.find((route) => route.siteRoot === root);\n const basePath = at?.basePath ?? \"/\";\n const trailingSlash = at?.trailingSlash ?? \"always\";\n const route = (id: string) => pageRoute(id, basePath, trailingSlash);\n const lines = linesOf(root);\n if (lines === undefined) return sidebarGroups(nav, entries, route, (e) => sourceFile(root, e));\n const current = lines.find((line) => line.status === \"current\")?.line;\n const shown = lineOf(options.entry) ?? current;\n const lineRoot = (entry: NavEntry) =>\n lines.find((line) => line.line === lineOf(entry))?.siteRoot ?? root;\n return sidebarGroups(nav, entries, route, (entry) => sourceFile(lineRoot(entry), entry), {\n // An older line's ids have the line in front.\n id: (entry) => {\n const line = lineOf(entry);\n if (line === undefined || line === current) return entry.id;\n return entry.id === line ? \"index\" : entry.id.slice(line.length + 1);\n },\n shown: (entry) => lineOf(entry) === shown,\n });\n}\n\n/** The line an entry of a versioned build's collection is from, or `undefined`. */\nfunction lineOf(entry: object | undefined): string | undefined {\n const data = (entry as { data?: unknown } | undefined)?.data;\n const line = (data as { line?: unknown } | undefined)?.line;\n return typeof line === \"string\" ? line : undefined;\n}\n\n/**\n * A collection entry's page record, from its build's `_ascribe/pages.json`:\n * its route, title, description, canonical address, and listing, as every\n * output Ascribe writes has them. `undefined` for an entry that isn't from an\n * Ascribe collection, or whose page has no record. An entry of an older line\n * has that line's record.\n */\nexport function pageRecord(entry: {\n filePath?: string | undefined;\n data?: unknown;\n}): PageRecord | undefined {\n const found = entryRoot(entry);\n if (found === undefined) return undefined;\n return recordFor(found.lineRoot, found.source);\n}\n\n/** A line that publishes a page: its route there, and whether it's the line the page is from. */\nexport interface PageVersion extends PageLine {\n /** Whether this is the line of the entry asked about. */\n own: boolean;\n}\n\n/**\n * The lines of a versioned build that publish a collection entry's page,\n * newest first, each with the page's route in it and whether it's the\n * entry's own line, from `<output-dir>/_ascribe/versions.json`. A line that\n * doesn't have the page isn't listed. Empty for an entry of a build that\n * isn't versioned. `Versions.astro` writes them as a list of links.\n */\nexport function pageVersions(entry: {\n filePath?: string | undefined;\n data?: unknown;\n}): PageVersion[] {\n const found = entryRoot(entry);\n if (found === undefined) return [];\n const own = lineOf(entry);\n return pageLines(found.root, found.source).map((line) => ({\n ...line,\n own: own === undefined ? line.current : line.line === own,\n }));\n}\n\n/** A line of a versioned build, as a page's list of versions shows it. */\nexport interface LineVersion extends ListedLine {\n /** Whether this is the line of the entry asked about. */\n own: boolean;\n}\n\n/**\n * Every line of a versioned build, for the list of versions on a collection\n * entry's page, newest first: each live line, with the page's route in it,\n * or the page in its place, and no route when it has neither; then each\n * archived line that has an address, with that address. Empty for an entry\n * of a build that isn't versioned. `Versions.astro` writes them.\n */\nexport function lineVersions(entry: {\n filePath?: string | undefined;\n data?: unknown;\n}): LineVersion[] {\n const found = entryRoot(entry);\n if (found === undefined) return [];\n const own = lineOf(entry);\n return listedLines(found.root, found.source).map((line) => ({\n ...line,\n own: own === undefined ? line.status === \"current\" : line.line === own,\n }));\n}\n\n/**\n * What a collection entry's page of an older line says about the line: its\n * label and number, the last day it's supported, and the page's route in the\n * current line, or the page that takes its place there, or else the current\n * line's home. `undefined` on the current line, and for an entry of a build\n * that isn't versioned. `VersionBanner.astro` writes it.\n */\nexport function versionNotice(entry: {\n filePath?: string | undefined;\n data?: unknown;\n}): LineNotice | undefined {\n const found = entryRoot(entry);\n const line = lineOf(entry);\n if (found === undefined || line === undefined) return undefined;\n const at = routes.find((route) => route.siteRoot === found.root);\n const home = pageRoute(\"index\", at?.basePath ?? \"/\", at?.trailingSlash ?? \"always\");\n return lineNotice(found.root, found.source, line, home);\n}\n\n/**\n * The site output root of the entry's build (the current line's, for a\n * versioned build), the root of the line it's from, and its page's source\n * path, or `undefined` for an entry from none of the integration's\n * collections.\n */\nfunction entryRoot(entry: {\n filePath?: string | undefined;\n data?: unknown;\n}): { root: string; lineRoot: string; source: string } | undefined {\n if (entry.filePath === undefined) return undefined;\n const file = entry.filePath;\n for (const root of declaredRoots()) {\n const lineRoot = [root, ...(linesOf(root) ?? []).map((line) => line.siteRoot)].find(\n (candidate) => inside(candidate, file),\n );\n if (lineRoot === undefined) continue;\n const data = entry.data as { source?: unknown } | undefined;\n const source =\n typeof data?.source === \"string\"\n ? data.source\n : path.relative(lineRoot, path.resolve(astroRoot, file)).split(path.sep).join(\"/\");\n return { root, lineRoot, source };\n }\n return undefined;\n}\n\n/** The site output roots of the integration's entries. */\nfunction declaredRoots(): string[] {\n return [...Object.values(collections), ...(siteRoot === \"\" ? [] : [siteRoot])];\n}\n\n/**\n * The site output root of the collection a sidebar lists: the entry `name`\n * names, or, with several and no name, the one the entries were loaded from.\n */\nfunction sidebarRoot(entries: readonly NavEntry[], name: string | undefined): string {\n const first = entries[0];\n if (name === undefined && Object.keys(collections).length > 1 && first !== undefined) {\n const from = entryRoot(first)?.root;\n if (from !== undefined) return from;\n }\n return rootFor(name);\n}\n\n/** Whether `file`, from the Astro root as an entry's `filePath` is, is under `root`. */\nfunction inside(root: string, file: string): boolean {\n const relative = path.relative(root, path.resolve(astroRoot, file));\n return !relative.startsWith(\"..\") && !path.isAbsolute(relative);\n}\n\n/** An entry's source file, by its path in the site output, which mirrors the content root. */\nfunction sourceFile(root: string, entry: NavEntry): string {\n if (entry.filePath === undefined) return `the page ${entry.id}`;\n return inside(root, entry.filePath)\n ? path.relative(root, path.resolve(astroRoot, entry.filePath)).split(path.sep).join(\"/\")\n : entry.filePath;\n}\n\n/** The site output root of the entry `name` names. */\nfunction rootFor(name: string | undefined): string {\n const names = Object.keys(collections);\n if (names.length === 0) {\n if (name !== undefined) {\n throw new Error(\n `@ascribed/astro: ascribeCollection was given the name \"${name}\", but ascribe() was given one project, which has no name. Leave out \\`name\\`, or list the project in \\`projects\\`.`,\n );\n }\n return siteRoot;\n }\n if (name === undefined) {\n if (names.length === 1) return collections[names[0] as string] as string;\n throw new Error(\n `@ascribed/astro: ascribeCollection needs a \\`name\\` when ascribe() has several projects: one of ${names.map((n) => `\"${n}\"`).join(\", \")}.`,\n );\n }\n const root = collections[name];\n if (root === undefined) {\n throw new Error(\n `@ascribed/astro: ascribeCollection was given the name \"${name}\", and ascribe()'s projects are ${names.map((n) => `\"${n}\"`).join(\", \")}.`,\n );\n }\n return root;\n}\n"]}
|
package/dist/files.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export declare function copyAgentFiles(plainRoot: string, outDir: URL, folder?:
|
|
|
14
14
|
type Middleware = (req: IncomingMessage, res: ServerResponse, next: () => void) => void;
|
|
15
15
|
/** A dev-server middleware serving the published files at `<base>_ascribe/files/`, `base` being the project's base path. */
|
|
16
16
|
export declare function filesMiddleware(siteRoot: string, base: string): Middleware;
|
|
17
|
+
/** Runs `middlewares` in order, each passing a request it doesn't serve to the next, and the last to `next`. */
|
|
18
|
+
export declare function inOrder(middlewares: readonly Middleware[], req: IncomingMessage, res: ServerResponse, next: () => void): void;
|
|
17
19
|
/**
|
|
18
20
|
* A dev-server middleware serving the plain output for agents at `<base>`: `llms.txt`, each page's
|
|
19
21
|
* `.md`, and their files, each with the headers `headers` gives its path from the host's root.
|
package/dist/files.js
CHANGED
|
@@ -65,6 +65,13 @@ const TYPES = {
|
|
|
65
65
|
export function filesMiddleware(siteRoot, base) {
|
|
66
66
|
return directoryMiddleware(publishedDir(siteRoot), `${base}_ascribe/files/`);
|
|
67
67
|
}
|
|
68
|
+
/** Runs `middlewares` in order, each passing a request it doesn't serve to the next, and the last to `next`. */
|
|
69
|
+
export function inOrder(middlewares, req, res, next) {
|
|
70
|
+
const [first, ...rest] = middlewares;
|
|
71
|
+
if (first === undefined)
|
|
72
|
+
return next();
|
|
73
|
+
first(req, res, () => inOrder(rest, req, res, next));
|
|
74
|
+
}
|
|
68
75
|
/**
|
|
69
76
|
* A dev-server middleware serving the plain output for agents at `<base>`: `llms.txt`, each page's
|
|
70
77
|
* `.md`, and their files, each with the headers `headers` gives its path from the host's root.
|
package/dist/files.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"files.js","sourceRoot":"","sources":["../src/files.ts"],"names":[],"mappings":"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","sourcesContent":["// Serving `_ascribe/files/`: the files pages link to that aren't pages or\n// images. Astro doesn't copy them, so the integration\n// does: into the build's output directory, and in dev through a middleware.\n// Both put them at `<base-path>_ascribe/files/`, the URL the site output links\n// to, where `<base-path>` is the project's `[consumer] base-path`: Astro's\n// `base` or a path under it.\n//\n// With `[consumer] agents = true`, the same for the plain output, which is\n// laid out by URL: `llms.txt`, each page's Markdown, and their files, all at\n// `<base-path>` and below.\nimport { createReadStream } from \"node:fs\";\nimport { cp, stat } from \"node:fs/promises\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { normalizeBase } from \"./project.js\";\n\n/** The directory of published files inside a site output root. */\nfunction publishedDir(siteRoot: string): string {\n return path.join(siteRoot, \"_ascribe\", \"files\");\n}\n\n/**\n * The base path's folder in Astro's build output, which is at Astro's `base`:\n * `docs/` for a base path of `/docs/` under a `base` of `/`, `\"\"` when they're the same.\n */\nexport function folderUnder(basePath: string, astroBase: string): string {\n const root = normalizeBase(astroBase);\n return basePath.startsWith(root) ? basePath.slice(root.length) : \"\";\n}\n\n/**\n * Copies the published files to `<outDir>/<folder>_ascribe/files/`, `folder` being the\n * base path's (`folderUnder`). Returns whether there were any.\n */\nexport async function copyPublishedFiles(\n siteRoot: string,\n outDir: URL,\n folder = \"\",\n): Promise<boolean> {\n return copyDirectory(\n publishedDir(siteRoot),\n path.join(fileURLToPath(outDir), folder, \"_ascribe\", \"files\"),\n );\n}\n\n/** Copies the plain output for agents to the base path's folder of the build output. Returns whether there was one. */\nexport async function copyAgentFiles(\n plainRoot: string,\n outDir: URL,\n folder = \"\",\n): Promise<boolean> {\n return copyDirectory(plainRoot, path.join(fileURLToPath(outDir), folder));\n}\n\nasync function copyDirectory(from: string, to: string): Promise<boolean> {\n if (\n !(await stat(from).then(\n (s) => s.isDirectory(),\n () => false,\n ))\n )\n return false;\n await cp(from, to, { recursive: true });\n return true;\n}\n\nconst TYPES: Record<string, string> = {\n \".pdf\": \"application/pdf\",\n \".zip\": \"application/zip\",\n \".json\": \"application/json\",\n \".yaml\": \"text/yaml; charset=utf-8\",\n \".yml\": \"text/yaml; charset=utf-8\",\n \".txt\": \"text/plain; charset=utf-8\",\n \".csv\": \"text/csv; charset=utf-8\",\n \".md\": \"text/markdown; charset=utf-8\",\n \".html\": \"text/html; charset=utf-8\",\n \".css\": \"text/css; charset=utf-8\",\n \".js\": \"text/javascript; charset=utf-8\",\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".webp\": \"image/webp\",\n \".svg\": \"image/svg+xml\",\n};\n\ntype Middleware = (req: IncomingMessage, res: ServerResponse, next: () => void) => void;\n\n/** A dev-server middleware serving the published files at `<base>_ascribe/files/`, `base` being the project's base path. */\nexport function filesMiddleware(siteRoot: string, base: string): Middleware {\n return directoryMiddleware(publishedDir(siteRoot), `${base}_ascribe/files/`);\n}\n\n/**\n * A dev-server middleware serving the plain output for agents at `<base>`: `llms.txt`, each page's\n * `.md`, and their files, each with the headers `headers` gives its path from the host's root.\n */\nexport function agentsMiddleware(\n plainRoot: string,\n base: string,\n headers: (pathname: string) => Record<string, string> = () => ({}),\n): Middleware {\n return directoryMiddleware(plainRoot, base, headers);\n}\n\n/**\n * A middleware serving the files in `root` at the URL path `prefix`, which ends in `/`, each with\n * the headers `headers` gives its path.\n */\nfunction directoryMiddleware(\n root: string,\n prefix: string,\n headers: (pathname: string) => Record<string, string> = () => ({}),\n): Middleware {\n // Only under the base path, as in the build. Vite strips the base from\n // `req.url` before a plugin's middleware runs, but connect keeps the original in `originalUrl`.\n return (req, res, next) => {\n const pathname =\n ((req as { originalUrl?: string }).originalUrl ?? req.url ?? \"\").split(/[?#]/, 1)[0] ?? \"\";\n if (!pathname.startsWith(prefix) || (req.method !== \"GET\" && req.method !== \"HEAD\"))\n return next();\n let relative: string;\n try {\n relative = decodeURIComponent(pathname.slice(prefix.length));\n } catch {\n return next();\n }\n const file = path.resolve(root, relative);\n if (file !== root && !file.startsWith(root + path.sep)) return next();\n stat(file).then(\n (info) => {\n if (!info.isFile()) return next();\n res.setHeader(\n \"Content-Type\",\n TYPES[path.extname(file).toLowerCase()] ?? \"application/octet-stream\",\n );\n res.setHeader(\"Content-Length\", info.size);\n for (const [name, value] of Object.entries(headers(pathname))) res.setHeader(name, value);\n if (req.method === \"HEAD\") return void res.end();\n createReadStream(file).pipe(res);\n },\n () => next(),\n );\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"files.js","sourceRoot":"","sources":["../src/files.ts"],"names":[],"mappings":"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","sourcesContent":["// Serving `_ascribe/files/`: the files pages link to that aren't pages or\n// images. Astro doesn't copy them, so the integration\n// does: into the build's output directory, and in dev through a middleware.\n// Both put them at `<base-path>_ascribe/files/`, the URL the site output links\n// to, where `<base-path>` is the project's `[consumer] base-path`: Astro's\n// `base` or a path under it.\n//\n// With `[consumer] agents = true`, the same for the plain output, which is\n// laid out by URL: `llms.txt`, each page's Markdown, and their files, all at\n// `<base-path>` and below.\nimport { createReadStream } from \"node:fs\";\nimport { cp, stat } from \"node:fs/promises\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { normalizeBase } from \"./project.js\";\n\n/** The directory of published files inside a site output root. */\nfunction publishedDir(siteRoot: string): string {\n return path.join(siteRoot, \"_ascribe\", \"files\");\n}\n\n/**\n * The base path's folder in Astro's build output, which is at Astro's `base`:\n * `docs/` for a base path of `/docs/` under a `base` of `/`, `\"\"` when they're the same.\n */\nexport function folderUnder(basePath: string, astroBase: string): string {\n const root = normalizeBase(astroBase);\n return basePath.startsWith(root) ? basePath.slice(root.length) : \"\";\n}\n\n/**\n * Copies the published files to `<outDir>/<folder>_ascribe/files/`, `folder` being the\n * base path's (`folderUnder`). Returns whether there were any.\n */\nexport async function copyPublishedFiles(\n siteRoot: string,\n outDir: URL,\n folder = \"\",\n): Promise<boolean> {\n return copyDirectory(\n publishedDir(siteRoot),\n path.join(fileURLToPath(outDir), folder, \"_ascribe\", \"files\"),\n );\n}\n\n/** Copies the plain output for agents to the base path's folder of the build output. Returns whether there was one. */\nexport async function copyAgentFiles(\n plainRoot: string,\n outDir: URL,\n folder = \"\",\n): Promise<boolean> {\n return copyDirectory(plainRoot, path.join(fileURLToPath(outDir), folder));\n}\n\nasync function copyDirectory(from: string, to: string): Promise<boolean> {\n if (\n !(await stat(from).then(\n (s) => s.isDirectory(),\n () => false,\n ))\n )\n return false;\n await cp(from, to, { recursive: true });\n return true;\n}\n\nconst TYPES: Record<string, string> = {\n \".pdf\": \"application/pdf\",\n \".zip\": \"application/zip\",\n \".json\": \"application/json\",\n \".yaml\": \"text/yaml; charset=utf-8\",\n \".yml\": \"text/yaml; charset=utf-8\",\n \".txt\": \"text/plain; charset=utf-8\",\n \".csv\": \"text/csv; charset=utf-8\",\n \".md\": \"text/markdown; charset=utf-8\",\n \".html\": \"text/html; charset=utf-8\",\n \".css\": \"text/css; charset=utf-8\",\n \".js\": \"text/javascript; charset=utf-8\",\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".webp\": \"image/webp\",\n \".svg\": \"image/svg+xml\",\n};\n\ntype Middleware = (req: IncomingMessage, res: ServerResponse, next: () => void) => void;\n\n/** A dev-server middleware serving the published files at `<base>_ascribe/files/`, `base` being the project's base path. */\nexport function filesMiddleware(siteRoot: string, base: string): Middleware {\n return directoryMiddleware(publishedDir(siteRoot), `${base}_ascribe/files/`);\n}\n\n/** Runs `middlewares` in order, each passing a request it doesn't serve to the next, and the last to `next`. */\nexport function inOrder(\n middlewares: readonly Middleware[],\n req: IncomingMessage,\n res: ServerResponse,\n next: () => void,\n): void {\n const [first, ...rest] = middlewares;\n if (first === undefined) return next();\n first(req, res, () => inOrder(rest, req, res, next));\n}\n\n/**\n * A dev-server middleware serving the plain output for agents at `<base>`: `llms.txt`, each page's\n * `.md`, and their files, each with the headers `headers` gives its path from the host's root.\n */\nexport function agentsMiddleware(\n plainRoot: string,\n base: string,\n headers: (pathname: string) => Record<string, string> = () => ({}),\n): Middleware {\n return directoryMiddleware(plainRoot, base, headers);\n}\n\n/**\n * A middleware serving the files in `root` at the URL path `prefix`, which ends in `/`, each with\n * the headers `headers` gives its path.\n */\nfunction directoryMiddleware(\n root: string,\n prefix: string,\n headers: (pathname: string) => Record<string, string> = () => ({}),\n): Middleware {\n // Only under the base path, as in the build. Vite strips the base from\n // `req.url` before a plugin's middleware runs, but connect keeps the original in `originalUrl`.\n return (req, res, next) => {\n const pathname =\n ((req as { originalUrl?: string }).originalUrl ?? req.url ?? \"\").split(/[?#]/, 1)[0] ?? \"\";\n if (!pathname.startsWith(prefix) || (req.method !== \"GET\" && req.method !== \"HEAD\"))\n return next();\n let relative: string;\n try {\n relative = decodeURIComponent(pathname.slice(prefix.length));\n } catch {\n return next();\n }\n const file = path.resolve(root, relative);\n if (file !== root && !file.startsWith(root + path.sep)) return next();\n stat(file).then(\n (info) => {\n if (!info.isFile()) return next();\n res.setHeader(\n \"Content-Type\",\n TYPES[path.extname(file).toLowerCase()] ?? \"application/octet-stream\",\n );\n res.setHeader(\"Content-Length\", info.size);\n for (const [name, value] of Object.entries(headers(pathname))) res.setHeader(name, value);\n if (req.method === \"HEAD\") return void res.end();\n createReadStream(file).pipe(res);\n },\n () => next(),\n );\n };\n}\n"]}
|