blogwright 0.4.0-beta.2 → 0.4.0-beta.4
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/agent/agent-manifest.json +1 -1
- package/dist/commands.d.ts +3 -3
- package/dist/commands.js +28 -7
- package/dist/nodes.js +7 -28
- package/package.json +3 -3
package/dist/commands.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { OpsContext } from './context.js';
|
|
|
3
3
|
import { type GraphContext } from './graph.js';
|
|
4
4
|
import { type StatusEntry } from './render.js';
|
|
5
5
|
/** Create the full infrastructure graph. */
|
|
6
|
-
export declare function bootstrap(ctx: OpsContext): Promise<void>;
|
|
6
|
+
export declare function bootstrap(ctx: OpsContext, nodes?: ResourceNode<OpsContext>[]): Promise<void>;
|
|
7
7
|
/**
|
|
8
8
|
* Refuse to destroy the site while any plugin's own state object still
|
|
9
9
|
* exists in the bucket - §State → Scoped state stores' "`blogwright
|
|
@@ -22,8 +22,8 @@ export declare function bootstrap(ctx: OpsContext): Promise<void>;
|
|
|
22
22
|
*
|
|
23
23
|
* Reads the bucket, not the plugin registry, so the refusal holds even for
|
|
24
24
|
* a plugin that has since been uninstalled. Runs inside the teardown verbs
|
|
25
|
-
* themselves, not `createContext
|
|
26
|
-
*
|
|
25
|
+
* themselves, not `createContext`, sharing the listing with the post-bootstrap
|
|
26
|
+
* warning while plugin discovery stays lazy - and ahead of
|
|
27
27
|
* `clearRunningMicrovms`/`destroyGraph` in each, so a refusal has zero side
|
|
28
28
|
* effects: nothing is terminated, nothing is deleted.
|
|
29
29
|
*
|
package/dist/commands.js
CHANGED
|
@@ -49,14 +49,15 @@ function siteBaseUrl(ctx) {
|
|
|
49
49
|
return typeof cf === 'string' ? `https://${cf}` : undefined;
|
|
50
50
|
}
|
|
51
51
|
/** Create the full infrastructure graph. */
|
|
52
|
-
export async function bootstrap(ctx) {
|
|
52
|
+
export async function bootstrap(ctx, nodes = buildNodes(ctx)) {
|
|
53
53
|
ctx.logger.info(colors.bold(`Bootstrapping "${ctx.env}" (bucket ${ctx.names.bucket})`));
|
|
54
54
|
// The state bucket must exist before anything else can persist state.
|
|
55
|
-
await applyGraph(
|
|
55
|
+
await applyGraph(nodes, ctx);
|
|
56
56
|
ctx.logger.ok(`bootstrap complete for "${ctx.env}"`);
|
|
57
57
|
const domain = ctx.state.resources['cloudfront-distribution']?.domainName;
|
|
58
58
|
if (typeof domain === 'string')
|
|
59
59
|
ctx.logger.info(`Site will be served at https://${domain}`);
|
|
60
|
+
await warnAboutScopedState(ctx);
|
|
60
61
|
}
|
|
61
62
|
/**
|
|
62
63
|
* The prefix every state object - scoped or not - is filed under
|
|
@@ -85,6 +86,27 @@ function scopedStateScopes(env, objects) {
|
|
|
85
86
|
.map((key) => key.slice(prefix.length, -'.json'.length));
|
|
86
87
|
return [...new Set(scopes)].sort();
|
|
87
88
|
}
|
|
89
|
+
/** Listing is strict; each caller decides whether a failure can be recovered. */
|
|
90
|
+
async function listScopedStateScopes(ctx) {
|
|
91
|
+
const objects = await ctx.clients.s3.listObjects(ctx.names.bucket, STATE_PREFIX);
|
|
92
|
+
return scopedStateScopes(ctx.env, objects);
|
|
93
|
+
}
|
|
94
|
+
/** A never-bootstrapped plugin has no scoped key, so cannot produce a warning. */
|
|
95
|
+
async function warnAboutScopedState(ctx) {
|
|
96
|
+
let scopes;
|
|
97
|
+
try {
|
|
98
|
+
scopes = await listScopedStateScopes(ctx);
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
ctx.logger.warn(`could not check plugin state for "${ctx.env}" at s3://${ctx.names.bucket}/${STATE_PREFIX} ` +
|
|
102
|
+
`(bootstrap succeeded): ${String(err)}`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
for (const scope of scopes) {
|
|
106
|
+
ctx.logger.warn(`plugin state for "${scope}" still exists in "${ctx.env}"; ` +
|
|
107
|
+
`run \`blogwright ${scope} bootstrap ${ctx.env}\` to reconcile its resources`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
88
110
|
/**
|
|
89
111
|
* Refuse to destroy the site while any plugin's own state object still
|
|
90
112
|
* exists in the bucket - §State → Scoped state stores' "`blogwright
|
|
@@ -103,8 +125,8 @@ function scopedStateScopes(env, objects) {
|
|
|
103
125
|
*
|
|
104
126
|
* Reads the bucket, not the plugin registry, so the refusal holds even for
|
|
105
127
|
* a plugin that has since been uninstalled. Runs inside the teardown verbs
|
|
106
|
-
* themselves, not `createContext
|
|
107
|
-
*
|
|
128
|
+
* themselves, not `createContext`, sharing the listing with the post-bootstrap
|
|
129
|
+
* warning while plugin discovery stays lazy - and ahead of
|
|
108
130
|
* `clearRunningMicrovms`/`destroyGraph` in each, so a refusal has zero side
|
|
109
131
|
* effects: nothing is terminated, nothing is deleted.
|
|
110
132
|
*
|
|
@@ -134,9 +156,9 @@ function scopedStateScopes(env, objects) {
|
|
|
134
156
|
* `destroy()` run over the entire production graph.
|
|
135
157
|
*/
|
|
136
158
|
export async function assertNoScopedState(ctx) {
|
|
137
|
-
let
|
|
159
|
+
let scopes;
|
|
138
160
|
try {
|
|
139
|
-
|
|
161
|
+
scopes = await listScopedStateScopes(ctx);
|
|
140
162
|
}
|
|
141
163
|
catch (err) {
|
|
142
164
|
// No bucket, no state objects - see this function's doc comment. Matched
|
|
@@ -152,7 +174,6 @@ export async function assertNoScopedState(ctx) {
|
|
|
152
174
|
return;
|
|
153
175
|
throw err;
|
|
154
176
|
}
|
|
155
|
-
const scopes = scopedStateScopes(ctx.env, objects);
|
|
156
177
|
if (scopes.length === 0)
|
|
157
178
|
return;
|
|
158
179
|
// The remedy MUST name the environment. `runPlugin` resolves a plugin
|
package/dist/nodes.js
CHANGED
|
@@ -788,7 +788,13 @@ const GITHUB_OIDC_THUMBPRINT = '6938fd4d98bab03faadb97b34396831e3780aea1';
|
|
|
788
788
|
/**
|
|
789
789
|
* IAM role a GitHub Actions workflow assumes via OIDC - to deploy/destroy previews
|
|
790
790
|
* (preview stack, any ref) or to deploy production (main branch only, plus CloudFront
|
|
791
|
-
* invalidation
|
|
791
|
+
* invalidation).
|
|
792
|
+
*
|
|
793
|
+
* The role carries the site's own grants and nothing else. A plugin needing the deploy
|
|
794
|
+
* role to reach one of *its* resources attaches its own **named** inline policy to this
|
|
795
|
+
* same role from its own graph, reconciled by its own `<plugin> bootstrap` - so this
|
|
796
|
+
* document never names a plugin's resource, and `applyOidcRole` rewriting it wholesale
|
|
797
|
+
* on every `blogwright bootstrap` cannot disturb the plugin's.
|
|
792
798
|
*/
|
|
793
799
|
function githubOidcRoleNode(preview) {
|
|
794
800
|
const roleName = (ctx) => ctx.names.githubRole;
|
|
@@ -879,33 +885,6 @@ export function oidcRolePolicyStatements(ctx) {
|
|
|
879
885
|
Action: ['cloudfront:CreateInvalidation'],
|
|
880
886
|
Resource: String(output(ctx, 'cloudfront-distribution').arn),
|
|
881
887
|
});
|
|
882
|
-
if (ctx.config.pds) {
|
|
883
|
-
// The post-deploy PDS sync reads the OAuth secret and writes it back:
|
|
884
|
-
// refresh tokens are single-use, so every sync persists the rotated
|
|
885
|
-
// session (PutSecretValue via the upsert helper, which tries CreateSecret
|
|
886
|
-
// first when the secret is missing).
|
|
887
|
-
//
|
|
888
|
-
// The `??` default duplicates the pds plugin's own `resolvePdsSecretName`
|
|
889
|
-
// deliberately, and must NOT be replaced by an import of it: this
|
|
890
|
-
// resource graph carries no plugin knowledge, which is the whole point of
|
|
891
|
-
// the plugin owning its own node (`packages/pds/src/nodes.ts`). Core
|
|
892
|
-
// stopped defaulting `pds.secretName`, so without this default the
|
|
893
|
-
// template literal would happily interpolate `undefined` into a live IAM
|
|
894
|
-
// policy - `applyOidcRole` rewrites this whole document on every
|
|
895
|
-
// `blogwright bootstrap`, so a wrong ARN here is a broken permission and
|
|
896
|
-
// not a test failure. Task 59 deletes this statement together with the
|
|
897
|
-
// `ctx.config.pds` branch around it; the duplication lives only until
|
|
898
|
-
// then.
|
|
899
|
-
statements.push({
|
|
900
|
-
Effect: 'Allow',
|
|
901
|
-
Action: [
|
|
902
|
-
'secretsmanager:GetSecretValue',
|
|
903
|
-
'secretsmanager:PutSecretValue',
|
|
904
|
-
'secretsmanager:CreateSecret',
|
|
905
|
-
],
|
|
906
|
-
Resource: `arn:aws:secretsmanager:${ctx.config.region}:${ctx.accountId}:secret:${ctx.config.pds.secretName ?? `${ctx.config.siteName}/atproto`}-*`,
|
|
907
|
-
});
|
|
908
|
-
}
|
|
909
888
|
}
|
|
910
889
|
return statements;
|
|
911
890
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blogwright",
|
|
3
|
-
"version": "0.4.0-beta.
|
|
3
|
+
"version": "0.4.0-beta.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"blogwright": "./dist/bin.js",
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"blogwright-core": "0.4.0-beta.
|
|
21
|
-
"blogwright-pds": "0.4.0-beta.
|
|
20
|
+
"blogwright-core": "0.4.0-beta.4",
|
|
21
|
+
"blogwright-pds": "0.4.0-beta.4",
|
|
22
22
|
"fflate": "^0.8.3"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|