blogwright 0.3.3 → 0.4.0-beta.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 (46) hide show
  1. package/README.md +11 -11
  2. package/agent/agent-manifest.json +1 -1
  3. package/agent/server.js +37 -19
  4. package/dist/adapters/fetch-ping.d.ts +1 -1
  5. package/dist/adapters/fetch-ping.js +3 -3
  6. package/dist/adapters/node-module-loader.d.ts +11 -0
  7. package/dist/adapters/node-module-loader.js +146 -0
  8. package/dist/adapters/process-package-manager.d.ts +41 -0
  9. package/dist/adapters/process-package-manager.js +116 -0
  10. package/dist/adapters/process-vcs.d.ts +5 -4
  11. package/dist/adapters/process-vcs.js +6 -5
  12. package/dist/agent-package.d.ts +1 -1
  13. package/dist/agent-package.js +4 -3
  14. package/dist/bin.js +13 -2
  15. package/dist/cli.d.ts +68 -1
  16. package/dist/cli.js +270 -88
  17. package/dist/commands.d.ts +70 -3
  18. package/dist/commands.js +177 -30
  19. package/dist/config-block.d.ts +34 -0
  20. package/dist/config-block.js +262 -0
  21. package/dist/context.d.ts +76 -6
  22. package/dist/context.js +98 -18
  23. package/dist/deploy.d.ts +2 -2
  24. package/dist/deploy.js +12 -12
  25. package/dist/graph.d.ts +27 -16
  26. package/dist/graph.js +1 -1
  27. package/dist/init.d.ts +37 -3
  28. package/dist/init.js +146 -22
  29. package/dist/known-commands.d.ts +63 -0
  30. package/dist/known-commands.js +78 -0
  31. package/dist/microvms.d.ts +2 -2
  32. package/dist/microvms.js +3 -3
  33. package/dist/nodes.d.ts +5 -3
  34. package/dist/nodes.js +97 -30
  35. package/dist/plugin-commands.d.ts +298 -0
  36. package/dist/plugin-commands.js +990 -0
  37. package/dist/plugins.d.ts +194 -0
  38. package/dist/plugins.js +523 -0
  39. package/dist/ports.d.ts +89 -1
  40. package/dist/render.d.ts +55 -0
  41. package/dist/render.js +89 -1
  42. package/dist/repo.d.ts +3 -3
  43. package/dist/repo.js +8 -8
  44. package/dist/seo.d.ts +1 -1
  45. package/dist/seo.js +1 -1
  46. package/package.json +6 -6
package/dist/render.d.ts CHANGED
@@ -20,6 +20,26 @@ export interface StatusEntry {
20
20
  }
21
21
  /** The pretty drift tree for `status` (the plain form keeps the classic lines). */
22
22
  export declare function renderStatusTree(entries: StatusEntry[]): string[];
23
+ /**
24
+ * Leveled-logger surface {@link logStatusEntries} reports through - both the
25
+ * CLI's own `Logger` (`packages/cli/src/logger.ts`) and core's
26
+ * `PluginLogger` (`blogwright-core`'s `plugin.ts`) satisfy it structurally,
27
+ * so this module needs neither type imported.
28
+ */
29
+ export interface StatusLogger {
30
+ info(msg: string): void;
31
+ warn(msg: string): void;
32
+ }
33
+ /**
34
+ * Report `entries` through `logger`, choosing the pretty tree ({@link
35
+ * renderStatusTree}) on an interactive terminal or the plain, CI-stable form
36
+ * otherwise: a `present`/`missing` line via `logger.info`, or a `read
37
+ * failed` line via `logger.warn` for an entry whose `read()` threw. Shared
38
+ * by the CLI's own `status` command (`commands.ts`) and a plugin's generic
39
+ * `status` verb (`plugin-commands.ts`), so the two never carry two
40
+ * near-identical copies of the same render branch.
41
+ */
42
+ export declare function logStatusEntries(entries: StatusEntry[], pretty: boolean, logger: StatusLogger): void;
23
43
  export interface HistoryEntry {
24
44
  hash: string;
25
45
  status: 'succeeded' | 'failed';
@@ -31,3 +51,38 @@ export interface HistoryEntry {
31
51
  * the newest success. Entries arrive newest-first (the caller sorts).
32
52
  */
33
53
  export declare function renderHistoryTable(entries: HistoryEntry[], now: number): string[];
54
+ /** One installed plugin's row in `blogwright plugin list`. */
55
+ export interface PluginListRow {
56
+ /** The CLI namespace the plugin claims - `Plugin.name`. */
57
+ namespace: string;
58
+ /** The npm package the plugin was loaded from. */
59
+ packageName: string;
60
+ /** From the package's own `package.json`; `undefined` when it declares none. */
61
+ version?: string | undefined;
62
+ /** The single top-level config key the plugin owns; `undefined` when it owns none. */
63
+ configKey?: string | undefined;
64
+ }
65
+ /** One plugin that failed to load, as `discover` reported it. */
66
+ interface PluginListFailure {
67
+ packageName: string;
68
+ reason: string;
69
+ }
70
+ /** What `blogwright plugin list` has to show: the plugins that loaded, and the ones that did not. */
71
+ export interface PluginListing {
72
+ rows: readonly PluginListRow[];
73
+ failures: readonly PluginListFailure[];
74
+ }
75
+ /**
76
+ * The `blogwright plugin list` listing, in the two forms every renderer in
77
+ * this module offers: an aligned table under a bold header on a TTY, and the
78
+ * same columns single-space separated otherwise. The plain form is the stable
79
+ * contract for CI logs and agents - the same split `history` makes
80
+ * (`commands.ts`), which is why the column set is identical in both and only
81
+ * the padding and the colour differ.
82
+ *
83
+ * Emits NOTHING for a listing with no rows and no failures, so a repo with no
84
+ * plugins installed gets its caller's empty-state line and not a header over
85
+ * an empty table.
86
+ */
87
+ export declare function renderPluginList(listing: PluginListing, pretty: boolean): string[];
88
+ export {};
package/dist/render.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Pure presentation helpers: data in, lines out — the caller writes them.
2
+ * Pure presentation helpers: data in, lines out - the caller writes them.
3
3
  * Every renderer takes a `pretty` flag; the plain form is stable, line-oriented
4
4
  * output for CI systems and agents, the pretty form is for humans on a TTY.
5
5
  */
@@ -60,6 +60,31 @@ export function renderStatusTree(entries) {
60
60
  return `${connector} ${STATUS_MARKS[entry.state]} ${entry.title}${detail}`;
61
61
  });
62
62
  }
63
+ /**
64
+ * Report `entries` through `logger`, choosing the pretty tree ({@link
65
+ * renderStatusTree}) on an interactive terminal or the plain, CI-stable form
66
+ * otherwise: a `present`/`missing` line via `logger.info`, or a `read
67
+ * failed` line via `logger.warn` for an entry whose `read()` threw. Shared
68
+ * by the CLI's own `status` command (`commands.ts`) and a plugin's generic
69
+ * `status` verb (`plugin-commands.ts`), so the two never carry two
70
+ * near-identical copies of the same render branch.
71
+ */
72
+ export function logStatusEntries(entries, pretty, logger) {
73
+ if (pretty) {
74
+ for (const line of renderStatusTree(entries))
75
+ logger.info(line);
76
+ return;
77
+ }
78
+ // The plain form is the stable contract for CI logs and agents.
79
+ for (const entry of entries) {
80
+ if (entry.state === 'error') {
81
+ logger.warn(`${entry.title}: read failed (${entry.detail})`);
82
+ continue;
83
+ }
84
+ const mark = entry.state === 'present' ? colors.green('present') : colors.yellow('missing');
85
+ logger.info(` ${mark} ${entry.title} ${entry.detail ? colors.dim(entry.detail) : ''}`);
86
+ }
87
+ }
63
88
  /**
64
89
  * The pretty deployment table for `history`: relative times, a live marker on
65
90
  * the newest success. Entries arrive newest-first (the caller sorts).
@@ -78,3 +103,66 @@ export function renderHistoryTable(entries, now) {
78
103
  });
79
104
  return [colors.bold(`${'hash'.padEnd(13)} ${' '} ${'finished'.padEnd(9)} duration`), ...rows];
80
105
  }
106
+ /**
107
+ * Printed in place of a cell the plugin genuinely has no value for. An
108
+ * explicit marker, never an empty cell: plain output is column-per-line and
109
+ * whitespace-separated, so a blank cell would silently shift every column
110
+ * after it for whatever is parsing the line.
111
+ */
112
+ const NO_CONFIG_KEY = '(none)';
113
+ /** Printed for a plugin whose own `package.json` declares no `version` field at all. */
114
+ const UNKNOWN_VERSION = '(unknown)';
115
+ /** Column headers, in the order {@link pluginListCells} emits the cells. */
116
+ const PLUGIN_LIST_HEADERS = ['namespace', 'package', 'version', 'configKey'];
117
+ /** Heading the failure lines are printed under, in both forms. */
118
+ const FAILED_TO_LOAD_HEADING = 'failed to load:';
119
+ function pluginListCells(row) {
120
+ return [
121
+ row.namespace,
122
+ row.packageName,
123
+ row.version ?? UNKNOWN_VERSION,
124
+ row.configKey ?? NO_CONFIG_KEY,
125
+ ];
126
+ }
127
+ /** Pad every cell but the last to the widest value in its column. */
128
+ function alignCells(cells, widths) {
129
+ return cells
130
+ .map((cell, i) => (i === cells.length - 1 ? cell : cell.padEnd(widths[i] ?? 0)))
131
+ .join(' ')
132
+ .trimEnd();
133
+ }
134
+ /**
135
+ * The `blogwright plugin list` listing, in the two forms every renderer in
136
+ * this module offers: an aligned table under a bold header on a TTY, and the
137
+ * same columns single-space separated otherwise. The plain form is the stable
138
+ * contract for CI logs and agents - the same split `history` makes
139
+ * (`commands.ts`), which is why the column set is identical in both and only
140
+ * the padding and the colour differ.
141
+ *
142
+ * Emits NOTHING for a listing with no rows and no failures, so a repo with no
143
+ * plugins installed gets its caller's empty-state line and not a header over
144
+ * an empty table.
145
+ */
146
+ export function renderPluginList(listing, pretty) {
147
+ const lines = [];
148
+ if (listing.rows.length > 0) {
149
+ const cells = listing.rows.map(pluginListCells);
150
+ if (pretty) {
151
+ const widths = PLUGIN_LIST_HEADERS.map((header, i) => Math.max(header.length, ...cells.map((row) => (row[i] ?? '').length)));
152
+ lines.push(colors.bold(alignCells(PLUGIN_LIST_HEADERS, widths)));
153
+ lines.push(...cells.map((row) => alignCells(row, widths)));
154
+ }
155
+ else {
156
+ lines.push(PLUGIN_LIST_HEADERS.join(' '));
157
+ lines.push(...cells.map((row) => row.join(' ')));
158
+ }
159
+ }
160
+ if (listing.failures.length > 0) {
161
+ lines.push(pretty ? colors.bold(FAILED_TO_LOAD_HEADING) : FAILED_TO_LOAD_HEADING);
162
+ // The same `<package>: <reason>` shape `--help` already prints a failed
163
+ // plugin in (`cli.ts`'s `renderPluginFailure`), and for the same reason:
164
+ // the reason is `discover`'s own message, never an `Error.stack`.
165
+ lines.push(...listing.failures.map((failure) => `${failure.packageName}: ${failure.reason}`));
166
+ }
167
+ return lines;
168
+ }
package/dist/repo.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Deploy source packaging: choose the repository files to ship and build a
3
- * deterministic zip of them. Pure domain — the VCS listing and file contents
3
+ * deterministic zip of them. Pure domain - the VCS listing and file contents
4
4
  * arrive through ports (see ports.ts; the process adapter owns jj/git).
5
5
  */
6
6
  import { type FileSystem } from 'blogwright-core';
@@ -8,7 +8,7 @@ import type { Ports } from './ports.js';
8
8
  /**
9
9
  * Filename injected into the source zip carrying the build revision. The site build
10
10
  * runs in a MicroVM from this zip (no `.git`), so `astro.config` reads the hash from
11
- * this file — see the repo's astro.config.mjs.
11
+ * this file - see the repo's astro.config.mjs.
12
12
  */
13
13
  export declare const COMMIT_FILE = ".commit-hash";
14
14
  /**
@@ -19,7 +19,7 @@ export declare const COMMIT_FILE = ".commit-hash";
19
19
  export declare function listRepoFiles(ports: Pick<Ports, 'vcs' | 'fs'>, cwd: string, ignore: string[], include?: string[]): Promise<string[]>;
20
20
  /**
21
21
  * Build a deterministic zip of the given files (read relative to cwd). Extra entries
22
- * (path → text content) are added in memory — used to inject build metadata such as
22
+ * (path → text content) are added in memory - used to inject build metadata such as
23
23
  * the commit hash without touching the working tree.
24
24
  */
25
25
  export declare function buildRepoZip(fs: FileSystem, cwd: string, files: string[], extra?: Record<string, string>): Promise<Uint8Array>;
package/dist/repo.js CHANGED
@@ -1,16 +1,16 @@
1
1
  /**
2
2
  * Deploy source packaging: choose the repository files to ship and build a
3
- * deterministic zip of them. Pure domain — the VCS listing and file contents
3
+ * deterministic zip of them. Pure domain - the VCS listing and file contents
4
4
  * arrive through ports (see ports.ts; the process adapter owns jj/git).
5
5
  */
6
6
  import { zipSync } from 'fflate';
7
- import { FileNotFoundError } from 'blogwright-core';
8
- /** Fixed timestamp for reproducible zips (fflate requires 1980-2099). */
9
- const ZIP_MTIME = new Date('1980-01-01T00:00:00Z');
7
+ import { FileNotFoundError, REPRODUCIBLE_ZIP_MTIME } from 'blogwright-core';
8
+ /** Fixed timestamp for reproducible zips. See {@link REPRODUCIBLE_ZIP_MTIME} for why it is not UTC-constructed. */
9
+ const ZIP_MTIME = REPRODUCIBLE_ZIP_MTIME;
10
10
  /**
11
11
  * Filename injected into the source zip carrying the build revision. The site build
12
12
  * runs in a MicroVM from this zip (no `.git`), so `astro.config` reads the hash from
13
- * this file — see the repo's astro.config.mjs.
13
+ * this file - see the repo's astro.config.mjs.
14
14
  */
15
15
  export const COMMIT_FILE = '.commit-hash';
16
16
  /**
@@ -20,7 +20,7 @@ export const COMMIT_FILE = '.commit-hash';
20
20
  */
21
21
  export async function listRepoFiles(ports, cwd, ignore, include = []) {
22
22
  const files = await ports.vcs.listFiles(cwd);
23
- // An ignore entry matches an exact path or a directory boundary — "dist"
23
+ // An ignore entry matches an exact path or a directory boundary - "dist"
24
24
  // drops dist and dist/**, never dist-notes.md (files silently missing from
25
25
  // the deployed site are painful to trace back to a prefix collision).
26
26
  const matches = (f, entry) => {
@@ -54,12 +54,12 @@ async function includedFiles(fs, cwd, entry) {
54
54
  catch (err) {
55
55
  if (err instanceof FileNotFoundError && (await fs.exists(abs)))
56
56
  return [rel]; // a single file
57
- throw new Error(`sourceInclude path "${entry}" is missing or empty — run the pre-deploy build that produces it before deploying`, { cause: err });
57
+ throw new Error(`sourceInclude path "${entry}" is missing or empty - run the pre-deploy build that produces it before deploying`, { cause: err });
58
58
  }
59
59
  }
60
60
  /**
61
61
  * Build a deterministic zip of the given files (read relative to cwd). Extra entries
62
- * (path → text content) are added in memory — used to inject build metadata such as
62
+ * (path → text content) are added in memory - used to inject build metadata such as
63
63
  * the commit hash without touching the working tree.
64
64
  */
65
65
  export async function buildRepoZip(fs, cwd, files, extra = {}) {
package/dist/seo.d.ts CHANGED
@@ -10,6 +10,6 @@ export interface SeoDirectives {
10
10
  * Resolve robots.txt / sitemap.xml directives for a deploy from the environment + config.
11
11
  * `baseUrl` is the canonical origin the site is served from (e.g. https://example.com).
12
12
  * Defaults are environment-aware: production is indexable with a sitemap; every other
13
- * environment blocks crawlers and skips the sitemap — all overridable via config.seo.
13
+ * environment blocks crawlers and skips the sitemap - all overridable via config.seo.
14
14
  */
15
15
  export declare function resolveSeo(ctx: OpsContext, baseUrl: string | undefined): SeoDirectives;
package/dist/seo.js CHANGED
@@ -11,7 +11,7 @@ function indexRobots(sitemapUrl) {
11
11
  * Resolve robots.txt / sitemap.xml directives for a deploy from the environment + config.
12
12
  * `baseUrl` is the canonical origin the site is served from (e.g. https://example.com).
13
13
  * Defaults are environment-aware: production is indexable with a sitemap; every other
14
- * environment blocks crawlers and skips the sitemap — all overridable via config.seo.
14
+ * environment blocks crawlers and skips the sitemap - all overridable via config.seo.
15
15
  */
16
16
  export function resolveSeo(ctx, baseUrl) {
17
17
  const isProd = !ctx.preview && ctx.env === 'production';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blogwright",
3
- "version": "0.3.3",
3
+ "version": "0.4.0-beta.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "blogwright": "./dist/bin.js",
@@ -17,16 +17,16 @@
17
17
  }
18
18
  },
19
19
  "dependencies": {
20
- "fflate": "^0.8.3",
21
- "blogwright-core": "0.3.3",
22
- "blogwright-pds": "0.3.3"
20
+ "blogwright-core": "0.4.0-beta.0",
21
+ "blogwright-pds": "0.4.0-beta.0",
22
+ "fflate": "^0.8.3"
23
23
  },
24
24
  "devDependencies": {
25
+ "blogwright-build-agent": "0.1.0",
25
26
  "@types/node": "^26.1.0",
26
27
  "oxlint": "^1.72.0",
27
28
  "typescript": "^6.0.3",
28
- "vitest": "^4.1.9",
29
- "blogwright-build-agent": "0.1.0"
29
+ "vitest": "^4.1.9"
30
30
  },
31
31
  "description": "Full operations for a blog site on AWS: S3 + CloudFront hosting with builds in a Lambda MicroVM, PR previews, GitHub-OIDC CI deploys, and standard.site (AT Protocol) publishing",
32
32
  "keywords": [