@ascribed/astro 0.2.1-next.11 → 0.2.1-next.12

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.
@@ -10,6 +10,7 @@
10
10
  // build with the pull request's base (or the default branch), and compares
11
11
  // again after each rebuild.
12
12
  import path from "node:path";
13
+ import { readFile } from "node:fs/promises";
13
14
  import { messageOf } from "./github.js";
14
15
  import { CHANGED_EVENT, REQUEST_EVENT, RESULT_EVENT, } from "./protocol.js";
15
16
  import { normalizeRoute } from "./routes.js";
@@ -120,6 +121,8 @@ export class ReviewServer {
120
121
  await this.refresh();
121
122
  this.changed(tab);
122
123
  return null;
124
+ case "prompt":
125
+ return (await this.prompt(params)) ?? null;
123
126
  }
124
127
  if (!OVERLAY.has(method))
125
128
  throw new Error(`Unknown request: ${method}.`);
@@ -145,6 +148,47 @@ export class ReviewServer {
145
148
  this.changed(tab);
146
149
  return answer.result;
147
150
  }
151
+ /**
152
+ * The agent prompt the page asked for: a thread's, or every open one's,
153
+ * from the pull request; the page's changes, or a fragment's reach, from
154
+ * `ascribe diff`. `undefined` when there's nothing to prompt about.
155
+ */
156
+ async prompt(params) {
157
+ if (!this.on)
158
+ throw new Error("Review is off.");
159
+ const request = params["request"];
160
+ const pagePath = typeof params["path"] === "string" ? params["path"] : undefined;
161
+ const connection = await this.connected();
162
+ switch (request?.kind) {
163
+ case "thread":
164
+ case "open-threads": {
165
+ if (connection.state !== "on")
166
+ throw new Error("Review comments aren't on.");
167
+ const github = await import("@ascribed/review/github");
168
+ const contentRoot = this.options.contentRoot;
169
+ return github.buildThreadsPrompt({
170
+ session: connection.session,
171
+ project: github.promptProject(this.options.projectDir),
172
+ changedPages: async () => {
173
+ const changes = await this.changes();
174
+ return "result" in changes ? changes.result.pages : [];
175
+ },
176
+ // The agent reads the files on disk, as the dev server does: no editor here.
177
+ readSource: (file) => readFile(path.join(contentRoot, ...file.split("/")), "utf8").then((text) => ({ text, unsaved: false }), () => undefined),
178
+ }, request);
179
+ }
180
+ case "page-changes":
181
+ case "fragment-reach": {
182
+ const target = request.kind === "page-changes" ? pagePath : request.fragment;
183
+ if (target === undefined)
184
+ throw new Error("That isn't a page of the project.");
185
+ const base = connection.state === "on" ? connection.base : undefined;
186
+ return this.options.prompt(target, base);
187
+ }
188
+ default:
189
+ throw new Error("Unknown prompt.");
190
+ }
191
+ }
148
192
  async start() {
149
193
  if (this.on)
150
194
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/review/server.ts"],"names":[],"mappings":"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","sourcesContent":["// Review in the site preview, on the dev server's side: everything the\n// toolbar app asks for, answered over Astro's dev toolbar channel. Every\n// `git`, `ascribe`, and `gh` call happens here; the page gets the changes and\n// the threads, never a token. There are no HTTP routes: any page open in the\n// same browser could call a route on localhost, while the toolbar's channel\n// takes only clients that have the dev server's token (`channelProblem`).\n//\n// Nothing runs until the reviewer turns the app on. Then the server opens\n// the pull request's review (when the GitHub CLI finds one), compares the\n// build with the pull request's base (or the default branch), and compares\n// again after each rebuild.\nimport path from \"node:path\";\nimport type { OverlayMethod, ReviewSession } from \"@ascribed/review/github\";\nimport type { PageRef } from \"@ascribed/review/place\";\nimport type { DiffResult } from \"./diff.js\";\nimport { messageOf, type Connection } from \"./github.js\";\nimport {\n CHANGED_EVENT,\n REQUEST_EVENT,\n RESULT_EVENT,\n type ChangedPage,\n type PageDiff,\n type PageView,\n type Request,\n type Result,\n type Status,\n type ThreadsState,\n} from \"./protocol.js\";\nimport { normalizeRoute, type RoutedPage } from \"./routes.js\";\n\n/** A page's end of the channel: the server answers it alone. */\nexport interface ChannelClient {\n send(event: string, payload?: unknown): void;\n}\n\n/**\n * The toolbar's channel, as `astro:server:setup`'s `toolbar` has it. Its\n * listeners get the sending page's client too (it is Vite's `server.hot`);\n * without one, an answer goes to every page, which tell theirs by `tab`.\n */\nexport interface ToolbarChannel {\n on(event: string, callback: (data: unknown, client?: ChannelClient) => void): void;\n send(event: string, payload: unknown): void;\n}\n\nexport interface ReviewServerOptions {\n channel: ToolbarChannel;\n logger: { info(message: string): void; warn(message: string): void };\n build: string;\n /** The content root, absolute. */\n contentRoot: string;\n /** Why someone else could use the channel, if they could: then GitHub stays off. */\n channelProblem: string | undefined;\n /** Compares the build with `base` (the default branch when `undefined`). */\n diff(base: string | undefined): Promise<DiffResult>;\n /** Opens the pull request's review. */\n connect(): Promise<Connection>;\n /** Writes the build's JSON output, in turn with the dev server's builds: it has the routes. */\n writeRoutes(): Promise<void>;\n /** Every page of the build, by normalized route. */\n readRoutes(): Promise<Map<string, RoutedPage>>;\n /** How long to wait after a rebuild before comparing again. Default 150 ms. */\n debounceMs?: number;\n}\n\n/** The overlay's requests, which go to the session. */\nconst OVERLAY: ReadonlySet<string> = new Set<OverlayMethod>([\n \"load\",\n \"commentTarget\",\n \"comment\",\n \"reply\",\n \"allThreads\",\n \"resolve\",\n \"submit\",\n \"discard\",\n]);\n\nexport class ReviewServer {\n private on = false;\n private problem: string | undefined;\n private connection: Connection | undefined;\n private connecting: Promise<Connection> | undefined;\n private diffed: { result: DiffResult } | { error: string } | undefined;\n private diffing: Promise<void> | undefined;\n private stale = false;\n private timer: ReturnType<typeof setTimeout> | undefined;\n private routes: Promise<Map<string, RoutedPage>> | undefined;\n /** The page each overlay last read, by content path: comments are made on it. */\n private readonly pages = new Map<string, PageRef>();\n /** How long the last comparison took, in milliseconds. */\n lastDiffMs: number | undefined;\n\n constructor(private readonly options: ReviewServerOptions) {\n this.problem = options.channelProblem;\n options.channel.on(REQUEST_EVENT, (data, client) => {\n void this.receive(data, client);\n });\n }\n\n /**\n * The address the dev server listens on, once it does: one other machines\n * can reach turns comments off, as `server.host` does.\n */\n listening(address: string): void {\n if (this.problem !== undefined || isLoopback(address)) return;\n this.problem = NETWORK_PROBLEM;\n this.connection = undefined;\n this.connecting = undefined;\n }\n\n /** Whether review is on: then the dev server's builds write JSON output too. */\n get active(): boolean {\n return this.on;\n }\n\n /** The dev server rebuilt: compare again soon, and read the routes again when asked. */\n rebuilt(): void {\n if (!this.on) return;\n this.stale = true;\n this.routes = undefined;\n if (this.timer) clearTimeout(this.timer);\n this.timer = setTimeout(() => {\n this.timer = undefined;\n void this.changes();\n }, this.options.debounceMs ?? 150);\n }\n\n /** The dev server stopped. */\n dispose(): void {\n if (this.timer) clearTimeout(this.timer);\n this.on = false;\n }\n\n private async receive(data: unknown, client: ChannelClient | undefined): Promise<void> {\n const request = data as Partial<Request> | null;\n if (typeof request?.id !== \"number\" || typeof request.method !== \"string\") return;\n const params =\n typeof request.params === \"object\" && request.params !== null ? request.params : {};\n const tab = typeof request.tab === \"string\" ? request.tab : \"\";\n const answer: Result = { tab, id: request.id };\n try {\n answer.result = await this.handle(request.method, params, tab);\n } catch (error) {\n const code = (error as { code?: unknown }).code;\n answer.error =\n typeof code === \"string\"\n ? { message: messageOf(error), code }\n : { message: messageOf(error) };\n }\n if (typeof client?.send === \"function\") client.send(RESULT_EVENT, answer);\n else this.options.channel.send(RESULT_EVENT, answer);\n }\n\n /** Answers one request. Rejects with a sentence to show. */\n async handle(method: string, params: Record<string, unknown>, tab = \"\"): Promise<unknown> {\n switch (method) {\n case \"status\":\n return { on: this.on } satisfies Status;\n case \"start\":\n await this.start();\n return { on: true } satisfies Status;\n case \"stop\":\n this.stop();\n this.changed(tab);\n return { on: false } satisfies Status;\n case \"page\":\n return this.pageView(params);\n case \"refresh\":\n await this.refresh();\n this.changed(tab);\n return null;\n }\n if (!OVERLAY.has(method)) throw new Error(`Unknown request: ${method}.`);\n const connection = this.on ? await this.connected() : undefined;\n if (connection?.state !== \"on\") throw new Error(\"Review comments aren't on.\");\n const github = await import(\"@ascribed/review/github\");\n const pagePath = typeof params[\"path\"] === \"string\" ? params[\"path\"] : undefined;\n const answer = await github.answerRequest(\n {\n session: connection.session,\n page: pagePath === undefined ? undefined : { build: this.options.build, path: pagePath },\n lastPage: pagePath === undefined ? undefined : this.pages.get(pagePath),\n changedPages: async () => {\n const [changes, routes] = await Promise.all([this.changes(), this.readRoutes()]);\n if (!(\"result\" in changes)) return [];\n return changes.result.pages.map((p) => listed(p, routes.get(normalizeRoute(p.route))));\n },\n },\n method as OverlayMethod,\n params,\n );\n if (answer.page) this.pages.set(answer.page.path, answer.page);\n if (answer.changed) this.changed(tab);\n return answer.result;\n }\n\n private async start(): Promise<void> {\n if (this.on) return;\n this.on = true;\n this.stale = true;\n this.routes = undefined;\n this.connection = undefined;\n const started = Date.now();\n // The routes come from the JSON output, which the next builds write too.\n const routes = this.options.writeRoutes().catch((error: unknown) => {\n this.options.logger.warn(`couldn't write the routes for review: ${messageOf(error)}`);\n });\n await this.connected();\n const changes = await this.changes();\n await routes;\n if (\"result\" in changes) {\n const base = changes.result.base;\n this.options.logger.info(\n `review: compared \"${this.options.build}\" with ${base.requested} in ${this.lastDiffMs ?? 0} ms (started in ${Date.now() - started} ms)`,\n );\n } else {\n this.options.logger.warn(`review: ${changes.error}`);\n }\n }\n\n private stop(): void {\n this.on = false;\n this.connection = undefined;\n this.connecting = undefined;\n this.diffed = undefined;\n this.routes = undefined;\n this.pages.clear();\n if (this.timer) clearTimeout(this.timer);\n this.timer = undefined;\n }\n\n private async refresh(): Promise<void> {\n if (!this.on) return;\n const connection = await this.connected();\n if (connection.state === \"on\") await connection.session.refresh();\n else this.connection = undefined;\n // A pull request found now changes the base.\n await this.connected();\n this.stale = true;\n await this.changes();\n }\n\n /** The pull request's review, opened once while review is on. */\n private connected(): Promise<Connection> {\n if (this.connection) return Promise.resolve(this.connection);\n if (this.problem !== undefined) {\n this.connection = {\n state: \"unprotected\",\n message: `Showing changes only. Comments are off because ${this.problem}.`,\n };\n return Promise.resolve(this.connection);\n }\n this.connecting ??= this.options\n .connect()\n .catch((error: unknown): Connection => ({ state: \"error\", message: messageOf(error) }))\n .then((connection) => {\n this.connecting = undefined;\n // The server turned out to listen on the network meanwhile.\n if (this.problem !== undefined) return this.connected();\n if (this.on) this.connection = connection;\n return connection;\n });\n return this.connecting;\n }\n\n /** The build's changes, compared again first if a rebuild made them stale. */\n private async changes(): Promise<{ result: DiffResult } | { error: string }> {\n while (this.diffing) await this.diffing;\n if (this.stale || this.diffed === undefined) {\n this.stale = false;\n this.diffing = this.compare().finally(() => {\n this.diffing = undefined;\n });\n await this.diffing;\n }\n return this.diffed ?? { error: \"Review is off.\" };\n }\n\n private async compare(): Promise<void> {\n const connection = await this.connected();\n const base = connection.state === \"on\" ? connection.base : undefined;\n const started = Date.now();\n try {\n this.diffed = { result: await this.options.diff(base) };\n } catch (error) {\n this.diffed = { error: messageOf(error) };\n }\n this.lastDiffMs = Date.now() - started;\n }\n\n private async pageView(params: Record<string, unknown>): Promise<PageView> {\n if (!this.on) throw new Error(\"Review is off.\");\n const route = normalizeRoute(typeof params[\"route\"] === \"string\" ? params[\"route\"] : \"/\");\n const fromPage = typeof params[\"path\"] === \"string\" ? params[\"path\"] : null;\n const [changes, connection, routes] = await Promise.all([\n this.changes(),\n this.connected(),\n this.readRoutes(),\n ]);\n const pages = \"result\" in changes ? changes.result.pages : [];\n const shown = pages.filter((p) => p.status !== \"removed\");\n let page = shown.find((p) => normalizeRoute(p.route) === route) ?? null;\n const pagePath = page?.path ?? fromPage ?? routes.get(route)?.path ?? null;\n if (page === null && pagePath !== null) page = shown.find((p) => p.path === pagePath) ?? null;\n return {\n kind: pagePath === null ? \"not-page\" : \"page\",\n path: pagePath,\n page,\n base: \"result\" in changes ? changes.result.base : null,\n problem: \"error\" in changes ? changes.error : null,\n errors: \"result\" in changes ? changes.result.errors : 0,\n threads: threadsState(connection),\n changedPages: pages.map((p) => listed(p, routes.get(normalizeRoute(p.route)))),\n contentRoot: this.options.contentRoot,\n separator: path.sep,\n };\n }\n\n /** The build's pages by route, read once until the next rebuild. */\n private readRoutes(): Promise<Map<string, RoutedPage>> {\n this.routes ??= this.options.readRoutes().catch(() => new Map<string, RoutedPage>());\n return this.routes;\n }\n\n /** Tells every page the threads or the changes changed; `from` is the page that did it. */\n private changed(from: string): void {\n this.options.channel.send(CHANGED_EVENT, { from });\n }\n}\n\n/** A changed page as the list shows it: without its changes, with its title. */\nfunction listed(page: PageDiff, routed: RoutedPage | undefined): ChangedPage {\n return {\n title: routed?.title ?? null,\n formatted_title: routed?.formatted_title ?? null,\n path: page.path,\n route: page.route,\n status: page.status,\n own_file_changed: page.own_file_changed,\n because: page.because,\n page_changed: page.page_changed,\n counts: page.counts,\n };\n}\n\n/** What the page shows about the threads. */\nfunction threadsState(connection: Connection): ThreadsState {\n if (connection.state !== \"on\") return connection;\n const pr: ReviewSession[\"pullRequest\"] = connection.session.pullRequest;\n return {\n state: \"on\",\n pullRequest: { number: pr.number, url: pr.url, baseRefName: pr.baseRefName },\n local: connection.local,\n };\n}\n\n/**\n * Why someone other than the developer could use the dev server's toolbar\n * channel, or `undefined` when they can't. Vite takes a browser's connection\n * only with the token in its client script, which the page's own origin can\n * read and, by default, only localhost origins can fetch. These settings undo\n * that, and a server listening on the network gives its pages, token and all,\n * to anyone who can reach it.\n */\nexport function channelProblem(config: {\n server: { cors?: unknown; allowedHosts?: unknown; host?: unknown };\n legacy?: { skipWebSocketTokenCheck?: unknown } | undefined;\n}): string | undefined {\n const pages = \"other web pages could talk to this dev server\";\n if (config.legacy?.skipWebSocketTokenCheck === true) {\n return `${pages}: \\`legacy.skipWebSocketTokenCheck\\` is on`;\n }\n if (config.server.allowedHosts === true) return `${pages}: \\`server.allowedHosts\\` is \\`true\\``;\n const cors = config.server.cors;\n const origin =\n typeof cors === \"object\" && cors !== null ? (cors as { origin?: unknown }).origin : undefined;\n if (cors === true || origin === true || origin === \"*\") {\n return `${pages}: \\`server.cors\\` lets any origin read its pages`;\n }\n if (!isLoopback(config.server.host)) return NETWORK_PROBLEM;\n return undefined;\n}\n\nconst NETWORK_PROBLEM =\n \"the dev server is listening on the network (`--host`), so anyone who can reach it could comment as you\";\n\n/** Whether Vite's `server.host`, or the address the server listens on, keeps the server on this machine: unset, `false`, or a loopback name. */\nfunction isLoopback(host: unknown): boolean {\n if (host === undefined || host === false) return true;\n if (typeof host !== \"string\") return false;\n const name = host.toLowerCase().replace(/^\\[(.*)\\]$/, \"$1\");\n return name === \"localhost\" || name === \"::1\" || /^127(\\.\\d{1,3}){3}$/.test(name);\n}\n"]}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/review/server.ts"],"names":[],"mappings":"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","sourcesContent":["// Review in the site preview, on the dev server's side: everything the\n// toolbar app asks for, answered over Astro's dev toolbar channel. Every\n// `git`, `ascribe`, and `gh` call happens here; the page gets the changes and\n// the threads, never a token. There are no HTTP routes: any page open in the\n// same browser could call a route on localhost, while the toolbar's channel\n// takes only clients that have the dev server's token (`channelProblem`).\n//\n// Nothing runs until the reviewer turns the app on. Then the server opens\n// the pull request's review (when the GitHub CLI finds one), compares the\n// build with the pull request's base (or the default branch), and compares\n// again after each rebuild.\nimport path from \"node:path\";\nimport { readFile } from \"node:fs/promises\";\nimport type { OverlayMethod, PromptRequest, ReviewSession } from \"@ascribed/review/github\";\nimport type { PageRef } from \"@ascribed/review/place\";\nimport type { DiffResult } from \"./diff.js\";\nimport { messageOf, type Connection } from \"./github.js\";\nimport {\n CHANGED_EVENT,\n REQUEST_EVENT,\n RESULT_EVENT,\n type ChangedPage,\n type PageDiff,\n type PageView,\n type Request,\n type Result,\n type Status,\n type ThreadsState,\n} from \"./protocol.js\";\nimport { normalizeRoute, type RoutedPage } from \"./routes.js\";\n\n/** A page's end of the channel: the server answers it alone. */\nexport interface ChannelClient {\n send(event: string, payload?: unknown): void;\n}\n\n/**\n * The toolbar's channel, as `astro:server:setup`'s `toolbar` has it. Its\n * listeners get the sending page's client too (it is Vite's `server.hot`);\n * without one, an answer goes to every page, which tell theirs by `tab`.\n */\nexport interface ToolbarChannel {\n on(event: string, callback: (data: unknown, client?: ChannelClient) => void): void;\n send(event: string, payload: unknown): void;\n}\n\nexport interface ReviewServerOptions {\n channel: ToolbarChannel;\n logger: { info(message: string): void; warn(message: string): void };\n build: string;\n /** The content root, absolute. */\n contentRoot: string;\n /** Why someone else could use the channel, if they could: then GitHub stays off. */\n channelProblem: string | undefined;\n /** Compares the build with `base` (the default branch when `undefined`). */\n diff(base: string | undefined): Promise<DiffResult>;\n /**\n * `ascribe diff`'s agent prompt about the page or fragment at a content\n * path, against `base`; `undefined` when it didn't change.\n */\n prompt(path: string, base: string | undefined): Promise<string | undefined>;\n /** The project's folder: where its `ascribe.toml` is. */\n projectDir: string;\n /** Opens the pull request's review. */\n connect(): Promise<Connection>;\n /** Writes the build's JSON output, in turn with the dev server's builds: it has the routes. */\n writeRoutes(): Promise<void>;\n /** Every page of the build, by normalized route. */\n readRoutes(): Promise<Map<string, RoutedPage>>;\n /** How long to wait after a rebuild before comparing again. Default 150 ms. */\n debounceMs?: number;\n}\n\n/** The overlay's requests, which go to the session. */\nconst OVERLAY: ReadonlySet<string> = new Set<OverlayMethod>([\n \"load\",\n \"commentTarget\",\n \"comment\",\n \"reply\",\n \"allThreads\",\n \"resolve\",\n \"submit\",\n \"discard\",\n]);\n\nexport class ReviewServer {\n private on = false;\n private problem: string | undefined;\n private connection: Connection | undefined;\n private connecting: Promise<Connection> | undefined;\n private diffed: { result: DiffResult } | { error: string } | undefined;\n private diffing: Promise<void> | undefined;\n private stale = false;\n private timer: ReturnType<typeof setTimeout> | undefined;\n private routes: Promise<Map<string, RoutedPage>> | undefined;\n /** The page each overlay last read, by content path: comments are made on it. */\n private readonly pages = new Map<string, PageRef>();\n /** How long the last comparison took, in milliseconds. */\n lastDiffMs: number | undefined;\n\n constructor(private readonly options: ReviewServerOptions) {\n this.problem = options.channelProblem;\n options.channel.on(REQUEST_EVENT, (data, client) => {\n void this.receive(data, client);\n });\n }\n\n /**\n * The address the dev server listens on, once it does: one other machines\n * can reach turns comments off, as `server.host` does.\n */\n listening(address: string): void {\n if (this.problem !== undefined || isLoopback(address)) return;\n this.problem = NETWORK_PROBLEM;\n this.connection = undefined;\n this.connecting = undefined;\n }\n\n /** Whether review is on: then the dev server's builds write JSON output too. */\n get active(): boolean {\n return this.on;\n }\n\n /** The dev server rebuilt: compare again soon, and read the routes again when asked. */\n rebuilt(): void {\n if (!this.on) return;\n this.stale = true;\n this.routes = undefined;\n if (this.timer) clearTimeout(this.timer);\n this.timer = setTimeout(() => {\n this.timer = undefined;\n void this.changes();\n }, this.options.debounceMs ?? 150);\n }\n\n /** The dev server stopped. */\n dispose(): void {\n if (this.timer) clearTimeout(this.timer);\n this.on = false;\n }\n\n private async receive(data: unknown, client: ChannelClient | undefined): Promise<void> {\n const request = data as Partial<Request> | null;\n if (typeof request?.id !== \"number\" || typeof request.method !== \"string\") return;\n const params =\n typeof request.params === \"object\" && request.params !== null ? request.params : {};\n const tab = typeof request.tab === \"string\" ? request.tab : \"\";\n const answer: Result = { tab, id: request.id };\n try {\n answer.result = await this.handle(request.method, params, tab);\n } catch (error) {\n const code = (error as { code?: unknown }).code;\n answer.error =\n typeof code === \"string\"\n ? { message: messageOf(error), code }\n : { message: messageOf(error) };\n }\n if (typeof client?.send === \"function\") client.send(RESULT_EVENT, answer);\n else this.options.channel.send(RESULT_EVENT, answer);\n }\n\n /** Answers one request. Rejects with a sentence to show. */\n async handle(method: string, params: Record<string, unknown>, tab = \"\"): Promise<unknown> {\n switch (method) {\n case \"status\":\n return { on: this.on } satisfies Status;\n case \"start\":\n await this.start();\n return { on: true } satisfies Status;\n case \"stop\":\n this.stop();\n this.changed(tab);\n return { on: false } satisfies Status;\n case \"page\":\n return this.pageView(params);\n case \"refresh\":\n await this.refresh();\n this.changed(tab);\n return null;\n case \"prompt\":\n return (await this.prompt(params)) ?? null;\n }\n if (!OVERLAY.has(method)) throw new Error(`Unknown request: ${method}.`);\n const connection = this.on ? await this.connected() : undefined;\n if (connection?.state !== \"on\") throw new Error(\"Review comments aren't on.\");\n const github = await import(\"@ascribed/review/github\");\n const pagePath = typeof params[\"path\"] === \"string\" ? params[\"path\"] : undefined;\n const answer = await github.answerRequest(\n {\n session: connection.session,\n page: pagePath === undefined ? undefined : { build: this.options.build, path: pagePath },\n lastPage: pagePath === undefined ? undefined : this.pages.get(pagePath),\n changedPages: async () => {\n const [changes, routes] = await Promise.all([this.changes(), this.readRoutes()]);\n if (!(\"result\" in changes)) return [];\n return changes.result.pages.map((p) => listed(p, routes.get(normalizeRoute(p.route))));\n },\n },\n method as OverlayMethod,\n params,\n );\n if (answer.page) this.pages.set(answer.page.path, answer.page);\n if (answer.changed) this.changed(tab);\n return answer.result;\n }\n\n /**\n * The agent prompt the page asked for: a thread's, or every open one's,\n * from the pull request; the page's changes, or a fragment's reach, from\n * `ascribe diff`. `undefined` when there's nothing to prompt about.\n */\n private async prompt(params: Record<string, unknown>): Promise<string | undefined> {\n if (!this.on) throw new Error(\"Review is off.\");\n const request = params[\"request\"] as PromptRequest | undefined;\n const pagePath = typeof params[\"path\"] === \"string\" ? params[\"path\"] : undefined;\n const connection = await this.connected();\n switch (request?.kind) {\n case \"thread\":\n case \"open-threads\": {\n if (connection.state !== \"on\") throw new Error(\"Review comments aren't on.\");\n const github = await import(\"@ascribed/review/github\");\n const contentRoot = this.options.contentRoot;\n return github.buildThreadsPrompt(\n {\n session: connection.session,\n project: github.promptProject(this.options.projectDir),\n changedPages: async () => {\n const changes = await this.changes();\n return \"result\" in changes ? changes.result.pages : [];\n },\n // The agent reads the files on disk, as the dev server does: no editor here.\n readSource: (file) =>\n readFile(path.join(contentRoot, ...file.split(\"/\")), \"utf8\").then(\n (text) => ({ text, unsaved: false }),\n () => undefined,\n ),\n },\n request,\n );\n }\n case \"page-changes\":\n case \"fragment-reach\": {\n const target = request.kind === \"page-changes\" ? pagePath : request.fragment;\n if (target === undefined) throw new Error(\"That isn't a page of the project.\");\n const base = connection.state === \"on\" ? connection.base : undefined;\n return this.options.prompt(target, base);\n }\n default:\n throw new Error(\"Unknown prompt.\");\n }\n }\n\n private async start(): Promise<void> {\n if (this.on) return;\n this.on = true;\n this.stale = true;\n this.routes = undefined;\n this.connection = undefined;\n const started = Date.now();\n // The routes come from the JSON output, which the next builds write too.\n const routes = this.options.writeRoutes().catch((error: unknown) => {\n this.options.logger.warn(`couldn't write the routes for review: ${messageOf(error)}`);\n });\n await this.connected();\n const changes = await this.changes();\n await routes;\n if (\"result\" in changes) {\n const base = changes.result.base;\n this.options.logger.info(\n `review: compared \"${this.options.build}\" with ${base.requested} in ${this.lastDiffMs ?? 0} ms (started in ${Date.now() - started} ms)`,\n );\n } else {\n this.options.logger.warn(`review: ${changes.error}`);\n }\n }\n\n private stop(): void {\n this.on = false;\n this.connection = undefined;\n this.connecting = undefined;\n this.diffed = undefined;\n this.routes = undefined;\n this.pages.clear();\n if (this.timer) clearTimeout(this.timer);\n this.timer = undefined;\n }\n\n private async refresh(): Promise<void> {\n if (!this.on) return;\n const connection = await this.connected();\n if (connection.state === \"on\") await connection.session.refresh();\n else this.connection = undefined;\n // A pull request found now changes the base.\n await this.connected();\n this.stale = true;\n await this.changes();\n }\n\n /** The pull request's review, opened once while review is on. */\n private connected(): Promise<Connection> {\n if (this.connection) return Promise.resolve(this.connection);\n if (this.problem !== undefined) {\n this.connection = {\n state: \"unprotected\",\n message: `Showing changes only. Comments are off because ${this.problem}.`,\n };\n return Promise.resolve(this.connection);\n }\n this.connecting ??= this.options\n .connect()\n .catch((error: unknown): Connection => ({ state: \"error\", message: messageOf(error) }))\n .then((connection) => {\n this.connecting = undefined;\n // The server turned out to listen on the network meanwhile.\n if (this.problem !== undefined) return this.connected();\n if (this.on) this.connection = connection;\n return connection;\n });\n return this.connecting;\n }\n\n /** The build's changes, compared again first if a rebuild made them stale. */\n private async changes(): Promise<{ result: DiffResult } | { error: string }> {\n while (this.diffing) await this.diffing;\n if (this.stale || this.diffed === undefined) {\n this.stale = false;\n this.diffing = this.compare().finally(() => {\n this.diffing = undefined;\n });\n await this.diffing;\n }\n return this.diffed ?? { error: \"Review is off.\" };\n }\n\n private async compare(): Promise<void> {\n const connection = await this.connected();\n const base = connection.state === \"on\" ? connection.base : undefined;\n const started = Date.now();\n try {\n this.diffed = { result: await this.options.diff(base) };\n } catch (error) {\n this.diffed = { error: messageOf(error) };\n }\n this.lastDiffMs = Date.now() - started;\n }\n\n private async pageView(params: Record<string, unknown>): Promise<PageView> {\n if (!this.on) throw new Error(\"Review is off.\");\n const route = normalizeRoute(typeof params[\"route\"] === \"string\" ? params[\"route\"] : \"/\");\n const fromPage = typeof params[\"path\"] === \"string\" ? params[\"path\"] : null;\n const [changes, connection, routes] = await Promise.all([\n this.changes(),\n this.connected(),\n this.readRoutes(),\n ]);\n const pages = \"result\" in changes ? changes.result.pages : [];\n const shown = pages.filter((p) => p.status !== \"removed\");\n let page = shown.find((p) => normalizeRoute(p.route) === route) ?? null;\n const pagePath = page?.path ?? fromPage ?? routes.get(route)?.path ?? null;\n if (page === null && pagePath !== null) page = shown.find((p) => p.path === pagePath) ?? null;\n return {\n kind: pagePath === null ? \"not-page\" : \"page\",\n path: pagePath,\n page,\n base: \"result\" in changes ? changes.result.base : null,\n problem: \"error\" in changes ? changes.error : null,\n errors: \"result\" in changes ? changes.result.errors : 0,\n threads: threadsState(connection),\n changedPages: pages.map((p) => listed(p, routes.get(normalizeRoute(p.route)))),\n contentRoot: this.options.contentRoot,\n separator: path.sep,\n };\n }\n\n /** The build's pages by route, read once until the next rebuild. */\n private readRoutes(): Promise<Map<string, RoutedPage>> {\n this.routes ??= this.options.readRoutes().catch(() => new Map<string, RoutedPage>());\n return this.routes;\n }\n\n /** Tells every page the threads or the changes changed; `from` is the page that did it. */\n private changed(from: string): void {\n this.options.channel.send(CHANGED_EVENT, { from });\n }\n}\n\n/** A changed page as the list shows it: without its changes, with its title. */\nfunction listed(page: PageDiff, routed: RoutedPage | undefined): ChangedPage {\n return {\n title: routed?.title ?? null,\n formatted_title: routed?.formatted_title ?? null,\n path: page.path,\n route: page.route,\n status: page.status,\n own_file_changed: page.own_file_changed,\n because: page.because,\n page_changed: page.page_changed,\n counts: page.counts,\n };\n}\n\n/** What the page shows about the threads. */\nfunction threadsState(connection: Connection): ThreadsState {\n if (connection.state !== \"on\") return connection;\n const pr: ReviewSession[\"pullRequest\"] = connection.session.pullRequest;\n return {\n state: \"on\",\n pullRequest: { number: pr.number, url: pr.url, baseRefName: pr.baseRefName },\n local: connection.local,\n };\n}\n\n/**\n * Why someone other than the developer could use the dev server's toolbar\n * channel, or `undefined` when they can't. Vite takes a browser's connection\n * only with the token in its client script, which the page's own origin can\n * read and, by default, only localhost origins can fetch. These settings undo\n * that, and a server listening on the network gives its pages, token and all,\n * to anyone who can reach it.\n */\nexport function channelProblem(config: {\n server: { cors?: unknown; allowedHosts?: unknown; host?: unknown };\n legacy?: { skipWebSocketTokenCheck?: unknown } | undefined;\n}): string | undefined {\n const pages = \"other web pages could talk to this dev server\";\n if (config.legacy?.skipWebSocketTokenCheck === true) {\n return `${pages}: \\`legacy.skipWebSocketTokenCheck\\` is on`;\n }\n if (config.server.allowedHosts === true) return `${pages}: \\`server.allowedHosts\\` is \\`true\\``;\n const cors = config.server.cors;\n const origin =\n typeof cors === \"object\" && cors !== null ? (cors as { origin?: unknown }).origin : undefined;\n if (cors === true || origin === true || origin === \"*\") {\n return `${pages}: \\`server.cors\\` lets any origin read its pages`;\n }\n if (!isLoopback(config.server.host)) return NETWORK_PROBLEM;\n return undefined;\n}\n\nconst NETWORK_PROBLEM =\n \"the dev server is listening on the network (`--host`), so anyone who can reach it could comment as you\";\n\n/** Whether Vite's `server.host`, or the address the server listens on, keeps the server on this machine: unset, `false`, or a loopback name. */\nfunction isLoopback(host: unknown): boolean {\n if (host === undefined || host === false) return true;\n if (typeof host !== \"string\") return false;\n const name = host.toLowerCase().replace(/^\\[(.*)\\]$/, \"$1\");\n return name === \"localhost\" || name === \"::1\" || /^127(\\.\\d{1,3}){3}$/.test(name);\n}\n"]}
package/dist/run.d.ts CHANGED
@@ -1,13 +1,14 @@
1
1
  /** What a successful build printed. */
2
2
  export interface BuildResult {
3
- /** The checks' report, which is more than a summary line when there are warnings. */
3
+ /** The checks' report, which is more than a summary line when there is anything to report. */
4
4
  diagnostics: string;
5
5
  /** The `built <build>/site: …` lines. */
6
6
  summary: string;
7
7
  }
8
8
  /**
9
- * Whether a build's report is worth a warning: anything but a clean check's
10
- * summary line, which is logged at info level.
9
+ * Whether a build's report is worth a warning: anything but a report whose
10
+ * summary line counts no errors and no warnings, which is logged at info
11
+ * level, advice and all.
11
12
  */
12
13
  export declare function hasWarnings(diagnostics: string): boolean;
13
14
  /**
@@ -31,5 +32,5 @@ export declare function runBuild(options: {
31
32
  /** Write source anchors (`--anchors`). */
32
33
  anchors?: boolean;
33
34
  /** The outputs to write. Default: `["site"]`. */
34
- outputs?: readonly ("site" | "json")[];
35
+ outputs?: readonly ("site" | "plain" | "json")[];
35
36
  }): Promise<BuildResult>;
package/dist/run.js CHANGED
@@ -1,14 +1,21 @@
1
1
  // Running `ascribe build` for the configured build: its site output, and
2
2
  // its JSON output while review is on.
3
3
  import { execFile } from "node:child_process";
4
- /** A check summary with nothing to report: `checked 14 files: 0 errors, 0 warnings`. */
5
- const CLEAN = /^checked [^:\n]*: 0 errors, 0 warnings$/;
6
4
  /**
7
- * Whether a build's report is worth a warning: anything but a clean check's
8
- * summary line, which is logged at info level.
5
+ * A check's summary line, the report's last: `checked 14 files: 0 errors, 0 warnings`,
6
+ * with `, 3 advice` after when there is advice.
7
+ */
8
+ const SUMMARY = /^checked [^:\n]*: (\d+) errors?, (\d+) warnings?(?:, \d+ advice)?$/;
9
+ /**
10
+ * Whether a build's report is worth a warning: anything but a report whose
11
+ * summary line counts no errors and no warnings, which is logged at info
12
+ * level, advice and all.
9
13
  */
10
14
  export function hasWarnings(diagnostics) {
11
- return diagnostics !== "" && !CLEAN.test(diagnostics);
15
+ if (diagnostics === "")
16
+ return false;
17
+ const summary = SUMMARY.exec(diagnostics.trimEnd().split("\n").at(-1) ?? "");
18
+ return summary === null || summary[1] !== "0" || summary[2] !== "0";
12
19
  }
13
20
  /**
14
21
  * Whether an Astro command builds with source anchors, given the integration's
package/dist/run.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,sCAAsC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAU9C,wFAAwF;AACxF,MAAM,KAAK,GAAG,yCAAyC,CAAC;AAExD;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,WAAmB;IAC7C,OAAO,WAAW,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;AACxD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CACxB,OAAoC,EACpC,OAAe,EACf,MAAM,GAAG,KAAK;IAEd,OAAO,OAAO,KAAK,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK,IAAI,CAAC,OAAO,KAAK,KAAK,IAAI,MAAM,CAAC,CAAC,CAAC;AAClF,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,SAAS,CAAC,MAA2B,EAAE,OAAe;IACpE,OAAO,OAAO,KAAK,KAAK,IAAI,MAAM,KAAK,KAAK,CAAC;AAC/C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,OASxB;IACC,MAAM,IAAI,GAAG;QACX,OAAO;QACP,QAAQ;QACR,CAAC,OAAO,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;QACvC,SAAS;QACT,OAAO,CAAC,KAAK;QACb,UAAU;QACV,OAAO,CAAC,UAAU;QAClB,SAAS;QACT,OAAO;KACR,CAAC;IACF,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI;QAAE,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACrD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,QAAQ,CACN,OAAO,CAAC,MAAM,EACd,IAAI,EACJ,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,EACjD,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACxB,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBACnB,MAAM,MAAM,GAAG,GAAG,MAAM,GAAG,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC;gBAC3C,MAAM,CACJ,IAAI,KAAK,CACP,2BAA2B,OAAO,CAAC,KAAK,YAAY,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,MAAM,MAAM,EAAE,EAAE,EAC3G,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CACF,CAAC;gBACF,OAAO;YACT,CAAC;YACD,OAAO,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAClE,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["// Running `ascribe build` for the configured build: its site output, and\n// its JSON output while review is on.\nimport { execFile } from \"node:child_process\";\n\n/** What a successful build printed. */\nexport interface BuildResult {\n /** The checks' report, which is more than a summary line when there are warnings. */\n diagnostics: string;\n /** The `built <build>/site: …` lines. */\n summary: string;\n}\n\n/** A check summary with nothing to report: `checked 14 files: 0 errors, 0 warnings`. */\nconst CLEAN = /^checked [^:\\n]*: 0 errors, 0 warnings$/;\n\n/**\n * Whether a build's report is worth a warning: anything but a clean check's\n * summary line, which is logged at info level.\n */\nexport function hasWarnings(diagnostics: string): boolean {\n return diagnostics !== \"\" && !CLEAN.test(diagnostics);\n}\n\n/**\n * Whether an Astro command builds with source anchors, given the integration's\n * `anchors` option: `\"dev\"` means in `astro dev` only, so `astro build` has\n * them only when it is `true`. Review in the site preview (`review`) needs\n * them in `astro dev`.\n */\nexport function anchorsFor(\n anchors: boolean | \"dev\" | undefined,\n command: string,\n review = false,\n): boolean {\n return anchors === true || (command === \"dev\" && (anchors === \"dev\" || review));\n}\n\n/** Whether an Astro command has review in the site preview, given the `review` option. */\nexport function reviewFor(review: boolean | undefined, command: string): boolean {\n return command === \"dev\" && review !== false;\n}\n\n/**\n * Builds one build's outputs, the site output by default. Rejects, with the compiler's report as the\n * message, if the build has errors: the Astro build fails with them.\n */\nexport function runBuild(options: {\n binary: string;\n configPath: string;\n build: string;\n cwd: string;\n /** Write source anchors (`--anchors`). */\n anchors?: boolean;\n /** The outputs to write. Default: `[\"site\"]`. */\n outputs?: readonly (\"site\" | \"json\")[];\n}): Promise<BuildResult> {\n const args = [\n \"build\",\n \"--emit\",\n (options.outputs ?? [\"site\"]).join(\",\"),\n \"--build\",\n options.build,\n \"--config\",\n options.configPath,\n \"--color\",\n \"never\",\n ];\n if (options.anchors === true) args.push(\"--anchors\");\n return new Promise((resolve, reject) => {\n execFile(\n options.binary,\n args,\n { cwd: options.cwd, maxBuffer: 64 * 1024 * 1024 },\n (error, stdout, stderr) => {\n if (error !== null) {\n const report = `${stdout}${stderr}`.trim();\n reject(\n new Error(\n `\\`ascribe build --build ${options.build}\\` failed${report === \"\" ? `: ${error.message}` : `:\\n${report}`}`,\n { cause: error },\n ),\n );\n return;\n }\n resolve({ diagnostics: stdout.trim(), summary: stderr.trim() });\n },\n );\n });\n}\n"]}
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,sCAAsC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAU9C;;;GAGG;AACH,MAAM,OAAO,GAAG,oEAAoE,CAAC;AAErF;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,WAAmB;IAC7C,IAAI,WAAW,KAAK,EAAE;QAAE,OAAO,KAAK,CAAC;IACrC,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC7E,OAAO,OAAO,KAAK,IAAI,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC;AACtE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CACxB,OAAoC,EACpC,OAAe,EACf,MAAM,GAAG,KAAK;IAEd,OAAO,OAAO,KAAK,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK,IAAI,CAAC,OAAO,KAAK,KAAK,IAAI,MAAM,CAAC,CAAC,CAAC;AAClF,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,SAAS,CAAC,MAA2B,EAAE,OAAe;IACpE,OAAO,OAAO,KAAK,KAAK,IAAI,MAAM,KAAK,KAAK,CAAC;AAC/C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,OASxB;IACC,MAAM,IAAI,GAAG;QACX,OAAO;QACP,QAAQ;QACR,CAAC,OAAO,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;QACvC,SAAS;QACT,OAAO,CAAC,KAAK;QACb,UAAU;QACV,OAAO,CAAC,UAAU;QAClB,SAAS;QACT,OAAO;KACR,CAAC;IACF,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI;QAAE,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACrD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,QAAQ,CACN,OAAO,CAAC,MAAM,EACd,IAAI,EACJ,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,EACjD,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACxB,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBACnB,MAAM,MAAM,GAAG,GAAG,MAAM,GAAG,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC;gBAC3C,MAAM,CACJ,IAAI,KAAK,CACP,2BAA2B,OAAO,CAAC,KAAK,YAAY,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,MAAM,MAAM,EAAE,EAAE,EAC3G,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CACF,CAAC;gBACF,OAAO;YACT,CAAC;YACD,OAAO,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAClE,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["// Running `ascribe build` for the configured build: its site output, and\n// its JSON output while review is on.\nimport { execFile } from \"node:child_process\";\n\n/** What a successful build printed. */\nexport interface BuildResult {\n /** The checks' report, which is more than a summary line when there is anything to report. */\n diagnostics: string;\n /** The `built <build>/site: …` lines. */\n summary: string;\n}\n\n/**\n * A check's summary line, the report's last: `checked 14 files: 0 errors, 0 warnings`,\n * with `, 3 advice` after when there is advice.\n */\nconst SUMMARY = /^checked [^:\\n]*: (\\d+) errors?, (\\d+) warnings?(?:, \\d+ advice)?$/;\n\n/**\n * Whether a build's report is worth a warning: anything but a report whose\n * summary line counts no errors and no warnings, which is logged at info\n * level, advice and all.\n */\nexport function hasWarnings(diagnostics: string): boolean {\n if (diagnostics === \"\") return false;\n const summary = SUMMARY.exec(diagnostics.trimEnd().split(\"\\n\").at(-1) ?? \"\");\n return summary === null || summary[1] !== \"0\" || summary[2] !== \"0\";\n}\n\n/**\n * Whether an Astro command builds with source anchors, given the integration's\n * `anchors` option: `\"dev\"` means in `astro dev` only, so `astro build` has\n * them only when it is `true`. Review in the site preview (`review`) needs\n * them in `astro dev`.\n */\nexport function anchorsFor(\n anchors: boolean | \"dev\" | undefined,\n command: string,\n review = false,\n): boolean {\n return anchors === true || (command === \"dev\" && (anchors === \"dev\" || review));\n}\n\n/** Whether an Astro command has review in the site preview, given the `review` option. */\nexport function reviewFor(review: boolean | undefined, command: string): boolean {\n return command === \"dev\" && review !== false;\n}\n\n/**\n * Builds one build's outputs, the site output by default. Rejects, with the compiler's report as the\n * message, if the build has errors: the Astro build fails with them.\n */\nexport function runBuild(options: {\n binary: string;\n configPath: string;\n build: string;\n cwd: string;\n /** Write source anchors (`--anchors`). */\n anchors?: boolean;\n /** The outputs to write. Default: `[\"site\"]`. */\n outputs?: readonly (\"site\" | \"plain\" | \"json\")[];\n}): Promise<BuildResult> {\n const args = [\n \"build\",\n \"--emit\",\n (options.outputs ?? [\"site\"]).join(\",\"),\n \"--build\",\n options.build,\n \"--config\",\n options.configPath,\n \"--color\",\n \"never\",\n ];\n if (options.anchors === true) args.push(\"--anchors\");\n return new Promise((resolve, reject) => {\n execFile(\n options.binary,\n args,\n { cwd: options.cwd, maxBuffer: 64 * 1024 * 1024 },\n (error, stdout, stderr) => {\n if (error !== null) {\n const report = `${stdout}${stderr}`.trim();\n reject(\n new Error(\n `\\`ascribe build --build ${options.build}\\` failed${report === \"\" ? `: ${error.message}` : `:\\n${report}`}`,\n { cause: error },\n ),\n );\n return;\n }\n resolve({ diagnostics: stdout.trim(), summary: stderr.trim() });\n },\n );\n });\n}\n"]}
package/dist/shapes.d.ts CHANGED
@@ -93,6 +93,12 @@ export interface DiffReport {
93
93
  working_tree_errors: number;
94
94
  /** What changed, per build, in the order asked for. */
95
95
  builds: BuildDiff[];
96
+ /**
97
+ * Whether every page's `changes` was left empty, as `ascribe diff
98
+ * --pages-only` leaves them: its `counts` still count them. Only there
99
+ * when it's `true`.
100
+ */
101
+ blocks_omitted?: boolean;
96
102
  }
97
103
  /** What changed on one page of a build. */
98
104
  export interface PageDiff {
@@ -125,7 +131,8 @@ export interface PageDiff {
125
131
  counts: Counts;
126
132
  /**
127
133
  * The block-level changes, in the page's order, a removed block where
128
- * it was. Empty for an added or removed page.
134
+ * it was. Empty for an added or removed page, and for every page when
135
+ * the report's `blocks_omitted` is `true`.
129
136
  */
130
137
  changes: Change[];
131
138
  }
@@ -1 +1 @@
1
- {"version":3,"file":"shapes.js","sourceRoot":"","sources":["../src/shapes.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,2EAA2E;AAC3E,6EAA6E;AAC7E,yEAAyE;AACzE,EAAE;AACF,8CAA8C","sourcesContent":["// The JSON Ascribe writes that this package reads, as TypeScript types.\n// Generated from the Rust types that write it, through the JSON Schemas in\n// schemas/, by crates/ascribe-cli/src/shapes.rs. Change the Rust types, then\n// run `ASCRIBE_BLESS=1 cargo test -p ascribe-cli shapes`. Don't edit it.\n//\n// - DiffReport (`ascribe diff --format json`)\n\n/**\n * Where a block's text is written: the README's anchor grammar, the same\n * string the rendered page carries in `data-ascribe-source` and\n * `data-ascribe-via`.\n */\nexport interface Anchor {\n /**\n * `<path>:<first>-<last>`: the file's content path, percent-encoded by\n * segment, and the block's first and last lines, from 1.\n */\n source: string;\n /**\n * The includes the block came through, outermost first, each\n * `<path>:<line>`. Empty for a block written in the page itself.\n */\n via: string[];\n}\n\n/** The base of a comparison, in the report. */\nexport interface BaseInfo {\n /** The revision asked for, or the default branch used. */\n requested: string;\n /** The commit it names. */\n commit: string;\n /**\n * The merge base of that commit and `HEAD`, which the comparison reads;\n * `null` with `--base-exact`, which reads `commit`.\n */\n merge_base: string | null;\n}\n\n/** What changed in one build. */\nexport interface BuildDiff {\n /** The build's name. */\n build: string;\n /** The pages that changed, in path order. */\n pages: PageDiff[];\n}\n\n/** One block's change. */\nexport interface Change {\n /** What happened to it. */\n kind: ChangeKind;\n /**\n * For a removed block, and a moved block's old place: the block of the\n * new version it came after, among its siblings. Absent when it was\n * first.\n */\n after?: Anchor;\n /** Where it's written now: every kind but `removed`. */\n now?: Anchor;\n /**\n * For a removed block, and a moved block's old place: the block of the\n * new version it was inside. Absent at the top of the page.\n */\n parent?: Anchor;\n /**\n * For a removed block, its text, whitespace collapsed, so it can be\n * shown where it was.\n */\n text?: string;\n /** Where it was written: every kind but `added`. */\n was?: Anchor;\n /** For changed prose, the words that differ. */\n words?: Words;\n}\n\n/** The kinds of block change. */\nexport type ChangeKind = \"changed\" | \"added\" | \"removed\" | \"moved\";\n\n/** How many changes of each kind a page has. */\nexport interface Counts {\n /** Blocks whose content changed. */\n changed: number;\n /** Blocks only in the new version. */\n added: number;\n /** Blocks only in the old version. */\n removed: number;\n /** Blocks in both, somewhere else. */\n moved: number;\n}\n\n/** The whole report, as `ascribe diff --format json` writes it. */\nexport interface DiffReport {\n /** `SCHEMA_VERSION`. */\n schema_version: number;\n /** The version of Ascribe that wrote it. */\n ascribe_version: string;\n /** What was compared with. */\n base: BaseInfo;\n /** Where the project is. */\n repository: RepositoryInfo;\n /**\n * How many errors `ascribe check` finds in the working tree, for the\n * builds compared. The comparison runs regardless, but a page with an\n * error may not render as it will once it's fixed, so a reviewer should\n * know. `diff_project` sets it; `Report::new` leaves it zero.\n */\n working_tree_errors: number;\n /** What changed, per build, in the order asked for. */\n builds: BuildDiff[];\n}\n\n/** What changed on one page of a build. */\nexport interface PageDiff {\n /** The page's content path. */\n path: string;\n /** Its route: the new one, or for a removed page the old one. */\n route: string;\n /**\n * Whether the build publishes it only now, only before, or in both\n * with a difference.\n */\n status: PageStatus;\n /** Whether the page's own file changed (or exists on one side only). */\n own_file_changed: boolean;\n /**\n * The other changed files the page's change can come from: fragments it\n * includes and pages its links take a title or a heading from, in path\n * order; then the snippets whose code changed, by address\n * (`code:app.py#main`); and `ascribe.toml` (last) when the content model\n * is a cause.\n */\n because: string[];\n /**\n * What changed about the page itself besides its blocks, in this\n * order: `title`, `frontmatter`, `availability` (the page-level one),\n * and `route`. Empty for an added or removed page.\n */\n page_changed: string[];\n /** How many changes of each kind. */\n counts: Counts;\n /**\n * The block-level changes, in the page's order, a removed block where\n * it was. Empty for an added or removed page.\n */\n changes: Change[];\n}\n\n/** Whether a page is new, gone, or different. */\nexport type PageStatus = \"added\" | \"removed\" | \"changed\";\n\n/** The repository, in the report. */\nexport interface RepositoryInfo {\n /** The repository's top-level directory, as git prints it. */\n root: string;\n /**\n * The project's folder inside it, with a trailing `/`, or empty when\n * the project is at the repository's root.\n */\n project_prefix: string;\n}\n\n/**\n * The words that differ inside a changed block of prose. Ranges are\n * `[start, end)` in characters (Unicode scalar values) of each side's\n * `text`, which is the block's text with whitespace collapsed.\n */\nexport interface Words {\n /** Ranges in `now_text`: words added or replacing others. */\n now: [number, number][];\n /** Ranges in `was_text`: words removed or replaced. */\n was: [number, number][];\n /** The block's text now. */\n now_text: string;\n /** The block's text before. */\n was_text: string;\n}\n"]}
1
+ {"version":3,"file":"shapes.js","sourceRoot":"","sources":["../src/shapes.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,2EAA2E;AAC3E,6EAA6E;AAC7E,yEAAyE;AACzE,EAAE;AACF,8CAA8C","sourcesContent":["// The JSON Ascribe writes that this package reads, as TypeScript types.\n// Generated from the Rust types that write it, through the JSON Schemas in\n// schemas/, by crates/ascribe-cli/src/shapes.rs. Change the Rust types, then\n// run `ASCRIBE_BLESS=1 cargo test -p ascribe-cli shapes`. Don't edit it.\n//\n// - DiffReport (`ascribe diff --format json`)\n\n/**\n * Where a block's text is written: the README's anchor grammar, the same\n * string the rendered page carries in `data-ascribe-source` and\n * `data-ascribe-via`.\n */\nexport interface Anchor {\n /**\n * `<path>:<first>-<last>`: the file's content path, percent-encoded by\n * segment, and the block's first and last lines, from 1.\n */\n source: string;\n /**\n * The includes the block came through, outermost first, each\n * `<path>:<line>`. Empty for a block written in the page itself.\n */\n via: string[];\n}\n\n/** The base of a comparison, in the report. */\nexport interface BaseInfo {\n /** The revision asked for, or the default branch used. */\n requested: string;\n /** The commit it names. */\n commit: string;\n /**\n * The merge base of that commit and `HEAD`, which the comparison reads;\n * `null` with `--base-exact`, which reads `commit`.\n */\n merge_base: string | null;\n}\n\n/** What changed in one build. */\nexport interface BuildDiff {\n /** The build's name. */\n build: string;\n /** The pages that changed, in path order. */\n pages: PageDiff[];\n}\n\n/** One block's change. */\nexport interface Change {\n /** What happened to it. */\n kind: ChangeKind;\n /**\n * For a removed block, and a moved block's old place: the block of the\n * new version it came after, among its siblings. Absent when it was\n * first.\n */\n after?: Anchor;\n /** Where it's written now: every kind but `removed`. */\n now?: Anchor;\n /**\n * For a removed block, and a moved block's old place: the block of the\n * new version it was inside. Absent at the top of the page.\n */\n parent?: Anchor;\n /**\n * For a removed block, its text, whitespace collapsed, so it can be\n * shown where it was.\n */\n text?: string;\n /** Where it was written: every kind but `added`. */\n was?: Anchor;\n /** For changed prose, the words that differ. */\n words?: Words;\n}\n\n/** The kinds of block change. */\nexport type ChangeKind = \"changed\" | \"added\" | \"removed\" | \"moved\";\n\n/** How many changes of each kind a page has. */\nexport interface Counts {\n /** Blocks whose content changed. */\n changed: number;\n /** Blocks only in the new version. */\n added: number;\n /** Blocks only in the old version. */\n removed: number;\n /** Blocks in both, somewhere else. */\n moved: number;\n}\n\n/** The whole report, as `ascribe diff --format json` writes it. */\nexport interface DiffReport {\n /** `SCHEMA_VERSION`. */\n schema_version: number;\n /** The version of Ascribe that wrote it. */\n ascribe_version: string;\n /** What was compared with. */\n base: BaseInfo;\n /** Where the project is. */\n repository: RepositoryInfo;\n /**\n * How many errors `ascribe check` finds in the working tree, for the\n * builds compared. The comparison runs regardless, but a page with an\n * error may not render as it will once it's fixed, so a reviewer should\n * know. `diff_project` sets it; `Report::new` leaves it zero.\n */\n working_tree_errors: number;\n /** What changed, per build, in the order asked for. */\n builds: BuildDiff[];\n /**\n * Whether every page's `changes` was left empty, as `ascribe diff\n * --pages-only` leaves them: its `counts` still count them. Only there\n * when it's `true`.\n */\n blocks_omitted?: boolean;\n}\n\n/** What changed on one page of a build. */\nexport interface PageDiff {\n /** The page's content path. */\n path: string;\n /** Its route: the new one, or for a removed page the old one. */\n route: string;\n /**\n * Whether the build publishes it only now, only before, or in both\n * with a difference.\n */\n status: PageStatus;\n /** Whether the page's own file changed (or exists on one side only). */\n own_file_changed: boolean;\n /**\n * The other changed files the page's change can come from: fragments it\n * includes and pages its links take a title or a heading from, in path\n * order; then the snippets whose code changed, by address\n * (`code:app.py#main`); and `ascribe.toml` (last) when the content model\n * is a cause.\n */\n because: string[];\n /**\n * What changed about the page itself besides its blocks, in this\n * order: `title`, `frontmatter`, `availability` (the page-level one),\n * and `route`. Empty for an added or removed page.\n */\n page_changed: string[];\n /** How many changes of each kind. */\n counts: Counts;\n /**\n * The block-level changes, in the page's order, a removed block where\n * it was. Empty for an added or removed page, and for every page when\n * the report's `blocks_omitted` is `true`.\n */\n changes: Change[];\n}\n\n/** Whether a page is new, gone, or different. */\nexport type PageStatus = \"added\" | \"removed\" | \"changed\";\n\n/** The repository, in the report. */\nexport interface RepositoryInfo {\n /** The repository's top-level directory, as git prints it. */\n root: string;\n /**\n * The project's folder inside it, with a trailing `/`, or empty when\n * the project is at the repository's root.\n */\n project_prefix: string;\n}\n\n/**\n * The words that differ inside a changed block of prose. Ranges are\n * `[start, end)` in characters (Unicode scalar values) of each side's\n * `text`, which is the block's text with whitespace collapsed.\n */\nexport interface Words {\n /** Ranges in `now_text`: words added or replacing others. */\n now: [number, number][];\n /** Ranges in `was_text`: words removed or replaced. */\n was: [number, number][];\n /** The block's text now. */\n now_text: string;\n /** The block's text before. */\n was_text: string;\n}\n"]}
@@ -26,6 +26,8 @@ const TIMEOUT_MS = 120_000;
26
26
  /** This tab's memory: whether the panel is open, and a page to step into. */
27
27
  const OPEN_KEY = "ascribe-review-open";
28
28
  const FIRST_KEY = "ascribe-review-first";
29
+ /** The model file, which a page can change through but isn't a file of content. */
30
+ const MODEL_FILE = "ascribe.toml";
29
31
  const SHOWS = [
30
32
  ["changes", "Changes"],
31
33
  ["will", "As it will be"],
@@ -95,6 +97,8 @@ class ReviewApp {
95
97
  at = -1;
96
98
  atEnd = false;
97
99
  breakdown = false;
100
+ /** Whether the More review actions row is open. */
101
+ more = false;
98
102
  overlay;
99
103
  overlayFor;
100
104
  listeners = [];
@@ -420,6 +424,7 @@ class ReviewApp {
420
424
  resolve: (threadId, resolved) => ask("resolve", { threadId, resolved }),
421
425
  submit: (event, body) => ask("submit", body === undefined ? { event } : { event, body }),
422
426
  discard: () => ask("discard"),
427
+ promptAgent: (request) => this.promptAgent(request),
423
428
  openSource: (anchor) => this.openSource(anchor.source),
424
429
  openThread: (threadId, pagePath) => this.openThread(threadId, pagePath),
425
430
  notify: (message) => this.notify(message),
@@ -428,6 +433,27 @@ class ReviewApp {
428
433
  },
429
434
  };
430
435
  }
436
+ /**
437
+ * Has the dev server build a prompt and puts it on the clipboard: a page
438
+ * can't fill in an agent on the machine. Rejects with what went wrong.
439
+ */
440
+ async promptAgent(request) {
441
+ const path = this.stage.kind === "on" ? this.stage.view.path : null;
442
+ const prompt = await this.channel.request("prompt", { request, path });
443
+ if (prompt === null) {
444
+ throw {
445
+ message: request.kind === "page-changes"
446
+ ? "nothing on this page changed."
447
+ : request.kind === "fragment-reach"
448
+ ? "no page changed through that file."
449
+ : "no review comment is open.",
450
+ };
451
+ }
452
+ if (!(await copyText(this.doc, prompt))) {
453
+ throw { message: "the browser didn't allow copying it." };
454
+ }
455
+ this.notify("Prompt copied. Paste it into your agent.");
456
+ }
431
457
  /** Every anchored block on the page, in its order. */
432
458
  anchors() {
433
459
  return anchoredBlocks(this.doc)
@@ -618,6 +644,9 @@ class ReviewApp {
618
644
  }
619
645
  onPage(view) {
620
646
  const parts = [this.controls(view)];
647
+ const more = this.moreRow(view);
648
+ if (more)
649
+ parts.push(more);
621
650
  if (this.breakdown && view.page) {
622
651
  parts.push(this.el("div", "legend", countsText(view.page.counts) || "No changes"));
623
652
  }
@@ -717,8 +746,45 @@ class ReviewApp {
717
746
  const refresh = this.button("Refresh", "ghost", () => void this.refresh());
718
747
  refresh.setAttribute("aria-label", "Refresh comments and changes");
719
748
  row.append(refresh);
749
+ if (page && page.status !== "removed") {
750
+ const more = this.button("⋯", "ghost square", () => {
751
+ this.more = !this.more;
752
+ this.render();
753
+ });
754
+ more.setAttribute("aria-label", "More review actions");
755
+ more.title = "More review actions";
756
+ more.setAttribute("aria-expanded", String(this.more));
757
+ row.append(more);
758
+ }
720
759
  return row;
721
760
  }
761
+ /** More review actions: Prompt agent about the page, and about each file it changed through. */
762
+ moreRow(view) {
763
+ const page = view.page;
764
+ if (!this.more || !page || page.status === "removed")
765
+ return undefined;
766
+ const menu = this.el("div", "row menu");
767
+ menu.setAttribute("role", "group");
768
+ menu.setAttribute("aria-label", "More review actions");
769
+ const items = [
770
+ ["Prompt agent: review this page", { kind: "page-changes" }],
771
+ ];
772
+ const fragments = page.because.filter((cause) => cause !== MODEL_FILE);
773
+ for (const fragment of fragments) {
774
+ items.push([
775
+ fragments.length === 1
776
+ ? "Prompt agent: check this fragment's pages"
777
+ : `Prompt agent: check the pages that show ${fragment}`,
778
+ { kind: "fragment-reach", fragment },
779
+ ]);
780
+ }
781
+ for (const [label, request] of items) {
782
+ menu.append(this.button(label, "ghost", () => {
783
+ this.promptAgent(request).catch((error) => this.notify(`The prompt couldn't be built: ${error.message}`));
784
+ }));
785
+ }
786
+ return menu;
787
+ }
722
788
  /** Past the last change: the next changed page. */
723
789
  endNotice(view) {
724
790
  const notice = this.notice(this.marks.length === 0
@@ -949,4 +1015,36 @@ function recall(key) {
949
1015
  return null;
950
1016
  }
951
1017
  }
1018
+ /**
1019
+ * Puts `text` on the clipboard, through the old `execCommand` way where the
1020
+ * Clipboard API isn't allowed. Whether it got there.
1021
+ */
1022
+ async function copyText(doc, text) {
1023
+ const clipboard = doc.defaultView?.navigator.clipboard;
1024
+ if (clipboard) {
1025
+ try {
1026
+ await clipboard.writeText(text);
1027
+ return true;
1028
+ }
1029
+ catch {
1030
+ // Not allowed here: the old way below.
1031
+ }
1032
+ }
1033
+ const area = doc.createElement("textarea");
1034
+ area.value = text;
1035
+ area.setAttribute("readonly", "");
1036
+ area.style.position = "fixed";
1037
+ area.style.left = "-9999px";
1038
+ doc.body.append(area);
1039
+ area.select();
1040
+ let copied = false;
1041
+ try {
1042
+ copied = doc.execCommand("copy");
1043
+ }
1044
+ catch {
1045
+ copied = false;
1046
+ }
1047
+ area.remove();
1048
+ return copied;
1049
+ }
952
1050
  //# sourceMappingURL=app.js.map