@ascribed/astro 0.2.1-next.9 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +32 -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
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/** How a page is treated: listed, listed and not to be quoted, or not listed. */
|
|
2
|
+
export type Listing = "listed" | "unquoted" | "unlisted";
|
|
3
|
+
/**
|
|
4
|
+
* Where a page's listing comes from: `default` when nothing sets it, `page`
|
|
5
|
+
* for the page's own, and for a rule of `[listing]` its level (`site`,
|
|
6
|
+
* `folder`, or `type`), its folder or type, the rule as written, and its line
|
|
7
|
+
* in `ascribe.toml`.
|
|
8
|
+
*/
|
|
9
|
+
export interface ListingFrom {
|
|
10
|
+
level: "default" | "page" | "site" | "folder" | "type";
|
|
11
|
+
folder?: string;
|
|
12
|
+
type?: string;
|
|
13
|
+
rule?: string;
|
|
14
|
+
line?: number;
|
|
15
|
+
}
|
|
16
|
+
/** One page's record, with the route it's keyed by. */
|
|
17
|
+
export interface PageRecord {
|
|
18
|
+
/** The page's route, from the host's root. */
|
|
19
|
+
route: string;
|
|
20
|
+
/** The page's path from the content root, with `/`. */
|
|
21
|
+
source: string;
|
|
22
|
+
/** The page's title, as plain text. */
|
|
23
|
+
title?: string;
|
|
24
|
+
/** The page's description, as plain text on one line. */
|
|
25
|
+
description?: string;
|
|
26
|
+
/** The page's own address, absolute, when the project sets `[consumer] site`. */
|
|
27
|
+
canonical?: string;
|
|
28
|
+
listing: Listing;
|
|
29
|
+
/** Where `listing` comes from: the page, a rule of `[listing]`, or neither. */
|
|
30
|
+
listing_from: ListingFrom;
|
|
31
|
+
/** The address of the page's Markdown version, from the host's root, when the build publishes one. */
|
|
32
|
+
markdown?: string;
|
|
33
|
+
/**
|
|
34
|
+
* The page's place in the site, from the top down to the page itself: the
|
|
35
|
+
* page at each of its route's folders that has one, then the page. Absent
|
|
36
|
+
* in records an earlier Ascribe wrote.
|
|
37
|
+
*/
|
|
38
|
+
breadcrumbs?: Step[];
|
|
39
|
+
}
|
|
40
|
+
/** One step of a page's breadcrumbs: a page's route, from the host's root, and its title. */
|
|
41
|
+
export interface Step {
|
|
42
|
+
route: string;
|
|
43
|
+
title: string;
|
|
44
|
+
}
|
|
45
|
+
/** What a kind of crawler is for: gathering training data, building an AI search index, or fetching at a person's request. */
|
|
46
|
+
type Purpose = "training" | "search" | "on-request";
|
|
47
|
+
/** The group of `robots.txt` a disallowed purpose writes. */
|
|
48
|
+
export interface RobotsGroup {
|
|
49
|
+
purpose: Purpose;
|
|
50
|
+
/** Every token of the purpose the binary knows, each a `User-agent` line. */
|
|
51
|
+
user_agents: string[];
|
|
52
|
+
/** The group's rules: `Disallow: /`. */
|
|
53
|
+
lines: string[];
|
|
54
|
+
}
|
|
55
|
+
/** What a project says to crawlers. */
|
|
56
|
+
export interface Robots {
|
|
57
|
+
/** The project's own lines, with paths from the host's root. */
|
|
58
|
+
lines: string[];
|
|
59
|
+
/** What the project says of each purpose it sets. */
|
|
60
|
+
purposes?: Partial<Record<Purpose, "allow" | "disallow">>;
|
|
61
|
+
/** A group for each purpose the project disallows. */
|
|
62
|
+
groups?: RobotsGroup[];
|
|
63
|
+
}
|
|
64
|
+
/** `_ascribe/pages.json`. */
|
|
65
|
+
export interface PageRecords {
|
|
66
|
+
version: number;
|
|
67
|
+
site?: string;
|
|
68
|
+
pages: Record<string, Omit<PageRecord, "route">>;
|
|
69
|
+
robots: Robots;
|
|
70
|
+
}
|
|
71
|
+
/** What one project says to crawlers, by the name of its first entry. */
|
|
72
|
+
export interface ProjectRobots {
|
|
73
|
+
name: string;
|
|
74
|
+
robots: Robots;
|
|
75
|
+
}
|
|
76
|
+
/** The records in a build's site output, or `undefined` when there aren't any. */
|
|
77
|
+
export declare function readRecords(siteRoot: string): PageRecords | undefined;
|
|
78
|
+
/**
|
|
79
|
+
* The record of the page whose source path is `source`, in the build whose
|
|
80
|
+
* site output is at `siteRoot`, or `undefined` when it has none. Read again
|
|
81
|
+
* when the file changes, as it does with each build in `astro dev`.
|
|
82
|
+
*/
|
|
83
|
+
export declare function recordFor(siteRoot: string, source: string): PageRecord | undefined;
|
|
84
|
+
/** The records' pages, each with its route, in the file's order. */
|
|
85
|
+
export declare function pageList(records: PageRecords | undefined): PageRecord[];
|
|
86
|
+
/**
|
|
87
|
+
* A route as the sitemap compares it: with a leading `/` and no trailing one,
|
|
88
|
+
* except at the root, so `/docs/` and `/docs` are one page.
|
|
89
|
+
*/
|
|
90
|
+
export declare function routeKey(route: string): string;
|
|
91
|
+
/** A route ending the way Astro's `trailingSlash` says, as a built page's address. */
|
|
92
|
+
export declare function routeAs(route: string, trailingSlash: "always" | "never" | "ignore"): string;
|
|
93
|
+
/** What the sitemap is drawn from. */
|
|
94
|
+
export interface SitemapSources {
|
|
95
|
+
/** Every project's records. */
|
|
96
|
+
projects: readonly PageRecord[][];
|
|
97
|
+
/**
|
|
98
|
+
* Whether a project's page has a route in the site: was built, or, in
|
|
99
|
+
* `astro dev`, matches one of the site's routes.
|
|
100
|
+
*/
|
|
101
|
+
routed: (route: string) => boolean;
|
|
102
|
+
/** The site's own pages, each by its route from the host's root. */
|
|
103
|
+
sitePages: readonly string[];
|
|
104
|
+
/** Routes from the host's root to leave out: forwarding pages, the not-found page, and the site's list. */
|
|
105
|
+
excluded: readonly string[];
|
|
106
|
+
/** Astro's `site`. */
|
|
107
|
+
site: string;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The sitemap's addresses, absolute, sorted, each once: every listed page of
|
|
111
|
+
* every project that has a route in the site, and every page of the site's
|
|
112
|
+
* own, less the excluded routes. A project's page that isn't listed is left
|
|
113
|
+
* out, and so is a page of the site's own at the same route as one.
|
|
114
|
+
*/
|
|
115
|
+
export declare function sitemapAddresses(sources: SitemapSources): string[];
|
|
116
|
+
/** A sitemap of `addresses`, with no dates. */
|
|
117
|
+
export declare function sitemapXml(addresses: readonly string[]): string;
|
|
118
|
+
/**
|
|
119
|
+
* A site's `robots.txt` from what each project says to crawlers: a group for
|
|
120
|
+
* each purpose a project disallows, once for the site, then a
|
|
121
|
+
* `User-agent: *` group with every project's lines before its first
|
|
122
|
+
* `User-agent` line, which are for every crawler, then the groups each
|
|
123
|
+
* project wrote whole, then the sitemap's address. `undefined` when it would
|
|
124
|
+
* say nothing.
|
|
125
|
+
*/
|
|
126
|
+
export declare function robotsText(projects: readonly Robots[], sitemap: string | undefined): string | undefined;
|
|
127
|
+
/**
|
|
128
|
+
* Two projects that say contradictory things about one purpose, as
|
|
129
|
+
* sentences: one allows it and the other disallows it. A site has one
|
|
130
|
+
* `robots.txt`, so they must agree. A project that says nothing of a purpose
|
|
131
|
+
* agrees with either.
|
|
132
|
+
*/
|
|
133
|
+
export declare function purposeConflicts(projects: readonly ProjectRobots[]): string[];
|
|
134
|
+
/** Where the sitemap is, from the host's root: at Astro's `base`. */
|
|
135
|
+
export declare function sitemapRoute(base: string): string;
|
|
136
|
+
export {};
|
package/dist/records.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// The page records: `_ascribe/pages.json` in a build's site output
|
|
2
|
+
// (output-layout contract §1.3), what each page tells every reader that isn't
|
|
3
|
+
// a person. The integration writes from them a page's head (`Head.astro`),
|
|
4
|
+
// the site's one sitemap, and its one `robots.txt`, from every project's
|
|
5
|
+
// records together, at the host's root.
|
|
6
|
+
import { readFileSync, statSync } from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { normalizeBase } from "./project.js";
|
|
9
|
+
/** The records' format version this reads. */
|
|
10
|
+
const VERSION = 1;
|
|
11
|
+
function recordsFile(siteRoot) {
|
|
12
|
+
return path.join(siteRoot, "_ascribe", "pages.json");
|
|
13
|
+
}
|
|
14
|
+
/** The records in a build's site output, or `undefined` when there aren't any. */
|
|
15
|
+
export function readRecords(siteRoot) {
|
|
16
|
+
const file = recordsFile(siteRoot);
|
|
17
|
+
let text;
|
|
18
|
+
try {
|
|
19
|
+
text = readFileSync(file, "utf8");
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
const records = JSON.parse(text);
|
|
25
|
+
if (records.version !== VERSION) {
|
|
26
|
+
throw new Error(`@ascribed/astro: ${file} is version ${String(records.version)} of the page records, and this version of the integration reads version ${VERSION}. Update @ascribed/astro and @ascribed/cli together.`);
|
|
27
|
+
}
|
|
28
|
+
return records;
|
|
29
|
+
}
|
|
30
|
+
/** A build's records by source path, as last read, and when its file was written. */
|
|
31
|
+
const bySource = new Map();
|
|
32
|
+
/**
|
|
33
|
+
* The record of the page whose source path is `source`, in the build whose
|
|
34
|
+
* site output is at `siteRoot`, or `undefined` when it has none. Read again
|
|
35
|
+
* when the file changes, as it does with each build in `astro dev`.
|
|
36
|
+
*/
|
|
37
|
+
export function recordFor(siteRoot, source) {
|
|
38
|
+
let written;
|
|
39
|
+
try {
|
|
40
|
+
written = statSync(recordsFile(siteRoot)).mtimeMs;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
let known = bySource.get(siteRoot);
|
|
46
|
+
if (known === undefined || known.written !== written) {
|
|
47
|
+
const pages = new Map();
|
|
48
|
+
for (const record of pageList(readRecords(siteRoot))) {
|
|
49
|
+
if (!pages.has(record.source))
|
|
50
|
+
pages.set(record.source, record);
|
|
51
|
+
}
|
|
52
|
+
known = { written, pages };
|
|
53
|
+
bySource.set(siteRoot, known);
|
|
54
|
+
}
|
|
55
|
+
return known.pages.get(source);
|
|
56
|
+
}
|
|
57
|
+
/** The records' pages, each with its route, in the file's order. */
|
|
58
|
+
export function pageList(records) {
|
|
59
|
+
return Object.entries(records?.pages ?? {}).map(([route, page]) => ({ route, ...page }));
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* A route as the sitemap compares it: with a leading `/` and no trailing one,
|
|
63
|
+
* except at the root, so `/docs/` and `/docs` are one page.
|
|
64
|
+
*/
|
|
65
|
+
export function routeKey(route) {
|
|
66
|
+
const withSlash = route.startsWith("/") ? route : `/${route}`;
|
|
67
|
+
return withSlash.length > 1 && withSlash.endsWith("/") ? withSlash.slice(0, -1) : withSlash;
|
|
68
|
+
}
|
|
69
|
+
/** A route ending the way Astro's `trailingSlash` says, as a built page's address. */
|
|
70
|
+
export function routeAs(route, trailingSlash) {
|
|
71
|
+
if (trailingSlash === "never")
|
|
72
|
+
return routeKey(route);
|
|
73
|
+
if (trailingSlash === "always" && !route.endsWith("/"))
|
|
74
|
+
return `${route}/`;
|
|
75
|
+
return route;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The sitemap's addresses, absolute, sorted, each once: every listed page of
|
|
79
|
+
* every project that has a route in the site, and every page of the site's
|
|
80
|
+
* own, less the excluded routes. A project's page that isn't listed is left
|
|
81
|
+
* out, and so is a page of the site's own at the same route as one.
|
|
82
|
+
*/
|
|
83
|
+
export function sitemapAddresses(sources) {
|
|
84
|
+
const excluded = new Set(sources.excluded.map(routeKey));
|
|
85
|
+
const records = new Set();
|
|
86
|
+
const routes = new Map();
|
|
87
|
+
for (const pages of sources.projects) {
|
|
88
|
+
for (const page of pages) {
|
|
89
|
+
const key = routeKey(page.route);
|
|
90
|
+
records.add(key);
|
|
91
|
+
if (page.listing === "unlisted" || excluded.has(key) || !sources.routed(page.route))
|
|
92
|
+
continue;
|
|
93
|
+
if (!routes.has(key))
|
|
94
|
+
routes.set(key, page.route);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
for (const route of sources.sitePages) {
|
|
98
|
+
const key = routeKey(route);
|
|
99
|
+
if (records.has(key) || excluded.has(key) || routes.has(key))
|
|
100
|
+
continue;
|
|
101
|
+
routes.set(key, route);
|
|
102
|
+
}
|
|
103
|
+
return [
|
|
104
|
+
...new Set([...routes.values()].map((route) => new URL(route, sources.site).href)),
|
|
105
|
+
].sort();
|
|
106
|
+
}
|
|
107
|
+
function escapeXml(text) {
|
|
108
|
+
return text
|
|
109
|
+
.replaceAll("&", "&")
|
|
110
|
+
.replaceAll("<", "<")
|
|
111
|
+
.replaceAll(">", ">")
|
|
112
|
+
.replaceAll('"', """)
|
|
113
|
+
.replaceAll("'", "'");
|
|
114
|
+
}
|
|
115
|
+
/** A sitemap of `addresses`, with no dates. */
|
|
116
|
+
export function sitemapXml(addresses) {
|
|
117
|
+
const urls = addresses.map((address) => ` <url><loc>${escapeXml(address)}</loc></url>\n`);
|
|
118
|
+
return ('<?xml version="1.0" encoding="UTF-8"?>\n' +
|
|
119
|
+
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n' +
|
|
120
|
+
urls.join("") +
|
|
121
|
+
"</urlset>\n");
|
|
122
|
+
}
|
|
123
|
+
const USER_AGENT = /^\s*user-agent\s*:/i;
|
|
124
|
+
/**
|
|
125
|
+
* A site's `robots.txt` from what each project says to crawlers: a group for
|
|
126
|
+
* each purpose a project disallows, once for the site, then a
|
|
127
|
+
* `User-agent: *` group with every project's lines before its first
|
|
128
|
+
* `User-agent` line, which are for every crawler, then the groups each
|
|
129
|
+
* project wrote whole, then the sitemap's address. `undefined` when it would
|
|
130
|
+
* say nothing.
|
|
131
|
+
*/
|
|
132
|
+
export function robotsText(projects, sitemap) {
|
|
133
|
+
const purposes = new Map();
|
|
134
|
+
const everyone = [];
|
|
135
|
+
const groups = [];
|
|
136
|
+
for (const robots of projects) {
|
|
137
|
+
for (const group of robots.groups ?? []) {
|
|
138
|
+
if (purposes.has(group.purpose))
|
|
139
|
+
continue;
|
|
140
|
+
purposes.set(group.purpose, [
|
|
141
|
+
...group.user_agents.map((agent) => `User-agent: ${agent}`),
|
|
142
|
+
...group.lines,
|
|
143
|
+
]);
|
|
144
|
+
}
|
|
145
|
+
const lines = robots.lines;
|
|
146
|
+
const first = lines.findIndex((line) => USER_AGENT.test(line));
|
|
147
|
+
everyone.push(...(first === -1 ? lines : lines.slice(0, first)));
|
|
148
|
+
if (first !== -1)
|
|
149
|
+
groups.push(lines.slice(first));
|
|
150
|
+
}
|
|
151
|
+
const blocks = [...purposes.values()];
|
|
152
|
+
if (everyone.length > 0)
|
|
153
|
+
blocks.push(["User-agent: *", ...everyone]);
|
|
154
|
+
blocks.push(...groups);
|
|
155
|
+
if (sitemap !== undefined)
|
|
156
|
+
blocks.push([`Sitemap: ${sitemap}`]);
|
|
157
|
+
if (blocks.length === 0)
|
|
158
|
+
return undefined;
|
|
159
|
+
return `${blocks.map((block) => block.join("\n")).join("\n\n")}\n`;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Two projects that say contradictory things about one purpose, as
|
|
163
|
+
* sentences: one allows it and the other disallows it. A site has one
|
|
164
|
+
* `robots.txt`, so they must agree. A project that says nothing of a purpose
|
|
165
|
+
* agrees with either.
|
|
166
|
+
*/
|
|
167
|
+
export function purposeConflicts(projects) {
|
|
168
|
+
const problems = [];
|
|
169
|
+
for (const purpose of ["training", "search", "on-request"]) {
|
|
170
|
+
const allow = projects.find((p) => p.robots.purposes?.[purpose] === "allow");
|
|
171
|
+
const disallow = projects.find((p) => p.robots.purposes?.[purpose] === "disallow");
|
|
172
|
+
if (allow === undefined || disallow === undefined)
|
|
173
|
+
continue;
|
|
174
|
+
problems.push(`"${disallow.name}" has [crawlers] ${purpose} = "disallow" and "${allow.name}" has ${purpose} = "allow". The site has one robots.txt, so its projects must agree: make them the same, or remove one.`);
|
|
175
|
+
}
|
|
176
|
+
return problems;
|
|
177
|
+
}
|
|
178
|
+
/** Where the sitemap is, from the host's root: at Astro's `base`. */
|
|
179
|
+
export function sitemapRoute(base) {
|
|
180
|
+
return `${normalizeBase(base)}sitemap.xml`;
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=records.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"records.js","sourceRoot":"","sources":["../src/records.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,8EAA8E;AAC9E,2EAA2E;AAC3E,yEAAyE;AACzE,wCAAwC;AACxC,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,8CAA8C;AAC9C,MAAM,OAAO,GAAG,CAAC,CAAC;AAsFlB,SAAS,WAAW,CAAC,QAAgB;IACnC,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC;AACvD,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,WAAW,CAAC,QAAgB;IAC1C,MAAM,IAAI,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IACnC,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAgB,CAAC;IAChD,IAAI,OAAO,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CACb,oBAAoB,IAAI,eAAe,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,2EAA2E,OAAO,sDAAsD,CACvM,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,qFAAqF;AACrF,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA+D,CAAC;AAExF;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,QAAgB,EAAE,MAAc;IACxD,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,QAAQ,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACnC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC;QACrD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;QAC5C,KAAK,MAAM,MAAM,IAAI,QAAQ,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;YACrD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC;gBAAE,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAClE,CAAC;QACD,KAAK,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAC3B,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AACjC,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,QAAQ,CAAC,OAAgC;IACvD,OAAO,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC;AAC3F,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAa;IACpC,MAAM,SAAS,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC;IAC9D,OAAO,SAAS,CAAC,MAAM,GAAG,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC9F,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,OAAO,CAAC,KAAa,EAAE,aAA4C;IACjF,IAAI,aAAa,KAAK,OAAO;QAAE,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC;IACtD,IAAI,aAAa,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,KAAK,GAAG,CAAC;IAC3E,OAAO,KAAK,CAAC;AACf,CAAC;AAmBD;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAuB;IACtD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;IACzD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;QACrC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACjC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACjB,IAAI,IAAI,CAAC,OAAO,KAAK,UAAU,IAAI,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;gBAAE,SAAS;YAC9F,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACpD,CAAC;IACH,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;QACtC,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC5B,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QACvE,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACzB,CAAC;IACD,OAAO;QACL,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;KACnF,CAAC,IAAI,EAAE,CAAC;AACX,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC7B,OAAO,IAAI;SACR,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC;SACxB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC;SACzB,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AAC/B,CAAC;AAED,+CAA+C;AAC/C,MAAM,UAAU,UAAU,CAAC,SAA4B;IACrD,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,eAAe,SAAS,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC3F,OAAO,CACL,0CAA0C;QAC1C,gEAAgE;QAChE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACb,aAAa,CACd,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,GAAG,qBAAqB,CAAC;AAEzC;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CACxB,QAA2B,EAC3B,OAA2B;IAE3B,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAqB,CAAC;IAC9C,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,MAAM,GAAe,EAAE,CAAC;IAC9B,KAAK,MAAM,MAAM,IAAI,QAAQ,EAAE,CAAC;QAC9B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;YACxC,IAAI,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC;gBAAE,SAAS;YAC1C,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE;gBAC1B,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,eAAe,KAAK,EAAE,CAAC;gBAC3D,GAAG,KAAK,CAAC,KAAK;aACf,CAAC,CAAC;QACL,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;QAC3B,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAC/D,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;QACjE,IAAI,KAAK,KAAK,CAAC,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;IACpD,CAAC;IACD,MAAM,MAAM,GAAe,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IAClD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,CAAC,IAAI,CAAC,CAAC,eAAe,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC;IACrE,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC;IACvB,IAAI,OAAO,KAAK,SAAS;QAAE,MAAM,CAAC,IAAI,CAAC,CAAC,YAAY,OAAO,EAAE,CAAC,CAAC,CAAC;IAChE,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC1C,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;AACrE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAkC;IACjE,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,OAAO,IAAI,CAAC,UAAU,EAAE,QAAQ,EAAE,YAAY,CAAU,EAAE,CAAC;QACpE,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,CAAC;QAC7E,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,UAAU,CAAC,CAAC;QACnF,IAAI,KAAK,KAAK,SAAS,IAAI,QAAQ,KAAK,SAAS;YAAE,SAAS;QAC5D,QAAQ,CAAC,IAAI,CACX,IAAI,QAAQ,CAAC,IAAI,oBAAoB,OAAO,sBAAsB,KAAK,CAAC,IAAI,SAAS,OAAO,yGAAyG,CACtM,CAAC;IACJ,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,GAAG,aAAa,CAAC,IAAI,CAAC,aAAa,CAAC;AAC7C,CAAC","sourcesContent":["// The page records: `_ascribe/pages.json` in a build's site output\n// (output-layout contract §1.3), what each page tells every reader that isn't\n// a person. The integration writes from them a page's head (`Head.astro`),\n// the site's one sitemap, and its one `robots.txt`, from every project's\n// records together, at the host's root.\nimport { readFileSync, statSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { normalizeBase } from \"./project.js\";\n\n/** The records' format version this reads. */\nconst VERSION = 1;\n\n/** How a page is treated: listed, listed and not to be quoted, or not listed. */\nexport type Listing = \"listed\" | \"unquoted\" | \"unlisted\";\n\n/**\n * Where a page's listing comes from: `default` when nothing sets it, `page`\n * for the page's own, and for a rule of `[listing]` its level (`site`,\n * `folder`, or `type`), its folder or type, the rule as written, and its line\n * in `ascribe.toml`.\n */\nexport interface ListingFrom {\n level: \"default\" | \"page\" | \"site\" | \"folder\" | \"type\";\n folder?: string;\n type?: string;\n rule?: string;\n line?: number;\n}\n\n/** One page's record, with the route it's keyed by. */\nexport interface PageRecord {\n /** The page's route, from the host's root. */\n route: string;\n /** The page's path from the content root, with `/`. */\n source: string;\n /** The page's title, as plain text. */\n title?: string;\n /** The page's description, as plain text on one line. */\n description?: string;\n /** The page's own address, absolute, when the project sets `[consumer] site`. */\n canonical?: string;\n listing: Listing;\n /** Where `listing` comes from: the page, a rule of `[listing]`, or neither. */\n listing_from: ListingFrom;\n /** The address of the page's Markdown version, from the host's root, when the build publishes one. */\n markdown?: string;\n /**\n * The page's place in the site, from the top down to the page itself: the\n * page at each of its route's folders that has one, then the page. Absent\n * in records an earlier Ascribe wrote.\n */\n breadcrumbs?: Step[];\n}\n\n/** One step of a page's breadcrumbs: a page's route, from the host's root, and its title. */\nexport interface Step {\n route: string;\n title: string;\n}\n\n/** What a kind of crawler is for: gathering training data, building an AI search index, or fetching at a person's request. */\ntype Purpose = \"training\" | \"search\" | \"on-request\";\n\n/** The group of `robots.txt` a disallowed purpose writes. */\nexport interface RobotsGroup {\n purpose: Purpose;\n /** Every token of the purpose the binary knows, each a `User-agent` line. */\n user_agents: string[];\n /** The group's rules: `Disallow: /`. */\n lines: string[];\n}\n\n/** What a project says to crawlers. */\nexport interface Robots {\n /** The project's own lines, with paths from the host's root. */\n lines: string[];\n /** What the project says of each purpose it sets. */\n purposes?: Partial<Record<Purpose, \"allow\" | \"disallow\">>;\n /** A group for each purpose the project disallows. */\n groups?: RobotsGroup[];\n}\n\n/** `_ascribe/pages.json`. */\nexport interface PageRecords {\n version: number;\n site?: string;\n pages: Record<string, Omit<PageRecord, \"route\">>;\n robots: Robots;\n}\n\n/** What one project says to crawlers, by the name of its first entry. */\nexport interface ProjectRobots {\n name: string;\n robots: Robots;\n}\n\nfunction recordsFile(siteRoot: string): string {\n return path.join(siteRoot, \"_ascribe\", \"pages.json\");\n}\n\n/** The records in a build's site output, or `undefined` when there aren't any. */\nexport function readRecords(siteRoot: string): PageRecords | undefined {\n const file = recordsFile(siteRoot);\n let text: string;\n try {\n text = readFileSync(file, \"utf8\");\n } catch {\n return undefined;\n }\n const records = JSON.parse(text) as PageRecords;\n if (records.version !== VERSION) {\n throw new Error(\n `@ascribed/astro: ${file} is version ${String(records.version)} of the page records, and this version of the integration reads version ${VERSION}. Update @ascribed/astro and @ascribed/cli together.`,\n );\n }\n return records;\n}\n\n/** A build's records by source path, as last read, and when its file was written. */\nconst bySource = new Map<string, { written: number; pages: Map<string, PageRecord> }>();\n\n/**\n * The record of the page whose source path is `source`, in the build whose\n * site output is at `siteRoot`, or `undefined` when it has none. Read again\n * when the file changes, as it does with each build in `astro dev`.\n */\nexport function recordFor(siteRoot: string, source: string): PageRecord | undefined {\n let written: number;\n try {\n written = statSync(recordsFile(siteRoot)).mtimeMs;\n } catch {\n return undefined;\n }\n let known = bySource.get(siteRoot);\n if (known === undefined || known.written !== written) {\n const pages = new Map<string, PageRecord>();\n for (const record of pageList(readRecords(siteRoot))) {\n if (!pages.has(record.source)) pages.set(record.source, record);\n }\n known = { written, pages };\n bySource.set(siteRoot, known);\n }\n return known.pages.get(source);\n}\n\n/** The records' pages, each with its route, in the file's order. */\nexport function pageList(records: PageRecords | undefined): PageRecord[] {\n return Object.entries(records?.pages ?? {}).map(([route, page]) => ({ route, ...page }));\n}\n\n/**\n * A route as the sitemap compares it: with a leading `/` and no trailing one,\n * except at the root, so `/docs/` and `/docs` are one page.\n */\nexport function routeKey(route: string): string {\n const withSlash = route.startsWith(\"/\") ? route : `/${route}`;\n return withSlash.length > 1 && withSlash.endsWith(\"/\") ? withSlash.slice(0, -1) : withSlash;\n}\n\n/** A route ending the way Astro's `trailingSlash` says, as a built page's address. */\nexport function routeAs(route: string, trailingSlash: \"always\" | \"never\" | \"ignore\"): string {\n if (trailingSlash === \"never\") return routeKey(route);\n if (trailingSlash === \"always\" && !route.endsWith(\"/\")) return `${route}/`;\n return route;\n}\n\n/** What the sitemap is drawn from. */\nexport interface SitemapSources {\n /** Every project's records. */\n projects: readonly PageRecord[][];\n /**\n * Whether a project's page has a route in the site: was built, or, in\n * `astro dev`, matches one of the site's routes.\n */\n routed: (route: string) => boolean;\n /** The site's own pages, each by its route from the host's root. */\n sitePages: readonly string[];\n /** Routes from the host's root to leave out: forwarding pages, the not-found page, and the site's list. */\n excluded: readonly string[];\n /** Astro's `site`. */\n site: string;\n}\n\n/**\n * The sitemap's addresses, absolute, sorted, each once: every listed page of\n * every project that has a route in the site, and every page of the site's\n * own, less the excluded routes. A project's page that isn't listed is left\n * out, and so is a page of the site's own at the same route as one.\n */\nexport function sitemapAddresses(sources: SitemapSources): string[] {\n const excluded = new Set(sources.excluded.map(routeKey));\n const records = new Set<string>();\n const routes = new Map<string, string>();\n for (const pages of sources.projects) {\n for (const page of pages) {\n const key = routeKey(page.route);\n records.add(key);\n if (page.listing === \"unlisted\" || excluded.has(key) || !sources.routed(page.route)) continue;\n if (!routes.has(key)) routes.set(key, page.route);\n }\n }\n for (const route of sources.sitePages) {\n const key = routeKey(route);\n if (records.has(key) || excluded.has(key) || routes.has(key)) continue;\n routes.set(key, route);\n }\n return [\n ...new Set([...routes.values()].map((route) => new URL(route, sources.site).href)),\n ].sort();\n}\n\nfunction escapeXml(text: string): string {\n return text\n .replaceAll(\"&\", \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\")\n .replaceAll('\"', \""\")\n .replaceAll(\"'\", \"'\");\n}\n\n/** A sitemap of `addresses`, with no dates. */\nexport function sitemapXml(addresses: readonly string[]): string {\n const urls = addresses.map((address) => ` <url><loc>${escapeXml(address)}</loc></url>\\n`);\n return (\n '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n' +\n '<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n' +\n urls.join(\"\") +\n \"</urlset>\\n\"\n );\n}\n\nconst USER_AGENT = /^\\s*user-agent\\s*:/i;\n\n/**\n * A site's `robots.txt` from what each project says to crawlers: a group for\n * each purpose a project disallows, once for the site, then a\n * `User-agent: *` group with every project's lines before its first\n * `User-agent` line, which are for every crawler, then the groups each\n * project wrote whole, then the sitemap's address. `undefined` when it would\n * say nothing.\n */\nexport function robotsText(\n projects: readonly Robots[],\n sitemap: string | undefined,\n): string | undefined {\n const purposes = new Map<Purpose, string[]>();\n const everyone: string[] = [];\n const groups: string[][] = [];\n for (const robots of projects) {\n for (const group of robots.groups ?? []) {\n if (purposes.has(group.purpose)) continue;\n purposes.set(group.purpose, [\n ...group.user_agents.map((agent) => `User-agent: ${agent}`),\n ...group.lines,\n ]);\n }\n const lines = robots.lines;\n const first = lines.findIndex((line) => USER_AGENT.test(line));\n everyone.push(...(first === -1 ? lines : lines.slice(0, first)));\n if (first !== -1) groups.push(lines.slice(first));\n }\n const blocks: string[][] = [...purposes.values()];\n if (everyone.length > 0) blocks.push([\"User-agent: *\", ...everyone]);\n blocks.push(...groups);\n if (sitemap !== undefined) blocks.push([`Sitemap: ${sitemap}`]);\n if (blocks.length === 0) return undefined;\n return `${blocks.map((block) => block.join(\"\\n\")).join(\"\\n\\n\")}\\n`;\n}\n\n/**\n * Two projects that say contradictory things about one purpose, as\n * sentences: one allows it and the other disallows it. A site has one\n * `robots.txt`, so they must agree. A project that says nothing of a purpose\n * agrees with either.\n */\nexport function purposeConflicts(projects: readonly ProjectRobots[]): string[] {\n const problems: string[] = [];\n for (const purpose of [\"training\", \"search\", \"on-request\"] as const) {\n const allow = projects.find((p) => p.robots.purposes?.[purpose] === \"allow\");\n const disallow = projects.find((p) => p.robots.purposes?.[purpose] === \"disallow\");\n if (allow === undefined || disallow === undefined) continue;\n problems.push(\n `\"${disallow.name}\" has [crawlers] ${purpose} = \"disallow\" and \"${allow.name}\" has ${purpose} = \"allow\". The site has one robots.txt, so its projects must agree: make them the same, or remove one.`,\n );\n }\n return problems;\n}\n\n/** Where the sitemap is, from the host's root: at Astro's `base`. */\nexport function sitemapRoute(base: string): string {\n return `${normalizeBase(base)}sitemap.xml`;\n}\n"]}
|
package/dist/review/devfile.d.ts
CHANGED
|
@@ -15,7 +15,12 @@ export declare function siteUrl(address: AddressInfo, options: {
|
|
|
15
15
|
https: boolean;
|
|
16
16
|
base: string;
|
|
17
17
|
}): string;
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Writes `dev.json`. A failure is the caller's to log: it only costs the editor its link.
|
|
20
|
+
* `.ascribe` or `dev.json` that's a link (a symbolic link, or a junction on Windows, which
|
|
21
|
+
* Node's `lstat` reports as one) is refused, not written through: a committed link could
|
|
22
|
+
* lead anywhere.
|
|
23
|
+
*/
|
|
19
24
|
export declare function writeDevFile(projectDir: string, file: DevFile): void;
|
|
20
25
|
/** Removes `dev.json` if this process wrote it. */
|
|
21
26
|
export declare function removeDevFile(projectDir: string, pid: number): void;
|
package/dist/review/devfile.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// `.ascribe/dev.json`: where the dev server is, for the editor's Open Site
|
|
2
2
|
// Preview. Written when the server starts, removed when it stops. A server
|
|
3
3
|
// that crashed leaves it behind, so a reader checks the URL answers first.
|
|
4
|
-
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { lstatSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
/** `<project>/.ascribe/dev.json`. */
|
|
7
7
|
export function devFilePath(projectDir) {
|
|
@@ -17,9 +17,19 @@ export function siteUrl(address, options) {
|
|
|
17
17
|
const base = options.base.endsWith("/") ? options.base : `${options.base}/`;
|
|
18
18
|
return `${options.https ? "https" : "http"}://${host}:${address.port}${base.startsWith("/") ? base : `/${base}`}`;
|
|
19
19
|
}
|
|
20
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Writes `dev.json`. A failure is the caller's to log: it only costs the editor its link.
|
|
22
|
+
* `.ascribe` or `dev.json` that's a link (a symbolic link, or a junction on Windows, which
|
|
23
|
+
* Node's `lstat` reports as one) is refused, not written through: a committed link could
|
|
24
|
+
* lead anywhere.
|
|
25
|
+
*/
|
|
21
26
|
export function writeDevFile(projectDir, file) {
|
|
22
27
|
const target = devFilePath(projectDir);
|
|
28
|
+
for (const at of [path.dirname(target), target]) {
|
|
29
|
+
if (lstatSync(at, { throwIfNoEntry: false })?.isSymbolicLink()) {
|
|
30
|
+
throw new Error(`${at} is a link; replace it with a folder or a file, or remove it`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
23
33
|
mkdirSync(path.dirname(target), { recursive: true });
|
|
24
34
|
writeFileSync(target, `${JSON.stringify(file, null, 2)}\n`);
|
|
25
35
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"devfile.js","sourceRoot":"","sources":["../../src/review/devfile.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,2EAA2E;AAC3E,2EAA2E;AAC3E,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"devfile.js","sourceRoot":"","sources":["../../src/review/devfile.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,2EAA2E;AAC3E,2EAA2E;AAC3E,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAEpF,OAAO,IAAI,MAAM,WAAW,CAAC;AAY7B,qCAAqC;AACrC,MAAM,UAAU,WAAW,CAAC,UAAkB;IAC5C,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;AACvD,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,OAAO,CAAC,OAAoB,EAAE,OAAyC;IACrF,IAAI,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;IAC3B,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,IAAI,GAAG,WAAW,CAAC;SAC5E,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,IAAI,GAAG,IAAI,IAAI,GAAG,CAAC;IAChD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,GAAG,CAAC;IAC5E,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,MAAM,IAAI,IAAI,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;AACpH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,UAAkB,EAAE,IAAa;IAC5D,MAAM,MAAM,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;IACvC,KAAK,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC;QAChD,IAAI,SAAS,CAAC,EAAE,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,EAAE,cAAc,EAAE,EAAE,CAAC;YAC/D,MAAM,IAAI,KAAK,CAAC,GAAG,EAAE,8DAA8D,CAAC,CAAC;QACvF,CAAC;IACH,CAAC;IACD,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrD,aAAa,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;AAC9D,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,aAAa,CAAC,UAAkB,EAAE,GAAW;IAC3D,MAAM,MAAM,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;IACvC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAqB,CAAC;QAC7E,IAAI,OAAO,CAAC,GAAG,KAAK,GAAG;YAAE,OAAO;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;IACT,CAAC;IACD,MAAM,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AAClC,CAAC","sourcesContent":["// `.ascribe/dev.json`: where the dev server is, for the editor's Open Site\n// Preview. Written when the server starts, removed when it stops. A server\n// that crashed leaves it behind, so a reader checks the URL answers first.\nimport { lstatSync, mkdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport type { AddressInfo } from \"node:net\";\nimport path from \"node:path\";\n\n/** What `dev.json` holds. */\nexport interface DevFile {\n /** The site's address: origin and base path, `http://localhost:4321/docs/`. */\n url: string;\n /** The build the dev server shows. */\n build: string;\n /** The dev server's process, so another server's file isn't removed. */\n pid: number;\n}\n\n/** `<project>/.ascribe/dev.json`. */\nexport function devFilePath(projectDir: string): string {\n return path.join(projectDir, \".ascribe\", \"dev.json\");\n}\n\n/** The site's address from the server's: a wildcard or loopback host is `localhost`. */\nexport function siteUrl(address: AddressInfo, options: { https: boolean; base: string }): string {\n let host = address.address;\n if ([\"::\", \"0.0.0.0\", \"::1\", \"\", \"localhost\"].includes(host)) host = \"localhost\";\n else if (host.includes(\":\")) host = `[${host}]`;\n const base = options.base.endsWith(\"/\") ? options.base : `${options.base}/`;\n return `${options.https ? \"https\" : \"http\"}://${host}:${address.port}${base.startsWith(\"/\") ? base : `/${base}`}`;\n}\n\n/**\n * Writes `dev.json`. A failure is the caller's to log: it only costs the editor its link.\n * `.ascribe` or `dev.json` that's a link (a symbolic link, or a junction on Windows, which\n * Node's `lstat` reports as one) is refused, not written through: a committed link could\n * lead anywhere.\n */\nexport function writeDevFile(projectDir: string, file: DevFile): void {\n const target = devFilePath(projectDir);\n for (const at of [path.dirname(target), target]) {\n if (lstatSync(at, { throwIfNoEntry: false })?.isSymbolicLink()) {\n throw new Error(`${at} is a link; replace it with a folder or a file, or remove it`);\n }\n }\n mkdirSync(path.dirname(target), { recursive: true });\n writeFileSync(target, `${JSON.stringify(file, null, 2)}\\n`);\n}\n\n/** Removes `dev.json` if this process wrote it. */\nexport function removeDevFile(projectDir: string, pid: number): void {\n const target = devFilePath(projectDir);\n try {\n const current = JSON.parse(readFileSync(target, \"utf8\")) as Partial<DevFile>;\n if (current.pid !== pid) return;\n } catch {\n return;\n }\n rmSync(target, { force: true });\n}\n"]}
|
package/dist/review/diff.d.ts
CHANGED
|
@@ -19,3 +19,16 @@ export declare function runDiff(options: {
|
|
|
19
19
|
}): Promise<DiffResult>;
|
|
20
20
|
/** The build's part of `ascribe diff --format json`'s output. */
|
|
21
21
|
export declare function parseDiff(json: string, build: string): DiffResult;
|
|
22
|
+
/**
|
|
23
|
+
* `ascribe diff --format prompt`'s prompt about a page, or a fragment's reach,
|
|
24
|
+
* at a content path; `undefined` when it didn't change. Rejects with
|
|
25
|
+
* `ascribe diff`'s reason as the message.
|
|
26
|
+
*/
|
|
27
|
+
export declare function runPrompt(options: {
|
|
28
|
+
binary: string;
|
|
29
|
+
configPath: string;
|
|
30
|
+
build: string;
|
|
31
|
+
cwd: string;
|
|
32
|
+
base: string | undefined;
|
|
33
|
+
path: string;
|
|
34
|
+
}): Promise<string | undefined>;
|
package/dist/review/diff.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
// Running `ascribe diff
|
|
1
|
+
// Running `ascribe diff` for the integration's build: its changes as JSON, and
|
|
2
|
+
// its agent prompts.
|
|
2
3
|
import { execFile } from "node:child_process";
|
|
3
4
|
/**
|
|
4
5
|
* Compares the build's pages with `base` (the default branch when it's
|
|
@@ -45,4 +46,36 @@ export function parseDiff(json, build) {
|
|
|
45
46
|
const errors = typeof report.working_tree_errors === "number" ? report.working_tree_errors : 0;
|
|
46
47
|
return { base: report.base, pages, errors };
|
|
47
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* `ascribe diff --format prompt`'s prompt about a page, or a fragment's reach,
|
|
51
|
+
* at a content path; `undefined` when it didn't change. Rejects with
|
|
52
|
+
* `ascribe diff`'s reason as the message.
|
|
53
|
+
*/
|
|
54
|
+
export function runPrompt(options) {
|
|
55
|
+
const args = [
|
|
56
|
+
"diff",
|
|
57
|
+
"--format",
|
|
58
|
+
"prompt",
|
|
59
|
+
"--build",
|
|
60
|
+
options.build,
|
|
61
|
+
"--config",
|
|
62
|
+
options.configPath,
|
|
63
|
+
"--color",
|
|
64
|
+
"never",
|
|
65
|
+
];
|
|
66
|
+
if (options.base !== undefined)
|
|
67
|
+
args.push("--base", options.base);
|
|
68
|
+
// After `--`: a content path that starts with `-` is still a path.
|
|
69
|
+
args.push("--", options.path);
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
execFile(options.binary, args, { cwd: options.cwd, maxBuffer: 16 * 1024 * 1024, windowsHide: true }, (error, stdout, stderr) => {
|
|
72
|
+
if (error !== null) {
|
|
73
|
+
const reason = stderr.trim();
|
|
74
|
+
reject(new Error(reason === "" ? error.message : reason, { cause: error }));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
resolve(stdout === "" ? undefined : stdout);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
}
|
|
48
81
|
//# sourceMappingURL=diff.js.map
|
package/dist/review/diff.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/review/diff.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/review/diff.ts"],"names":[],"mappings":"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","sourcesContent":["// Running `ascribe diff` for the integration's build: its changes as JSON, and\n// its agent prompts.\nimport { execFile } from \"node:child_process\";\nimport type { DiffReport } from \"../shapes.js\";\nimport type { BaseInfo, PageDiff } from \"./protocol.js\";\n\n/** One build's changes against a base. */\nexport interface DiffResult {\n base: BaseInfo;\n pages: PageDiff[];\n /** How many errors `ascribe check` finds in the working tree for the build. */\n errors: number;\n}\n\n/**\n * Compares the build's pages with `base` (the default branch when it's\n * `undefined`). Rejects with `ascribe diff`'s reason as the message.\n */\nexport function runDiff(options: {\n binary: string;\n configPath: string;\n build: string;\n cwd: string;\n base: string | undefined;\n}): Promise<DiffResult> {\n const args = [\n \"diff\",\n \"--format\",\n \"json\",\n \"--build\",\n options.build,\n \"--config\",\n options.configPath,\n \"--color\",\n \"never\",\n ];\n if (options.base !== undefined) args.push(\"--base\", options.base);\n return new Promise((resolve, reject) => {\n execFile(\n options.binary,\n args,\n { cwd: options.cwd, maxBuffer: 256 * 1024 * 1024, windowsHide: true },\n (error, stdout, stderr) => {\n if (error !== null) {\n const reason = stderr.trim();\n reject(new Error(reason === \"\" ? error.message : reason, { cause: error }));\n return;\n }\n try {\n resolve(parseDiff(stdout, options.build));\n } catch (cause) {\n reject(new Error(\"`ascribe diff` wrote output that isn't its JSON.\", { cause }));\n }\n },\n );\n });\n}\n\n/** The build's part of `ascribe diff --format json`'s output. */\nexport function parseDiff(json: string, build: string): DiffResult {\n // Checked as far as telling a diff report from other JSON.\n const report = JSON.parse(json) as Partial<DiffReport>;\n if (report.base === undefined || !Array.isArray(report.builds)) {\n throw new Error(\"not a diff report\");\n }\n const pages = report.builds.find((b) => b.build === build)?.pages ?? [];\n const errors = typeof report.working_tree_errors === \"number\" ? report.working_tree_errors : 0;\n return { base: report.base, pages, errors };\n}\n\n/**\n * `ascribe diff --format prompt`'s prompt about a page, or a fragment's reach,\n * at a content path; `undefined` when it didn't change. Rejects with\n * `ascribe diff`'s reason as the message.\n */\nexport function runPrompt(options: {\n binary: string;\n configPath: string;\n build: string;\n cwd: string;\n base: string | undefined;\n path: string;\n}): Promise<string | undefined> {\n const args = [\n \"diff\",\n \"--format\",\n \"prompt\",\n \"--build\",\n options.build,\n \"--config\",\n options.configPath,\n \"--color\",\n \"never\",\n ];\n if (options.base !== undefined) args.push(\"--base\", options.base);\n // After `--`: a content path that starts with `-` is still a path.\n args.push(\"--\", options.path);\n return new Promise((resolve, reject) => {\n execFile(\n options.binary,\n args,\n { cwd: options.cwd, maxBuffer: 16 * 1024 * 1024, windowsHide: true },\n (error, stdout, stderr) => {\n if (error !== null) {\n const reason = stderr.trim();\n reject(new Error(reason === \"\" ? error.message : reason, { cause: error }));\n return;\n }\n resolve(stdout === \"\" ? undefined : stdout);\n },\n );\n });\n}\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BaseInfo, PageDiff } from "../shapes.js";
|
|
2
|
+
import type { FormattedPiece } from "@ascribed/review/overlay";
|
|
2
3
|
/** `ascribe diff --format json`'s types: a changed page, its block changes, and the base. */
|
|
3
4
|
export type { BaseInfo, Change, Counts, PageDiff } from "../shapes.js";
|
|
4
5
|
/** The toolbar app's id, also the prefix of its events. */
|
|
@@ -22,9 +23,13 @@ export interface Result {
|
|
|
22
23
|
code?: string;
|
|
23
24
|
};
|
|
24
25
|
}
|
|
25
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* A changed page in the list: without its changes, and with its title when
|
|
28
|
+
* the build has it, formatted when its field sets `inline = "code"`.
|
|
29
|
+
*/
|
|
26
30
|
export type ChangedPage = Omit<PageDiff, "changes"> & {
|
|
27
31
|
title: string | null;
|
|
32
|
+
formatted_title: FormattedPiece[] | null;
|
|
28
33
|
};
|
|
29
34
|
/** How the checkout's `HEAD` relates to the pull request's head commit. */
|
|
30
35
|
export type LocalState = "same" | "behind" | "ahead" | "diverged" | "missing";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/review/protocol.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,wEAAwE;AACxE,EAAE;AACF,8EAA8E;AAC9E,yEAAyE;AACzE,8EAA8E;AAC9E,wEAAwE;AACxE,mCAAmC;
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/review/protocol.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,wEAAwE;AACxE,EAAE;AACF,8EAA8E;AAC9E,yEAAyE;AACzE,8EAA8E;AAC9E,wEAAwE;AACxE,mCAAmC;AAQnC,2DAA2D;AAC3D,MAAM,CAAC,MAAM,MAAM,GAAG,gBAAgB,CAAC;AACvC,MAAM,CAAC,MAAM,aAAa,GAAG,wBAAwB,CAAC;AACtD,MAAM,CAAC,MAAM,YAAY,GAAG,uBAAuB,CAAC;AACpD,MAAM,CAAC,MAAM,aAAa,GAAG,wBAAwB,CAAC","sourcesContent":["// The messages between the toolbar app (in the page) and the dev server,\n// over Astro's dev toolbar channel. Types only: both sides import them.\n//\n// The page sends `ascribe:review:request` with `{ tab, id, method, params }`;\n// the server answers that page with `ascribe:review:result` and the same\n// `tab` and `id`. When a page's request changes the threads, or review stops,\n// the server tells every page with `ascribe:review:changed`, naming the\n// `from` tab, which already knows.\n\nimport type { BaseInfo, PageDiff } from \"../shapes.js\";\nimport type { FormattedPiece } from \"@ascribed/review/overlay\";\n\n/** `ascribe diff --format json`'s types: a changed page, its block changes, and the base. */\nexport type { BaseInfo, Change, Counts, PageDiff } from \"../shapes.js\";\n\n/** The toolbar app's id, also the prefix of its events. */\nexport const APP_ID = \"ascribe:review\";\nexport const REQUEST_EVENT = \"ascribe:review:request\";\nexport const RESULT_EVENT = \"ascribe:review:result\";\nexport const CHANGED_EVENT = \"ascribe:review:changed\";\n\nexport interface Request {\n /** The page's own id, made when it loads. */\n tab: string;\n id: number;\n method: string;\n params: Record<string, unknown>;\n}\n\nexport interface Result {\n tab: string;\n id: number;\n result?: unknown;\n error?: { message: string; code?: string };\n}\n\n/**\n * A changed page in the list: without its changes, and with its title when\n * the build has it, formatted when its field sets `inline = \"code\"`.\n */\nexport type ChangedPage = Omit<PageDiff, \"changes\"> & {\n title: string | null;\n formatted_title: FormattedPiece[] | null;\n};\n\n/** How the checkout's `HEAD` relates to the pull request's head commit. */\nexport type LocalState = \"same\" | \"behind\" | \"ahead\" | \"diverged\" | \"missing\";\n\n/** The review threads: on, or why not, with a sentence to show. */\nexport type ThreadsState =\n | {\n state: \"on\";\n pullRequest: { number: number; url: string; baseRefName: string };\n local: { state: LocalState; behind: number; ahead: number };\n }\n /** No pull request, no GitHub CLI, no sign-in, a channel other pages could use, or a failure. */\n | {\n state: \"no-pull-request\" | \"gh-missing\" | \"signed-out\" | \"unprotected\" | \"error\";\n message: string;\n };\n\n/** `status`'s answer. */\nexport interface Status {\n on: boolean;\n}\n\n/** `page`'s answer: what the app shows for the page at a route. */\nexport interface PageView {\n /** `\"page\"` for a page of the Ascribe build, `\"not-page\"` for the site's own routes. */\n kind: \"page\" | \"not-page\";\n /** The page's content path; `null` for a route that isn't one. */\n path: string | null;\n /** The page's changes; `null` when it didn't change. */\n page: PageDiff | null;\n base: BaseInfo | null;\n /** Why the changes couldn't be read, when they couldn't. */\n problem: string | null;\n /** How many errors `ascribe check` finds in the working tree for the build. */\n errors: number;\n threads: ThreadsState;\n /** The build's changed pages, in path order. */\n changedPages: ChangedPage[];\n /** The content root, absolute, and the platform's path separator: for opening a source file. */\n contentRoot: string;\n separator: string;\n}\n"]}
|
package/dist/review/routes.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FormattedPiece } from "@ascribed/review/overlay";
|
|
1
2
|
/** A route as compared: decoded, without a trailing `/` (except the root's). */
|
|
2
3
|
export declare function normalizeRoute(route: string): string;
|
|
3
4
|
/** A page of the build, as its JSON output names it. */
|
|
@@ -5,6 +6,8 @@ export interface RoutedPage {
|
|
|
5
6
|
/** The content path. */
|
|
6
7
|
path: string;
|
|
7
8
|
title: string | null;
|
|
9
|
+
/** The title formatted, when its field sets `inline = "code"`. */
|
|
10
|
+
formatted_title: FormattedPiece[] | null;
|
|
8
11
|
}
|
|
9
12
|
/**
|
|
10
13
|
* Every page of the build's JSON output (`<output-dir>/<build>/json/`), by
|