blogwright 0.3.2 → 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.
- package/README.md +11 -11
- package/agent/agent-manifest.json +1 -1
- package/agent/server.js +37 -19
- package/dist/adapters/fetch-ping.d.ts +1 -1
- package/dist/adapters/fetch-ping.js +3 -4
- package/dist/adapters/node-module-loader.d.ts +11 -0
- package/dist/adapters/node-module-loader.js +146 -0
- package/dist/adapters/process-package-manager.d.ts +41 -0
- package/dist/adapters/process-package-manager.js +116 -0
- package/dist/adapters/process-vcs.d.ts +5 -4
- package/dist/adapters/process-vcs.js +6 -6
- package/dist/agent-package.d.ts +1 -1
- package/dist/agent-package.js +4 -4
- package/dist/bin.js +13 -3
- package/dist/cli.d.ts +68 -1
- package/dist/cli.js +270 -89
- package/dist/commands.d.ts +70 -3
- package/dist/commands.js +180 -32
- package/dist/config-block.d.ts +34 -0
- package/dist/config-block.js +262 -0
- package/dist/context.d.ts +76 -6
- package/dist/context.js +98 -19
- package/dist/deploy.d.ts +2 -2
- package/dist/deploy.js +14 -14
- package/dist/graph.d.ts +27 -16
- package/dist/graph.js +1 -2
- package/dist/init.d.ts +37 -3
- package/dist/init.js +146 -23
- package/dist/known-commands.d.ts +63 -0
- package/dist/known-commands.js +78 -0
- package/dist/logger.js +0 -1
- package/dist/microvms.d.ts +2 -2
- package/dist/microvms.js +3 -4
- package/dist/nodes.d.ts +5 -3
- package/dist/nodes.js +97 -31
- package/dist/plugin-commands.d.ts +298 -0
- package/dist/plugin-commands.js +990 -0
- package/dist/plugins.d.ts +194 -0
- package/dist/plugins.js +523 -0
- package/dist/ports.d.ts +89 -1
- package/dist/ports.js +0 -1
- package/dist/render.d.ts +55 -0
- package/dist/render.js +89 -2
- package/dist/repo.d.ts +3 -3
- package/dist/repo.js +8 -9
- package/dist/rkey.js +0 -1
- package/dist/seo.d.ts +1 -1
- package/dist/seo.js +1 -2
- package/package.json +6 -6
- package/dist/adapters/fetch-ping.js.map +0 -1
- package/dist/adapters/process-vcs.js.map +0 -1
- package/dist/agent-package.js.map +0 -1
- package/dist/bin.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/commands.js.map +0 -1
- package/dist/context.js.map +0 -1
- package/dist/deploy.js.map +0 -1
- package/dist/graph.js.map +0 -1
- package/dist/init.js.map +0 -1
- package/dist/logger.js.map +0 -1
- package/dist/microvms.js.map +0 -1
- package/dist/nodes.js.map +0 -1
- package/dist/ports.js.map +0 -1
- package/dist/render.js.map +0 -1
- package/dist/repo.js.map +0 -1
- package/dist/rkey.js.map +0 -1
- package/dist/seo.js.map +0 -1
- package/dist/test-support.d.ts +0 -45
- package/dist/test-support.js +0 -126
- package/dist/test-support.js.map +0 -1
package/dist/graph.d.ts
CHANGED
|
@@ -1,20 +1,31 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
/**
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
import type { ResourceNode, ResourceOutputs } from 'blogwright-core';
|
|
2
|
+
/**
|
|
3
|
+
* The structural minimum the engine below (`topoSort`, `applyGraph`,
|
|
4
|
+
* `destroyGraph`) actually reads off a node's context: a logger it calls
|
|
5
|
+
* `step`/`ok`/`warn` on, a way to persist state, and the state's resources
|
|
6
|
+
* map (`destroyGraph` deletes an entry from it). Exported so a caller
|
|
7
|
+
* running this engine over a different context - `OpsContext` (`context.ts`)
|
|
8
|
+
* and core's `PluginContext` (`blogwright-core`) both do already - knows
|
|
9
|
+
* exactly what that context must supply, without this module depending on
|
|
10
|
+
* either one. Neither `OpsContext` nor `PluginContext` is named here on
|
|
11
|
+
* purpose: this is the structural minimum both happen to satisfy, not a
|
|
12
|
+
* fixed supertype of them (see the doc comment on core's `ResourceNode` for
|
|
13
|
+
* why no such supertype is worth naming).
|
|
14
|
+
*/
|
|
15
|
+
export interface GraphContext {
|
|
16
|
+
logger: {
|
|
17
|
+
step(msg: string): void;
|
|
18
|
+
ok(msg: string): void;
|
|
19
|
+
warn(msg: string): void;
|
|
20
|
+
};
|
|
21
|
+
state: {
|
|
22
|
+
resources: Record<string, ResourceOutputs>;
|
|
23
|
+
};
|
|
24
|
+
save(): Promise<void>;
|
|
14
25
|
}
|
|
15
26
|
/** Topologically order nodes so dependencies come before dependents (Kahn's algorithm). */
|
|
16
|
-
export declare function topoSort(nodes: ResourceNode[]): ResourceNode[];
|
|
27
|
+
export declare function topoSort<Ctx>(nodes: ResourceNode<Ctx>[]): ResourceNode<Ctx>[];
|
|
17
28
|
/** Reconcile the graph in dependency order (create missing, update existing). */
|
|
18
|
-
export declare function applyGraph(nodes: ResourceNode[], ctx:
|
|
29
|
+
export declare function applyGraph<Ctx extends GraphContext>(nodes: ResourceNode<Ctx>[], ctx: Ctx): Promise<void>;
|
|
19
30
|
/** Tear down the graph in reverse dependency order. */
|
|
20
|
-
export declare function destroyGraph(nodes: ResourceNode[], ctx:
|
|
31
|
+
export declare function destroyGraph<Ctx extends GraphContext>(nodes: ResourceNode<Ctx>[], ctx: Ctx): Promise<void>;
|
package/dist/graph.js
CHANGED
|
@@ -77,9 +77,8 @@ export async function destroyGraph(nodes, ctx) {
|
|
|
77
77
|
await node.delete(ctx);
|
|
78
78
|
delete ctx.state.resources[node.id];
|
|
79
79
|
// The state lives in the bucket that is itself being deleted, so persisting it may
|
|
80
|
-
// fail (NoSuchBucket) once the bucket node is gone
|
|
80
|
+
// fail (NoSuchBucket) once the bucket node is gone - never let that abort teardown.
|
|
81
81
|
await ctx.save().catch(() => undefined);
|
|
82
82
|
ctx.logger.ok(`deleted ${node.title}`);
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
|
-
//# sourceMappingURL=graph.js.map
|
package/dist/init.d.ts
CHANGED
|
@@ -1,4 +1,38 @@
|
|
|
1
|
-
import { type FileSystem, type Terminal } from 'blogwright-core';
|
|
1
|
+
import { type FileSystem, type Plugin, type Terminal } from 'blogwright-core';
|
|
2
2
|
import type { Logger } from './logger.js';
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* One question, and how to ask it: shown text, a prefilled default, whether
|
|
5
|
+
* an empty answer must re-prompt, and an optional validator. Exported - and
|
|
6
|
+
* `ask` below along with it - because `plugin-commands.ts`'s `io.ask` (the
|
|
7
|
+
* surface an `init?(io)` contributor asks its own questions through) reuses
|
|
8
|
+
* this exact prompt/validate/retry loop rather than writing a second one; no
|
|
9
|
+
* plugin path may reach `node:readline` itself. Structurally the same shape
|
|
10
|
+
* as core's `PluginQuestion` (`blogwright-core`'s `plugin.ts`), so a
|
|
11
|
+
* contributor's question passes straight through with no conversion.
|
|
12
|
+
*/
|
|
13
|
+
export interface Question {
|
|
14
|
+
prompt: string;
|
|
15
|
+
defaultValue?: string | undefined;
|
|
16
|
+
required?: boolean | undefined;
|
|
17
|
+
validate?: ((answer: string) => string | undefined) | undefined;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Ask `q.prompt` over `terminal`, retrying up to `MAX_ATTEMPTS` times on a
|
|
21
|
+
* required-but-empty answer or a `validate` failure, and resolving with
|
|
22
|
+
* `undefined` for an unanswered optional question. Throws once every attempt
|
|
23
|
+
* is spent. The one prompt/validate/retry loop every wizard-shaped question -
|
|
24
|
+
* `blogwright init`'s own four and any plugin's `init?(io)` contributor -
|
|
25
|
+
* asks through.
|
|
26
|
+
*/
|
|
27
|
+
export declare function ask(terminal: Terminal, logger: Logger, q: Question): Promise<string | undefined>;
|
|
28
|
+
/**
|
|
29
|
+
* Run the wizard. Returns a process exit code; never throws for expected
|
|
30
|
+
* refusals (non-interactive, an existing config file). `plugins` is every
|
|
31
|
+
* plugin the caller has ALREADY discovered (`cli.ts`, over
|
|
32
|
+
* `DiscoveryPortsFactory`) - this function asks their questions and writes
|
|
33
|
+
* their blocks but never runs discovery itself. A plugin's `init(io)`
|
|
34
|
+
* contributor throwing propagates unchanged, rejecting this call before the
|
|
35
|
+
* single `fs.writeText` below ever runs, so the config file stays exactly
|
|
36
|
+
* what it was - absent, on this path - rather than a partial write.
|
|
37
|
+
*/
|
|
38
|
+
export declare function initSite(fs: FileSystem, terminal: Terminal, logger: Logger, plugins: readonly Plugin[], root?: string): Promise<number>;
|
package/dist/init.js
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* `blogwright init`
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* `blogwright init` - the first-run wizard. Asks the four core questions a
|
|
3
|
+
* new site needs, then each already-discovered plugin's own `init(io)`
|
|
4
|
+
* questions (in deterministic, name-sorted order), and writes ONE commented
|
|
5
|
+
* config/production.jsonc carrying every answered block. Runs before any
|
|
6
|
+
* context exists (there is no config to load yet), so it takes its ports
|
|
7
|
+
* directly. Plugin discovery itself is the composition root's job
|
|
8
|
+
* (`cli.ts`), never this module's: `initSite` takes the already-discovered
|
|
9
|
+
* plugins as a plain array, never a `ModuleLoader`, so this stays a domain
|
|
10
|
+
* module.
|
|
6
11
|
*/
|
|
7
|
-
import { colors, findRepoRoot } from 'blogwright-core';
|
|
12
|
+
import { colors, findRepoRoot, parseConfig, } from 'blogwright-core';
|
|
13
|
+
import { renderConfigBlock } from './config-block.js';
|
|
8
14
|
const SITE_NAME_PATTERN = /^[a-z0-9-]+$/;
|
|
9
15
|
const GITHUB_REPO_PATTERN = /^[\w.-]+\/[\w.-]+$/;
|
|
10
16
|
const MAX_ATTEMPTS = 3;
|
|
11
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Ask `q.prompt` over `terminal`, retrying up to `MAX_ATTEMPTS` times on a
|
|
19
|
+
* required-but-empty answer or a `validate` failure, and resolving with
|
|
20
|
+
* `undefined` for an unanswered optional question. Throws once every attempt
|
|
21
|
+
* is spent. The one prompt/validate/retry loop every wizard-shaped question -
|
|
22
|
+
* `blogwright init`'s own four and any plugin's `init?(io)` contributor -
|
|
23
|
+
* asks through.
|
|
24
|
+
*/
|
|
25
|
+
export async function ask(terminal, logger, q) {
|
|
12
26
|
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
13
27
|
const suffix = q.defaultValue ? ` [${q.defaultValue}]` : '';
|
|
14
28
|
const answer = (await terminal.question(`${q.prompt}${suffix}: `)).trim() || q.defaultValue;
|
|
@@ -25,44 +39,140 @@ async function ask(terminal, logger, q) {
|
|
|
25
39
|
}
|
|
26
40
|
return answer;
|
|
27
41
|
}
|
|
28
|
-
throw new Error(`no valid answer after ${MAX_ATTEMPTS} attempts
|
|
42
|
+
throw new Error(`no valid answer after ${MAX_ATTEMPTS} attempts - giving up`);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Build the `io` an `init?(io)` contributor asks its own questions through,
|
|
46
|
+
* entirely over the `ask` loop above - never a second prompt/validate/retry
|
|
47
|
+
* loop. Mirrors `plugin-commands.ts`'s own `buildInitIo` (the other path
|
|
48
|
+
* that reaches a plugin's contributor, `blogwright <plugin> init`) rather
|
|
49
|
+
* than importing it: that module already imports `ask` from here, so the
|
|
50
|
+
* reverse import would be a cycle between the two (see `known-commands.ts`'s
|
|
51
|
+
* module comment for what that class of cycle already broke once in this
|
|
52
|
+
* package). `ask` resolves `undefined` for an unanswered optional question;
|
|
53
|
+
* `PluginInitIo.ask` promises a `string` always, per the no-null rule, so
|
|
54
|
+
* the empty string stands in for "declined" here too.
|
|
55
|
+
*/
|
|
56
|
+
function buildInitIo(terminal, logger) {
|
|
57
|
+
return {
|
|
58
|
+
isInteractive: terminal.isInteractive,
|
|
59
|
+
logger,
|
|
60
|
+
ask: async (question) => (await ask(terminal, logger, question)) ?? '',
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Ask one plugin's `init(io)` contributor its questions and render what it
|
|
65
|
+
* returns as a `"key": { ... }` block in `renderConfigBlock`'s style, or
|
|
66
|
+
* `undefined` when the operator answered nothing (an empty array). A
|
|
67
|
+
* contributor with no `configKey` to file its answers under is a
|
|
68
|
+
* plugin-authoring bug, not an operator refusal - raised naming the plugin,
|
|
69
|
+
* the same check `plugin-commands.ts`'s `runGenericInit` makes for the other
|
|
70
|
+
* path that reaches a contributor, so both refuse identically.
|
|
71
|
+
*
|
|
72
|
+
* This DOES abort the whole wizard - a misconfigured plugin among several
|
|
73
|
+
* stops every other plugin's block (and the core entries) from being
|
|
74
|
+
* written at all, unlike a candidate-level `plugins.ts` discovery failure,
|
|
75
|
+
* which is collected and never blocks an unrelated command. The
|
|
76
|
+
* inconsistency is deliberate, not overlooked: this is a plugin-authoring
|
|
77
|
+
* bug the operator cannot fix by re-running (unlike a transient discovery
|
|
78
|
+
* failure), `runGenericInit` - the sibling path - has no "continue past it"
|
|
79
|
+
* option either, since it operates on exactly one plugin, and this task's
|
|
80
|
+
* own contract is for the two paths to reach a contributor identically.
|
|
81
|
+
* Diverging here would need `plugin-commands.ts` to diverge too, which is
|
|
82
|
+
* outside this module's ownership.
|
|
83
|
+
*/
|
|
84
|
+
async function askPluginBlock(plugin, contributor, io) {
|
|
85
|
+
const configKey = plugin.configKey;
|
|
86
|
+
if (!configKey) {
|
|
87
|
+
throw new Error(`plugin "${plugin.name}" declares an init(io) contributor but no configKey - there is ` +
|
|
88
|
+
'nothing to file its answered block under');
|
|
89
|
+
}
|
|
90
|
+
const entries = await contributor(io);
|
|
91
|
+
if (entries.length === 0)
|
|
92
|
+
return undefined;
|
|
93
|
+
return renderConfigBlock(configKey, entries.map((entry) => ({ prop: entry.property, comment: entry.comment })));
|
|
29
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Ask every plugin in `plugins` that declares an `init(io)` contributor, in
|
|
97
|
+
* deterministic order - sorted by `name`, never discovery or argument order
|
|
98
|
+
* - and return each answered block. A plugin declining (an empty array) or
|
|
99
|
+
* carrying no contributor at all contributes nothing: never a stray entry,
|
|
100
|
+
* never a stray comma once `renderConfig` composes the result.
|
|
101
|
+
*/
|
|
102
|
+
async function collectPluginBlocks(plugins, terminal, logger) {
|
|
103
|
+
const io = buildInitIo(terminal, logger);
|
|
104
|
+
const sorted = [...plugins].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
105
|
+
const blocks = [];
|
|
106
|
+
for (const plugin of sorted) {
|
|
107
|
+
const contributor = plugin.init;
|
|
108
|
+
if (!contributor)
|
|
109
|
+
continue;
|
|
110
|
+
const block = await askPluginBlock(plugin, contributor, io);
|
|
111
|
+
if (block)
|
|
112
|
+
blocks.push(block);
|
|
113
|
+
}
|
|
114
|
+
return blocks;
|
|
115
|
+
}
|
|
116
|
+
/** Render one top-level item with its own trailing comma, or none when it is last. */
|
|
117
|
+
function renderTopLevelItem(item, last) {
|
|
118
|
+
const comma = last ? '' : ',';
|
|
119
|
+
if ('block' in item)
|
|
120
|
+
return `${item.block}${comma}`;
|
|
121
|
+
return ` ${item.prop}${comma}${item.comment ? ` // ${item.comment}` : ''}`;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Render the whole `config/production.jsonc` body: the four core entries in
|
|
125
|
+
* their fixed, commented style, followed by every plugin block already
|
|
126
|
+
* rendered by `collectPluginBlocks` - each its own top-level property, comma
|
|
127
|
+
* discipline shared with the core entries via `renderTopLevelItem` so no
|
|
128
|
+
* block ever leaves (or follows) a stray comma. `pluginBlocks` empty
|
|
129
|
+
* reproduces exactly the entries-only output this wizard has always
|
|
130
|
+
* written - byte for byte, pinned by `init.test.ts`.
|
|
131
|
+
*/
|
|
30
132
|
function renderConfig(opts) {
|
|
31
|
-
const
|
|
133
|
+
const items = [
|
|
32
134
|
{ prop: `"region": "${opts.region}"` },
|
|
33
135
|
{
|
|
34
136
|
prop: `"siteName": "${opts.siteName}"`,
|
|
35
|
-
comment: 'stable slug in every AWS resource name
|
|
137
|
+
comment: 'stable slug in every AWS resource name - never change it',
|
|
36
138
|
},
|
|
37
139
|
];
|
|
38
140
|
if (opts.domain)
|
|
39
|
-
|
|
141
|
+
items.push({ prop: `"domain": "${opts.domain}"` });
|
|
40
142
|
if (opts.githubRepo) {
|
|
41
|
-
|
|
143
|
+
items.push({
|
|
42
144
|
prop: `"githubRepo": "${opts.githubRepo}"`,
|
|
43
145
|
comment: 'enables the GitHub OIDC deploy role',
|
|
44
146
|
});
|
|
45
147
|
}
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
});
|
|
50
|
-
return ['// config/production.jsonc — created by `blogwright init`', '{', ...body, '}', ''].join('\n');
|
|
148
|
+
for (const block of opts.pluginBlocks)
|
|
149
|
+
items.push({ block });
|
|
150
|
+
const body = items.map((item, i) => renderTopLevelItem(item, i === items.length - 1));
|
|
151
|
+
return ['// config/production.jsonc - created by `blogwright init`', '{', ...body, '}', ''].join('\n');
|
|
51
152
|
}
|
|
52
|
-
/**
|
|
53
|
-
|
|
153
|
+
/**
|
|
154
|
+
* Run the wizard. Returns a process exit code; never throws for expected
|
|
155
|
+
* refusals (non-interactive, an existing config file). `plugins` is every
|
|
156
|
+
* plugin the caller has ALREADY discovered (`cli.ts`, over
|
|
157
|
+
* `DiscoveryPortsFactory`) - this function asks their questions and writes
|
|
158
|
+
* their blocks but never runs discovery itself. A plugin's `init(io)`
|
|
159
|
+
* contributor throwing propagates unchanged, rejecting this call before the
|
|
160
|
+
* single `fs.writeText` below ever runs, so the config file stays exactly
|
|
161
|
+
* what it was - absent, on this path - rather than a partial write.
|
|
162
|
+
*/
|
|
163
|
+
export async function initSite(fs, terminal, logger, plugins, root) {
|
|
54
164
|
if (!terminal.isInteractive) {
|
|
55
165
|
logger.error('init is an interactive wizard; in CI or plain mode create config/production.jsonc ' +
|
|
56
|
-
'by hand instead (see README
|
|
166
|
+
'by hand instead (see README - only "region" and "siteName" are required)');
|
|
57
167
|
return 1;
|
|
58
168
|
}
|
|
59
169
|
const repoRoot = root ?? (await findRepoRoot(fs).catch(() => process.cwd()));
|
|
60
170
|
const configPath = `${repoRoot}/config/production.jsonc`;
|
|
61
171
|
if (await fs.exists(configPath)) {
|
|
62
|
-
logger.error(`${configPath} already exists
|
|
172
|
+
logger.error(`${configPath} already exists - edit it directly, or pass --config elsewhere`);
|
|
63
173
|
return 1;
|
|
64
174
|
}
|
|
65
|
-
logger.info(colors.bold('Welcome to blogwright
|
|
175
|
+
logger.info(colors.bold('Welcome to blogwright - four questions and you are live.'));
|
|
66
176
|
const siteName = await ask(terminal, logger, {
|
|
67
177
|
prompt: 'site name (lowercase slug, names every AWS resource)',
|
|
68
178
|
required: true,
|
|
@@ -80,7 +190,21 @@ export async function initSite(fs, terminal, logger, root) {
|
|
|
80
190
|
prompt: 'GitHub repo for CI deploys, owner/repo (blank to skip)',
|
|
81
191
|
validate: (v) => (GITHUB_REPO_PATTERN.test(v) ? undefined : 'expected owner/repo'),
|
|
82
192
|
});
|
|
83
|
-
|
|
193
|
+
const pluginBlocks = await collectPluginBlocks(plugins, terminal, logger);
|
|
194
|
+
const rendered = renderConfig({
|
|
195
|
+
region: region,
|
|
196
|
+
siteName: siteName,
|
|
197
|
+
domain,
|
|
198
|
+
githubRepo,
|
|
199
|
+
pluginBlocks,
|
|
200
|
+
});
|
|
201
|
+
// Re-parsed before it is trusted onto disk, mirroring `plugin-commands.ts`'s
|
|
202
|
+
// `runGenericInit` (the sibling path composing a plugin's block into an
|
|
203
|
+
// existing file): a bug in this composition must never reach the operator
|
|
204
|
+
// as an unloadable config file, which is the one thing this whole feature
|
|
205
|
+
// promises never to do.
|
|
206
|
+
parseConfig(rendered);
|
|
207
|
+
await fs.writeText(configPath, rendered);
|
|
84
208
|
logger.ok(`wrote ${configPath}`);
|
|
85
209
|
logger.info('');
|
|
86
210
|
logger.info(colors.bold('Next steps:'));
|
|
@@ -89,4 +213,3 @@ export async function initSite(fs, terminal, logger, root) {
|
|
|
89
213
|
logger.info(colors.dim(' (bootstrap prints ACM validation CNAMEs when a domain is set)'));
|
|
90
214
|
return 0;
|
|
91
215
|
}
|
|
92
|
-
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A LEAF. This module must never import anything.
|
|
3
|
+
*
|
|
4
|
+
* Task 09 moved `KNOWN_COMMANDS` and `RESERVED_COMMANDS` here out of `cli.ts`
|
|
5
|
+
* because `plugins.ts` needs the reserved set and `cli.ts` imports `plugins.ts`
|
|
6
|
+
* for dispatch - a cycle between the composition root and a domain module. It
|
|
7
|
+
* did not throw, because the set was only read inside a function body, but
|
|
8
|
+
* adding one ordinary top-level derivation to `plugins.ts` under that cycle
|
|
9
|
+
* produced `ReferenceError: Cannot access 'RESERVED_COMMANDS' before
|
|
10
|
+
* initialization` and killed every command including `--help`.
|
|
11
|
+
*
|
|
12
|
+
* A single import added here re-opens that fault, silently, for whichever
|
|
13
|
+
* module happens to be entered first. Keep it dependency-free.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* The CLI's command-name registries, isolated in their own leaf module with
|
|
17
|
+
* no imports of its own. Both `cli.ts` (the dispatcher - it pulls in every
|
|
18
|
+
* built-in command implementation, and since task 29 no plugin package by
|
|
19
|
+
* name: `blogwright-pds` reaches it only transitively, through the
|
|
20
|
+
* post-deploy sync `commands.ts` imports) and
|
|
21
|
+
* `plugins.ts` (a domain module that must stay inside the port boundary -
|
|
22
|
+
* see DEVELOPMENT.md §Hexagonal architecture) need to read the same set of
|
|
23
|
+
* reserved names, and neither may import the other: `plugins.ts` importing
|
|
24
|
+
* `cli.ts` for this alone becomes a real cycle the moment `cli.ts` imports
|
|
25
|
+
* `discover` from `plugins.ts` too (task 10 adds exactly that edge, to
|
|
26
|
+
* dispatch `blogwright plugin list`) - two modules each waiting on the
|
|
27
|
+
* other to finish initialising before either can run. This module gives
|
|
28
|
+
* both callers a shared, dependency-free home instead.
|
|
29
|
+
*
|
|
30
|
+
* `KNOWN_COMMANDS` is the eight names `cli.ts`'s `main` dispatches through
|
|
31
|
+
* its `switch`, after the `KNOWN_COMMANDS.has(command)` membership test:
|
|
32
|
+
* `bootstrap`, `deploy`, `rollback`, `delete`, `destroy`, `history`, `logs`,
|
|
33
|
+
* `status`.
|
|
34
|
+
*
|
|
35
|
+
* `RESERVED_COMMANDS` is every name a plugin may never claim as its own -
|
|
36
|
+
* see `discover`'s namespace-collision check in `plugins.ts`. It is
|
|
37
|
+
* deliberately NOT `KNOWN_COMMANDS` alone: `init` (dispatched in `cli.ts`
|
|
38
|
+
* ahead of the `KNOWN_COMMANDS` membership test) and `preview` (likewise)
|
|
39
|
+
* never enter that set, so deriving from it alone would under-reserve by
|
|
40
|
+
* two and let a plugin shadow either one. `plugin` is named explicitly for
|
|
41
|
+
* the same reason, one task early: `cli.ts` does not yet dispatch a
|
|
42
|
+
* `blogwright plugin` namespace (task 10 adds `'plugin'` to
|
|
43
|
+
* `KNOWN_COMMANDS`), but the name is reserved from the moment any plugin
|
|
44
|
+
* could collide with it, and the union keeps this set correct both before
|
|
45
|
+
* and after that command exists - no second edit required either way.
|
|
46
|
+
*
|
|
47
|
+
* `pds` is deliberately absent from `RESERVED_COMMANDS`, and adding it here
|
|
48
|
+
* would break `blogwright pds` outright. There is no built-in `pds` command:
|
|
49
|
+
* task 29 deleted `cli.ts`'s hardcoded `command === 'pds'` branch, and the
|
|
50
|
+
* namespace is now served by the bundled `blogwright-pds` package, which
|
|
51
|
+
* declares the plugin name `pds` and is discovered like any other plugin.
|
|
52
|
+
* Reserving the name would make `discover`'s reserved-name check
|
|
53
|
+
* (`plugins.ts`) reject that package - a load `failures` entry, never an
|
|
54
|
+
* installed plugin - so `blogwright pds <action>` would exit 1 with `no
|
|
55
|
+
* built-in command or installed plugin claims "pds"`, `--help` would list
|
|
56
|
+
* none of its six actions, and `blogwright plugin list` would blame a
|
|
57
|
+
* built-in command that does not exist. This is the one name in this file
|
|
58
|
+
* whose reservation would REMOVE a working command rather than protect one;
|
|
59
|
+
* it stays unreserved on purpose, pinned by a test in `plugins.test.ts` and
|
|
60
|
+
* `cli.test.ts`. See `plugins.ts`'s module comment for the full reasoning.
|
|
61
|
+
*/
|
|
62
|
+
export declare const KNOWN_COMMANDS: Set<string>;
|
|
63
|
+
export declare const RESERVED_COMMANDS: ReadonlySet<string>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A LEAF. This module must never import anything.
|
|
3
|
+
*
|
|
4
|
+
* Task 09 moved `KNOWN_COMMANDS` and `RESERVED_COMMANDS` here out of `cli.ts`
|
|
5
|
+
* because `plugins.ts` needs the reserved set and `cli.ts` imports `plugins.ts`
|
|
6
|
+
* for dispatch - a cycle between the composition root and a domain module. It
|
|
7
|
+
* did not throw, because the set was only read inside a function body, but
|
|
8
|
+
* adding one ordinary top-level derivation to `plugins.ts` under that cycle
|
|
9
|
+
* produced `ReferenceError: Cannot access 'RESERVED_COMMANDS' before
|
|
10
|
+
* initialization` and killed every command including `--help`.
|
|
11
|
+
*
|
|
12
|
+
* A single import added here re-opens that fault, silently, for whichever
|
|
13
|
+
* module happens to be entered first. Keep it dependency-free.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* The CLI's command-name registries, isolated in their own leaf module with
|
|
17
|
+
* no imports of its own. Both `cli.ts` (the dispatcher - it pulls in every
|
|
18
|
+
* built-in command implementation, and since task 29 no plugin package by
|
|
19
|
+
* name: `blogwright-pds` reaches it only transitively, through the
|
|
20
|
+
* post-deploy sync `commands.ts` imports) and
|
|
21
|
+
* `plugins.ts` (a domain module that must stay inside the port boundary -
|
|
22
|
+
* see DEVELOPMENT.md §Hexagonal architecture) need to read the same set of
|
|
23
|
+
* reserved names, and neither may import the other: `plugins.ts` importing
|
|
24
|
+
* `cli.ts` for this alone becomes a real cycle the moment `cli.ts` imports
|
|
25
|
+
* `discover` from `plugins.ts` too (task 10 adds exactly that edge, to
|
|
26
|
+
* dispatch `blogwright plugin list`) - two modules each waiting on the
|
|
27
|
+
* other to finish initialising before either can run. This module gives
|
|
28
|
+
* both callers a shared, dependency-free home instead.
|
|
29
|
+
*
|
|
30
|
+
* `KNOWN_COMMANDS` is the eight names `cli.ts`'s `main` dispatches through
|
|
31
|
+
* its `switch`, after the `KNOWN_COMMANDS.has(command)` membership test:
|
|
32
|
+
* `bootstrap`, `deploy`, `rollback`, `delete`, `destroy`, `history`, `logs`,
|
|
33
|
+
* `status`.
|
|
34
|
+
*
|
|
35
|
+
* `RESERVED_COMMANDS` is every name a plugin may never claim as its own -
|
|
36
|
+
* see `discover`'s namespace-collision check in `plugins.ts`. It is
|
|
37
|
+
* deliberately NOT `KNOWN_COMMANDS` alone: `init` (dispatched in `cli.ts`
|
|
38
|
+
* ahead of the `KNOWN_COMMANDS` membership test) and `preview` (likewise)
|
|
39
|
+
* never enter that set, so deriving from it alone would under-reserve by
|
|
40
|
+
* two and let a plugin shadow either one. `plugin` is named explicitly for
|
|
41
|
+
* the same reason, one task early: `cli.ts` does not yet dispatch a
|
|
42
|
+
* `blogwright plugin` namespace (task 10 adds `'plugin'` to
|
|
43
|
+
* `KNOWN_COMMANDS`), but the name is reserved from the moment any plugin
|
|
44
|
+
* could collide with it, and the union keeps this set correct both before
|
|
45
|
+
* and after that command exists - no second edit required either way.
|
|
46
|
+
*
|
|
47
|
+
* `pds` is deliberately absent from `RESERVED_COMMANDS`, and adding it here
|
|
48
|
+
* would break `blogwright pds` outright. There is no built-in `pds` command:
|
|
49
|
+
* task 29 deleted `cli.ts`'s hardcoded `command === 'pds'` branch, and the
|
|
50
|
+
* namespace is now served by the bundled `blogwright-pds` package, which
|
|
51
|
+
* declares the plugin name `pds` and is discovered like any other plugin.
|
|
52
|
+
* Reserving the name would make `discover`'s reserved-name check
|
|
53
|
+
* (`plugins.ts`) reject that package - a load `failures` entry, never an
|
|
54
|
+
* installed plugin - so `blogwright pds <action>` would exit 1 with `no
|
|
55
|
+
* built-in command or installed plugin claims "pds"`, `--help` would list
|
|
56
|
+
* none of its six actions, and `blogwright plugin list` would blame a
|
|
57
|
+
* built-in command that does not exist. This is the one name in this file
|
|
58
|
+
* whose reservation would REMOVE a working command rather than protect one;
|
|
59
|
+
* it stays unreserved on purpose, pinned by a test in `plugins.test.ts` and
|
|
60
|
+
* `cli.test.ts`. See `plugins.ts`'s module comment for the full reasoning.
|
|
61
|
+
*/
|
|
62
|
+
export const KNOWN_COMMANDS = new Set([
|
|
63
|
+
'bootstrap',
|
|
64
|
+
'deploy',
|
|
65
|
+
'rollback',
|
|
66
|
+
'delete',
|
|
67
|
+
'destroy',
|
|
68
|
+
'history',
|
|
69
|
+
'logs',
|
|
70
|
+
'status',
|
|
71
|
+
'plugin',
|
|
72
|
+
]);
|
|
73
|
+
export const RESERVED_COMMANDS = new Set([
|
|
74
|
+
...KNOWN_COMMANDS,
|
|
75
|
+
'init',
|
|
76
|
+
'preview',
|
|
77
|
+
'plugin',
|
|
78
|
+
]);
|
package/dist/logger.js
CHANGED
package/dist/microvms.d.ts
CHANGED
|
@@ -3,12 +3,12 @@ import type { OpsContext } from './context.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* Active MicroVMs launched from *this* environment's builder image. Matches on the image
|
|
5
5
|
* ARN recorded in state, falling back to the (env-scoped) image name so a stack whose
|
|
6
|
-
* state predates the ARN still resolves
|
|
6
|
+
* state predates the ARN still resolves - and a sibling env's VMs are never touched.
|
|
7
7
|
*/
|
|
8
8
|
export declare function runningStackMicrovms(ctx: OpsContext): Promise<Microvm[]>;
|
|
9
9
|
/**
|
|
10
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
|
|
11
|
+
* any are alive). Lists them, then interactively offers to terminate - default yes - or to
|
|
12
12
|
* wait, which cancels the destroy. Non-interactive callers get the default (terminate) so
|
|
13
13
|
* automation isn't blocked. Returns true to proceed with the destroy, false to abort.
|
|
14
14
|
*/
|
package/dist/microvms.js
CHANGED
|
@@ -8,7 +8,7 @@ function isActive(vm) {
|
|
|
8
8
|
/**
|
|
9
9
|
* Active MicroVMs launched from *this* environment's builder image. Matches on the image
|
|
10
10
|
* ARN recorded in state, falling back to the (env-scoped) image name so a stack whose
|
|
11
|
-
* state predates the ARN still resolves
|
|
11
|
+
* state predates the ARN still resolves - and a sibling env's VMs are never touched.
|
|
12
12
|
*/
|
|
13
13
|
export async function runningStackMicrovms(ctx) {
|
|
14
14
|
const imageArn = ctx.state.resources['microvm-image']?.arn;
|
|
@@ -22,7 +22,7 @@ export async function runningStackMicrovms(ctx) {
|
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
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
|
|
25
|
+
* any are alive). Lists them, then interactively offers to terminate - default yes - or to
|
|
26
26
|
* wait, which cancels the destroy. Non-interactive callers get the default (terminate) so
|
|
27
27
|
* automation isn't blocked. Returns true to proceed with the destroy, false to abort.
|
|
28
28
|
*/
|
|
@@ -37,7 +37,7 @@ export async function clearRunningMicrovms(ctx) {
|
|
|
37
37
|
defaultYes: true,
|
|
38
38
|
});
|
|
39
39
|
if (!proceed) {
|
|
40
|
-
ctx.logger.info('Leaving MicroVMs running
|
|
40
|
+
ctx.logger.info('Leaving MicroVMs running - destroy cancelled.');
|
|
41
41
|
return false;
|
|
42
42
|
}
|
|
43
43
|
for (const vm of running) {
|
|
@@ -53,4 +53,3 @@ export async function clearRunningMicrovms(ctx) {
|
|
|
53
53
|
ctx.logger.ok('MicroVMs terminated');
|
|
54
54
|
return true;
|
|
55
55
|
}
|
|
56
|
-
//# sourceMappingURL=microvms.js.map
|
package/dist/nodes.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { type ResourceNode as CoreResourceNode } from 'blogwright-core';
|
|
1
2
|
import type { OpsContext } from './context.js';
|
|
2
|
-
|
|
3
|
+
type ResourceNode = CoreResourceNode<OpsContext>;
|
|
3
4
|
export type BuilderImageAction = 'create' | 'update' | 'skip';
|
|
4
5
|
/**
|
|
5
6
|
* Decide what a builder-image reconcile should do: create when the image is missing or
|
|
@@ -17,14 +18,14 @@ export declare function builderImageAction(image: {
|
|
|
17
18
|
* Create, rebuild, or leave the MicroVM builder image, depending on what's deployed:
|
|
18
19
|
* create it if missing, rebuild it if the agent bundle (or its log group) changed or the
|
|
19
20
|
* last build is unhealthy, otherwise no-op. Idempotent and cheap in the common case (a
|
|
20
|
-
* single GetMicrovmImage + hash compare), so it's safe to run before every deploy
|
|
21
|
+
* single GetMicrovmImage + hash compare), so it's safe to run before every deploy - which
|
|
21
22
|
* is how build-agent changes propagate through CI without a separate `bootstrap`.
|
|
22
23
|
*/
|
|
23
24
|
export declare function reconcileBuilderImage(ctx: OpsContext): Promise<void>;
|
|
24
25
|
/**
|
|
25
26
|
* The workflow's OIDC subject claim, scoped per environment to match how each one
|
|
26
27
|
* deploys: previews from any PR ref; staging from pushes to main; production from the
|
|
27
|
-
* `production` GitHub Environment (release-gated
|
|
28
|
+
* `production` GitHub Environment (release-gated - see production.yml), which lets
|
|
28
29
|
* deploys be gated behind environment protection rules.
|
|
29
30
|
*/
|
|
30
31
|
export declare function oidcSubClaim(repo: string, env: string, preview: boolean): string;
|
|
@@ -32,3 +33,4 @@ export declare function oidcSubClaim(repo: string, env: string, preview: boolean
|
|
|
32
33
|
export declare function oidcRolePolicyStatements(ctx: OpsContext): object[];
|
|
33
34
|
/** Build the full node set for the current context (production or preview stack). */
|
|
34
35
|
export declare function buildNodes(ctx: OpsContext): ResourceNode[];
|
|
36
|
+
export {};
|