@ascribed/astro 0.1.0 → 0.1.2-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/dist/attributes.d.ts +17 -0
- package/dist/attributes.js +93 -5
- package/dist/attributes.js.map +1 -1
- package/dist/dev.d.ts +24 -1
- package/dist/dev.js +69 -9
- package/dist/dev.js.map +1 -1
- package/dist/index.d.ts +13 -0
- package/dist/index.js +84 -9
- package/dist/index.js.map +1 -1
- package/dist/rehype.d.ts +1 -1
- package/dist/rehype.js +19 -5
- package/dist/rehype.js.map +1 -1
- package/dist/review/devfile.d.ts +21 -0
- package/dist/review/devfile.js +39 -0
- package/dist/review/devfile.js.map +1 -0
- package/dist/review/diff.d.ts +21 -0
- package/dist/review/diff.js +47 -0
- package/dist/review/diff.js.map +1 -0
- package/dist/review/github.d.ts +19 -0
- package/dist/review/github.js +73 -0
- package/dist/review/github.js.map +1 -0
- package/dist/review/protocol.d.ts +98 -0
- package/dist/review/protocol.js +14 -0
- package/dist/review/protocol.js.map +1 -0
- package/dist/review/routes.d.ts +13 -0
- package/dist/review/routes.js +92 -0
- package/dist/review/routes.js.map +1 -0
- package/dist/review/server.d.ts +100 -0
- package/dist/review/server.js +336 -0
- package/dist/review/server.js.map +1 -0
- package/dist/run.d.ts +14 -1
- package/dist/run.js +19 -3
- package/dist/run.js.map +1 -1
- package/dist/satteri.d.ts +1 -1
- package/dist/satteri.js +22 -5
- package/dist/satteri.js.map +1 -1
- package/dist/toolbar/app.d.ts +2 -0
- package/dist/toolbar/app.js +951 -0
- package/dist/toolbar/app.js.map +1 -0
- package/dist/toolbar/page.d.ts +40 -0
- package/dist/toolbar/page.js +120 -0
- package/dist/toolbar/page.js.map +1 -0
- package/dist/toolbar/style.d.ts +1 -0
- package/dist/toolbar/style.js +250 -0
- package/dist/toolbar/style.js.map +1 -0
- package/dist/toolbar/text.d.ts +49 -0
- package/dist/toolbar/text.js +114 -0
- package/dist/toolbar/text.js.map +1 -0
- package/package.json +6 -3
package/dist/rehype.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// The attribute marker as a rehype plugin, for Astro's
|
|
2
|
-
// processor (`@astrojs/markdown-remark`).
|
|
1
|
+
// The attribute marker and source anchors as a rehype plugin, for Astro's
|
|
2
|
+
// `unified()` markdown processor (`@astrojs/markdown-remark`).
|
|
3
3
|
//
|
|
4
4
|
// Astro builds that processor as: remark-parse, remark-gfm, remark-smartypants,
|
|
5
5
|
// the user's remark plugins, remark-collect-images, remark-rehype (raw HTML
|
|
@@ -10,14 +10,28 @@
|
|
|
10
10
|
// and `rehypeHeadingIds` keeps an id a heading already has and records it for
|
|
11
11
|
// the table of contents. A rehype plugin, not a remark one, so that both of
|
|
12
12
|
// Astro's processors can share `findEdits`.
|
|
13
|
-
import { findEdits, toProperty } from "./attributes.js";
|
|
14
|
-
/** Applies the site output's attribute markers (`ascribe-attributes`). */
|
|
13
|
+
import { findAnchors, findEdits, toProperty } from "./attributes.js";
|
|
14
|
+
/** Applies the site output's attribute markers (`ascribe-attributes`) and source anchors. */
|
|
15
15
|
export default function rehypeAscribeAttributes() {
|
|
16
16
|
return (tree) => {
|
|
17
17
|
const edits = findEdits(tree);
|
|
18
|
-
|
|
18
|
+
const anchors = findAnchors(tree);
|
|
19
|
+
if (edits.length === 0 && anchors.length === 0)
|
|
19
20
|
return;
|
|
20
21
|
const removed = new Set();
|
|
22
|
+
for (const anchor of anchors) {
|
|
23
|
+
for (const { node, attributes } of anchor.targets) {
|
|
24
|
+
const properties = (node.properties ??= {});
|
|
25
|
+
for (const attribute of attributes) {
|
|
26
|
+
const [name, value] = toProperty(attribute);
|
|
27
|
+
properties[name] = value;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (anchor.replace)
|
|
31
|
+
anchor.replace.node.value = anchor.replace.value;
|
|
32
|
+
for (const node of anchor.remove)
|
|
33
|
+
removed.add(node);
|
|
34
|
+
}
|
|
21
35
|
for (const edit of edits) {
|
|
22
36
|
const properties = (edit.target.properties ??= {});
|
|
23
37
|
for (const attribute of edit.attributes) {
|
package/dist/rehype.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rehype.js","sourceRoot":"","sources":["../src/rehype.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"rehype.js","sourceRoot":"","sources":["../src/rehype.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,+DAA+D;AAC/D,EAAE;AACF,gFAAgF;AAChF,4EAA4E;AAC5E,4EAA4E;AAC5E,+EAA+E;AAC/E,8EAA8E;AAC9E,6EAA6E;AAC7E,8EAA8E;AAC9E,4EAA4E;AAC5E,4CAA4C;AAG5C,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAiB,MAAM,iBAAiB,CAAC;AAEpF,6FAA6F;AAC7F,MAAM,CAAC,OAAO,UAAU,uBAAuB;IAC7C,OAAO,CAAC,IAAI,EAAE,EAAE;QACd,MAAM,KAAK,GAAG,SAAS,CAAC,IAAgB,CAAC,CAAC;QAC1C,MAAM,OAAO,GAAG,WAAW,CAAC,IAAgB,CAAC,CAAC;QAC9C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QACvD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAY,CAAC;QACpC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,KAAK,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBAClD,MAAM,UAAU,GAAG,CAAC,IAAI,CAAC,UAAU,KAAK,EAAE,CAAC,CAAC;gBAC5C,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;oBACnC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;oBAC5C,UAAU,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;gBAC3B,CAAC;YACH,CAAC;YACD,IAAI,MAAM,CAAC,OAAO;gBAAE,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;YACrE,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,MAAM;gBAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACtD,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,UAAU,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,KAAK,EAAE,CAAC,CAAC;YACnD,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;gBACxC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;gBAC5C,UAAU,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;YAC3B,CAAC;YACD,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,MAAM;gBAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAClD,KAAK,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;gBACxC,IAAI,KAAK,KAAK,EAAE;oBAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;;oBAC/B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;YAC1B,CAAC;QACH,CAAC;QACD,SAAS,CAAC,IAAgB,EAAE,OAAO,CAAC,CAAC;IACvC,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,IAAc,EAAE,OAAsB;IACvD,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS;QAAE,OAAO;IACxC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;IACrE,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ;QAAE,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;AAC/D,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AddressInfo } from "node:net";
|
|
2
|
+
/** What `dev.json` holds. */
|
|
3
|
+
export interface DevFile {
|
|
4
|
+
/** The site's address: origin and base path, `http://localhost:4321/docs/`. */
|
|
5
|
+
url: string;
|
|
6
|
+
/** The build the dev server shows. */
|
|
7
|
+
build: string;
|
|
8
|
+
/** The dev server's process, so another server's file isn't removed. */
|
|
9
|
+
pid: number;
|
|
10
|
+
}
|
|
11
|
+
/** `<project>/.ascribe/dev.json`. */
|
|
12
|
+
export declare function devFilePath(projectDir: string): string;
|
|
13
|
+
/** The site's address from the server's: a wildcard or loopback host is `localhost`. */
|
|
14
|
+
export declare function siteUrl(address: AddressInfo, options: {
|
|
15
|
+
https: boolean;
|
|
16
|
+
base: string;
|
|
17
|
+
}): string;
|
|
18
|
+
/** Writes `dev.json`. A failure is the caller's to log: it only costs the editor its link. */
|
|
19
|
+
export declare function writeDevFile(projectDir: string, file: DevFile): void;
|
|
20
|
+
/** Removes `dev.json` if this process wrote it. */
|
|
21
|
+
export declare function removeDevFile(projectDir: string, pid: number): void;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// `.ascribe/dev.json`: where the dev server is, for the editor's Open Site
|
|
2
|
+
// Preview. Written when the server starts, removed when it stops. A server
|
|
3
|
+
// that crashed leaves it behind, so a reader checks the URL answers first.
|
|
4
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
/** `<project>/.ascribe/dev.json`. */
|
|
7
|
+
export function devFilePath(projectDir) {
|
|
8
|
+
return path.join(projectDir, ".ascribe", "dev.json");
|
|
9
|
+
}
|
|
10
|
+
/** The site's address from the server's: a wildcard or loopback host is `localhost`. */
|
|
11
|
+
export function siteUrl(address, options) {
|
|
12
|
+
let host = address.address;
|
|
13
|
+
if (["::", "0.0.0.0", "::1", "", "localhost"].includes(host))
|
|
14
|
+
host = "localhost";
|
|
15
|
+
else if (host.includes(":"))
|
|
16
|
+
host = `[${host}]`;
|
|
17
|
+
const base = options.base.endsWith("/") ? options.base : `${options.base}/`;
|
|
18
|
+
return `${options.https ? "https" : "http"}://${host}:${address.port}${base.startsWith("/") ? base : `/${base}`}`;
|
|
19
|
+
}
|
|
20
|
+
/** Writes `dev.json`. A failure is the caller's to log: it only costs the editor its link. */
|
|
21
|
+
export function writeDevFile(projectDir, file) {
|
|
22
|
+
const target = devFilePath(projectDir);
|
|
23
|
+
mkdirSync(path.dirname(target), { recursive: true });
|
|
24
|
+
writeFileSync(target, `${JSON.stringify(file, null, 2)}\n`);
|
|
25
|
+
}
|
|
26
|
+
/** Removes `dev.json` if this process wrote it. */
|
|
27
|
+
export function removeDevFile(projectDir, pid) {
|
|
28
|
+
const target = devFilePath(projectDir);
|
|
29
|
+
try {
|
|
30
|
+
const current = JSON.parse(readFileSync(target, "utf8"));
|
|
31
|
+
if (current.pid !== pid)
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
rmSync(target, { force: true });
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=devfile.js.map
|
|
@@ -0,0 +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;AAEzE,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,8FAA8F;AAC9F,MAAM,UAAU,YAAY,CAAC,UAAkB,EAAE,IAAa;IAC5D,MAAM,MAAM,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;IACvC,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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { DiffBase, DiffPage } from "./protocol.js";
|
|
2
|
+
/** One build's changes against a base. */
|
|
3
|
+
export interface DiffResult {
|
|
4
|
+
base: DiffBase;
|
|
5
|
+
pages: DiffPage[];
|
|
6
|
+
/** How many errors `ascribe check` finds in the working tree for the build. */
|
|
7
|
+
errors: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Compares the build's pages with `base` (the default branch when it's
|
|
11
|
+
* `undefined`). Rejects with `ascribe diff`'s reason as the message.
|
|
12
|
+
*/
|
|
13
|
+
export declare function runDiff(options: {
|
|
14
|
+
binary: string;
|
|
15
|
+
configPath: string;
|
|
16
|
+
build: string;
|
|
17
|
+
cwd: string;
|
|
18
|
+
base: string | undefined;
|
|
19
|
+
}): Promise<DiffResult>;
|
|
20
|
+
/** The build's part of `ascribe diff --format json`'s output. */
|
|
21
|
+
export declare function parseDiff(json: string, build: string): DiffResult;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Running `ascribe diff --format json` for the integration's build.
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
/**
|
|
4
|
+
* Compares the build's pages with `base` (the default branch when it's
|
|
5
|
+
* `undefined`). Rejects with `ascribe diff`'s reason as the message.
|
|
6
|
+
*/
|
|
7
|
+
export function runDiff(options) {
|
|
8
|
+
const args = [
|
|
9
|
+
"diff",
|
|
10
|
+
"--format",
|
|
11
|
+
"json",
|
|
12
|
+
"--build",
|
|
13
|
+
options.build,
|
|
14
|
+
"--config",
|
|
15
|
+
options.configPath,
|
|
16
|
+
"--color",
|
|
17
|
+
"never",
|
|
18
|
+
];
|
|
19
|
+
if (options.base !== undefined)
|
|
20
|
+
args.push("--base", options.base);
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
execFile(options.binary, args, { cwd: options.cwd, maxBuffer: 256 * 1024 * 1024, windowsHide: true }, (error, stdout, stderr) => {
|
|
23
|
+
if (error !== null) {
|
|
24
|
+
const reason = stderr.trim();
|
|
25
|
+
reject(new Error(reason === "" ? error.message : reason, { cause: error }));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
resolve(parseDiff(stdout, options.build));
|
|
30
|
+
}
|
|
31
|
+
catch (cause) {
|
|
32
|
+
reject(new Error("`ascribe diff` wrote output that isn't its JSON.", { cause }));
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/** The build's part of `ascribe diff --format json`'s output. */
|
|
38
|
+
export function parseDiff(json, build) {
|
|
39
|
+
const report = JSON.parse(json);
|
|
40
|
+
if (report.base === undefined || !Array.isArray(report.builds)) {
|
|
41
|
+
throw new Error("not a diff report");
|
|
42
|
+
}
|
|
43
|
+
const pages = report.builds.find((b) => b.build === build)?.pages ?? [];
|
|
44
|
+
const errors = typeof report.working_tree_errors === "number" ? report.working_tree_errors : 0;
|
|
45
|
+
return { base: report.base, pages, errors };
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/review/diff.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAW9C;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,OAMvB;IACC,MAAM,IAAI,GAAG;QACX,MAAM;QACN,UAAU;QACV,MAAM;QACN,SAAS;QACT,OAAO,CAAC,KAAK;QACb,UAAU;QACV,OAAO,CAAC,UAAU;QAClB,SAAS;QACT,OAAO;KACR,CAAC;IACF,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS;QAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,QAAQ,CACN,OAAO,CAAC,MAAM,EACd,IAAI,EACJ,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,EACrE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACxB,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBACnB,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;gBAC7B,MAAM,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;gBAC5E,OAAO;YACT,CAAC;YACD,IAAI,CAAC;gBACH,OAAO,CAAC,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;YAC5C,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,IAAI,KAAK,CAAC,kDAAkD,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;YACnF,CAAC;QACH,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,KAAa;IACnD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAI7B,CAAC;IACF,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/D,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;IACvC,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC;IACxE,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,mBAAmB,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/F,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AAC9C,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ReviewSession } from "@ascribed/review/github";
|
|
2
|
+
import type { LocalState, ThreadsState } from "./protocol.js";
|
|
3
|
+
/** A project's connection to its pull request: a session, or why there's none. */
|
|
4
|
+
export type Connection = {
|
|
5
|
+
state: "on";
|
|
6
|
+
session: ReviewSession;
|
|
7
|
+
/** The git revision for the pull request's base: `origin/main`. */
|
|
8
|
+
base: string;
|
|
9
|
+
local: {
|
|
10
|
+
state: LocalState;
|
|
11
|
+
behind: number;
|
|
12
|
+
ahead: number;
|
|
13
|
+
};
|
|
14
|
+
} | Exclude<ThreadsState, {
|
|
15
|
+
state: "on";
|
|
16
|
+
}>;
|
|
17
|
+
/** Finds the checkout's pull request and opens a review of it with the GitHub CLI's sign-in. */
|
|
18
|
+
export declare function connect(projectDir: string): Promise<Connection>;
|
|
19
|
+
export declare function messageOf(error: unknown): string;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// The pull request's review, through the GitHub CLI: `@ascribed/review`'s
|
|
2
|
+
// session with `ghTransport`, found for the project's checkout. Loaded only
|
|
3
|
+
// when review starts, so `astro build` never reads it.
|
|
4
|
+
import { execFile } from "node:child_process";
|
|
5
|
+
const CHANGES_ONLY = "Showing changes only.";
|
|
6
|
+
/** Finds the checkout's pull request and opens a review of it with the GitHub CLI's sign-in. */
|
|
7
|
+
export async function connect(projectDir) {
|
|
8
|
+
const github = await import("@ascribed/review/github");
|
|
9
|
+
let checkout;
|
|
10
|
+
try {
|
|
11
|
+
checkout = await github.readCheckout(projectDir);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return {
|
|
15
|
+
state: "no-pull-request",
|
|
16
|
+
message: `${CHANGES_ONLY} Comments need a git checkout with a pull request on GitHub.`,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const host = checkout.bases[0]?.host;
|
|
20
|
+
if (host === undefined) {
|
|
21
|
+
return {
|
|
22
|
+
state: "no-pull-request",
|
|
23
|
+
message: `${CHANGES_ONLY} Comments need the branch pushed to GitHub, with a pull request.`,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
let session;
|
|
27
|
+
try {
|
|
28
|
+
session = await github.openReview({ projectDir });
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
const code = error instanceof github.ReviewError ? error.code : undefined;
|
|
32
|
+
if (code === "gh-missing" || code === "gh-too-old") {
|
|
33
|
+
return {
|
|
34
|
+
state: "gh-missing",
|
|
35
|
+
message: `${CHANGES_ONLY} Comments need the GitHub CLI: install gh, run \`gh auth login\`, then Refresh.`,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
if (code === "not-signed-in") {
|
|
39
|
+
return {
|
|
40
|
+
state: "signed-out",
|
|
41
|
+
message: `${CHANGES_ONLY} Comments need GitHub: run \`gh auth login --hostname ${host}\`, then Refresh.`,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
// The first line: what `gh` or GitHub said, without any trace after it.
|
|
45
|
+
return { state: "error", message: messageOf(error).split("\n")[0] ?? "" };
|
|
46
|
+
}
|
|
47
|
+
if (session === undefined) {
|
|
48
|
+
return {
|
|
49
|
+
state: "no-pull-request",
|
|
50
|
+
message: `${CHANGES_ONLY} This branch has no open pull request: open one to comment here, then Refresh.`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const pr = session.pullRequest;
|
|
54
|
+
return {
|
|
55
|
+
state: "on",
|
|
56
|
+
session,
|
|
57
|
+
base: await github.baseRevision(checkout.root, pr),
|
|
58
|
+
local: { state: pr.local, ...(await aheadBehind(checkout.root, pr.headOid)) },
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** How many commits `HEAD` is ahead of and behind the pull request's head. */
|
|
62
|
+
function aheadBehind(root, head) {
|
|
63
|
+
return new Promise((resolve) => {
|
|
64
|
+
execFile("git", ["rev-list", "--left-right", "--count", `HEAD...${head}`], { cwd: root, timeout: 15_000, windowsHide: true, encoding: "utf8" }, (error, stdout) => {
|
|
65
|
+
const [ahead, behind] = (error ? "" : stdout).trim().split(/\s+/).map(Number);
|
|
66
|
+
resolve({ ahead: ahead || 0, behind: behind || 0 });
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
export function messageOf(error) {
|
|
71
|
+
return error instanceof Error ? error.message : String(error);
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=github.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/review/github.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { Change } from "@ascribed/review/marks";
|
|
2
|
+
/** A block change, as `ascribe diff --format json` reports it and the marks take it. */
|
|
3
|
+
export type { Change };
|
|
4
|
+
/** The toolbar app's id, also the prefix of its events. */
|
|
5
|
+
export declare const APP_ID = "ascribe:review";
|
|
6
|
+
export declare const REQUEST_EVENT = "ascribe:review:request";
|
|
7
|
+
export declare const RESULT_EVENT = "ascribe:review:result";
|
|
8
|
+
export declare const CHANGED_EVENT = "ascribe:review:changed";
|
|
9
|
+
/** What the page asks for, besides the overlay's own requests (`OverlayMethod`). */
|
|
10
|
+
export type AppMethod = "status" | "start" | "stop" | "page" | "refresh";
|
|
11
|
+
export interface Request {
|
|
12
|
+
/** The page's own id, made when it loads. */
|
|
13
|
+
tab: string;
|
|
14
|
+
id: number;
|
|
15
|
+
method: string;
|
|
16
|
+
params: Record<string, unknown>;
|
|
17
|
+
}
|
|
18
|
+
export interface Result {
|
|
19
|
+
tab: string;
|
|
20
|
+
id: number;
|
|
21
|
+
result?: unknown;
|
|
22
|
+
error?: {
|
|
23
|
+
message: string;
|
|
24
|
+
code?: string;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export interface Counts {
|
|
28
|
+
changed: number;
|
|
29
|
+
added: number;
|
|
30
|
+
removed: number;
|
|
31
|
+
moved: number;
|
|
32
|
+
}
|
|
33
|
+
/** A changed page, as `ascribe diff --format json` lists it. */
|
|
34
|
+
export interface DiffPage {
|
|
35
|
+
path: string;
|
|
36
|
+
route: string;
|
|
37
|
+
status: "added" | "removed" | "changed";
|
|
38
|
+
own_file_changed: boolean;
|
|
39
|
+
because: string[];
|
|
40
|
+
page_changed: string[];
|
|
41
|
+
counts: Counts;
|
|
42
|
+
changes: Change[];
|
|
43
|
+
}
|
|
44
|
+
/** A changed page in the list: without its changes, and with its title when the build has it. */
|
|
45
|
+
export type ChangedPage = Omit<DiffPage, "changes"> & {
|
|
46
|
+
title: string | null;
|
|
47
|
+
};
|
|
48
|
+
/** The base compared with, as `ascribe diff` reports it. */
|
|
49
|
+
export interface DiffBase {
|
|
50
|
+
requested: string;
|
|
51
|
+
commit: string;
|
|
52
|
+
merge_base: string | null;
|
|
53
|
+
}
|
|
54
|
+
/** How the checkout's `HEAD` relates to the pull request's head commit. */
|
|
55
|
+
export type LocalState = "same" | "behind" | "ahead" | "diverged" | "missing";
|
|
56
|
+
/** The review threads: on, or why not, with a sentence to show. */
|
|
57
|
+
export type ThreadsState = {
|
|
58
|
+
state: "on";
|
|
59
|
+
pullRequest: {
|
|
60
|
+
number: number;
|
|
61
|
+
url: string;
|
|
62
|
+
baseRefName: string;
|
|
63
|
+
};
|
|
64
|
+
local: {
|
|
65
|
+
state: LocalState;
|
|
66
|
+
behind: number;
|
|
67
|
+
ahead: number;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** No pull request, no GitHub CLI, no sign-in, a channel other pages could use, or a failure. */
|
|
71
|
+
| {
|
|
72
|
+
state: "no-pull-request" | "gh-missing" | "signed-out" | "unprotected" | "error";
|
|
73
|
+
message: string;
|
|
74
|
+
};
|
|
75
|
+
/** `status`'s answer. */
|
|
76
|
+
export interface Status {
|
|
77
|
+
on: boolean;
|
|
78
|
+
}
|
|
79
|
+
/** `page`'s answer: what the app shows for the page at a route. */
|
|
80
|
+
export interface PageView {
|
|
81
|
+
/** `"page"` for a page of the Ascribe build, `"not-page"` for the site's own routes. */
|
|
82
|
+
kind: "page" | "not-page";
|
|
83
|
+
/** The page's content path; `null` for a route that isn't one. */
|
|
84
|
+
path: string | null;
|
|
85
|
+
/** The page's changes; `null` when it didn't change. */
|
|
86
|
+
page: DiffPage | null;
|
|
87
|
+
base: DiffBase | null;
|
|
88
|
+
/** Why the changes couldn't be read, when they couldn't. */
|
|
89
|
+
problem: string | null;
|
|
90
|
+
/** How many errors `ascribe check` finds in the working tree for the build. */
|
|
91
|
+
errors: number;
|
|
92
|
+
threads: ThreadsState;
|
|
93
|
+
/** The build's changed pages, in path order. */
|
|
94
|
+
changedPages: ChangedPage[];
|
|
95
|
+
/** The content root, absolute, and the platform's path separator: for opening a source file. */
|
|
96
|
+
contentRoot: string;
|
|
97
|
+
separator: string;
|
|
98
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// The messages between the toolbar app (in the page) and the dev server,
|
|
2
|
+
// over Astro's dev toolbar channel. Types only: both sides import them.
|
|
3
|
+
//
|
|
4
|
+
// The page sends `ascribe:review:request` with `{ tab, id, method, params }`;
|
|
5
|
+
// the server answers that page with `ascribe:review:result` and the same
|
|
6
|
+
// `tab` and `id`. When a page's request changes the threads, or review stops,
|
|
7
|
+
// the server tells every page with `ascribe:review:changed`, naming the
|
|
8
|
+
// `from` tab, which already knows.
|
|
9
|
+
/** The toolbar app's id, also the prefix of its events. */
|
|
10
|
+
export const APP_ID = "ascribe:review";
|
|
11
|
+
export const REQUEST_EVENT = "ascribe:review:request";
|
|
12
|
+
export const RESULT_EVENT = "ascribe:review:result";
|
|
13
|
+
export const CHANGED_EVENT = "ascribe:review:changed";
|
|
14
|
+
//# sourceMappingURL=protocol.js.map
|
|
@@ -0,0 +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;AAOnC,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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** A route as compared: decoded, without a trailing `/` (except the root's). */
|
|
2
|
+
export declare function normalizeRoute(route: string): string;
|
|
3
|
+
/** A page of the build, as its JSON output names it. */
|
|
4
|
+
export interface RoutedPage {
|
|
5
|
+
/** The content path. */
|
|
6
|
+
path: string;
|
|
7
|
+
title: string | null;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Every page of the build's JSON output (`<output-dir>/<build>/json/`), by
|
|
11
|
+
* normalized route. Empty when there's no JSON output.
|
|
12
|
+
*/
|
|
13
|
+
export declare function readRoutes(jsonRoot: string): Promise<Map<string, RoutedPage>>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Which page of the build is at a route, and its title: read from the JSON
|
|
2
|
+
// output, which the dev server writes while review is on, since the site
|
|
3
|
+
// output doesn't say.
|
|
4
|
+
import { open, readFile } from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
/** A route as compared: decoded, without a trailing `/` (except the root's). */
|
|
7
|
+
export function normalizeRoute(route) {
|
|
8
|
+
let decoded;
|
|
9
|
+
try {
|
|
10
|
+
decoded = decodeURI(route);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
decoded = route;
|
|
14
|
+
}
|
|
15
|
+
const trimmed = (decoded.split(/[?#]/)[0] ?? "").replace(/\/+$/, "");
|
|
16
|
+
return trimmed === "" ? "/" : trimmed;
|
|
17
|
+
}
|
|
18
|
+
/** How much of a page's JSON to read: its `path`, `route`, and `title` come first. */
|
|
19
|
+
const HEAD_BYTES = 4096;
|
|
20
|
+
/**
|
|
21
|
+
* Every page of the build's JSON output (`<output-dir>/<build>/json/`), by
|
|
22
|
+
* normalized route. Empty when there's no JSON output.
|
|
23
|
+
*/
|
|
24
|
+
export async function readRoutes(jsonRoot) {
|
|
25
|
+
const routes = new Map();
|
|
26
|
+
let files;
|
|
27
|
+
try {
|
|
28
|
+
const manifest = JSON.parse(await readFile(path.join(path.dirname(jsonRoot), "json.manifest.json"), "utf8"));
|
|
29
|
+
files = Array.isArray(manifest.files) ? manifest.files : [];
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return routes;
|
|
33
|
+
}
|
|
34
|
+
await Promise.all(files.map(async (entry) => {
|
|
35
|
+
if (entry.kind !== "page" || typeof entry.path !== "string")
|
|
36
|
+
return;
|
|
37
|
+
const page = await readHead(path.join(jsonRoot, ...entry.path.split("/")));
|
|
38
|
+
if (page)
|
|
39
|
+
routes.set(normalizeRoute(page.route), { path: page.path, title: page.title });
|
|
40
|
+
}));
|
|
41
|
+
return routes;
|
|
42
|
+
}
|
|
43
|
+
/** A page's `path`, `route`, and `title`, from the start of its JSON, or the whole of it if need be. */
|
|
44
|
+
async function readHead(file) {
|
|
45
|
+
let head = "";
|
|
46
|
+
try {
|
|
47
|
+
const handle = await open(file, "r");
|
|
48
|
+
try {
|
|
49
|
+
const buffer = Buffer.alloc(HEAD_BYTES);
|
|
50
|
+
const { bytesRead } = await handle.read(buffer, 0, HEAD_BYTES, 0);
|
|
51
|
+
head = buffer.subarray(0, bytesRead).toString("utf8");
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
await handle.close();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
const field = (name) => {
|
|
61
|
+
const match = new RegExp(`"${name}"\\s*:\\s*("(?:[^"\\\\]|\\\\.)*")`).exec(head);
|
|
62
|
+
if (!match?.[1])
|
|
63
|
+
return undefined;
|
|
64
|
+
try {
|
|
65
|
+
return JSON.parse(match[1]);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
// The page's own keys come before `frontmatter`, so the first of each is the page's.
|
|
72
|
+
const pagePath = field("path");
|
|
73
|
+
const route = field("route");
|
|
74
|
+
const title = field("title");
|
|
75
|
+
if (pagePath !== undefined && route !== undefined && title !== undefined) {
|
|
76
|
+
return { path: pagePath, route, title };
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const page = JSON.parse(await readFile(file, "utf8"));
|
|
80
|
+
return typeof page.path === "string" && typeof page.route === "string"
|
|
81
|
+
? {
|
|
82
|
+
path: page.path,
|
|
83
|
+
route: page.route,
|
|
84
|
+
title: typeof page.title === "string" ? page.title : null,
|
|
85
|
+
}
|
|
86
|
+
: undefined;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/review/routes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { DiffResult } from "./diff.js";
|
|
2
|
+
import { type Connection } from "./github.js";
|
|
3
|
+
import { type RoutedPage } from "./routes.js";
|
|
4
|
+
/** A page's end of the channel: the server answers it alone. */
|
|
5
|
+
export interface ChannelClient {
|
|
6
|
+
send(event: string, payload?: unknown): void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The toolbar's channel, as `astro:server:setup`'s `toolbar` has it. Its
|
|
10
|
+
* listeners get the sending page's client too (it is Vite's `server.hot`);
|
|
11
|
+
* without one, an answer goes to every page, which tell theirs by `tab`.
|
|
12
|
+
*/
|
|
13
|
+
export interface ToolbarChannel {
|
|
14
|
+
on(event: string, callback: (data: unknown, client?: ChannelClient) => void): void;
|
|
15
|
+
send(event: string, payload: unknown): void;
|
|
16
|
+
}
|
|
17
|
+
export interface ReviewServerOptions {
|
|
18
|
+
channel: ToolbarChannel;
|
|
19
|
+
logger: {
|
|
20
|
+
info(message: string): void;
|
|
21
|
+
warn(message: string): void;
|
|
22
|
+
};
|
|
23
|
+
build: string;
|
|
24
|
+
/** The content root, absolute. */
|
|
25
|
+
contentRoot: string;
|
|
26
|
+
/** Why someone else could use the channel, if they could: then GitHub stays off. */
|
|
27
|
+
channelProblem: string | undefined;
|
|
28
|
+
/** Compares the build with `base` (the default branch when `undefined`). */
|
|
29
|
+
diff(base: string | undefined): Promise<DiffResult>;
|
|
30
|
+
/** Opens the pull request's review. */
|
|
31
|
+
connect(): Promise<Connection>;
|
|
32
|
+
/** Writes the build's JSON output, in turn with the dev server's builds: it has the routes. */
|
|
33
|
+
writeRoutes(): Promise<void>;
|
|
34
|
+
/** Every page of the build, by normalized route. */
|
|
35
|
+
readRoutes(): Promise<Map<string, RoutedPage>>;
|
|
36
|
+
/** How long to wait after a rebuild before comparing again. Default 150 ms. */
|
|
37
|
+
debounceMs?: number;
|
|
38
|
+
}
|
|
39
|
+
export declare class ReviewServer {
|
|
40
|
+
private readonly options;
|
|
41
|
+
private on;
|
|
42
|
+
private problem;
|
|
43
|
+
private connection;
|
|
44
|
+
private connecting;
|
|
45
|
+
private diffed;
|
|
46
|
+
private diffing;
|
|
47
|
+
private stale;
|
|
48
|
+
private timer;
|
|
49
|
+
private routes;
|
|
50
|
+
/** The page each overlay last read, by content path: comments are made on it. */
|
|
51
|
+
private readonly pages;
|
|
52
|
+
/** How long the last comparison took, in milliseconds. */
|
|
53
|
+
lastDiffMs: number | undefined;
|
|
54
|
+
constructor(options: ReviewServerOptions);
|
|
55
|
+
/**
|
|
56
|
+
* The address the dev server listens on, once it does: one other machines
|
|
57
|
+
* can reach turns comments off, as `server.host` does.
|
|
58
|
+
*/
|
|
59
|
+
listening(address: string): void;
|
|
60
|
+
/** Whether review is on: then the dev server's builds write JSON output too. */
|
|
61
|
+
get active(): boolean;
|
|
62
|
+
/** The dev server rebuilt: compare again soon, and read the routes again when asked. */
|
|
63
|
+
rebuilt(): void;
|
|
64
|
+
/** The dev server stopped. */
|
|
65
|
+
dispose(): void;
|
|
66
|
+
private receive;
|
|
67
|
+
/** Answers one request. Rejects with a sentence to show. */
|
|
68
|
+
handle(method: string, params: Record<string, unknown>, tab?: string): Promise<unknown>;
|
|
69
|
+
private start;
|
|
70
|
+
private stop;
|
|
71
|
+
private refresh;
|
|
72
|
+
/** The pull request's review, opened once while review is on. */
|
|
73
|
+
private connected;
|
|
74
|
+
/** The build's changes, compared again first if a rebuild made them stale. */
|
|
75
|
+
private changes;
|
|
76
|
+
private compare;
|
|
77
|
+
private pageView;
|
|
78
|
+
/** Tells every page the threads or the changes changed; `from` is the page that did it. */
|
|
79
|
+
private changed;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Why someone other than the developer could use the dev server's toolbar
|
|
83
|
+
* channel, or `undefined` when they can't. Vite takes a browser's connection
|
|
84
|
+
* only with the token in its client script, which the page's own origin can
|
|
85
|
+
* read and, by default, only localhost origins can fetch. These settings undo
|
|
86
|
+
* that, and a server listening on the network gives its pages, token and all,
|
|
87
|
+
* to anyone who can reach it.
|
|
88
|
+
*/
|
|
89
|
+
export declare function channelProblem(config: {
|
|
90
|
+
server: {
|
|
91
|
+
cors?: unknown;
|
|
92
|
+
allowedHosts?: unknown;
|
|
93
|
+
host?: unknown;
|
|
94
|
+
};
|
|
95
|
+
legacy?: {
|
|
96
|
+
skipWebSocketTokenCheck?: unknown;
|
|
97
|
+
} | undefined;
|
|
98
|
+
}): string | undefined;
|
|
99
|
+
/** Whether Vite's `server.host`, or the address the server listens on, keeps the server on this machine: unset, `false`, or a loopback name. */
|
|
100
|
+
export declare function isLoopback(host: unknown): boolean;
|