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.
- package/LICENSE +24 -0
- package/README.md +12 -0
- package/agent/Dockerfile +20 -0
- package/agent/agent-manifest.json +3 -0
- package/agent/server.js +7935 -0
- package/dist/adapters/fetch-ping.d.ts +9 -0
- package/dist/adapters/fetch-ping.js +26 -0
- package/dist/adapters/fetch-ping.js.map +1 -0
- package/dist/adapters/process-vcs.d.ts +12 -0
- package/dist/adapters/process-vcs.js +49 -0
- package/dist/adapters/process-vcs.js.map +1 -0
- package/dist/agent-package.d.ts +19 -0
- package/dist/agent-package.js +49 -0
- package/dist/agent-package.js.map +1 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +14 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +249 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands.d.ts +31 -0
- package/dist/commands.js +269 -0
- package/dist/commands.js.map +1 -0
- package/dist/context.d.ts +50 -0
- package/dist/context.js +77 -0
- package/dist/context.js.map +1 -0
- package/dist/deploy.d.ts +57 -0
- package/dist/deploy.js +247 -0
- package/dist/deploy.js.map +1 -0
- package/dist/graph.d.ts +20 -0
- package/dist/graph.js +73 -0
- package/dist/graph.js.map +1 -0
- package/dist/init.d.ts +4 -0
- package/dist/init.js +92 -0
- package/dist/init.js.map +1 -0
- package/dist/logger.d.ts +18 -0
- package/dist/logger.js +34 -0
- package/dist/logger.js.map +1 -0
- package/dist/microvms.d.ts +15 -0
- package/dist/microvms.js +56 -0
- package/dist/microvms.js.map +1 -0
- package/dist/nodes.d.ts +34 -0
- package/dist/nodes.js +858 -0
- package/dist/nodes.js.map +1 -0
- package/dist/ports.d.ts +25 -0
- package/dist/ports.js +7 -0
- package/dist/ports.js.map +1 -0
- package/dist/render.d.ts +33 -0
- package/dist/render.js +81 -0
- package/dist/render.js.map +1 -0
- package/dist/repo.d.ts +25 -0
- package/dist/repo.js +75 -0
- package/dist/repo.js.map +1 -0
- package/dist/rkey.d.ts +6 -0
- package/dist/rkey.js +7 -0
- package/dist/rkey.js.map +1 -0
- package/dist/seo.d.ts +15 -0
- package/dist/seo.js +30 -0
- package/dist/seo.js.map +1 -0
- package/dist/test-support.d.ts +44 -0
- package/dist/test-support.js +120 -0
- package/dist/test-support.js.map +1 -0
- package/package.json +57 -0
package/dist/commands.js
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { colors, findRepoRoot } from 'blogwright-core';
|
|
2
|
+
import { syncAfterDeploy } from 'blogwright-pds';
|
|
3
|
+
import { invalidateChanged, invalidateCloudFront, manifestKey, microvmLogGroup, runBuild, } from './deploy.js';
|
|
4
|
+
import { applyGraph, destroyGraph } from './graph.js';
|
|
5
|
+
import { clearRunningMicrovms } from './microvms.js';
|
|
6
|
+
import { buildNodes, reconcileBuilderImage } from './nodes.js';
|
|
7
|
+
import { formatDuration, renderHistoryTable, renderStatusTree, renderSummary, } from './render.js';
|
|
8
|
+
import { buildRepoZip, COMMIT_FILE, listRepoFiles } from './repo.js';
|
|
9
|
+
/** One line per invalidation outcome, shared by the summary card and logs. */
|
|
10
|
+
function describeInvalidation(inv) {
|
|
11
|
+
if (inv.mode === 'none')
|
|
12
|
+
return 'nothing changed — skipped';
|
|
13
|
+
if (inv.mode === 'paths')
|
|
14
|
+
return `${inv.count} changed path${inv.count === 1 ? '' : 's'}`;
|
|
15
|
+
return inv.count > 0 ? `everything (/*) — ${inv.count} paths over cap` : 'everything (/*)';
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Canonical origin the live site is served from: the custom domain if configured,
|
|
19
|
+
* else the distribution's CloudFront domain. Used for robots.txt / sitemap.xml URLs.
|
|
20
|
+
*/
|
|
21
|
+
function siteBaseUrl(ctx) {
|
|
22
|
+
if (ctx.domain)
|
|
23
|
+
return `https://${ctx.domain}`;
|
|
24
|
+
const cf = ctx.state.resources['cloudfront-distribution']?.domainName;
|
|
25
|
+
return typeof cf === 'string' ? `https://${cf}` : undefined;
|
|
26
|
+
}
|
|
27
|
+
/** Create the full infrastructure graph. */
|
|
28
|
+
export async function bootstrap(ctx) {
|
|
29
|
+
ctx.logger.info(colors.bold(`Bootstrapping "${ctx.env}" (bucket ${ctx.names.bucket})`));
|
|
30
|
+
// The state bucket must exist before anything else can persist state.
|
|
31
|
+
await applyGraph(buildNodes(ctx), ctx);
|
|
32
|
+
ctx.logger.ok(`bootstrap complete for "${ctx.env}"`);
|
|
33
|
+
const domain = ctx.state.resources['cloudfront-distribution']?.domainName;
|
|
34
|
+
if (typeof domain === 'string')
|
|
35
|
+
ctx.logger.info(`Site will be served at https://${domain}`);
|
|
36
|
+
}
|
|
37
|
+
/** Destroy the full infrastructure graph. */
|
|
38
|
+
export async function destroy(ctx, opts) {
|
|
39
|
+
if (!opts.yes) {
|
|
40
|
+
throw new Error(`refusing to destroy "${ctx.env}" without --yes`);
|
|
41
|
+
}
|
|
42
|
+
ctx.logger.info(colors.bold(`Destroying "${ctx.env}"`));
|
|
43
|
+
// Running builder MicroVMs pin the image and make its deletion fail; clear them first
|
|
44
|
+
// (or let the operator cancel and wait for in-flight builds to finish).
|
|
45
|
+
if (!(await clearRunningMicrovms(ctx)))
|
|
46
|
+
return;
|
|
47
|
+
await destroyGraph(buildNodes(ctx), ctx);
|
|
48
|
+
await ctx.store.delete();
|
|
49
|
+
ctx.logger.ok(`destroyed "${ctx.env}"`);
|
|
50
|
+
}
|
|
51
|
+
/** Zip the repo, upload it, run the builder MicroVM, and invalidate the cache. */
|
|
52
|
+
export async function deploy(ctx) {
|
|
53
|
+
const startedAt = Date.now();
|
|
54
|
+
const cwd = await findRepoRoot(ctx.ports.fs);
|
|
55
|
+
const hash = await ctx.ports.vcs.revisionHash(cwd);
|
|
56
|
+
ctx.logger.info(colors.bold(`Deploying ${hash} to "${ctx.env}"`));
|
|
57
|
+
const files = await listRepoFiles(ctx.ports, cwd, ctx.config.sourceIgnore, ctx.config.sourceInclude);
|
|
58
|
+
ctx.logger.step(`zipping ${files.length} files`);
|
|
59
|
+
const zip = await buildRepoZip(ctx.ports.fs, cwd, files, { [COMMIT_FILE]: hash });
|
|
60
|
+
const sourceKey = `build/${hash}.zip`;
|
|
61
|
+
await ctx.clients.s3.putObject(ctx.names.bucket, sourceKey, zip, 'application/zip');
|
|
62
|
+
ctx.logger.ok(`uploaded ${sourceKey} (${(zip.byteLength / 1024).toFixed(0)} KiB)`);
|
|
63
|
+
// Rebuild the builder image first if the agent bundle changed, so build-agent fixes
|
|
64
|
+
// ship on the same deploy (no-op when unchanged).
|
|
65
|
+
await reconcileBuilderImage(ctx);
|
|
66
|
+
const manifest = await runBuild(ctx, { hash, sourceKey, baseUrl: siteBaseUrl(ctx) });
|
|
67
|
+
const invalidation = await invalidateChanged(ctx, hash);
|
|
68
|
+
// Production content changed — mirror it to the PDS (non-fatal; see syncAfterDeploy).
|
|
69
|
+
await syncAfterDeploy(ctx);
|
|
70
|
+
const url = siteBaseUrl(ctx);
|
|
71
|
+
const rows = [
|
|
72
|
+
{ label: 'revision', value: hash },
|
|
73
|
+
{ label: 'environment', value: ctx.env },
|
|
74
|
+
{ label: 'source', value: `${files.length} files, ${(zip.byteLength / 1024).toFixed(0)} KiB` },
|
|
75
|
+
{ label: 'build', value: formatDuration(manifest.durationMs) },
|
|
76
|
+
{ label: 'invalidated', value: describeInvalidation(invalidation) },
|
|
77
|
+
...(url ? [{ label: 'site', value: colors.cyan(url) }] : []),
|
|
78
|
+
];
|
|
79
|
+
for (const line of renderSummary('deploy summary', rows, ctx.ports.terminal.isInteractive)) {
|
|
80
|
+
ctx.logger.info(line);
|
|
81
|
+
}
|
|
82
|
+
ctx.logger.ok(`deployed ${hash} in ${formatDuration(Date.now() - startedAt)}`);
|
|
83
|
+
}
|
|
84
|
+
/** Re-run the builder against an existing source zip for the given hash. */
|
|
85
|
+
export async function rollback(ctx, hash) {
|
|
86
|
+
const sourceKey = `build/${hash}.zip`;
|
|
87
|
+
if (!(await ctx.clients.s3.objectExists(ctx.names.bucket, sourceKey))) {
|
|
88
|
+
throw new Error(`no build artifact at ${sourceKey}; cannot roll back to ${hash}`);
|
|
89
|
+
}
|
|
90
|
+
ctx.logger.info(colors.bold(`Rolling back "${ctx.env}" to ${hash}`));
|
|
91
|
+
const startedAt = Date.now();
|
|
92
|
+
await runBuild(ctx, { hash, sourceKey, baseUrl: siteBaseUrl(ctx) });
|
|
93
|
+
await invalidateChanged(ctx, hash);
|
|
94
|
+
// A rollback changes production content too, but the PDS mirrors the *working tree*
|
|
95
|
+
// content, which a rollback does not restore — so only warn about the divergence.
|
|
96
|
+
if (ctx.env === 'production' && ctx.config.pds) {
|
|
97
|
+
ctx.logger.warn('rollback does not sync the PDS (records mirror the current repo content); ' +
|
|
98
|
+
'check out the rolled-back revision and run `blogwright pds sync` if needed');
|
|
99
|
+
}
|
|
100
|
+
ctx.logger.ok(`rolled back to ${hash} in ${formatDuration(Date.now() - startedAt)}`);
|
|
101
|
+
}
|
|
102
|
+
function assertPreviewId(id) {
|
|
103
|
+
if (!/^[a-z0-9-]+$/.test(id)) {
|
|
104
|
+
throw new Error(`preview id must be lowercase alphanumeric/dashes (e.g. pr-42), got "${id}"`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** Provision the shared preview stack (one CloudFront distribution + host router + OIDC role). */
|
|
108
|
+
export async function previewBootstrap(ctx) {
|
|
109
|
+
if (!ctx.domain)
|
|
110
|
+
throw new Error('preview bootstrap requires a domain (e.g. preview.example.com)');
|
|
111
|
+
ctx.logger.info(colors.bold(`Bootstrapping preview stack (bucket ${ctx.names.bucket})`));
|
|
112
|
+
await applyGraph(buildNodes(ctx), ctx);
|
|
113
|
+
ctx.logger.ok('preview stack ready');
|
|
114
|
+
}
|
|
115
|
+
/** Build the current repo and publish it to this PR's preview prefix. Prints the URL. */
|
|
116
|
+
export async function previewDeploy(ctx, id) {
|
|
117
|
+
assertPreviewId(id);
|
|
118
|
+
const startedAt = Date.now();
|
|
119
|
+
const cwd = await findRepoRoot(ctx.ports.fs);
|
|
120
|
+
const hash = await ctx.ports.vcs.revisionHash(cwd);
|
|
121
|
+
ctx.logger.info(colors.bold(`Preview deploy ${id} (${hash})`));
|
|
122
|
+
const files = await listRepoFiles(ctx.ports, cwd, ctx.config.sourceIgnore, ctx.config.sourceInclude);
|
|
123
|
+
ctx.logger.step(`zipping ${files.length} files`);
|
|
124
|
+
const zip = await buildRepoZip(ctx.ports.fs, cwd, files, { [COMMIT_FILE]: hash });
|
|
125
|
+
const sourceKey = `build/${hash}.zip`;
|
|
126
|
+
await ctx.clients.s3.putObject(ctx.names.bucket, sourceKey, zip, 'application/zip');
|
|
127
|
+
// Rebuild the shared preview builder image if the agent bundle changed (no-op when
|
|
128
|
+
// unchanged), so agent fixes reach PR previews too.
|
|
129
|
+
await reconcileBuilderImage(ctx);
|
|
130
|
+
const url = `https://${id}.${ctx.domain}`;
|
|
131
|
+
await runBuild(ctx, {
|
|
132
|
+
hash,
|
|
133
|
+
sourceKey,
|
|
134
|
+
sitePrefix: `previews/${id}/site/`,
|
|
135
|
+
target: `preview-${id}`,
|
|
136
|
+
baseUrl: url,
|
|
137
|
+
});
|
|
138
|
+
ctx.logger.ok(`preview ready in ${formatDuration(Date.now() - startedAt)}: ${url}`);
|
|
139
|
+
return url;
|
|
140
|
+
}
|
|
141
|
+
/** Remove one PR's preview (delete its prefix). No invalidation — previews aren't cached. */
|
|
142
|
+
export async function previewDestroy(ctx, id) {
|
|
143
|
+
assertPreviewId(id);
|
|
144
|
+
const count = await ctx.clients.s3.deletePrefix(ctx.names.bucket, `previews/${id}/`);
|
|
145
|
+
ctx.logger.ok(`removed preview ${id} (${count} object(s))`);
|
|
146
|
+
}
|
|
147
|
+
/** List active previews (by prefix). */
|
|
148
|
+
export async function previewList(ctx) {
|
|
149
|
+
const objects = await ctx.clients.s3.listObjects(ctx.names.bucket, 'previews/');
|
|
150
|
+
const ids = [...new Set(objects.map((o) => o.key.split('/')[1]).filter(Boolean))].sort();
|
|
151
|
+
if (ids.length === 0) {
|
|
152
|
+
ctx.logger.info('no active previews');
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
for (const id of ids)
|
|
156
|
+
ctx.logger.info(` ${id} https://${id}.${ctx.domain}`);
|
|
157
|
+
}
|
|
158
|
+
/** Tear down the entire shared preview stack. */
|
|
159
|
+
export async function previewTeardown(ctx, opts) {
|
|
160
|
+
if (!opts.yes)
|
|
161
|
+
throw new Error('refusing to tear down the preview stack without --yes');
|
|
162
|
+
ctx.logger.info(colors.bold('Tearing down preview stack'));
|
|
163
|
+
if (!(await clearRunningMicrovms(ctx)))
|
|
164
|
+
return;
|
|
165
|
+
await destroyGraph(buildNodes(ctx), ctx);
|
|
166
|
+
await ctx.store.delete();
|
|
167
|
+
ctx.logger.ok('preview stack destroyed');
|
|
168
|
+
}
|
|
169
|
+
/** Empty the live site/ prefix (leaves infra and build history intact). */
|
|
170
|
+
export async function deleteSite(ctx) {
|
|
171
|
+
const count = await ctx.clients.s3.deletePrefix(ctx.names.bucket, 'site/');
|
|
172
|
+
ctx.logger.ok(`deleted ${count} object(s) under site/`);
|
|
173
|
+
await invalidateCloudFront(ctx);
|
|
174
|
+
}
|
|
175
|
+
/** List deployment history from build manifests. */
|
|
176
|
+
export async function history(ctx) {
|
|
177
|
+
const objects = await ctx.clients.s3.listObjects(ctx.names.bucket, 'build/manifests/');
|
|
178
|
+
if (objects.length === 0) {
|
|
179
|
+
ctx.logger.info('no deployments yet');
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const manifests = [];
|
|
183
|
+
for (const obj of objects) {
|
|
184
|
+
const text = await ctx.clients.s3.getObjectText(ctx.names.bucket, obj.key);
|
|
185
|
+
if (!text)
|
|
186
|
+
continue;
|
|
187
|
+
try {
|
|
188
|
+
manifests.push(JSON.parse(text));
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// One corrupt manifest must not take down the whole listing.
|
|
192
|
+
ctx.logger.warn(`skipping unreadable manifest ${obj.key}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
manifests.sort((a, b) => b.finishedAt.localeCompare(a.finishedAt));
|
|
196
|
+
if (ctx.ports.terminal.isInteractive) {
|
|
197
|
+
for (const line of renderHistoryTable(manifests, Date.now()))
|
|
198
|
+
ctx.logger.info(line);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
// The plain form is the stable contract for CI logs and agents.
|
|
202
|
+
ctx.logger.info(colors.bold('hash status finished duration'));
|
|
203
|
+
for (const m of manifests) {
|
|
204
|
+
const cell = m.status === 'succeeded'
|
|
205
|
+
? colors.green(m.status.padEnd(10))
|
|
206
|
+
: colors.red(m.status.padEnd(10));
|
|
207
|
+
ctx.logger.info(`${m.hash.padEnd(13)} ${cell} ${m.finishedAt} ${(m.durationMs / 1000).toFixed(0)}s`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/** Show CloudWatch build logs for a given hash. */
|
|
211
|
+
export async function logs(ctx, hash) {
|
|
212
|
+
const text = await ctx.clients.s3.getObjectText(ctx.names.bucket, manifestKey(hash));
|
|
213
|
+
let manifest;
|
|
214
|
+
try {
|
|
215
|
+
manifest = text ? JSON.parse(text) : undefined;
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
ctx.logger.warn(`manifest for ${hash} is unreadable — showing the unfiltered log window`);
|
|
219
|
+
}
|
|
220
|
+
// Filter to the build's time window (± a minute) from the manifest.
|
|
221
|
+
const startTime = manifest ? Date.parse(manifest.startedAt) - 60_000 : undefined;
|
|
222
|
+
const endTime = manifest ? Date.parse(manifest.finishedAt) + 60_000 : undefined;
|
|
223
|
+
const events = await ctx.clients.logs.filterEvents(microvmLogGroup(ctx), {
|
|
224
|
+
...(startTime !== undefined ? { startTime } : {}),
|
|
225
|
+
...(endTime !== undefined ? { endTime } : {}),
|
|
226
|
+
});
|
|
227
|
+
if (events.length === 0) {
|
|
228
|
+
ctx.logger.info(`no log events for ${hash}`);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
for (const e of events) {
|
|
232
|
+
ctx.logger.info(`${colors.dim(new Date(e.timestamp).toISOString())} ${e.message.trimEnd()}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/** Show the planned graph against live state (drift view). */
|
|
236
|
+
export async function status(ctx) {
|
|
237
|
+
ctx.logger.info(colors.bold(`Status for "${ctx.env}" (bucket ${ctx.names.bucket})`));
|
|
238
|
+
const pretty = ctx.ports.terminal.isInteractive;
|
|
239
|
+
const entries = [];
|
|
240
|
+
for (const node of buildNodes(ctx)) {
|
|
241
|
+
let exists = false;
|
|
242
|
+
try {
|
|
243
|
+
exists = await node.read(ctx);
|
|
244
|
+
}
|
|
245
|
+
catch (err) {
|
|
246
|
+
if (pretty) {
|
|
247
|
+
entries.push({ title: node.title, state: 'error', detail: err.message });
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
ctx.logger.warn(`${node.title}: read failed (${err.message})`);
|
|
251
|
+
}
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
const outputs = ctx.state.resources[node.id];
|
|
255
|
+
const detail = outputs ? JSON.stringify(outputs) : undefined;
|
|
256
|
+
if (pretty) {
|
|
257
|
+
entries.push({ title: node.title, state: exists ? 'present' : 'missing', detail });
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
// The plain form is the stable contract for CI logs and agents.
|
|
261
|
+
const mark = exists ? colors.green('present') : colors.yellow('missing');
|
|
262
|
+
ctx.logger.info(` ${mark} ${node.title} ${detail ? colors.dim(detail) : ''}`);
|
|
263
|
+
}
|
|
264
|
+
if (pretty) {
|
|
265
|
+
for (const line of renderStatusTree(entries))
|
|
266
|
+
ctx.logger.info(line);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { StateStore, type AwsClients, type FileSystem, type Names, type OpsConfig, type OpsState } from 'blogwright-core';
|
|
2
|
+
import { type Logger } from './logger.js';
|
|
3
|
+
import type { Ports } from './ports.js';
|
|
4
|
+
export interface OpsContext {
|
|
5
|
+
env: string;
|
|
6
|
+
domain: string | undefined;
|
|
7
|
+
/** True for the shared preview stack (host-routed, per-PR prefixes). */
|
|
8
|
+
preview: boolean;
|
|
9
|
+
config: OpsConfig;
|
|
10
|
+
names: Names;
|
|
11
|
+
accountId: string;
|
|
12
|
+
clients: AwsClients;
|
|
13
|
+
ports: Ports;
|
|
14
|
+
/**
|
|
15
|
+
* Directory holding the build-agent artifacts — Dockerfile, bundled server.js,
|
|
16
|
+
* and agent-manifest.json — copied into this package by its build
|
|
17
|
+
* (scripts/copy-agent.mjs). Resolved at the composition root; tests inject one.
|
|
18
|
+
*/
|
|
19
|
+
agentDir: string;
|
|
20
|
+
state: OpsState;
|
|
21
|
+
store: StateStore;
|
|
22
|
+
logger: Logger;
|
|
23
|
+
/** Persist the working state to S3. */
|
|
24
|
+
save(): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
export interface ContextOptions {
|
|
27
|
+
env: string;
|
|
28
|
+
configPath?: string | undefined;
|
|
29
|
+
domain?: string | undefined;
|
|
30
|
+
endpointOverride?: string | undefined;
|
|
31
|
+
preview?: boolean | undefined;
|
|
32
|
+
/** Adapter overrides; anything omitted defaults to the real (node) adapter. */
|
|
33
|
+
ports?: Partial<Ports> | undefined;
|
|
34
|
+
}
|
|
35
|
+
export interface ConfigSource {
|
|
36
|
+
env: string;
|
|
37
|
+
/** Repo root the default config candidates resolve against. */
|
|
38
|
+
root: string;
|
|
39
|
+
/** Explicit config file; when set it is the only candidate. */
|
|
40
|
+
configPath?: string | undefined;
|
|
41
|
+
}
|
|
42
|
+
/** Load and parse the first config candidate that exists. Exported for tests. */
|
|
43
|
+
export declare function loadConfig(fs: FileSystem, source: ConfigSource): Promise<OpsConfig>;
|
|
44
|
+
/**
|
|
45
|
+
* Build the runtime context: load config, resolve the account id, derive names, create
|
|
46
|
+
* clients, and load topology state from S3. The state bucket name is deterministic, which
|
|
47
|
+
* resolves the bootstrap chicken-and-egg. This is the composition root — the only place
|
|
48
|
+
* real adapters are constructed and wired.
|
|
49
|
+
*/
|
|
50
|
+
export declare function createContext(opts: ContextOptions): Promise<OpsContext>;
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { createClients, createNodeFileSystem, createNodeTerminal, deriveNames, FileNotFoundError, findRepoRoot, parseConfig, StateStore, } from 'blogwright-core';
|
|
4
|
+
import { createFetchPing } from './adapters/fetch-ping.js';
|
|
5
|
+
import { createProcessVcs } from './adapters/process-vcs.js';
|
|
6
|
+
import { createLogger } from './logger.js';
|
|
7
|
+
/** Load and parse the first config candidate that exists. Exported for tests. */
|
|
8
|
+
export async function loadConfig(fs, source) {
|
|
9
|
+
const candidates = source.configPath
|
|
10
|
+
? [source.configPath]
|
|
11
|
+
: [
|
|
12
|
+
resolve(source.root, `config/${source.env}.jsonc`),
|
|
13
|
+
resolve(source.root, 'ops.config.jsonc'),
|
|
14
|
+
];
|
|
15
|
+
for (const path of candidates) {
|
|
16
|
+
try {
|
|
17
|
+
return parseConfig(await fs.readText(path));
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
if (!(err instanceof FileNotFoundError))
|
|
21
|
+
throw err;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
throw new Error(`no config found for environment "${source.env}" — looked for ${candidates.join(', ')}`);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Build the runtime context: load config, resolve the account id, derive names, create
|
|
28
|
+
* clients, and load topology state from S3. The state bucket name is deterministic, which
|
|
29
|
+
* resolves the bootstrap chicken-and-egg. This is the composition root — the only place
|
|
30
|
+
* real adapters are constructed and wired.
|
|
31
|
+
*/
|
|
32
|
+
export async function createContext(opts) {
|
|
33
|
+
const ports = {
|
|
34
|
+
fs: opts.ports?.fs ?? createNodeFileSystem(),
|
|
35
|
+
vcs: opts.ports?.vcs ?? createProcessVcs(),
|
|
36
|
+
terminal: opts.ports?.terminal ?? createNodeTerminal(),
|
|
37
|
+
ping: opts.ports?.ping ?? createFetchPing(),
|
|
38
|
+
};
|
|
39
|
+
const logger = createLogger(ports.terminal);
|
|
40
|
+
const agentDir = fileURLToPath(new URL('../agent', import.meta.url));
|
|
41
|
+
const root = await findRepoRoot(ports.fs);
|
|
42
|
+
const config = await loadConfig(ports.fs, {
|
|
43
|
+
env: opts.env,
|
|
44
|
+
root,
|
|
45
|
+
configPath: opts.configPath,
|
|
46
|
+
});
|
|
47
|
+
const domain = opts.domain ?? config.domain;
|
|
48
|
+
const clients = createClients({
|
|
49
|
+
region: config.region,
|
|
50
|
+
endpointOverride: opts.endpointOverride,
|
|
51
|
+
});
|
|
52
|
+
const accountId = await clients.sts.getAccountId();
|
|
53
|
+
const names = deriveNames(opts.env, accountId, config);
|
|
54
|
+
const store = new StateStore(clients.s3, names.bucket, opts.env);
|
|
55
|
+
// load() already returns empty state for a not-yet-created bucket/object; any other
|
|
56
|
+
// error (corrupt state, AccessDenied, transient S3) must surface, not be masked.
|
|
57
|
+
const state = await store.load();
|
|
58
|
+
const ctx = {
|
|
59
|
+
env: opts.env,
|
|
60
|
+
domain,
|
|
61
|
+
preview: opts.preview ?? false,
|
|
62
|
+
config,
|
|
63
|
+
names,
|
|
64
|
+
accountId,
|
|
65
|
+
clients,
|
|
66
|
+
ports,
|
|
67
|
+
agentDir,
|
|
68
|
+
state,
|
|
69
|
+
store,
|
|
70
|
+
logger,
|
|
71
|
+
save: async () => {
|
|
72
|
+
await store.save(state);
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
return ctx;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}
|
package/dist/deploy.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { OpsContext } from './context.js';
|
|
2
|
+
export interface DeployManifest {
|
|
3
|
+
hash: string;
|
|
4
|
+
sourceKey: string;
|
|
5
|
+
status: 'succeeded' | 'failed';
|
|
6
|
+
startedAt: string;
|
|
7
|
+
finishedAt: string;
|
|
8
|
+
durationMs: number;
|
|
9
|
+
microvmId: string;
|
|
10
|
+
message?: string;
|
|
11
|
+
}
|
|
12
|
+
/** Terminal outcome the log poller extracts from the agent's build markers. */
|
|
13
|
+
export interface AgentStatus {
|
|
14
|
+
state: 'idle' | 'building' | 'done' | 'failed';
|
|
15
|
+
message?: string | undefined;
|
|
16
|
+
}
|
|
17
|
+
declare function manifestKey(hash: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Resolve the MicroVM build log group from bootstrapped state rather than re-deriving it.
|
|
20
|
+
* The image and the build-role IAM policy bake this name in at bootstrap, so if the derived
|
|
21
|
+
* name (`ctx.names.microvmLogGroup`) is renamed in code later, a deploy must still target
|
|
22
|
+
* the group the running stack actually logs to — otherwise the VM's logs land in (or are
|
|
23
|
+
* denied to) a different group and `pollBuild` waits on an empty one.
|
|
24
|
+
*/
|
|
25
|
+
export declare function microvmLogGroup(ctx: OpsContext): string;
|
|
26
|
+
/**
|
|
27
|
+
* Poll the CloudWatch build log group for the agent's output, streaming new lines and
|
|
28
|
+
* detecting the terminal marker (the source of truth). Each cycle also nudges the VM
|
|
29
|
+
* endpoint to keep the agent's event loop alive so the build triggers reliably.
|
|
30
|
+
* Exported for tests.
|
|
31
|
+
*/
|
|
32
|
+
export declare function pollBuild(ctx: OpsContext, hash: string, startTime: number, endpoint: string, token: string): Promise<AgentStatus>;
|
|
33
|
+
/**
|
|
34
|
+
* Run the builder MicroVM against a source zip already present at `sourceKey`, stream
|
|
35
|
+
* its logs, terminate it, and record a deployment manifest. Shared by deploy + rollback.
|
|
36
|
+
*/
|
|
37
|
+
export declare function runBuild(ctx: OpsContext, opts: {
|
|
38
|
+
hash: string;
|
|
39
|
+
sourceKey: string;
|
|
40
|
+
sitePrefix?: string;
|
|
41
|
+
target?: string;
|
|
42
|
+
/** Canonical origin the site is served from, for robots.txt/sitemap.xml. */
|
|
43
|
+
baseUrl?: string | undefined;
|
|
44
|
+
}): Promise<DeployManifest>;
|
|
45
|
+
export declare function invalidateCloudFront(ctx: OpsContext, paths?: string[]): Promise<void>;
|
|
46
|
+
/** What a deploy's cache invalidation actually did, for the deploy summary. */
|
|
47
|
+
export interface InvalidationSummary {
|
|
48
|
+
mode: 'none' | 'paths' | 'all';
|
|
49
|
+
count: number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Invalidate only the URL paths that changed in this build (from the agent's manifest),
|
|
53
|
+
* so unchanged pages stay cached. Falls back to `/*` when the manifest is missing or the
|
|
54
|
+
* changed set is larger than the configured cap.
|
|
55
|
+
*/
|
|
56
|
+
export declare function invalidateChanged(ctx: OpsContext, hash: string): Promise<InvalidationSummary>;
|
|
57
|
+
export { manifestKey };
|