@ascribed/astro 0.2.1-next.9 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +41 -9
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +62 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/content.d.ts +30 -0
  6. package/dist/content.js +101 -2
  7. package/dist/content.js.map +1 -1
  8. package/dist/dev.d.ts +2 -1
  9. package/dist/dev.js +13 -3
  10. package/dist/dev.js.map +1 -1
  11. package/dist/files.d.ts +21 -4
  12. package/dist/files.js +46 -8
  13. package/dist/files.js.map +1 -1
  14. package/dist/head-check.d.ts +6 -0
  15. package/dist/head-check.js +28 -0
  16. package/dist/head-check.js.map +1 -0
  17. package/dist/head.d.ts +54 -0
  18. package/dist/head.js +114 -0
  19. package/dist/head.js.map +1 -0
  20. package/dist/headers.d.ts +50 -0
  21. package/dist/headers.js +155 -0
  22. package/dist/headers.js.map +1 -0
  23. package/dist/index.d.ts +40 -9
  24. package/dist/index.js +330 -95
  25. package/dist/index.js.map +1 -1
  26. package/dist/init.d.ts +41 -0
  27. package/dist/init.js +130 -0
  28. package/dist/init.js.map +1 -0
  29. package/dist/names.d.ts +5 -0
  30. package/dist/names.js +5 -0
  31. package/dist/names.js.map +1 -1
  32. package/dist/nav.d.ts +51 -0
  33. package/dist/nav.js +97 -0
  34. package/dist/nav.js.map +1 -0
  35. package/dist/permalinks.d.ts +78 -0
  36. package/dist/permalinks.js +114 -0
  37. package/dist/permalinks.js.map +1 -0
  38. package/dist/project.d.ts +37 -0
  39. package/dist/project.js +47 -2
  40. package/dist/project.js.map +1 -1
  41. package/dist/projects.d.ts +38 -0
  42. package/dist/projects.js +127 -0
  43. package/dist/projects.js.map +1 -0
  44. package/dist/records.d.ts +136 -0
  45. package/dist/records.js +182 -0
  46. package/dist/records.js.map +1 -0
  47. package/dist/review/devfile.d.ts +6 -1
  48. package/dist/review/devfile.js +12 -2
  49. package/dist/review/devfile.js.map +1 -1
  50. package/dist/review/diff.d.ts +13 -0
  51. package/dist/review/diff.js +34 -1
  52. package/dist/review/diff.js.map +1 -1
  53. package/dist/review/protocol.d.ts +6 -1
  54. package/dist/review/protocol.js.map +1 -1
  55. package/dist/review/routes.d.ts +3 -0
  56. package/dist/review/routes.js +49 -35
  57. package/dist/review/routes.js.map +1 -1
  58. package/dist/review/server.d.ts +15 -0
  59. package/dist/review/server.js +58 -7
  60. package/dist/review/server.js.map +1 -1
  61. package/dist/root-files.d.ts +68 -0
  62. package/dist/root-files.js +103 -0
  63. package/dist/root-files.js.map +1 -0
  64. package/dist/run.d.ts +7 -6
  65. package/dist/run.js +14 -6
  66. package/dist/run.js.map +1 -1
  67. package/dist/shapes.d.ts +8 -1
  68. package/dist/shapes.js.map +1 -1
  69. package/dist/toolbar/app.js +98 -0
  70. package/dist/toolbar/app.js.map +1 -1
  71. package/dist/toolbar/style.d.ts +1 -1
  72. package/dist/toolbar/style.js +31 -23
  73. package/dist/toolbar/style.js.map +1 -1
  74. package/package.json +32 -9
  75. package/src/Head.astro +58 -0
  76. package/src/MissingPermalink.astro +59 -0
  77. package/src/Permalink.astro +78 -0
  78. package/starter/src/content.config.ts +8 -0
  79. package/starter/src/layouts/Docs.astro +87 -0
  80. package/starter/src/nav.ts +13 -0
  81. package/starter/src/pages/[...slug].astro +20 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"root-files.js","sourceRoot":"","sources":["../src/root-files.ts"],"names":[],"mappings":"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","sourcesContent":["// The site's sitemap and `robots.txt`, from every project's page records\n// together: one of each for the site, at the host's root, which is above the\n// base path of every project that isn't mounted there. `astro build` writes\n// them into its output, and `astro dev` serves them.\n//\n// A site whose Astro `base` isn't `/` has no host root: its sitemap goes\n// under the base, and no `robots.txt` is written, since one under a path does\n// nothing. The build says what the host's own needs instead.\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { normalizeBase } from \"./project.js\";\nimport {\n purposeConflicts,\n robotsText,\n routeAs,\n routeKey,\n sitemapAddresses,\n sitemapRoute,\n sitemapXml,\n type ProjectRobots,\n type SitemapSources,\n} from \"./records.js\";\n\n/** What the files are drawn from. */\nexport interface RootFileSources extends Omit<SitemapSources, \"site\"> {\n /** What each project says to crawlers, each project once. */\n robots: readonly ProjectRobots[];\n /** Astro's `site`, when it's set. */\n site: string | undefined;\n /** Astro's `base`. */\n base: string;\n /** Whether the site has its own `robots.txt`, in `public/`. */\n ownRobots: boolean;\n /** Whether the site has its own `sitemap.xml`, in `public/`. */\n ownSitemap: boolean;\n}\n\n/** A thing the build says about the files, and how loudly. */\ninterface RootFileNote {\n level: \"info\" | \"warn\";\n text: string;\n}\n\n/**\n * The files, each `undefined` when none is written, what to say about them,\n * and the problems that stop `robots.txt` from being written, which fail a\n * build.\n */\nexport interface RootFiles {\n sitemap: string | undefined;\n robots: string | undefined;\n notes: RootFileNote[];\n problems: string[];\n}\n\n/** The sitemap and `robots.txt` for a site, and what the build says about them. */\nexport function rootFiles(sources: RootFileSources): RootFiles {\n const notes: RootFileNote[] = [];\n const base = normalizeBase(sources.base);\n let sitemap: string | undefined;\n let sitemapAddress: string | undefined;\n if (sources.ownSitemap) {\n // The site's own, at the same address, which robots.txt points to when there's an address.\n notes.push({ level: \"info\", text: \"no sitemap.xml: public/sitemap.xml is the site's own.\" });\n if (sources.site !== undefined) sitemapAddress = new URL(sitemapRoute(base), sources.site).href;\n } else if (sources.site === undefined) {\n notes.push({\n level: \"warn\",\n text: \"no sitemap.xml: a sitemap lists pages by their absolute addresses, and the site has no address. Set [consumer] site in ascribe.toml, or Astro's `site`.\",\n });\n } else {\n sitemap = sitemapXml(sitemapAddresses({ ...sources, site: sources.site }));\n sitemapAddress = new URL(sitemapRoute(base), sources.site).href;\n }\n const problems = purposeConflicts(sources.robots);\n let robots =\n problems.length > 0\n ? undefined\n : robotsText(\n sources.robots.map((project) => project.robots),\n sitemapAddress,\n );\n if (robots !== undefined && base !== \"/\") {\n notes.push({\n level: \"info\",\n text: `no robots.txt: Astro's \\`base\\` is \"${base}\", and a crawler reads robots.txt only at the host's root. Put these lines in the host's own:\\n${robots}`,\n });\n robots = undefined;\n } else if (robots !== undefined && sources.ownRobots) {\n notes.push({\n level: \"info\",\n text: `no robots.txt: public/robots.txt is the site's own. Ascribe's lines for it:\\n${robots}`,\n });\n robots = undefined;\n }\n return { sitemap, robots, notes, problems };\n}\n\ntype Middleware = (req: IncomingMessage, res: ServerResponse, next: () => void) => void;\n\n/**\n * A dev-server middleware serving the sitemap at `<base>sitemap.xml` and\n * `robots.txt` at `/`, each as `files` has it when it's asked for, so each\n * rebuild's records are served.\n */\nexport function rootFilesMiddleware(base: string, files: () => RootFiles): Middleware {\n const sitemapPath = sitemapRoute(base);\n return (req, res, next) => {\n if (req.method !== \"GET\" && req.method !== \"HEAD\") return next();\n // Vite strips the base from `req.url`; connect keeps the original in `originalUrl`.\n const pathname =\n ((req as { originalUrl?: string }).originalUrl ?? req.url ?? \"\").split(/[?#]/, 1)[0] ?? \"\";\n if (pathname !== sitemapPath && pathname !== \"/robots.txt\") return next();\n const current = files();\n const [text, type] =\n pathname === sitemapPath\n ? [current.sitemap, \"application/xml; charset=utf-8\"]\n : [current.robots, \"text/plain; charset=utf-8\"];\n if (text === undefined) return next();\n res.setHeader(\"Content-Type\", type);\n res.setHeader(\"Content-Length\", Buffer.byteLength(text));\n res.end(req.method === \"HEAD\" ? undefined : text);\n };\n}\n\n/** A route as `astro:routes:resolved` gives it, as much as these read. */\nexport interface AstroRoute {\n pattern: string;\n patternRegex: RegExp;\n params: string[];\n type: string;\n origin: string;\n}\n\n/** Astro's routing, as much as these read. */\ninterface Routing {\n base: string;\n trailingSlash: \"always\" | \"never\" | \"ignore\";\n}\n\n/**\n * The site's own pages `astro dev` can name: each route of the site's own\n * with no parameters, other than the not-found and error pages, by its route\n * from the host's root. A page a route's `getStaticPaths` lists is known only\n * to a build.\n */\nexport function devSitePages(routes: readonly AstroRoute[], astro: Routing): string[] {\n const root = normalizeBase(astro.base);\n return routes\n .filter(\n (route) =>\n route.type === \"page\" &&\n route.origin === \"project\" &&\n route.params.length === 0 &&\n route.pattern !== \"/404\" &&\n route.pattern !== \"/500\",\n )\n .map((route) => routeAs(`${root}${route.pattern.replace(/^\\//, \"\")}`, astro.trailingSlash));\n}\n\n/**\n * Whether a project's page, by its route from the host's root, has a route\n * in the site in `astro dev`: whether one of the site's own routes matches\n * it. A project no route shows, such as one whose collection a page only\n * quotes from, has no pages in the sitemap.\n */\nexport function devRouted(\n routes: readonly AstroRoute[],\n astro: Routing,\n): (route: string) => boolean {\n const root = normalizeBase(astro.base);\n const pages = routes.filter((route) => route.type === \"page\" && route.origin === \"project\");\n return (route) => {\n if (!`${route}/`.startsWith(root)) return false;\n const under = `/${route.slice(root.length)}`;\n const forms = new Set([under, routeKey(under), `${routeKey(under)}/`]);\n return pages.some((page) => [...forms].some((form) => page.patternRegex.test(form)));\n };\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
  /**
@@ -17,8 +18,8 @@ export declare function hasWarnings(diagnostics: string): boolean;
17
18
  * them in `astro dev`.
18
19
  */
19
20
  export declare function anchorsFor(anchors: boolean | "dev" | undefined, command: string, review?: boolean): boolean;
20
- /** Whether an Astro command has review in the site preview, given the `review` option. */
21
- export declare function reviewFor(review: boolean | undefined, command: string): boolean;
21
+ /** Whether an Astro command has review in the site preview, given the `review` option (an entry's name is on). */
22
+ export declare function reviewFor(review: boolean | string | undefined, command: string): boolean;
22
23
  /**
23
24
  * Builds one build's outputs, the site output by default. Rejects, with the compiler's report as the
24
25
  * message, if the build has errors: the Astro build fails with them.
@@ -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,22 @@
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 for build \`site\`, reported on 14:
6
+ * 0 errors, 0 warnings, 0 advice`. An older binary wrote `checked 14 files: 0 errors,
7
+ * 0 warnings`, with `, 3 advice` after only when there was advice.
8
+ */
9
+ const SUMMARY = /^checked [^:\n]*: (\d+) errors?, (\d+) warnings?(?:, \d+ advice)?$/;
10
+ /**
11
+ * Whether a build's report is worth a warning: anything but a report whose
12
+ * summary line counts no errors and no warnings, which is logged at info
13
+ * level, advice and all.
9
14
  */
10
15
  export function hasWarnings(diagnostics) {
11
- return diagnostics !== "" && !CLEAN.test(diagnostics);
16
+ if (diagnostics === "")
17
+ return false;
18
+ const summary = SUMMARY.exec(diagnostics.trimEnd().split("\n").at(-1) ?? "");
19
+ return summary === null || summary[1] !== "0" || summary[2] !== "0";
12
20
  }
13
21
  /**
14
22
  * Whether an Astro command builds with source anchors, given the integration's
@@ -19,7 +27,7 @@ export function hasWarnings(diagnostics) {
19
27
  export function anchorsFor(anchors, command, review = false) {
20
28
  return anchors === true || (command === "dev" && (anchors === "dev" || review));
21
29
  }
22
- /** Whether an Astro command has review in the site preview, given the `review` option. */
30
+ /** Whether an Astro command has review in the site preview, given the `review` option (an entry's name is on). */
23
31
  export function reviewFor(review, command) {
24
32
  return command === "dev" && review !== false;
25
33
  }
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;;;;GAIG;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,kHAAkH;AAClH,MAAM,UAAU,SAAS,CAAC,MAAoC,EAAE,OAAe;IAC7E,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 for build \\`site\\`, reported on 14:\n * 0 errors, 0 warnings, 0 advice`. An older binary wrote `checked 14 files: 0 errors,\n * 0 warnings`, with `, 3 advice` after only when there was 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 (an entry's name is on). */\nexport function reviewFor(review: boolean | string | 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