blogwright 0.1.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 (63) hide show
  1. package/LICENSE +24 -0
  2. package/README.md +12 -0
  3. package/agent/Dockerfile +20 -0
  4. package/agent/agent-manifest.json +3 -0
  5. package/agent/server.js +7935 -0
  6. package/dist/adapters/fetch-ping.d.ts +9 -0
  7. package/dist/adapters/fetch-ping.js +26 -0
  8. package/dist/adapters/fetch-ping.js.map +1 -0
  9. package/dist/adapters/process-vcs.d.ts +12 -0
  10. package/dist/adapters/process-vcs.js +49 -0
  11. package/dist/adapters/process-vcs.js.map +1 -0
  12. package/dist/agent-package.d.ts +19 -0
  13. package/dist/agent-package.js +49 -0
  14. package/dist/agent-package.js.map +1 -0
  15. package/dist/bin.d.ts +2 -0
  16. package/dist/bin.js +14 -0
  17. package/dist/bin.js.map +1 -0
  18. package/dist/cli.d.ts +6 -0
  19. package/dist/cli.js +249 -0
  20. package/dist/cli.js.map +1 -0
  21. package/dist/commands.d.ts +31 -0
  22. package/dist/commands.js +269 -0
  23. package/dist/commands.js.map +1 -0
  24. package/dist/context.d.ts +50 -0
  25. package/dist/context.js +77 -0
  26. package/dist/context.js.map +1 -0
  27. package/dist/deploy.d.ts +57 -0
  28. package/dist/deploy.js +247 -0
  29. package/dist/deploy.js.map +1 -0
  30. package/dist/graph.d.ts +20 -0
  31. package/dist/graph.js +73 -0
  32. package/dist/graph.js.map +1 -0
  33. package/dist/init.d.ts +4 -0
  34. package/dist/init.js +92 -0
  35. package/dist/init.js.map +1 -0
  36. package/dist/logger.d.ts +18 -0
  37. package/dist/logger.js +34 -0
  38. package/dist/logger.js.map +1 -0
  39. package/dist/microvms.d.ts +15 -0
  40. package/dist/microvms.js +56 -0
  41. package/dist/microvms.js.map +1 -0
  42. package/dist/nodes.d.ts +34 -0
  43. package/dist/nodes.js +858 -0
  44. package/dist/nodes.js.map +1 -0
  45. package/dist/ports.d.ts +25 -0
  46. package/dist/ports.js +7 -0
  47. package/dist/ports.js.map +1 -0
  48. package/dist/render.d.ts +33 -0
  49. package/dist/render.js +81 -0
  50. package/dist/render.js.map +1 -0
  51. package/dist/repo.d.ts +25 -0
  52. package/dist/repo.js +75 -0
  53. package/dist/repo.js.map +1 -0
  54. package/dist/rkey.d.ts +6 -0
  55. package/dist/rkey.js +7 -0
  56. package/dist/rkey.js.map +1 -0
  57. package/dist/seo.d.ts +15 -0
  58. package/dist/seo.js +30 -0
  59. package/dist/seo.js.map +1 -0
  60. package/dist/test-support.d.ts +44 -0
  61. package/dist/test-support.js +120 -0
  62. package/dist/test-support.js.map +1 -0
  63. package/package.json +57 -0
package/dist/deploy.js ADDED
@@ -0,0 +1,247 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { networkConnectors, pollUntil, sleep } from 'blogwright-core';
3
+ import { formatDuration, spinnerFrame } from './render.js';
4
+ import { resolveSeo } from './seo.js';
5
+ function manifestKey(hash) {
6
+ return `build/manifests/${hash}.json`;
7
+ }
8
+ /**
9
+ * Nudge the MicroVM's endpoint to wake its event loop. A Firecracker-resumed process
10
+ * can sit idle with its poll timer pending until some I/O arrives; a connection here —
11
+ * even one the agent's HTTP/1 server can't fully parse — wakes the loop so the timer
12
+ * fires and the build starts. The wake-up, not the response, is the point: a missing
13
+ * endpoint or token means nothing to nudge, and a rejecting ping never fails the poll.
14
+ */
15
+ async function nudge(ctx, endpoint, token) {
16
+ if (!endpoint || !token)
17
+ return;
18
+ await ctx.ports.ping(endpoint, token).catch(() => undefined);
19
+ }
20
+ /**
21
+ * Resolve the MicroVM build log group from bootstrapped state rather than re-deriving it.
22
+ * The image and the build-role IAM policy bake this name in at bootstrap, so if the derived
23
+ * name (`ctx.names.microvmLogGroup`) is renamed in code later, a deploy must still target
24
+ * the group the running stack actually logs to — otherwise the VM's logs land in (or are
25
+ * denied to) a different group and `pollBuild` waits on an empty one.
26
+ */
27
+ export function microvmLogGroup(ctx) {
28
+ const recorded = ctx.state.resources['microvm-image']?.logGroup;
29
+ if (typeof recorded === 'string' && recorded)
30
+ return recorded;
31
+ const arn = ctx.state.resources['microvm-log-group']?.arn;
32
+ if (typeof arn === 'string') {
33
+ // arn:aws:logs:<region>:<acct>:log-group:<name>:*
34
+ const name = arn.split(':log-group:')[1]?.replace(/:\*$/, '');
35
+ if (name)
36
+ return name;
37
+ }
38
+ return ctx.names.microvmLogGroup;
39
+ }
40
+ /**
41
+ * Poll the CloudWatch build log group for the agent's output, streaming new lines and
42
+ * detecting the terminal marker (the source of truth). Each cycle also nudges the VM
43
+ * endpoint to keep the agent's event loop alive so the build triggers reliably.
44
+ * Exported for tests.
45
+ */
46
+ export async function pollBuild(ctx, hash, startTime, endpoint, token) {
47
+ const seen = new Set();
48
+ // Anchored at VM launch (startTime), like the VM's own maximumDuration —
49
+ // anchoring at poll start would keep polling a VM that is already dead for
50
+ // however long the RUNNING-wait consumed. One grace minute for log delivery.
51
+ const deadline = startTime + ctx.config.microvm.maxDurationSeconds * 1000 + 60_000;
52
+ const { terminal } = ctx.ports;
53
+ let tick = 0;
54
+ try {
55
+ for (;;) {
56
+ terminal.status(`${spinnerFrame(tick++)} building ${hash} in MicroVM … ${formatDuration(Date.now() - startTime)}`);
57
+ await nudge(ctx, endpoint, token);
58
+ const events = await ctx.clients.logs
59
+ .filterEvents(microvmLogGroup(ctx), { startTime })
60
+ .catch(() => []);
61
+ events.sort((a, b) => a.timestamp - b.timestamp);
62
+ let result;
63
+ for (const e of events) {
64
+ if (seen.has(e.eventId))
65
+ continue;
66
+ seen.add(e.eventId);
67
+ ctx.logger.info(` ${e.message.trimEnd()}`);
68
+ // Hash-scoped structured markers so unrelated log lines (raw pnpm/astro output, or
69
+ // an orphaned VM from a different deploy) can't be mistaken for this build's result.
70
+ if (e.message.includes(`##build:done:${hash}`))
71
+ result = { state: 'done' };
72
+ else if (e.message.includes(`##build:failed:${hash}`)) {
73
+ result = {
74
+ state: 'failed',
75
+ message: e.message.split(`##build:failed:${hash}:`)[1]?.trim(),
76
+ };
77
+ }
78
+ }
79
+ if (result)
80
+ return result;
81
+ // Log delivery can lag or drop (e.g. the VM logging to a group the deploy isn't tailing).
82
+ // The agent writes build/changed/<hash>.json as its final step, so treat that artifact as
83
+ // an authoritative completion signal too — a successful build then can't hang until the
84
+ // deadline just because its logs never reached CloudWatch. runBuild clears any stale copy
85
+ // before launch, so its presence means *this* build finished.
86
+ if (await ctx.clients.s3.objectExists(ctx.names.bucket, `build/changed/${hash}.json`)) {
87
+ return { state: 'done' };
88
+ }
89
+ if (Date.now() >= deadline)
90
+ return { state: 'failed', message: 'build timed out' };
91
+ await sleep(3000);
92
+ }
93
+ }
94
+ finally {
95
+ terminal.status('');
96
+ }
97
+ }
98
+ /**
99
+ * Run the builder MicroVM against a source zip already present at `sourceKey`, stream
100
+ * its logs, terminate it, and record a deployment manifest. Shared by deploy + rollback.
101
+ */
102
+ export async function runBuild(ctx, opts) {
103
+ const sitePrefix = opts.sitePrefix ?? 'site/';
104
+ const seo = resolveSeo(ctx, opts.baseUrl);
105
+ const pendingKey = `build/pending/${opts.target ?? 'site'}.json`;
106
+ const imageArn = ctx.state.resources['microvm-image']?.arn;
107
+ const execRoleArn = ctx.state.resources['iam-exec-role']?.arn;
108
+ if (typeof imageArn !== 'string' || typeof execRoleArn !== 'string') {
109
+ throw new Error('infrastructure not bootstrapped (missing MicroVM image or exec role); run bootstrap first');
110
+ }
111
+ // Clear any stale completion manifest from a prior aborted build of this same hash, so its
112
+ // reappearance is an unambiguous "this build finished" signal for pollBuild's S3 fallback.
113
+ await ctx.clients.s3
114
+ .deleteObject(ctx.names.bucket, `build/changed/${opts.hash}.json`)
115
+ .catch(() => undefined);
116
+ const connectors = networkConnectors(ctx.config.region);
117
+ const startedAt = new Date();
118
+ ctx.logger.step(`running builder MicroVM for ${opts.hash}`);
119
+ // Launch first: if runMicrovm throws, no pending.json is left behind (a leaked job
120
+ // could otherwise be picked up during a later image bake and poison the snapshot).
121
+ // A MicroVM is ephemeral compute, so the client token is unique per launch — keying it
122
+ // on the hash would make a re-deploy (or workflow re-run) of the same source idempotently
123
+ // return the ALREADY-TERMINATED original VM instead of launching a fresh one. Generated
124
+ // once here so a network retry of this single call still dedupes.
125
+ const clientToken = `run-${opts.hash}-${randomUUID()}`;
126
+ const run = await ctx.clients.microvms.runMicrovm({
127
+ imageIdentifier: imageArn,
128
+ executionRoleArn: execRoleArn,
129
+ clientToken,
130
+ maximumDurationInSeconds: ctx.config.microvm.maxDurationSeconds,
131
+ idlePolicy: ctx.config.microvm.idle,
132
+ ingressNetworkConnectors: [connectors.allIngress],
133
+ egressNetworkConnectors: [connectors.internetEgress],
134
+ logGroupName: microvmLogGroup(ctx),
135
+ });
136
+ let result = { state: 'failed', message: 'did not start' };
137
+ try {
138
+ // The agent (booting ~30s) polls this for the job; write it now that the VM exists.
139
+ await ctx.clients.s3.putObject(ctx.names.bucket, pendingKey, JSON.stringify({
140
+ hash: opts.hash,
141
+ sourceKey: opts.sourceKey,
142
+ sitePrefix,
143
+ // Repo-relative build cwd and output dir (config paths.app/paths.dist);
144
+ // older agents ignore unknown fields and use their root/dist defaults.
145
+ appDir: ctx.config.paths.app,
146
+ distDir: ctx.config.paths.dist,
147
+ ...seo,
148
+ }), 'application/json');
149
+ // Confirm the MicroVM actually reached RUNNING (a FAILED launch must not fall through
150
+ // to the build poll, which would otherwise wait out the full maxDuration).
151
+ const ready = await pollUntil(() => ctx.clients.microvms.getMicrovm(run.microvmId), (vm) => Boolean(vm) && /RUNNING|FAILED|TERMINATED/i.test(vm?.state ?? ''), { intervalMs: 3000, timeoutMs: 300_000 });
152
+ if (!ready || !/RUNNING/i.test(ready.state)) {
153
+ throw new Error(`MicroVM did not reach RUNNING (state=${ready?.state ?? 'unknown'})`);
154
+ }
155
+ // Nudge the endpoint each cycle to keep the agent's event loop awake; the token must
156
+ // outlive the build window.
157
+ const endpoint = run.endpoint || ready.endpoint || '';
158
+ const tokenMinutes = Math.min(60, Math.ceil(ctx.config.microvm.maxDurationSeconds / 60) + 1);
159
+ const token = await ctx.clients.microvms
160
+ .createAuthToken(run.microvmId, tokenMinutes)
161
+ .catch(() => '');
162
+ result = await pollBuild(ctx, opts.hash, startedAt.getTime(), endpoint, token);
163
+ }
164
+ finally {
165
+ // The pending-job cleanup must survive a terminate failure: a leaked
166
+ // pending.json is the poison-the-next-image-bake hazard the launch
167
+ // ordering above exists to avoid. A failed terminate is only logged —
168
+ // the VM self-terminates at maxDuration, and the build outcome (already
169
+ // determined) must not be masked by a cleanup error.
170
+ await ctx.clients.microvms.terminateMicrovm(run.microvmId).catch((err) => {
171
+ ctx.logger.warn(`failed to terminate MicroVM ${run.microvmId} (it will expire on its own): ` +
172
+ (err instanceof Error ? err.message : String(err)));
173
+ });
174
+ // Clear the job so a future MicroVM does not re-run it.
175
+ await ctx.clients.s3.deleteObject(ctx.names.bucket, pendingKey).catch(() => undefined);
176
+ }
177
+ const finishedAt = new Date();
178
+ const manifest = {
179
+ hash: opts.hash,
180
+ sourceKey: opts.sourceKey,
181
+ status: result.state === 'done' ? 'succeeded' : 'failed',
182
+ startedAt: startedAt.toISOString(),
183
+ finishedAt: finishedAt.toISOString(),
184
+ durationMs: finishedAt.getTime() - startedAt.getTime(),
185
+ microvmId: run.microvmId,
186
+ ...(result.message ? { message: result.message } : {}),
187
+ };
188
+ await ctx.clients.s3.putObject(ctx.names.bucket, manifestKey(opts.hash), JSON.stringify(manifest, null, 2), 'application/json');
189
+ if (manifest.status === 'failed') {
190
+ throw new Error(`build failed for ${opts.hash}${manifest.message ? `: ${manifest.message}` : ''}`);
191
+ }
192
+ return manifest;
193
+ }
194
+ export async function invalidateCloudFront(ctx, paths = ['/*']) {
195
+ const id = ctx.state.resources['cloudfront-distribution']?.id;
196
+ if (typeof id !== 'string' || paths.length === 0)
197
+ return;
198
+ await ctx.clients.cloudfront.createInvalidation(id, paths, `inv-${Date.now()}`);
199
+ const shown = paths.length > 6
200
+ ? `${paths.slice(0, 6).join(', ')}, …(${paths.length} total)`
201
+ : paths.join(', ');
202
+ ctx.logger.ok(`CloudFront invalidation requested (${shown})`);
203
+ }
204
+ /**
205
+ * Invalidate only the URL paths that changed in this build (from the agent's manifest),
206
+ * so unchanged pages stay cached. Falls back to `/*` when the manifest is missing or the
207
+ * changed set is larger than the configured cap.
208
+ */
209
+ export async function invalidateChanged(ctx, hash) {
210
+ const text = await ctx.clients.s3
211
+ .getObjectText(ctx.names.bucket, `build/changed/${hash}.json`)
212
+ .catch(() => undefined);
213
+ let paths;
214
+ if (text) {
215
+ try {
216
+ paths = JSON.parse(text).paths;
217
+ }
218
+ catch {
219
+ /* fall through to the /* fallback */
220
+ }
221
+ }
222
+ if (!paths) {
223
+ ctx.logger.warn('no changed-paths manifest — invalidating everything (/*)');
224
+ await invalidateCloudFront(ctx, ['/*']);
225
+ return { mode: 'all', count: 0 };
226
+ }
227
+ let summary;
228
+ if (paths.length === 0) {
229
+ ctx.logger.ok('no content changed — skipping CloudFront invalidation');
230
+ summary = { mode: 'none', count: 0 };
231
+ }
232
+ else if (paths.length > ctx.config.invalidationMaxPaths) {
233
+ ctx.logger.step(`${paths.length} paths changed (> cap) — invalidating everything (/*)`);
234
+ await invalidateCloudFront(ctx, ['/*']);
235
+ summary = { mode: 'all', count: paths.length };
236
+ }
237
+ else {
238
+ await invalidateCloudFront(ctx, paths);
239
+ summary = { mode: 'paths', count: paths.length };
240
+ }
241
+ await ctx.clients.s3
242
+ .deleteObject(ctx.names.bucket, `build/changed/${hash}.json`)
243
+ .catch(() => undefined);
244
+ return summary;
245
+ }
246
+ export { manifestKey };
247
+ //# sourceMappingURL=deploy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deploy.js","sourceRoot":"","sources":["../src/deploy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { OpsContext } from './context.js';
2
+ /** A node in the infrastructure dependency graph. */
3
+ export interface ResourceNode {
4
+ id: string;
5
+ dependsOn: string[];
6
+ /** Human label for logging. */
7
+ title: string;
8
+ /** Does the resource already exist? (Also hydrates outputs into ctx.state.) */
9
+ read(ctx: OpsContext): Promise<boolean>;
10
+ create(ctx: OpsContext): Promise<void>;
11
+ /** Reconcile an existing resource (optional). */
12
+ update?(ctx: OpsContext): Promise<void>;
13
+ delete(ctx: OpsContext): Promise<void>;
14
+ }
15
+ /** Topologically order nodes so dependencies come before dependents (Kahn's algorithm). */
16
+ export declare function topoSort(nodes: ResourceNode[]): ResourceNode[];
17
+ /** Reconcile the graph in dependency order (create missing, update existing). */
18
+ export declare function applyGraph(nodes: ResourceNode[], ctx: OpsContext): Promise<void>;
19
+ /** Tear down the graph in reverse dependency order. */
20
+ export declare function destroyGraph(nodes: ResourceNode[], ctx: OpsContext): Promise<void>;
package/dist/graph.js ADDED
@@ -0,0 +1,73 @@
1
+ /** Topologically order nodes so dependencies come before dependents (Kahn's algorithm). */
2
+ export function topoSort(nodes) {
3
+ const byId = new Map(nodes.map((n) => [n.id, n]));
4
+ const indegree = new Map();
5
+ const dependents = new Map();
6
+ for (const node of nodes) {
7
+ indegree.set(node.id, 0);
8
+ dependents.set(node.id, []);
9
+ }
10
+ for (const node of nodes) {
11
+ for (const dep of node.dependsOn) {
12
+ if (!byId.has(dep))
13
+ throw new Error(`node "${node.id}" depends on unknown node "${dep}"`);
14
+ indegree.set(node.id, (indegree.get(node.id) ?? 0) + 1);
15
+ dependents.get(dep).push(node.id);
16
+ }
17
+ }
18
+ const queue = [...indegree.entries()]
19
+ .filter(([, d]) => d === 0)
20
+ .map(([id]) => id)
21
+ .sort();
22
+ const order = [];
23
+ while (queue.length > 0) {
24
+ const id = queue.shift();
25
+ order.push(byId.get(id));
26
+ for (const dependent of dependents.get(id)) {
27
+ const d = (indegree.get(dependent) ?? 0) - 1;
28
+ indegree.set(dependent, d);
29
+ if (d === 0) {
30
+ queue.push(dependent);
31
+ queue.sort();
32
+ }
33
+ }
34
+ }
35
+ if (order.length !== nodes.length)
36
+ throw new Error('dependency cycle detected in resource graph');
37
+ return order;
38
+ }
39
+ /** Reconcile the graph in dependency order (create missing, update existing). */
40
+ export async function applyGraph(nodes, ctx) {
41
+ for (const node of topoSort(nodes)) {
42
+ const exists = await node.read(ctx);
43
+ if (exists) {
44
+ if (node.update) {
45
+ ctx.logger.step(`reconcile ${node.title}`);
46
+ await node.update(ctx);
47
+ }
48
+ else {
49
+ ctx.logger.ok(`${node.title} (exists)`);
50
+ }
51
+ }
52
+ else {
53
+ ctx.logger.step(`create ${node.title}`);
54
+ await node.create(ctx);
55
+ ctx.logger.ok(`created ${node.title}`);
56
+ }
57
+ await ctx.save();
58
+ }
59
+ }
60
+ /** Tear down the graph in reverse dependency order. */
61
+ export async function destroyGraph(nodes, ctx) {
62
+ const order = topoSort(nodes).reverse();
63
+ for (const node of order) {
64
+ ctx.logger.step(`delete ${node.title}`);
65
+ await node.delete(ctx);
66
+ delete ctx.state.resources[node.id];
67
+ // The state lives in the bucket that is itself being deleted, so persisting it may
68
+ // fail (NoSuchBucket) once the bucket node is gone — never let that abort teardown.
69
+ await ctx.save().catch(() => undefined);
70
+ ctx.logger.ok(`deleted ${node.title}`);
71
+ }
72
+ }
73
+ //# sourceMappingURL=graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph.js","sourceRoot":"","sources":["../src/graph.ts"],"names":[],"mappings":"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"}
package/dist/init.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import { type FileSystem, type Terminal } from 'blogwright-core';
2
+ import type { Logger } from './logger.js';
3
+ /** Run the wizard. Returns a process exit code; never throws for expected refusals. */
4
+ export declare function initSite(fs: FileSystem, terminal: Terminal, logger: Logger, root?: string): Promise<number>;
package/dist/init.js ADDED
@@ -0,0 +1,92 @@
1
+ /*
2
+ * `blogwright init` — the first-run wizard. Asks the four questions a new site
3
+ * needs, writes a commented config/production.jsonc, and prints the path to a
4
+ * live site. Runs before any context exists (there is no config to load yet),
5
+ * so it takes its ports directly.
6
+ */
7
+ import { colors, findRepoRoot } from 'blogwright-core';
8
+ const SITE_NAME_PATTERN = /^[a-z0-9-]+$/;
9
+ const GITHUB_REPO_PATTERN = /^[\w.-]+\/[\w.-]+$/;
10
+ const MAX_ATTEMPTS = 3;
11
+ async function ask(terminal, logger, q) {
12
+ for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
13
+ const suffix = q.defaultValue ? ` [${q.defaultValue}]` : '';
14
+ const answer = (await terminal.question(`${q.prompt}${suffix}: `)).trim() || q.defaultValue;
15
+ if (!answer) {
16
+ if (!q.required)
17
+ return undefined;
18
+ logger.warn('a value is required');
19
+ continue;
20
+ }
21
+ const problem = q.validate?.(answer);
22
+ if (problem) {
23
+ logger.warn(problem);
24
+ continue;
25
+ }
26
+ return answer;
27
+ }
28
+ throw new Error(`no valid answer after ${MAX_ATTEMPTS} attempts — giving up`);
29
+ }
30
+ function renderConfig(opts) {
31
+ const entries = [
32
+ { prop: `"region": "${opts.region}"` },
33
+ {
34
+ prop: `"siteName": "${opts.siteName}"`,
35
+ comment: 'stable slug in every AWS resource name — never change it',
36
+ },
37
+ ];
38
+ if (opts.domain)
39
+ entries.push({ prop: `"domain": "${opts.domain}"` });
40
+ if (opts.githubRepo) {
41
+ entries.push({
42
+ prop: `"githubRepo": "${opts.githubRepo}"`,
43
+ comment: 'enables the GitHub OIDC deploy role',
44
+ });
45
+ }
46
+ const body = entries.map((e, i) => {
47
+ const comma = i < entries.length - 1 ? ',' : '';
48
+ return ` ${e.prop}${comma}${e.comment ? ` // ${e.comment}` : ''}`;
49
+ });
50
+ return ['// config/production.jsonc — created by `blogwright init`', '{', ...body, '}', ''].join('\n');
51
+ }
52
+ /** Run the wizard. Returns a process exit code; never throws for expected refusals. */
53
+ export async function initSite(fs, terminal, logger, root) {
54
+ if (!terminal.isInteractive) {
55
+ logger.error('init is an interactive wizard; in CI or plain mode create config/production.jsonc ' +
56
+ 'by hand instead (see README — only "region" and "siteName" are required)');
57
+ return 1;
58
+ }
59
+ const repoRoot = root ?? (await findRepoRoot(fs).catch(() => process.cwd()));
60
+ const configPath = `${repoRoot}/config/production.jsonc`;
61
+ if (await fs.exists(configPath)) {
62
+ logger.error(`${configPath} already exists — edit it directly, or pass --config elsewhere`);
63
+ return 1;
64
+ }
65
+ logger.info(colors.bold('Welcome to blogwright — four questions and you are live.'));
66
+ const siteName = await ask(terminal, logger, {
67
+ prompt: 'site name (lowercase slug, names every AWS resource)',
68
+ required: true,
69
+ validate: (v) => SITE_NAME_PATTERN.test(v) ? undefined : 'must be lowercase letters, digits, or dashes',
70
+ });
71
+ const region = await ask(terminal, logger, {
72
+ prompt: 'AWS region',
73
+ defaultValue: 'us-east-1',
74
+ required: true,
75
+ });
76
+ const domain = await ask(terminal, logger, {
77
+ prompt: 'custom domain (blank to use the CloudFront domain)',
78
+ });
79
+ const githubRepo = await ask(terminal, logger, {
80
+ prompt: 'GitHub repo for CI deploys, owner/repo (blank to skip)',
81
+ validate: (v) => (GITHUB_REPO_PATTERN.test(v) ? undefined : 'expected owner/repo'),
82
+ });
83
+ await fs.writeText(configPath, renderConfig({ region: region, siteName: siteName, domain, githubRepo }));
84
+ logger.ok(`wrote ${configPath}`);
85
+ logger.info('');
86
+ logger.info(colors.bold('Next steps:'));
87
+ logger.step(`blogwright bootstrap${domain ? '' : ' # add --domain later if you get one'}`);
88
+ logger.step('blogwright deploy');
89
+ logger.info(colors.dim(' (bootstrap prints ACM validation CNAMEs when a domain is set)'));
90
+ return 0;
91
+ }
92
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import { type Terminal } from 'blogwright-core';
2
+ export interface Logger {
3
+ info(msg: string): void;
4
+ step(msg: string): void;
5
+ ok(msg: string): void;
6
+ warn(msg: string): void;
7
+ error(msg: string): void;
8
+ }
9
+ /** Build the leveled logger: info/step/ok to standard output, warn/error to standard error. */
10
+ export declare function createLogger(terminal: Terminal): Logger;
11
+ /**
12
+ * Ask a yes/no question on the terminal. Returns the default for a
13
+ * non-interactive session (CI or a piped invocation) so automation isn't left
14
+ * hanging on a prompt.
15
+ */
16
+ export declare function confirm(terminal: Terminal, question: string, opts?: {
17
+ defaultYes?: boolean;
18
+ }): Promise<boolean>;
package/dist/logger.js ADDED
@@ -0,0 +1,34 @@
1
+ /*
2
+ * Leveled logger and confirm prompt over the Terminal port. Messages are
3
+ * composed with ANSI colour; the logger strips the codes for non-interactive
4
+ * sessions (piped output, CI) so they stay plain text.
5
+ */
6
+ import { colors, stripColors } from 'blogwright-core';
7
+ /** Build the leveled logger: info/step/ok to standard output, warn/error to standard error. */
8
+ export function createLogger(terminal) {
9
+ const render = (msg) => (terminal.isInteractive ? msg : stripColors(msg));
10
+ return {
11
+ info: (msg) => terminal.write(render(msg)),
12
+ step: (msg) => terminal.write(render(`${colors.cyan('›')} ${msg}`)),
13
+ ok: (msg) => terminal.write(render(`${colors.green('✓')} ${msg}`)),
14
+ warn: (msg) => terminal.error(render(`${colors.yellow('!')} ${msg}`)),
15
+ error: (msg) => terminal.error(render(`${colors.red('✗')} ${msg}`)),
16
+ };
17
+ }
18
+ /**
19
+ * Ask a yes/no question on the terminal. Returns the default for a
20
+ * non-interactive session (CI or a piped invocation) so automation isn't left
21
+ * hanging on a prompt.
22
+ */
23
+ export async function confirm(terminal, question, opts = {}) {
24
+ const defaultYes = opts.defaultYes ?? true;
25
+ if (!terminal.isInteractive)
26
+ return defaultYes;
27
+ const answer = (await terminal.question(`${question} [${defaultYes ? 'Y/n' : 'y/N'}] `))
28
+ .trim()
29
+ .toLowerCase();
30
+ if (answer === '')
31
+ return defaultYes;
32
+ return answer === 'y' || answer === 'yes';
33
+ }
34
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,EAAE,WAAW,EAAiB,MAAM,iBAAiB,CAAC;AAUrE,+FAA+F;AAC/F,MAAM,UAAU,YAAY,CAAC,QAAkB;IAC7C,MAAM,MAAM,GAAG,CAAC,GAAW,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;IAClF,OAAO;QACL,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC1C,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;QACnE,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;QAClE,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;QACrE,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;KACpE,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,QAAkB,EAClB,QAAgB,EAChB,OAAiC,EAAE;IAEnC,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC;IAC3C,IAAI,CAAC,QAAQ,CAAC,aAAa;QAAE,OAAO,UAAU,CAAC;IAC/C,MAAM,MAAM,GAAG,CAAC,MAAM,QAAQ,CAAC,QAAQ,CAAC,GAAG,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;SACrF,IAAI,EAAE;SACN,WAAW,EAAE,CAAC;IACjB,IAAI,MAAM,KAAK,EAAE;QAAE,OAAO,UAAU,CAAC;IACrC,OAAO,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,KAAK,CAAC;AAC5C,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { type Microvm } from 'blogwright-core';
2
+ import type { OpsContext } from './context.js';
3
+ /**
4
+ * Active MicroVMs launched from *this* environment's builder image. Matches on the image
5
+ * ARN recorded in state, falling back to the (env-scoped) image name so a stack whose
6
+ * state predates the ARN still resolves — and a sibling env's VMs are never touched.
7
+ */
8
+ export declare function runningStackMicrovms(ctx: OpsContext): Promise<Microvm[]>;
9
+ /**
10
+ * Guard a destroy/teardown against running builder MicroVMs (deleting the image 400s while
11
+ * any are alive). Lists them, then interactively offers to terminate — default yes — or to
12
+ * wait, which cancels the destroy. Non-interactive callers get the default (terminate) so
13
+ * automation isn't blocked. Returns true to proceed with the destroy, false to abort.
14
+ */
15
+ export declare function clearRunningMicrovms(ctx: OpsContext): Promise<boolean>;
@@ -0,0 +1,56 @@
1
+ import { colors, pollUntil } from 'blogwright-core';
2
+ import { confirm } from './logger.js';
3
+ /** A MicroVM keeps its image pinned until it fully terminates, so anything that isn't
4
+ * already TERMINATED/TERMINATING still blocks deleting the builder image. */
5
+ function isActive(vm) {
6
+ return !/TERMINATED|TERMINATING/i.test(vm.state);
7
+ }
8
+ /**
9
+ * Active MicroVMs launched from *this* environment's builder image. Matches on the image
10
+ * ARN recorded in state, falling back to the (env-scoped) image name so a stack whose
11
+ * state predates the ARN still resolves — and a sibling env's VMs are never touched.
12
+ */
13
+ export async function runningStackMicrovms(ctx) {
14
+ const imageArn = ctx.state.resources['microvm-image']?.arn;
15
+ const imageName = ctx.names.microvmImage;
16
+ // Scope server-side by the builder image when we know its ARN; the client-side filter
17
+ // below is kept as a guard (and covers the name-only fallback when state has no ARN yet).
18
+ const all = await ctx.clients.microvms.listMicrovms(typeof imageArn === 'string' ? { imageIdentifier: imageArn } : {});
19
+ return all.filter((vm) => isActive(vm) &&
20
+ ((typeof imageArn === 'string' && vm.imageArn === imageArn) ||
21
+ (vm.imageArn?.includes(imageName) ?? false)));
22
+ }
23
+ /**
24
+ * Guard a destroy/teardown against running builder MicroVMs (deleting the image 400s while
25
+ * any are alive). Lists them, then interactively offers to terminate — default yes — or to
26
+ * wait, which cancels the destroy. Non-interactive callers get the default (terminate) so
27
+ * automation isn't blocked. Returns true to proceed with the destroy, false to abort.
28
+ */
29
+ export async function clearRunningMicrovms(ctx) {
30
+ const running = await runningStackMicrovms(ctx);
31
+ if (running.length === 0)
32
+ return true;
33
+ ctx.logger.warn(colors.yellow(`${running.length} builder MicroVM(s) must be terminated before the image can be deleted:`));
34
+ for (const vm of running)
35
+ ctx.logger.info(` ${vm.microvmId} (${vm.state})`);
36
+ const proceed = await confirm(ctx.ports.terminal, 'Terminate them and continue?', {
37
+ defaultYes: true,
38
+ });
39
+ if (!proceed) {
40
+ ctx.logger.info('Leaving MicroVMs running — destroy cancelled.');
41
+ return false;
42
+ }
43
+ for (const vm of running) {
44
+ ctx.logger.step(`terminating ${vm.microvmId}`);
45
+ await ctx.clients.microvms.terminateMicrovm(vm.microvmId);
46
+ }
47
+ // Wait for them to actually clear, so the image delete later in the graph doesn't 400.
48
+ ctx.logger.step('waiting for MicroVMs to terminate…');
49
+ const remaining = await pollUntil(() => runningStackMicrovms(ctx), (vms) => vms.length === 0, { intervalMs: 5000, timeoutMs: 180_000 });
50
+ if (remaining.length > 0) {
51
+ throw new Error(`${remaining.length} MicroVM(s) did not terminate in time; re-run destroy once they clear`);
52
+ }
53
+ ctx.logger.ok('MicroVMs terminated');
54
+ return true;
55
+ }
56
+ //# sourceMappingURL=microvms.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"microvms.js","sourceRoot":"","sources":["../src/microvms.ts"],"names":[],"mappings":"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"}