@ascribed/astro 0.2.1-next.9 → 0.3.1-next.18
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 +41 -9
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +62 -0
- package/dist/cli.js.map +1 -0
- package/dist/content.d.ts +30 -0
- package/dist/content.js +101 -2
- package/dist/content.js.map +1 -1
- package/dist/dev.d.ts +2 -1
- package/dist/dev.js +13 -3
- package/dist/dev.js.map +1 -1
- package/dist/files.d.ts +21 -4
- package/dist/files.js +46 -8
- package/dist/files.js.map +1 -1
- package/dist/head-check.d.ts +6 -0
- package/dist/head-check.js +28 -0
- package/dist/head-check.js.map +1 -0
- package/dist/head.d.ts +54 -0
- package/dist/head.js +114 -0
- package/dist/head.js.map +1 -0
- package/dist/headers.d.ts +50 -0
- package/dist/headers.js +155 -0
- package/dist/headers.js.map +1 -0
- package/dist/index.d.ts +40 -9
- package/dist/index.js +330 -95
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +41 -0
- package/dist/init.js +130 -0
- package/dist/init.js.map +1 -0
- package/dist/names.d.ts +5 -0
- package/dist/names.js +5 -0
- package/dist/names.js.map +1 -1
- package/dist/nav.d.ts +51 -0
- package/dist/nav.js +97 -0
- package/dist/nav.js.map +1 -0
- package/dist/permalinks.d.ts +78 -0
- package/dist/permalinks.js +114 -0
- package/dist/permalinks.js.map +1 -0
- package/dist/project.d.ts +37 -0
- package/dist/project.js +47 -2
- package/dist/project.js.map +1 -1
- package/dist/projects.d.ts +38 -0
- package/dist/projects.js +127 -0
- package/dist/projects.js.map +1 -0
- package/dist/records.d.ts +136 -0
- package/dist/records.js +182 -0
- package/dist/records.js.map +1 -0
- package/dist/review/devfile.d.ts +6 -1
- package/dist/review/devfile.js +12 -2
- package/dist/review/devfile.js.map +1 -1
- package/dist/review/diff.d.ts +13 -0
- package/dist/review/diff.js +34 -1
- package/dist/review/diff.js.map +1 -1
- package/dist/review/protocol.d.ts +6 -1
- package/dist/review/protocol.js.map +1 -1
- package/dist/review/routes.d.ts +3 -0
- package/dist/review/routes.js +49 -35
- package/dist/review/routes.js.map +1 -1
- package/dist/review/server.d.ts +15 -0
- package/dist/review/server.js +58 -7
- package/dist/review/server.js.map +1 -1
- package/dist/root-files.d.ts +68 -0
- package/dist/root-files.js +103 -0
- package/dist/root-files.js.map +1 -0
- package/dist/run.d.ts +7 -6
- package/dist/run.js +14 -6
- package/dist/run.js.map +1 -1
- package/dist/shapes.d.ts +8 -1
- package/dist/shapes.js.map +1 -1
- package/dist/toolbar/app.js +98 -0
- package/dist/toolbar/app.js.map +1 -1
- package/dist/toolbar/style.d.ts +1 -1
- package/dist/toolbar/style.js +31 -23
- package/dist/toolbar/style.js.map +1 -1
- package/package.json +33 -9
- package/src/Head.astro +58 -0
- package/src/MissingPermalink.astro +59 -0
- package/src/Permalink.astro +78 -0
- package/starter/src/content.config.ts +8 -0
- package/starter/src/layouts/Docs.astro +87 -0
- package/starter/src/nav.ts +13 -0
- package/starter/src/pages/[...slug].astro +20 -0
package/README.md
CHANGED
|
@@ -2,25 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
The [Astro](https://astro.build) integration for [Ascribe](https://github.com/ascribed-dev/ascribe), for Astro 7.3.5 and later 7.x releases.
|
|
4
4
|
|
|
5
|
-
It runs `ascribe build --emit site` before Astro loads content, gives you a content collection over the output with a schema generated from `ascribe.toml`, applies heading ids and image attributes in Astro's own Markdown pipeline (so Astro keeps its heading, table-of-contents, and image handling), loads the element library,
|
|
5
|
+
It runs `ascribe build --emit site` before Astro loads content, gives you a content collection over the output with a schema generated from `ascribe.toml`, applies heading ids and image attributes in Astro's own Markdown pipeline (so Astro keeps its heading, table-of-contents, and image handling), loads the element library, serves the files pages link to, and gives each permalink an address that forwards to its heading. In `astro dev`, it rebuilds as you edit.
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
8
|
npm install @ascribed/astro
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
`satteri` and `@types/hast` are optional peer dependencies: the declarations of `@ascribed/astro/satteri` and `@ascribed/astro/rehype` import types from them. Astro's default Sätteri processor brings the first, and a `unified()` processor the second, so a project that uses one of those plugins has what it needs.
|
|
12
|
+
|
|
11
13
|
```js
|
|
12
14
|
// astro.config.mjs
|
|
13
15
|
import { defineConfig } from "astro/config";
|
|
14
16
|
import ascribe from "@ascribed/astro";
|
|
15
17
|
|
|
16
18
|
export default defineConfig({
|
|
17
|
-
site
|
|
18
|
-
base: "/docs",
|
|
19
|
-
trailingSlash: "never",
|
|
19
|
+
// `site`, `base`, and `trailingSlash` come from ascribe.toml's [consumer].
|
|
20
20
|
integrations: [ascribe({ build: "site" })],
|
|
21
21
|
});
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
+
Astro's `site`, `base`, and `trailingSlash` come from `ascribe.toml`'s `[consumer]` when the Astro config leaves them out. With `projects`, `site` and `trailingSlash` come from the first entry whose project sets them, and `base` stays the site's. Set in the Astro config, each must agree with `[consumer]`, or the build fails.
|
|
25
|
+
|
|
24
26
|
```ts
|
|
25
27
|
// src/content.config.ts
|
|
26
28
|
import { defineCollection } from "astro:content";
|
|
@@ -33,45 +35,75 @@ export const collections = { docs: defineCollection(ascribeCollection({ schema }
|
|
|
33
35
|
|
|
34
36
|
If the Ascribe project is outside the Astro root (`project: "../docs"`), import `../.astro/integrations/_ascribed_astro/schema.ts` instead: the integration copies the schema there after each build, so TypeScript finds `astro/zod` from it.
|
|
35
37
|
|
|
38
|
+
`[consumer] base-path` can be Astro's `base` or a path under it, so a home page can sit at `/` in front of docs at `/docs/`: list the docs in `projects`, even alone, so Astro's `base` stays `/`. Several projects can share one site, each under its own base path:
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
ascribe({
|
|
42
|
+
projects: [
|
|
43
|
+
{ name: "docs", project: "../docs", build: "site" },
|
|
44
|
+
{ name: "home", project: "home", build: "site" },
|
|
45
|
+
],
|
|
46
|
+
review: "docs",
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
36
50
|
```astro
|
|
37
51
|
---
|
|
38
52
|
// A layout
|
|
39
53
|
import Elements from "@ascribed/astro/Elements.astro";
|
|
40
54
|
import Availability from "@ascribed/astro/Availability.astro";
|
|
55
|
+
import Head from "@ascribed/astro/Head.astro";
|
|
41
56
|
const { entry } = Astro.props;
|
|
42
57
|
---
|
|
43
|
-
<head
|
|
58
|
+
<head>
|
|
59
|
+
<!-- Everything the head says about the page, from its record. -->
|
|
60
|
+
<Head entry={entry} title={(title, record) => `${title} · Loom`} name="Loom" />
|
|
61
|
+
<Elements />
|
|
62
|
+
</head>
|
|
44
63
|
<Availability available={entry.data.available} />
|
|
45
64
|
<!-- With `inline = "code"` on `title`: its code spans as <code>. -->
|
|
46
65
|
<h1 set:html={entry.data.formatted.title} />
|
|
47
66
|
```
|
|
48
67
|
|
|
49
|
-
|
|
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
|
+
|
|
70
|
+
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
|
+
|
|
72
|
+
`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.
|
|
73
|
+
|
|
74
|
+
The [Astro guide](https://ascribed-dev.com/go/astro-guide/) walks through a site: `ascribe.toml`, the collection, the route, the layout, the options, [a home page in front of the docs](https://ascribed-dev.com/go/a-home-page-in-front-of-the-docs/), [several projects in one site](https://ascribed-dev.com/go/several-projects-in-one-site/), search engines and crawlers, what `astro dev` does, and [review in the site preview](https://ascribed-dev.com/go/review-in-the-site-preview/). [Review](https://ascribed-dev.com/go/review-guide/) walks through reviewing a pull request on your site.
|
|
50
75
|
|
|
51
76
|
## Options
|
|
52
77
|
|
|
53
78
|
| Option | Meaning |
|
|
54
79
|
|---|---|
|
|
55
|
-
| `build` | The build whose site output is the collection: a build name in `ascribe.toml`. Required. |
|
|
80
|
+
| `build` | The build whose site output is the collection: a build name in `ascribe.toml`. Required, unless `projects` is given. |
|
|
56
81
|
| `project` | The directory holding `ascribe.toml`, relative to the Astro root. By default, the root. |
|
|
82
|
+
| `projects` | Several projects in one site, in place of `build` and `project`: a list of entries, each `{ name, build, project }`. Each collection names its entry, `ascribeCollection({ schema, name })`, and each entry's schema is copied to `.astro/integrations/_ascribed_astro/<name>/schema.ts`. |
|
|
57
83
|
| `binary` | The `ascribe` binary, relative to the Astro root. By default, `ASCRIBE_BIN`, then the binary `@ascribed/cli` installed. |
|
|
58
84
|
| `anchors` | Source anchors (`ascribe build --anchors`), for review: `"dev"` in `astro dev` only, `true` always. By default, `false`, though `review` turns them on in `astro dev`. |
|
|
59
|
-
| `review` | The **Ascribe review** app in `astro dev`'s toolbar: a pull request's changes and comments on the real page. `false` leaves it out. By default, `true`. Never in `astro build`. |
|
|
85
|
+
| `review` | The **Ascribe review** app in `astro dev`'s toolbar: a pull request's changes and comments on the real page. `false` leaves it out. By default, `true`. Never in `astro build`. With `projects`, an entry's name reviews that entry, and `true` the first. |
|
|
60
86
|
| `codeTitles` | A code block's title (`title="…"` in its info string, as `@snippet` writes it) above it: a Shiki transformer puts the block in a `<figure class="code-title">` with a `<figcaption>`. `false` leaves it out. By default, `true`. |
|
|
87
|
+
| `sitemap` | `{ exclude: [...] }`: routes of the site's own pages, from the host's root, to leave out of the sitemap, such as one that carries `noindex`. |
|
|
61
88
|
|
|
62
89
|
## Development
|
|
63
90
|
|
|
64
91
|
| File | Role |
|
|
65
92
|
|---|---|
|
|
66
93
|
| `src/index.ts` | The integration |
|
|
67
|
-
| `src/content.ts` | `ascribeCollection`, for `content.config.ts` |
|
|
94
|
+
| `src/content.ts` | `ascribeCollection`, for `content.config.ts`, and `sidebar`, for a layout |
|
|
95
|
+
| `src/nav.ts` | The sidebar: entry ids, routes, and the check of the site's list against the collection |
|
|
96
|
+
| `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` |
|
|
68
97
|
| `src/Elements.astro` | Loads `@ascribed/elements` (stylesheet and script) |
|
|
69
98
|
| `src/Availability.astro` | A page's availability as the element library's badge |
|
|
70
99
|
| `src/code-titles.ts` | The Shiki transformer that shows code block titles |
|
|
100
|
+
| `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
|
+
| `src/MissingPermalink.astro` | The explanation on a site's 404 page for an address under the permalinks' path that matches nothing |
|
|
71
102
|
| `src/schema.ts` | Copies the generated schema inside the Astro root |
|
|
72
103
|
| `src/attributes.ts` | The attribute-marker and source-anchor rules, on a hast tree |
|
|
73
104
|
| `src/rehype.ts`, `src/satteri.ts` | The rules applied in each of Astro's Markdown processors |
|
|
74
105
|
| `src/project.ts`, `src/binary.ts`, `src/run.ts`, `src/files.ts`, `src/dev.ts` | `ascribe.toml`, the binary, running it, serving files, dev rebuilds |
|
|
106
|
+
| `src/projects.ts` | Several projects in one site: the entries' names, and what the projects can't share |
|
|
75
107
|
| `src/review/` | Review in the site preview, on the dev server's side: `ascribe diff`, the pull request (through `@ascribed/review/github` and `gh`), the routes, `.ascribe/dev.json`, and the toolbar channel's requests (`server.ts`, `protocol.ts`) |
|
|
76
108
|
| `src/toolbar/` | The **Ascribe review** toolbar app, in the page: the panel, and the `@ascribed/review` overlay on the page's blocks |
|
|
77
109
|
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// `npx @ascribed/astro`: the package's command, `ascribe-astro`. Its one
|
|
3
|
+
// subcommand, `init`, writes the starter (src/init.ts).
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
import { init, STARTER_FILES } from "./init.js";
|
|
6
|
+
const USAGE = `Usage: npx @ascribed/astro init [--project <dir>] [--build <name>]
|
|
7
|
+
|
|
8
|
+
Writes the files every site built with Ascribe starts from into the Astro
|
|
9
|
+
project in the current folder, once: ${STARTER_FILES.join(", ")}.
|
|
10
|
+
It writes nothing if any of them is there.
|
|
11
|
+
|
|
12
|
+
Options:
|
|
13
|
+
--project <dir> The folder holding ascribe.toml, from the current folder. Default: the current folder.
|
|
14
|
+
--build <name> The build the site shows. Default: the project's one build.
|
|
15
|
+
-h, --help Print this.`;
|
|
16
|
+
function main(argv) {
|
|
17
|
+
let parsed;
|
|
18
|
+
try {
|
|
19
|
+
parsed = parseArgs({
|
|
20
|
+
args: argv,
|
|
21
|
+
allowPositionals: true,
|
|
22
|
+
options: {
|
|
23
|
+
project: { type: "string" },
|
|
24
|
+
build: { type: "string" },
|
|
25
|
+
help: { type: "boolean", short: "h" },
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n\n${USAGE}\n`);
|
|
31
|
+
return 2;
|
|
32
|
+
}
|
|
33
|
+
const { values, positionals } = parsed;
|
|
34
|
+
if (values.help === true) {
|
|
35
|
+
process.stdout.write(`${USAGE}\n`);
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
if (positionals.length !== 1 || positionals[0] !== "init") {
|
|
39
|
+
process.stderr.write(`${USAGE}\n`);
|
|
40
|
+
return 2;
|
|
41
|
+
}
|
|
42
|
+
return run(values.project, values.build);
|
|
43
|
+
}
|
|
44
|
+
async function run(project, build) {
|
|
45
|
+
try {
|
|
46
|
+
const result = await init({ root: process.cwd(), project, build });
|
|
47
|
+
const options = [`build: ${JSON.stringify(result.build)}`];
|
|
48
|
+
if (project !== undefined)
|
|
49
|
+
options.push(`project: ${JSON.stringify(project)}`);
|
|
50
|
+
process.stdout.write(`${result.written.map((file) => `wrote ${file}`).join("\n")}\n\n` +
|
|
51
|
+
`Next, if astro.config.mjs doesn't have it, add the integration:\n` +
|
|
52
|
+
` integrations: [ascribe({ ${options.join(", ")} })],\n` +
|
|
53
|
+
`then run \`npx astro dev\`.\n`);
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
58
|
+
return 2;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
process.exitCode = await main(process.argv.slice(2));
|
|
62
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n// `npx @ascribed/astro`: the package's command, `ascribe-astro`. Its one\n// subcommand, `init`, writes the starter (src/init.ts).\nimport { parseArgs } from \"node:util\";\nimport { init, STARTER_FILES } from \"./init.js\";\n\nconst USAGE = `Usage: npx @ascribed/astro init [--project <dir>] [--build <name>]\n\nWrites the files every site built with Ascribe starts from into the Astro\nproject in the current folder, once: ${STARTER_FILES.join(\", \")}.\nIt writes nothing if any of them is there.\n\nOptions:\n --project <dir> The folder holding ascribe.toml, from the current folder. Default: the current folder.\n --build <name> The build the site shows. Default: the project's one build.\n -h, --help Print this.`;\n\nfunction main(argv: string[]): Promise<number> | number {\n let parsed;\n try {\n parsed = parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n project: { type: \"string\" },\n build: { type: \"string\" },\n help: { type: \"boolean\", short: \"h\" },\n },\n });\n } catch (error) {\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n\\n${USAGE}\\n`);\n return 2;\n }\n const { values, positionals } = parsed;\n if (values.help === true) {\n process.stdout.write(`${USAGE}\\n`);\n return 0;\n }\n if (positionals.length !== 1 || positionals[0] !== \"init\") {\n process.stderr.write(`${USAGE}\\n`);\n return 2;\n }\n return run(values.project, values.build);\n}\n\nasync function run(project: string | undefined, build: string | undefined): Promise<number> {\n try {\n const result = await init({ root: process.cwd(), project, build });\n const options = [`build: ${JSON.stringify(result.build)}`];\n if (project !== undefined) options.push(`project: ${JSON.stringify(project)}`);\n process.stdout.write(\n `${result.written.map((file) => `wrote ${file}`).join(\"\\n\")}\\n\\n` +\n `Next, if astro.config.mjs doesn't have it, add the integration:\\n` +\n ` integrations: [ascribe({ ${options.join(\", \")} })],\\n` +\n `then run \\`npx astro dev\\`.\\n`,\n );\n return 0;\n } catch (error) {\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`);\n return 2;\n }\n}\n\nprocess.exitCode = await main(process.argv.slice(2));\n"]}
|
package/dist/content.d.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { glob } from "astro/loaders";
|
|
2
|
+
import { type PageRecord } from "./records.js";
|
|
3
|
+
import { type NavEntry, type NavGroup, type SidebarGroup } from "./nav.js";
|
|
4
|
+
export type { NavGroup, SidebarGroup, SidebarPage } from "./nav.js";
|
|
5
|
+
export type { Listing, ListingFrom, PageRecord, Step } from "./records.js";
|
|
6
|
+
export { headTags, scriptJson, type Card, type HeadOptions, type HeadTag } from "./head.js";
|
|
2
7
|
/**
|
|
3
8
|
* The collection's loader and schema: the `glob` loader on the site output's
|
|
4
9
|
* pages, with the generated Zod schema (`_ascribe/schema.ts`'s `schema`).
|
|
10
|
+
* With several projects (`ascribe({ projects })`), `name` says which entry's
|
|
11
|
+
* site output; it can be left out when there's one entry.
|
|
5
12
|
*
|
|
6
13
|
* Entry ids are Astro's own (`guides/my-setup` for `Guides/My Setup.md`, `index`
|
|
7
14
|
* for the root page), which is what `AstroRouter` in `ascribe-resolve`
|
|
@@ -10,7 +17,30 @@ import { glob } from "astro/loaders";
|
|
|
10
17
|
*/
|
|
11
18
|
export declare function ascribeCollection<S>(options: {
|
|
12
19
|
schema: S;
|
|
20
|
+
name?: string;
|
|
13
21
|
}): {
|
|
14
22
|
loader: ReturnType<typeof glob>;
|
|
15
23
|
schema: S;
|
|
16
24
|
};
|
|
25
|
+
/**
|
|
26
|
+
* A layout's sidebar: `nav`'s groups, each page with its collection entry and
|
|
27
|
+
* its address, which is the one Ascribe's links use. `entries` is the
|
|
28
|
+
* collection (`await getCollection("docs")`), and `name` says which project's,
|
|
29
|
+
* as for `ascribeCollection`; with several projects and no `name`, it's the one
|
|
30
|
+
* the entries were loaded from. Throws, naming each, when `nav` leaves out a page
|
|
31
|
+
* of the collection, names a file that isn't one, or names a page twice, so the
|
|
32
|
+
* build fails until the list is complete.
|
|
33
|
+
*/
|
|
34
|
+
export declare function sidebar<E extends NavEntry>(nav: readonly NavGroup[], entries: readonly E[], options?: {
|
|
35
|
+
name?: string;
|
|
36
|
+
}): SidebarGroup<E>[];
|
|
37
|
+
/**
|
|
38
|
+
* A collection entry's page record, from its build's `_ascribe/pages.json`:
|
|
39
|
+
* its route, title, description, canonical address, and listing, as every
|
|
40
|
+
* output Ascribe writes has them. `undefined` for an entry that isn't from an
|
|
41
|
+
* Ascribe collection, or whose page has no record.
|
|
42
|
+
*/
|
|
43
|
+
export declare function pageRecord(entry: {
|
|
44
|
+
filePath?: string | undefined;
|
|
45
|
+
data?: unknown;
|
|
46
|
+
}): PageRecord | undefined;
|
package/dist/content.js
CHANGED
|
@@ -6,13 +6,30 @@
|
|
|
6
6
|
// import { ascribeCollection } from "@ascribed/astro/content";
|
|
7
7
|
// import { schema } from "../.ascribe/build/site/site/_ascribe/schema.ts";
|
|
8
8
|
// export const collections = { docs: defineCollection(ascribeCollection({ schema })) };
|
|
9
|
+
//
|
|
10
|
+
// With several projects, each collection names its entry:
|
|
11
|
+
//
|
|
12
|
+
// import { schema as docs } from "../.astro/integrations/_ascribed_astro/docs/schema.ts";
|
|
13
|
+
// export const collections = {
|
|
14
|
+
// docs: defineCollection(ascribeCollection({ schema: docs, name: "docs" })),
|
|
15
|
+
// };
|
|
16
|
+
//
|
|
17
|
+
// A layout's sidebar, from the site's list of pages (`src/nav.ts`, which
|
|
18
|
+
// `npx @ascribed/astro init` writes), checked against the collection:
|
|
19
|
+
//
|
|
20
|
+
// const groups = sidebar(nav, await getCollection("docs"));
|
|
9
21
|
import { pathToFileURL } from "node:url";
|
|
10
22
|
import path from "node:path";
|
|
11
23
|
import { glob } from "astro/loaders";
|
|
12
|
-
import { siteRoot } from "virtual:ascribe/site";
|
|
24
|
+
import { astroRoot, collections, routes, siteRoot } from "virtual:ascribe/site";
|
|
25
|
+
import { recordFor } from "./records.js";
|
|
26
|
+
import { pageRoute, sidebarGroups, } from "./nav.js";
|
|
27
|
+
export { headTags, scriptJson } from "./head.js";
|
|
13
28
|
/**
|
|
14
29
|
* The collection's loader and schema: the `glob` loader on the site output's
|
|
15
30
|
* pages, with the generated Zod schema (`_ascribe/schema.ts`'s `schema`).
|
|
31
|
+
* With several projects (`ascribe({ projects })`), `name` says which entry's
|
|
32
|
+
* site output; it can be left out when there's one entry.
|
|
16
33
|
*
|
|
17
34
|
* Entry ids are Astro's own (`guides/my-setup` for `Guides/My Setup.md`, `index`
|
|
18
35
|
* for the root page), which is what `AstroRouter` in `ascribe-resolve`
|
|
@@ -21,7 +38,89 @@ import { siteRoot } from "virtual:ascribe/site";
|
|
|
21
38
|
*/
|
|
22
39
|
export function ascribeCollection(options) {
|
|
23
40
|
// `_ascribe/` holds the schema and published files, never pages.
|
|
24
|
-
const base = pathToFileURL(
|
|
41
|
+
const base = pathToFileURL(rootFor(options.name) + path.sep);
|
|
25
42
|
return { loader: glob({ pattern: ["**/*.md", "!_ascribe/**"], base }), schema: options.schema };
|
|
26
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* A layout's sidebar: `nav`'s groups, each page with its collection entry and
|
|
46
|
+
* its address, which is the one Ascribe's links use. `entries` is the
|
|
47
|
+
* collection (`await getCollection("docs")`), and `name` says which project's,
|
|
48
|
+
* as for `ascribeCollection`; with several projects and no `name`, it's the one
|
|
49
|
+
* the entries were loaded from. Throws, naming each, when `nav` leaves out a page
|
|
50
|
+
* of the collection, names a file that isn't one, or names a page twice, so the
|
|
51
|
+
* build fails until the list is complete.
|
|
52
|
+
*/
|
|
53
|
+
export function sidebar(nav, entries, options = {}) {
|
|
54
|
+
const root = sidebarRoot(entries, options.name);
|
|
55
|
+
const at = routes.find((route) => route.siteRoot === root);
|
|
56
|
+
const basePath = at?.basePath ?? "/";
|
|
57
|
+
const trailingSlash = at?.trailingSlash ?? "always";
|
|
58
|
+
return sidebarGroups(nav, entries, (id) => pageRoute(id, basePath, trailingSlash), (entry) => sourceFile(root, entry));
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A collection entry's page record, from its build's `_ascribe/pages.json`:
|
|
62
|
+
* its route, title, description, canonical address, and listing, as every
|
|
63
|
+
* output Ascribe writes has them. `undefined` for an entry that isn't from an
|
|
64
|
+
* Ascribe collection, or whose page has no record.
|
|
65
|
+
*/
|
|
66
|
+
export function pageRecord(entry) {
|
|
67
|
+
if (entry.filePath === undefined)
|
|
68
|
+
return undefined;
|
|
69
|
+
const file = path.resolve(astroRoot, entry.filePath);
|
|
70
|
+
const roots = [...Object.values(collections), ...(siteRoot === "" ? [] : [siteRoot])];
|
|
71
|
+
const root = roots.find((candidate) => inside(candidate, file));
|
|
72
|
+
if (root === undefined)
|
|
73
|
+
return undefined;
|
|
74
|
+
const data = entry.data;
|
|
75
|
+
const source = typeof data?.source === "string"
|
|
76
|
+
? data.source
|
|
77
|
+
: path.relative(root, file).split(path.sep).join("/");
|
|
78
|
+
return recordFor(root, source);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The site output root of the collection a sidebar lists: the entry `name`
|
|
82
|
+
* names, or, with several and no name, the one the entries were loaded from.
|
|
83
|
+
*/
|
|
84
|
+
function sidebarRoot(entries, name) {
|
|
85
|
+
const file = entries[0]?.filePath;
|
|
86
|
+
if (name === undefined && Object.keys(collections).length > 1 && file !== undefined) {
|
|
87
|
+
const from = Object.values(collections).find((root) => inside(root, file));
|
|
88
|
+
if (from !== undefined)
|
|
89
|
+
return from;
|
|
90
|
+
}
|
|
91
|
+
return rootFor(name);
|
|
92
|
+
}
|
|
93
|
+
/** Whether `file`, from the Astro root as an entry's `filePath` is, is under `root`. */
|
|
94
|
+
function inside(root, file) {
|
|
95
|
+
const relative = path.relative(root, path.resolve(astroRoot, file));
|
|
96
|
+
return !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
97
|
+
}
|
|
98
|
+
/** An entry's source file, by its path in the site output, which mirrors the content root. */
|
|
99
|
+
function sourceFile(root, entry) {
|
|
100
|
+
if (entry.filePath === undefined)
|
|
101
|
+
return `the page ${entry.id}`;
|
|
102
|
+
return inside(root, entry.filePath)
|
|
103
|
+
? path.relative(root, path.resolve(astroRoot, entry.filePath)).split(path.sep).join("/")
|
|
104
|
+
: entry.filePath;
|
|
105
|
+
}
|
|
106
|
+
/** The site output root of the entry `name` names. */
|
|
107
|
+
function rootFor(name) {
|
|
108
|
+
const names = Object.keys(collections);
|
|
109
|
+
if (names.length === 0) {
|
|
110
|
+
if (name !== undefined) {
|
|
111
|
+
throw new Error(`@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\`.`);
|
|
112
|
+
}
|
|
113
|
+
return siteRoot;
|
|
114
|
+
}
|
|
115
|
+
if (name === undefined) {
|
|
116
|
+
if (names.length === 1)
|
|
117
|
+
return collections[names[0]];
|
|
118
|
+
throw new Error(`@ascribed/astro: ascribeCollection needs a \`name\` when ascribe() has several projects: one of ${names.map((n) => `"${n}"`).join(", ")}.`);
|
|
119
|
+
}
|
|
120
|
+
const root = collections[name];
|
|
121
|
+
if (root === undefined) {
|
|
122
|
+
throw new Error(`@ascribed/astro: ascribeCollection was given the name "${name}", and ascribe()'s projects are ${names.map((n) => `"${n}"`).join(", ")}.`);
|
|
123
|
+
}
|
|
124
|
+
return root;
|
|
125
|
+
}
|
|
27
126
|
//# sourceMappingURL=content.js.map
|
package/dist/content.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content.js","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,6EAA6E;AAC7E,2EAA2E;AAC3E,EAAE;AACF,sDAAsD;AACtD,iEAAiE;AACjE,6EAA6E;AAC7E,0FAA0F;AAC1F,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEhD;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAAI,OAAsB;IAIzD,iEAAiE;IACjE,MAAM,IAAI,GAAG,aAAa,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;IAChD,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC,SAAS,EAAE,cAAc,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;AAClG,CAAC","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 })) };\nimport { pathToFileURL } from \"node:url\";\nimport path from \"node:path\";\nimport { glob } from \"astro/loaders\";\nimport { siteRoot } from \"virtual:ascribe/site\";\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 *\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 }): {\n loader: ReturnType<typeof glob>;\n schema: S;\n} {\n // `_ascribe/` holds the schema and published files, never pages.\n const base = pathToFileURL(siteRoot + path.sep);\n return { loader: glob({ pattern: [\"**/*.md\", \"!_ascribe/**\"], base }), schema: options.schema };\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 } 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"]}
|
package/dist/dev.d.ts
CHANGED
|
@@ -38,7 +38,8 @@ export declare function watchDev(options: {
|
|
|
38
38
|
trailingSlash: string;
|
|
39
39
|
site: string | undefined;
|
|
40
40
|
};
|
|
41
|
-
|
|
41
|
+
/** The project's builds the site shows: one, or more when entries share the project. */
|
|
42
|
+
builds: readonly string[];
|
|
42
43
|
rebuild: () => Promise<void>;
|
|
43
44
|
/** Called after each successful rebuild, before Astro reloads the page. */
|
|
44
45
|
onBuilt?: () => void;
|
package/dist/dev.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { permalinksText } from "./permalinks.js";
|
|
3
4
|
import { consumerMismatches, readProject } from "./project.js";
|
|
4
5
|
/**
|
|
5
6
|
* The assets a build copied from outside the content root, as absolute paths,
|
|
@@ -88,7 +89,7 @@ export function watchDev(options) {
|
|
|
88
89
|
let assets = new Set();
|
|
89
90
|
const seen = options.seen ?? new SeenFiles();
|
|
90
91
|
const watchAssets = () => {
|
|
91
|
-
assets = new Set(outsideAssets(project,
|
|
92
|
+
assets = new Set(options.builds.flatMap((build) => outsideAssets(project, build)));
|
|
92
93
|
if (assets.size > 0)
|
|
93
94
|
server.watcher.add([...assets]);
|
|
94
95
|
seen.prime([...assets]);
|
|
@@ -96,7 +97,9 @@ export function watchDev(options) {
|
|
|
96
97
|
const drain = async () => {
|
|
97
98
|
running = true;
|
|
98
99
|
const previousProject = project;
|
|
99
|
-
const
|
|
100
|
+
const previousSiteRoots = options.builds.map((build) => previousProject.siteRoot(build));
|
|
101
|
+
const permalinkTexts = () => previousSiteRoots.map((root) => permalinksText(root) ?? "");
|
|
102
|
+
const previousPermalinks = permalinkTexts().join("\0");
|
|
100
103
|
while (dirty) {
|
|
101
104
|
dirty = false;
|
|
102
105
|
try {
|
|
@@ -112,7 +115,7 @@ export function watchDev(options) {
|
|
|
112
115
|
options.onBuilt?.();
|
|
113
116
|
try {
|
|
114
117
|
const reloaded = readProject(project.dir);
|
|
115
|
-
if (reloaded.siteRoot(
|
|
118
|
+
if (options.builds.some((build, i) => reloaded.siteRoot(build) !== previousSiteRoots[i])) {
|
|
116
119
|
throw new Error("ascribe.toml changed output-dir; restart astro dev to reload the collection.");
|
|
117
120
|
}
|
|
118
121
|
const mismatches = consumerMismatches(reloaded, options.astro);
|
|
@@ -124,6 +127,13 @@ export function watchDev(options) {
|
|
|
124
127
|
server.watcher.add(project.configPath);
|
|
125
128
|
watchAssets();
|
|
126
129
|
await refreshContent();
|
|
130
|
+
// Astro keeps each route's `getStaticPaths` until content changes, and adding or removing a
|
|
131
|
+
// permalink may change no page: clear it, as Astro does, so the addresses follow the list.
|
|
132
|
+
if (permalinkTexts().join("\0") !== previousPermalinks) {
|
|
133
|
+
for (const name of ["ssr", "prerender"]) {
|
|
134
|
+
server.environments[name]?.hot.send("astro:content-changed", {});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
127
137
|
server.ws.send({ type: "full-reload" });
|
|
128
138
|
}
|
|
129
139
|
catch (error) {
|
package/dist/dev.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev.js","sourceRoot":"","sources":["../src/dev.ts"],"names":[],"mappings":"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","sourcesContent":["import { readdirSync, readFileSync, statSync } from \"node:fs\";\nimport path from \"node:path\";\nimport type { AstroIntegration } from \"astro\";\nimport { consumerMismatches, readProject, type ProjectInfo } from \"./project.js\";\n\ntype ServerOptions = Parameters<NonNullable<AstroIntegration[\"hooks\"][\"astro:server:setup\"]>>[0];\n\n/**\n * The assets a build copied from outside the content root, as absolute paths,\n * read from the site output's manifest. Watching the content root doesn't\n * cover them. A manifest that's missing or unreadable gives none: the next\n * successful build writes one.\n */\nexport function outsideAssets(project: ProjectInfo, build: string): string[] {\n const manifest = path.join(path.dirname(project.siteRoot(build)), \"site.manifest.json\");\n let files: unknown;\n try {\n files = (JSON.parse(readFileSync(manifest, \"utf8\")) as { files?: unknown }).files;\n } catch {\n return [];\n }\n if (!Array.isArray(files)) return [];\n const assets = new Set<string>();\n for (const entry of files as { kind?: unknown; source?: unknown }[]) {\n if (entry.kind !== \"asset\" || typeof entry.source !== \"string\") continue;\n const absolute = path.resolve(project.contentRoot, entry.source);\n if (!absolute.startsWith(project.contentRoot + path.sep)) assets.add(absolute);\n }\n return [...assets].sort();\n}\n\n/**\n * Each watched file's size and modification time, as last seen. A watcher\n * can report an edit late, after a build already read it: macOS delivers\n * edits made just before the dev server started some seconds after it\n * started. An event that changes neither is no edit, and rebuilding for it\n * would reload the page under the reader.\n */\nexport class SeenFiles {\n private readonly stamps = new Map<string, string>();\n\n /** Records the files under `paths` (files or folders) as they are now. */\n prime(paths: readonly string[]): void {\n for (const at of paths) {\n let entries: string[];\n try {\n entries = statSync(at).isDirectory()\n ? readdirSync(at, { recursive: true, encoding: \"utf8\" }).map((f) => path.join(at, f))\n : [at];\n } catch {\n continue;\n }\n for (const file of entries) {\n const stamp = stampOf(file);\n if (stamp !== undefined && !this.stamps.has(file)) this.stamps.set(file, stamp);\n }\n }\n }\n\n /** Whether `file` differs from when it was last seen (or wasn't seen), and records it. */\n changed(file: string): boolean {\n const stamp = stampOf(file);\n const before = this.stamps.get(file);\n if (stamp === undefined) this.stamps.delete(file);\n else this.stamps.set(file, stamp);\n return stamp === undefined || stamp !== before;\n }\n}\n\nfunction stampOf(file: string): string | undefined {\n try {\n const stat = statSync(file);\n return stat.isFile() ? `${stat.size}:${stat.mtimeMs}` : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** What `watchDev` gives back. */\nexport interface DevBuilds {\n /** Runs `task` in turn with the compiler runs, never during one. */\n inTurn(task: () => Promise<void>): Promise<void>;\n}\n\n/** Watch source files, serialize compiler runs, and refresh Astro after successful output. */\nexport function watchDev(options: {\n server: ServerOptions[\"server\"];\n refreshContent: () => Promise<void>;\n logger: ServerOptions[\"logger\"];\n project: ProjectInfo;\n astro: { base: string; trailingSlash: string; site: string | undefined };\n build: string;\n rebuild: () => Promise<void>;\n /** Called after each successful rebuild, before Astro reloads the page. */\n onBuilt?: () => void;\n /** The sources as the first build read them. */\n seen?: SeenFiles;\n}): DevBuilds {\n const { server, refreshContent, logger, rebuild } = options;\n let project = options.project;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let pending: Promise<void> = Promise.resolve();\n let dirty = false;\n let running = false;\n let failed = false;\n let assets = new Set<string>();\n const seen = options.seen ?? new SeenFiles();\n const watchAssets = () => {\n assets = new Set(outsideAssets(project, options.build));\n if (assets.size > 0) server.watcher.add([...assets]);\n seen.prime([...assets]);\n };\n\n const drain = async () => {\n running = true;\n const previousProject = project;\n const previousSiteRoot = previousProject.siteRoot(options.build);\n while (dirty) {\n dirty = false;\n try {\n await rebuild();\n failed = false;\n } catch (error) {\n failed = true;\n logger.error(error instanceof Error ? error.message : String(error));\n }\n }\n if (!failed) {\n options.onBuilt?.();\n try {\n const reloaded = readProject(project.dir);\n if (reloaded.siteRoot(options.build) !== previousSiteRoot) {\n throw new Error(\n \"ascribe.toml changed output-dir; restart astro dev to reload the collection.\",\n );\n }\n const mismatches = consumerMismatches(reloaded, options.astro);\n if (mismatches.length > 0) {\n throw new Error(\n `@ascribed/astro: ascribe.toml and astro.config disagree:\\n- ${mismatches.join(\"\\n- \")}`,\n );\n }\n project = reloaded;\n server.watcher.add(project.contentRoot);\n server.watcher.add(project.configPath);\n watchAssets();\n await refreshContent();\n server.ws.send({ type: \"full-reload\" });\n } catch (error) {\n failed = true;\n logger.error(\n `could not refresh Ascribe content: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n running = false;\n };\n const schedule = () => {\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = undefined;\n pending = pending.then(drain);\n }, 50);\n };\n const onChange = (event: string, file: string) => {\n if (![\"add\", \"change\", \"unlink\"].includes(event)) return;\n const absolute = path.resolve(file);\n const source =\n absolute === project.configPath ||\n assets.has(absolute) ||\n (absolute.startsWith(project.contentRoot + path.sep) &&\n !absolute.startsWith(project.outputRoot + path.sep));\n if (!source) return;\n // A late report of an edit the last build already had.\n if (event !== \"unlink\" && !seen.changed(absolute)) return;\n dirty = true;\n schedule();\n };\n server.watcher.add(project.contentRoot);\n server.watcher.add(project.configPath);\n seen.prime([project.contentRoot, project.configPath]);\n watchAssets();\n server.watcher.on(\"all\", onChange);\n server.httpServer?.once(\"close\", () => {\n if (timer) clearTimeout(timer);\n server.watcher.off(\"all\", onChange);\n });\n server.middlewares.use((_req, res, next) => {\n if (!dirty && !running && !failed) return next();\n if (timer) {\n clearTimeout(timer);\n timer = undefined;\n pending = pending.then(drain);\n }\n void pending.then(() => {\n if (failed) {\n res.statusCode = 503;\n res.end(\"Ascribe build failed; fix the source and save to retry.\");\n } else next();\n });\n });\n return {\n inTurn: (task) => {\n const run = pending.then(task);\n pending = run.catch(() => undefined);\n return run;\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"dev.js","sourceRoot":"","sources":["../src/dev.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC9D,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAoB,MAAM,cAAc,CAAC;AAIjF;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,OAAoB,EAAE,KAAa;IAC/D,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,oBAAoB,CAAC,CAAC;IACxF,IAAI,KAAc,CAAC;IACnB,IAAI,CAAC;QACH,KAAK,GAAI,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAyB,CAAC,KAAK,CAAC;IACpF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACrC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IACjC,KAAK,MAAM,KAAK,IAAI,KAA+C,EAAE,CAAC;QACpE,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ;YAAE,SAAS;QACzE,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QACjE,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC;YAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACjF,CAAC;IACD,OAAO,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;AAC5B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,OAAO,SAAS;IACH,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEpD,0EAA0E;IAC1E,KAAK,CAAC,KAAwB;QAC5B,KAAK,MAAM,EAAE,IAAI,KAAK,EAAE,CAAC;YACvB,IAAI,OAAiB,CAAC;YACtB,IAAI,CAAC;gBACH,OAAO,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE;oBAClC,CAAC,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;oBACrF,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACX,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;YACD,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;gBAC3B,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;gBAC5B,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;oBAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAClF,CAAC;QACH,CAAC;IACH,CAAC;IAED,0FAA0F;IAC1F,OAAO,CAAC,IAAY;QAClB,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAC5B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,KAAK,KAAK,SAAS;YAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;;YAC7C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAClC,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,MAAM,CAAC;IACjD,CAAC;CACF;AAED,SAAS,OAAO,CAAC,IAAY;IAC3B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC5B,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IACpE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAQD,8FAA8F;AAC9F,MAAM,UAAU,QAAQ,CAAC,OAaxB;IACC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAC5D,IAAI,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAC9B,IAAI,KAAgD,CAAC;IACrD,IAAI,OAAO,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;IAC/C,IAAI,KAAK,GAAG,KAAK,CAAC;IAClB,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,IAAI,SAAS,EAAE,CAAC;IAC7C,MAAM,WAAW,GAAG,GAAG,EAAE;QACvB,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;QACnF,IAAI,MAAM,CAAC,IAAI,GAAG,CAAC;YAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;QACrD,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;IAC1B,CAAC,CAAC;IAEF,MAAM,KAAK,GAAG,KAAK,IAAI,EAAE;QACvB,OAAO,GAAG,IAAI,CAAC;QACf,MAAM,eAAe,GAAG,OAAO,CAAC;QAChC,MAAM,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,eAAe,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;QACzF,MAAM,cAAc,GAAG,GAAG,EAAE,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACzF,MAAM,kBAAkB,GAAG,cAAc,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvD,OAAO,KAAK,EAAE,CAAC;YACb,KAAK,GAAG,KAAK,CAAC;YACd,IAAI,CAAC;gBACH,MAAM,OAAO,EAAE,CAAC;gBAChB,MAAM,GAAG,KAAK,CAAC;YACjB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,GAAG,IAAI,CAAC;gBACd,MAAM,CAAC,KAAK,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YACvE,CAAC;QACH,CAAC;QACD,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;YACpB,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;gBAC1C,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,iBAAiB,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;oBACzF,MAAM,IAAI,KAAK,CACb,8EAA8E,CAC/E,CAAC;gBACJ,CAAC;gBACD,MAAM,UAAU,GAAG,kBAAkB,CAAC,QAAQ,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;gBAC/D,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC1B,MAAM,IAAI,KAAK,CACb,+DAA+D,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CACzF,CAAC;gBACJ,CAAC;gBACD,OAAO,GAAG,QAAQ,CAAC;gBACnB,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;gBACxC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;gBACvC,WAAW,EAAE,CAAC;gBACd,MAAM,cAAc,EAAE,CAAC;gBACvB,4FAA4F;gBAC5F,2FAA2F;gBAC3F,IAAI,cAAc,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,kBAAkB,EAAE,CAAC;oBACvD,KAAK,MAAM,IAAI,IAAI,CAAC,KAAK,EAAE,WAAW,CAAC,EAAE,CAAC;wBACxC,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,uBAAuB,EAAE,EAAE,CAAC,CAAC;oBACnE,CAAC;gBACH,CAAC;gBACD,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC;YAC1C,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,GAAG,IAAI,CAAC;gBACd,MAAM,CAAC,KAAK,CACV,sCAAsC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAC/F,CAAC;YACJ,CAAC;QACH,CAAC;QACD,OAAO,GAAG,KAAK,CAAC;IAClB,CAAC,CAAC;IACF,MAAM,QAAQ,GAAG,GAAG,EAAE;QACpB,IAAI,KAAK;YAAE,YAAY,CAAC,KAAK,CAAC,CAAC;QAC/B,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YACtB,KAAK,GAAG,SAAS,CAAC;YAClB,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChC,CAAC,EAAE,EAAE,CAAC,CAAC;IACT,CAAC,CAAC;IACF,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAE,IAAY,EAAE,EAAE;QAC/C,IAAI,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,OAAO;QACzD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM,MAAM,GACV,QAAQ,KAAK,OAAO,CAAC,UAAU;YAC/B,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YACpB,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC;gBAClD,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QACzD,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,uDAAuD;QACvD,IAAI,KAAK,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC;YAAE,OAAO;QAC1D,KAAK,GAAG,IAAI,CAAC;QACb,QAAQ,EAAE,CAAC;IACb,CAAC,CAAC;IACF,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACxC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACvC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;IACtD,WAAW,EAAE,CAAC;IACd,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACnC,MAAM,CAAC,UAAU,EAAE,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;QACpC,IAAI,KAAK;YAAE,YAAY,CAAC,KAAK,CAAC,CAAC;QAC/B,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IACH,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACzC,IAAI,CAAC,KAAK,IAAI,CAAC,OAAO,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,EAAE,CAAC;QACjD,IAAI,KAAK,EAAE,CAAC;YACV,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,KAAK,GAAG,SAAS,CAAC;YAClB,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChC,CAAC;QACD,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE;YACrB,IAAI,MAAM,EAAE,CAAC;gBACX,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC;gBACrB,GAAG,CAAC,GAAG,CAAC,yDAAyD,CAAC,CAAC;YACrE,CAAC;;gBAAM,IAAI,EAAE,CAAC;QAChB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IACH,OAAO;QACL,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACf,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC/B,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YACrC,OAAO,GAAG,CAAC;QACb,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import { readdirSync, readFileSync, statSync } from \"node:fs\";\nimport path from \"node:path\";\nimport type { AstroIntegration } from \"astro\";\nimport { permalinksText } from \"./permalinks.js\";\nimport { consumerMismatches, readProject, type ProjectInfo } from \"./project.js\";\n\ntype ServerOptions = Parameters<NonNullable<AstroIntegration[\"hooks\"][\"astro:server:setup\"]>>[0];\n\n/**\n * The assets a build copied from outside the content root, as absolute paths,\n * read from the site output's manifest. Watching the content root doesn't\n * cover them. A manifest that's missing or unreadable gives none: the next\n * successful build writes one.\n */\nexport function outsideAssets(project: ProjectInfo, build: string): string[] {\n const manifest = path.join(path.dirname(project.siteRoot(build)), \"site.manifest.json\");\n let files: unknown;\n try {\n files = (JSON.parse(readFileSync(manifest, \"utf8\")) as { files?: unknown }).files;\n } catch {\n return [];\n }\n if (!Array.isArray(files)) return [];\n const assets = new Set<string>();\n for (const entry of files as { kind?: unknown; source?: unknown }[]) {\n if (entry.kind !== \"asset\" || typeof entry.source !== \"string\") continue;\n const absolute = path.resolve(project.contentRoot, entry.source);\n if (!absolute.startsWith(project.contentRoot + path.sep)) assets.add(absolute);\n }\n return [...assets].sort();\n}\n\n/**\n * Each watched file's size and modification time, as last seen. A watcher\n * can report an edit late, after a build already read it: macOS delivers\n * edits made just before the dev server started some seconds after it\n * started. An event that changes neither is no edit, and rebuilding for it\n * would reload the page under the reader.\n */\nexport class SeenFiles {\n private readonly stamps = new Map<string, string>();\n\n /** Records the files under `paths` (files or folders) as they are now. */\n prime(paths: readonly string[]): void {\n for (const at of paths) {\n let entries: string[];\n try {\n entries = statSync(at).isDirectory()\n ? readdirSync(at, { recursive: true, encoding: \"utf8\" }).map((f) => path.join(at, f))\n : [at];\n } catch {\n continue;\n }\n for (const file of entries) {\n const stamp = stampOf(file);\n if (stamp !== undefined && !this.stamps.has(file)) this.stamps.set(file, stamp);\n }\n }\n }\n\n /** Whether `file` differs from when it was last seen (or wasn't seen), and records it. */\n changed(file: string): boolean {\n const stamp = stampOf(file);\n const before = this.stamps.get(file);\n if (stamp === undefined) this.stamps.delete(file);\n else this.stamps.set(file, stamp);\n return stamp === undefined || stamp !== before;\n }\n}\n\nfunction stampOf(file: string): string | undefined {\n try {\n const stat = statSync(file);\n return stat.isFile() ? `${stat.size}:${stat.mtimeMs}` : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** What `watchDev` gives back. */\nexport interface DevBuilds {\n /** Runs `task` in turn with the compiler runs, never during one. */\n inTurn(task: () => Promise<void>): Promise<void>;\n}\n\n/** Watch source files, serialize compiler runs, and refresh Astro after successful output. */\nexport function watchDev(options: {\n server: ServerOptions[\"server\"];\n refreshContent: () => Promise<void>;\n logger: ServerOptions[\"logger\"];\n project: ProjectInfo;\n astro: { base: string; trailingSlash: string; site: string | undefined };\n /** The project's builds the site shows: one, or more when entries share the project. */\n builds: readonly string[];\n rebuild: () => Promise<void>;\n /** Called after each successful rebuild, before Astro reloads the page. */\n onBuilt?: () => void;\n /** The sources as the first build read them. */\n seen?: SeenFiles;\n}): DevBuilds {\n const { server, refreshContent, logger, rebuild } = options;\n let project = options.project;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let pending: Promise<void> = Promise.resolve();\n let dirty = false;\n let running = false;\n let failed = false;\n let assets = new Set<string>();\n const seen = options.seen ?? new SeenFiles();\n const watchAssets = () => {\n assets = new Set(options.builds.flatMap((build) => outsideAssets(project, build)));\n if (assets.size > 0) server.watcher.add([...assets]);\n seen.prime([...assets]);\n };\n\n const drain = async () => {\n running = true;\n const previousProject = project;\n const previousSiteRoots = options.builds.map((build) => previousProject.siteRoot(build));\n const permalinkTexts = () => previousSiteRoots.map((root) => permalinksText(root) ?? \"\");\n const previousPermalinks = permalinkTexts().join(\"\\0\");\n while (dirty) {\n dirty = false;\n try {\n await rebuild();\n failed = false;\n } catch (error) {\n failed = true;\n logger.error(error instanceof Error ? error.message : String(error));\n }\n }\n if (!failed) {\n options.onBuilt?.();\n try {\n const reloaded = readProject(project.dir);\n if (options.builds.some((build, i) => reloaded.siteRoot(build) !== previousSiteRoots[i])) {\n throw new Error(\n \"ascribe.toml changed output-dir; restart astro dev to reload the collection.\",\n );\n }\n const mismatches = consumerMismatches(reloaded, options.astro);\n if (mismatches.length > 0) {\n throw new Error(\n `@ascribed/astro: ascribe.toml and astro.config disagree:\\n- ${mismatches.join(\"\\n- \")}`,\n );\n }\n project = reloaded;\n server.watcher.add(project.contentRoot);\n server.watcher.add(project.configPath);\n watchAssets();\n await refreshContent();\n // Astro keeps each route's `getStaticPaths` until content changes, and adding or removing a\n // permalink may change no page: clear it, as Astro does, so the addresses follow the list.\n if (permalinkTexts().join(\"\\0\") !== previousPermalinks) {\n for (const name of [\"ssr\", \"prerender\"]) {\n server.environments[name]?.hot.send(\"astro:content-changed\", {});\n }\n }\n server.ws.send({ type: \"full-reload\" });\n } catch (error) {\n failed = true;\n logger.error(\n `could not refresh Ascribe content: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n running = false;\n };\n const schedule = () => {\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = undefined;\n pending = pending.then(drain);\n }, 50);\n };\n const onChange = (event: string, file: string) => {\n if (![\"add\", \"change\", \"unlink\"].includes(event)) return;\n const absolute = path.resolve(file);\n const source =\n absolute === project.configPath ||\n assets.has(absolute) ||\n (absolute.startsWith(project.contentRoot + path.sep) &&\n !absolute.startsWith(project.outputRoot + path.sep));\n if (!source) return;\n // A late report of an edit the last build already had.\n if (event !== \"unlink\" && !seen.changed(absolute)) return;\n dirty = true;\n schedule();\n };\n server.watcher.add(project.contentRoot);\n server.watcher.add(project.configPath);\n seen.prime([project.contentRoot, project.configPath]);\n watchAssets();\n server.watcher.on(\"all\", onChange);\n server.httpServer?.once(\"close\", () => {\n if (timer) clearTimeout(timer);\n server.watcher.off(\"all\", onChange);\n });\n server.middlewares.use((_req, res, next) => {\n if (!dirty && !running && !failed) return next();\n if (timer) {\n clearTimeout(timer);\n timer = undefined;\n pending = pending.then(drain);\n }\n void pending.then(() => {\n if (failed) {\n res.statusCode = 503;\n res.end(\"Ascribe build failed; fix the source and save to retry.\");\n } else next();\n });\n });\n return {\n inTurn: (task) => {\n const run = pending.then(task);\n pending = run.catch(() => undefined);\n return run;\n },\n };\n}\n"]}
|
package/dist/files.d.ts
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
-
/**
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
/**
|
|
3
|
+
* The base path's folder in Astro's build output, which is at Astro's `base`:
|
|
4
|
+
* `docs/` for a base path of `/docs/` under a `base` of `/`, `""` when they're the same.
|
|
5
|
+
*/
|
|
6
|
+
export declare function folderUnder(basePath: string, astroBase: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Copies the published files to `<outDir>/<folder>_ascribe/files/`, `folder` being the
|
|
9
|
+
* base path's (`folderUnder`). Returns whether there were any.
|
|
10
|
+
*/
|
|
11
|
+
export declare function copyPublishedFiles(siteRoot: string, outDir: URL, folder?: string): Promise<boolean>;
|
|
12
|
+
/** Copies the plain output for agents to the base path's folder of the build output. Returns whether there was one. */
|
|
13
|
+
export declare function copyAgentFiles(plainRoot: string, outDir: URL, folder?: string): Promise<boolean>;
|
|
14
|
+
type Middleware = (req: IncomingMessage, res: ServerResponse, next: () => void) => void;
|
|
15
|
+
/** A dev-server middleware serving the published files at `<base>_ascribe/files/`, `base` being the project's base path. */
|
|
16
|
+
export declare function filesMiddleware(siteRoot: string, base: string): Middleware;
|
|
17
|
+
/**
|
|
18
|
+
* A dev-server middleware serving the plain output for agents at `<base>`: `llms.txt`, each page's
|
|
19
|
+
* `.md`, and their files, each with the headers `headers` gives its path from the host's root.
|
|
20
|
+
*/
|
|
21
|
+
export declare function agentsMiddleware(plainRoot: string, base: string, headers?: (pathname: string) => Record<string, string>): Middleware;
|
|
22
|
+
export {};
|