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/nodes.js
CHANGED
|
@@ -54,7 +54,7 @@ function bucketNode() {
|
|
|
54
54
|
}
|
|
55
55
|
function logGroupNode(id, title, name, days,
|
|
56
56
|
// CloudFront vended log delivery exists only in us-east-1, so its log group
|
|
57
|
-
// must live there too
|
|
57
|
+
// must live there too - regardless of the stack's primary region.
|
|
58
58
|
usEast1 = false) {
|
|
59
59
|
const logs = (ctx) => (usEast1 ? ctx.clients.logsUsEast1 : ctx.clients.logs);
|
|
60
60
|
const region = (ctx) => (usEast1 ? 'us-east-1' : ctx.config.region);
|
|
@@ -92,7 +92,7 @@ const LAMBDA_TRUST = {
|
|
|
92
92
|
],
|
|
93
93
|
};
|
|
94
94
|
/** IAM role Lambda assumes while building the MicroVM image. */
|
|
95
|
-
/** Apply the build role's inline policy (idempotent
|
|
95
|
+
/** Apply the build role's inline policy (idempotent - used by create + update). */
|
|
96
96
|
async function applyBuildRolePolicy(ctx) {
|
|
97
97
|
// The build role is BOTH the image-build role AND the MicroVM's ambient runtime
|
|
98
98
|
// identity (via IMDS), so it needs the build's runtime S3 permissions: read the
|
|
@@ -104,7 +104,7 @@ async function applyBuildRolePolicy(ctx) {
|
|
|
104
104
|
{ Effect: 'Allow', Action: ['s3:ListBucket'], Resource: `arn:aws:s3:::${ctx.names.bucket}` },
|
|
105
105
|
{
|
|
106
106
|
// s3:PutObjectTagging is required even though the tags ride on the PUT
|
|
107
|
-
// itself (x-amz-tagging header)
|
|
107
|
+
// itself (x-amz-tagging header) - AWS checks it as a distinct action, and
|
|
108
108
|
// PutObject does not imply it. Without it every tagged upload 403s.
|
|
109
109
|
Effect: 'Allow',
|
|
110
110
|
Action: ['s3:PutObject', 's3:PutObjectTagging', 's3:DeleteObject'],
|
|
@@ -158,7 +158,7 @@ async function applyExecRolePolicy(ctx) {
|
|
|
158
158
|
Resource: `arn:aws:s3:::${ctx.names.bucket}/*`,
|
|
159
159
|
},
|
|
160
160
|
{
|
|
161
|
-
// s3:PutObjectTagging: see the build role
|
|
161
|
+
// s3:PutObjectTagging: see the build role - a tagged PUT needs it explicitly.
|
|
162
162
|
Effect: 'Allow',
|
|
163
163
|
Action: ['s3:PutObject', 's3:PutObjectTagging', 's3:DeleteObject'],
|
|
164
164
|
Resource: siteWriteResource(ctx),
|
|
@@ -235,7 +235,7 @@ async function imageInput(ctx) {
|
|
|
235
235
|
}
|
|
236
236
|
/**
|
|
237
237
|
* Poll until the image reaches its expected terminal state, treating a timeout (still
|
|
238
|
-
* CREATING/UPDATING) as failure
|
|
238
|
+
* CREATING/UPDATING) as failure - never success. For updates, require the version to
|
|
239
239
|
* advance past `priorVersion` so a stale pre-update state can't be read as done.
|
|
240
240
|
*/
|
|
241
241
|
async function awaitImageSettled(ctx, arn, expected, priorVersion) {
|
|
@@ -272,7 +272,7 @@ export function builderImageAction(image, recorded, hash, logGroup) {
|
|
|
272
272
|
* Create, rebuild, or leave the MicroVM builder image, depending on what's deployed:
|
|
273
273
|
* create it if missing, rebuild it if the agent bundle (or its log group) changed or the
|
|
274
274
|
* last build is unhealthy, otherwise no-op. Idempotent and cheap in the common case (a
|
|
275
|
-
* single GetMicrovmImage + hash compare), so it's safe to run before every deploy
|
|
275
|
+
* single GetMicrovmImage + hash compare), so it's safe to run before every deploy - which
|
|
276
276
|
* is how build-agent changes propagate through CI without a separate `bootstrap`.
|
|
277
277
|
*/
|
|
278
278
|
export async function reconcileBuilderImage(ctx) {
|
|
@@ -333,12 +333,12 @@ function microvmImageNode() {
|
|
|
333
333
|
},
|
|
334
334
|
};
|
|
335
335
|
}
|
|
336
|
-
/** ACM certificate (us-east-1)
|
|
336
|
+
/** ACM certificate (us-east-1) - only present when a custom domain is configured. */
|
|
337
337
|
function certificateNode() {
|
|
338
338
|
return {
|
|
339
339
|
id: 'acm-certificate',
|
|
340
340
|
// Depends on the bucket so state (with the cert ARN) can be saved before the long
|
|
341
|
-
// ISSUED wait
|
|
341
|
+
// ISSUED wait - the id sorts before 'bucket', so without this it would run first.
|
|
342
342
|
dependsOn: ['bucket'],
|
|
343
343
|
title: 'ACM certificate',
|
|
344
344
|
async read(ctx) {
|
|
@@ -373,7 +373,7 @@ function certificateNode() {
|
|
|
373
373
|
let initial = await ctx.clients.acm.describeCertificate(arn);
|
|
374
374
|
if (initial.status === 'PENDING_VALIDATION' && initial.validation.length === 0) {
|
|
375
375
|
// ACM populates the validation ResourceRecords asynchronously after
|
|
376
|
-
// RequestCertificate
|
|
376
|
+
// RequestCertificate - an empty set here is that race, not "nothing to
|
|
377
377
|
// validate". Acting on it would skip record creation entirely and the
|
|
378
378
|
// issuance poll below could never succeed.
|
|
379
379
|
initial = await pollUntil(() => ctx.clients.acm.describeCertificate(arn), (s) => s.status !== 'PENDING_VALIDATION' || s.validation.length > 0, { intervalMs: 5_000, timeoutMs: 5 * 60_000 });
|
|
@@ -383,7 +383,7 @@ function certificateNode() {
|
|
|
383
383
|
}
|
|
384
384
|
if (initial.status !== 'ISSUED' && initial.validation.length > 0) {
|
|
385
385
|
if (ctx.preview) {
|
|
386
|
-
// Preview domain is a Route53 hosted zone
|
|
386
|
+
// Preview domain is a Route53 hosted zone - create the validation records for you.
|
|
387
387
|
const zoneId = await ctx.clients.route53.hostedZoneId(domain);
|
|
388
388
|
if (!zoneId)
|
|
389
389
|
throw new Error(`no Route53 hosted zone found for ${domain}`);
|
|
@@ -454,7 +454,7 @@ const PREVIEW_FUNCTION_CODE = `function handler(event) {
|
|
|
454
454
|
/**
|
|
455
455
|
* CloudFront Function (viewer-request) for staging/production: resolve a directory URL
|
|
456
456
|
* to its index document (`/projects/` → `/projects/index.html`). Required because the S3
|
|
457
|
-
* origin is the private REST endpoint (via OAC), which
|
|
457
|
+
* origin is the private REST endpoint (via OAC), which - unlike an S3 website endpoint -
|
|
458
458
|
* does no index-document resolution, so `DefaultRootObject` only covers the apex. The
|
|
459
459
|
* `/site` origin path is applied by the distribution, so this function must not add it.
|
|
460
460
|
*/
|
|
@@ -496,7 +496,7 @@ function routerFunctionNode(preview) {
|
|
|
496
496
|
/**
|
|
497
497
|
* Find the distribution a crashed earlier bootstrap created but never recorded in
|
|
498
498
|
* state. The deterministic comment (`"<siteName> <env>"`) narrows candidates; identity
|
|
499
|
-
* is confirmed by CallerReference
|
|
499
|
+
* is confirmed by CallerReference - comments are editable in the console, while the
|
|
500
500
|
* reference is immutable for the distribution's life and collision-free across
|
|
501
501
|
* environments by construction.
|
|
502
502
|
*/
|
|
@@ -548,7 +548,7 @@ function distributionNode(hasDomain, preview) {
|
|
|
548
548
|
functionArn: String(output(ctx, 'cloudfront-function').arn),
|
|
549
549
|
// Previews are served uncached (per-PR content, host-routed); staging/production
|
|
550
550
|
// keep the default cache policy. Non-preview stacks map the S3 REST origin's
|
|
551
|
-
// 403/404 (a missing key) to the site's 404 page
|
|
551
|
+
// 403/404 (a missing key) to the site's 404 page - or, in SPA mode, to
|
|
552
552
|
// /index.html with a 200 so client-side routes deep-link correctly.
|
|
553
553
|
...(preview
|
|
554
554
|
? { cachePolicyId: CACHING_DISABLED }
|
|
@@ -563,8 +563,8 @@ function distributionNode(hasDomain, preview) {
|
|
|
563
563
|
}
|
|
564
564
|
catch (err) {
|
|
565
565
|
// A crashed earlier bootstrap can leave a distribution in AWS that state never
|
|
566
|
-
// recorded. Retrying then 409s on the duplicate alias
|
|
567
|
-
// conflict check fires before the CallerReference idempotency match
|
|
566
|
+
// recorded. Retrying then 409s on the duplicate alias - CloudFront's CNAME
|
|
567
|
+
// conflict check fires before the CallerReference idempotency match - so adopt
|
|
568
568
|
// the orphan instead. No verified match means the alias belongs to a foreign
|
|
569
569
|
// distribution: that conflict is real and must surface.
|
|
570
570
|
const conflict = err instanceof AwsError &&
|
|
@@ -599,7 +599,7 @@ function distributionNode(hasDomain, preview) {
|
|
|
599
599
|
},
|
|
600
600
|
async update(ctx) {
|
|
601
601
|
// A domain added (or changed) after the first bootstrap must reach the
|
|
602
|
-
// existing distribution
|
|
602
|
+
// existing distribution - the certificate node validates the cert, but
|
|
603
603
|
// only this reconcile attaches the alias + viewer certificate.
|
|
604
604
|
const id = output(ctx, 'cloudfront-distribution').id;
|
|
605
605
|
if (typeof id !== 'string')
|
|
@@ -607,7 +607,7 @@ function distributionNode(hasDomain, preview) {
|
|
|
607
607
|
if (!ctx.domain) {
|
|
608
608
|
// Deliberately no automatic alias removal: dropping --domain from a
|
|
609
609
|
// later run must not detach a live site's hostname.
|
|
610
|
-
ctx.logger.ok('no domain configured
|
|
610
|
+
ctx.logger.ok('no domain configured - existing aliases left as-is');
|
|
611
611
|
return;
|
|
612
612
|
}
|
|
613
613
|
const alias = preview ? `*.${ctx.domain}` : ctx.domain;
|
|
@@ -635,6 +635,52 @@ function distributionNode(hasDomain, preview) {
|
|
|
635
635
|
},
|
|
636
636
|
};
|
|
637
637
|
}
|
|
638
|
+
/**
|
|
639
|
+
* True when `delivery` is the one this site's node created. AWS permits exactly
|
|
640
|
+
* one delivery source per distribution, so the analytics plugin necessarily
|
|
641
|
+
* hangs its own delivery off the site's source: ownership is the destination a
|
|
642
|
+
* delivery feeds, and nothing else. The final `:`-separated segment of a
|
|
643
|
+
* `delivery-destination` ARN is its name, which is derived from the environment
|
|
644
|
+
* (`deriveNames`) and therefore also matches the previous stack's delivery on a
|
|
645
|
+
* destroy → bootstrap cycle - exactly the delivery the ConflictException retry
|
|
646
|
+
* exists to remove.
|
|
647
|
+
*
|
|
648
|
+
* Position cannot stand in for this (`findDeliveryIdBySource` takes whichever
|
|
649
|
+
* delivery AWS lists first), and neither can the recorded `destination` output:
|
|
650
|
+
* it is empty precisely when the retry needs it, because `putDeliverySource`
|
|
651
|
+
* throws the Conflict before `putDeliveryDestination` ever runs.
|
|
652
|
+
*/
|
|
653
|
+
function isOwnDelivery(delivery, destinationName) {
|
|
654
|
+
return delivery.deliveryDestinationArn.split(':').pop() === destinationName;
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* The ids of the site's own deliveries on `names.deliverySource`, after
|
|
658
|
+
* refusing outright when the shared source carries a delivery the site did not
|
|
659
|
+
* create. Both the `delete()` teardown and the ConflictException retry call
|
|
660
|
+
* this BEFORE deleting anything: each of them goes on to delete the delivery
|
|
661
|
+
* source, and AWS rejects that while another delivery is attached - a refusal
|
|
662
|
+
* `deleteDeliverySource` does not catch (it swallows only not-found). Refusing
|
|
663
|
+
* up front turns a teardown that throws part-way, after the site's own delivery
|
|
664
|
+
* is already gone, into an actionable stop with nothing removed.
|
|
665
|
+
*/
|
|
666
|
+
async function ownDeliveryIdsOrRefuse(ctx) {
|
|
667
|
+
const deliveries = await ctx.clients.logsUsEast1.deliveriesForSource(ctx.names.deliverySource);
|
|
668
|
+
const foreign = deliveries.filter((d) => !isOwnDelivery(d, ctx.names.deliveryDestination));
|
|
669
|
+
if (foreign.length > 0) {
|
|
670
|
+
const listed = foreign
|
|
671
|
+
.map((d) => `${d.id} (destination ${d.deliveryDestinationArn})`)
|
|
672
|
+
.join(', ');
|
|
673
|
+
throw new Error(`delivery source ${ctx.names.deliverySource} still carries ` +
|
|
674
|
+
`${foreign.length === 1 ? 'a delivery' : `${foreign.length} deliveries`} this site does ` +
|
|
675
|
+
`not own (${listed}); removing the source would break whoever does. Tear that stack ` +
|
|
676
|
+
`down first, then retry: blogwright analytics destroy ${ctx.env} --yes`);
|
|
677
|
+
}
|
|
678
|
+
// Filtered, not `deliveries.map(...)`: the throw above already guarantees every
|
|
679
|
+
// delivery here is the site's own, but that is a non-local guarantee. Re-applying
|
|
680
|
+
// the predicate makes the name and the body agree on their own, so softening the
|
|
681
|
+
// refusal cannot silently start feeding foreign ids to the delete loop.
|
|
682
|
+
return deliveries.filter((d) => isOwnDelivery(d, ctx.names.deliveryDestination)).map((d) => d.id);
|
|
683
|
+
}
|
|
638
684
|
/** Wire CloudFront access logs to the CloudWatch log group via vended log delivery. */
|
|
639
685
|
function logDeliveryNode() {
|
|
640
686
|
async function wire(ctx) {
|
|
@@ -663,13 +709,21 @@ function logDeliveryNode() {
|
|
|
663
709
|
}
|
|
664
710
|
catch (err) {
|
|
665
711
|
// delete() below leaves the delivery plumbing behind, and PutDeliverySource
|
|
666
|
-
// refuses to repoint an existing source at a new distribution ARN
|
|
712
|
+
// refuses to repoint an existing source at a new distribution ARN - so a
|
|
667
713
|
// destroy → bootstrap cycle hits ConflictException here. Remove the stale
|
|
668
714
|
// delivery/source/destination trio and retry once.
|
|
669
715
|
if (!(err instanceof AwsError && /Conflict/i.test(err.code)))
|
|
670
716
|
throw err;
|
|
671
|
-
ctx.logger.step('stale log delivery from a previous stack
|
|
672
|
-
|
|
717
|
+
ctx.logger.step('stale log delivery from a previous stack - removing and retrying');
|
|
718
|
+
// Refuse before deleting anything when the source is shared: removing it
|
|
719
|
+
// IS the retry (PutDeliverySource will not repoint a source that still
|
|
720
|
+
// exists), so a foreign delivery forecloses the retry entirely. Deleting
|
|
721
|
+
// only the site's own delivery and pressing on would leave the stack with
|
|
722
|
+
// no CloudWatch delivery and a failed bootstrap - worse than the conflict
|
|
723
|
+
// being healed. Only the site's own ids are removed here; the old loop
|
|
724
|
+
// deleted every delivery on the source, silently unwiring the other owner.
|
|
725
|
+
const ownDeliveryIds = await ownDeliveryIdsOrRefuse(ctx);
|
|
726
|
+
for (const id of ownDeliveryIds) {
|
|
673
727
|
await ctx.clients.logsUsEast1.deleteDelivery(id);
|
|
674
728
|
}
|
|
675
729
|
await ctx.clients.logsUsEast1.deleteDeliverySource(ctx.names.deliverySource);
|
|
@@ -682,10 +736,11 @@ function logDeliveryNode() {
|
|
|
682
736
|
// delivery source/destination persist, and a later bootstrap against a new
|
|
683
737
|
// distribution ARN fails with ConflictException ("Update to existing Delivery Source
|
|
684
738
|
// with new ResourceId is not allowed"). Delete the delivery first (it references both),
|
|
685
|
-
// then the source and destination. The
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
739
|
+
// then the source and destination. The ids aren't in state, so look them up by source
|
|
740
|
+
// name - which also settles whether this site still owns the source at all.
|
|
741
|
+
const deliveryIds = await ownDeliveryIdsOrRefuse(ctx);
|
|
742
|
+
for (const id of deliveryIds)
|
|
743
|
+
await ctx.clients.logsUsEast1.deleteDelivery(id);
|
|
689
744
|
await ctx.clients.logsUsEast1.deleteDeliverySource(ctx.names.deliverySource);
|
|
690
745
|
await ctx.clients.logsUsEast1.deleteDeliveryDestination(ctx.names.deliveryDestination);
|
|
691
746
|
},
|
|
@@ -731,12 +786,12 @@ const GITHUB_OIDC_URL = 'token.actions.githubusercontent.com';
|
|
|
731
786
|
// this value, but the API requires one.
|
|
732
787
|
const GITHUB_OIDC_THUMBPRINT = '6938fd4d98bab03faadb97b34396831e3780aea1';
|
|
733
788
|
/**
|
|
734
|
-
* IAM role a GitHub Actions workflow assumes via OIDC
|
|
789
|
+
* IAM role a GitHub Actions workflow assumes via OIDC - to deploy/destroy previews
|
|
735
790
|
* (preview stack, any ref) or to deploy production (main branch only, plus CloudFront
|
|
736
791
|
* invalidation and read access to the PDS credentials secret).
|
|
737
792
|
*/
|
|
738
793
|
function githubOidcRoleNode(preview) {
|
|
739
|
-
const roleName = (ctx) =>
|
|
794
|
+
const roleName = (ctx) => ctx.names.githubRole;
|
|
740
795
|
return {
|
|
741
796
|
id: 'gh-oidc-role',
|
|
742
797
|
// Production deploys invalidate the distribution, so its ARN must be in state.
|
|
@@ -763,7 +818,7 @@ function githubOidcRoleNode(preview) {
|
|
|
763
818
|
/**
|
|
764
819
|
* The workflow's OIDC subject claim, scoped per environment to match how each one
|
|
765
820
|
* deploys: previews from any PR ref; staging from pushes to main; production from the
|
|
766
|
-
* `production` GitHub Environment (release-gated
|
|
821
|
+
* `production` GitHub Environment (release-gated - see production.yml), which lets
|
|
767
822
|
* deploys be gated behind environment protection rules.
|
|
768
823
|
*/
|
|
769
824
|
export function oidcSubClaim(repo, env, preview) {
|
|
@@ -829,6 +884,18 @@ export function oidcRolePolicyStatements(ctx) {
|
|
|
829
884
|
// refresh tokens are single-use, so every sync persists the rotated
|
|
830
885
|
// session (PutSecretValue via the upsert helper, which tries CreateSecret
|
|
831
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.
|
|
832
899
|
statements.push({
|
|
833
900
|
Effect: 'Allow',
|
|
834
901
|
Action: [
|
|
@@ -836,7 +903,7 @@ export function oidcRolePolicyStatements(ctx) {
|
|
|
836
903
|
'secretsmanager:PutSecretValue',
|
|
837
904
|
'secretsmanager:CreateSecret',
|
|
838
905
|
],
|
|
839
|
-
Resource: `arn:aws:secretsmanager:${ctx.config.region}:${ctx.accountId}:secret:${ctx.config.pds.secretName}-*`,
|
|
906
|
+
Resource: `arn:aws:secretsmanager:${ctx.config.region}:${ctx.accountId}:secret:${ctx.config.pds.secretName ?? `${ctx.config.siteName}/atproto`}-*`,
|
|
840
907
|
});
|
|
841
908
|
}
|
|
842
909
|
}
|
|
@@ -872,7 +939,7 @@ async function applyOidcRole(ctx, roleName) {
|
|
|
872
939
|
}
|
|
873
940
|
/**
|
|
874
941
|
* Route53 wildcard record pointing *.<domain> at the preview CloudFront
|
|
875
|
-
* distribution
|
|
942
|
+
* distribution - A/AAAA alias records (free queries, apex-safe), not a CNAME.
|
|
876
943
|
*/
|
|
877
944
|
function previewDnsNode() {
|
|
878
945
|
async function upsertAliases(ctx) {
|
|
@@ -885,7 +952,7 @@ function previewDnsNode() {
|
|
|
885
952
|
const cf = String(output(ctx, 'cloudfront-distribution').domainName);
|
|
886
953
|
const out = output(ctx, 'preview-dns');
|
|
887
954
|
// Route53 refuses A/AAAA alongside a CNAME at the same name, so clear any
|
|
888
|
-
// CNAME first
|
|
955
|
+
// CNAME first - a pre-0.2.1 bootstrap's (recorded in state) or an
|
|
889
956
|
// operator's manual workaround (pointing at the distribution). Deleting a
|
|
890
957
|
// record that is not there is a no-op.
|
|
891
958
|
if (out.type !== 'ALIAS') {
|
|
@@ -979,4 +1046,3 @@ export function buildNodes(ctx) {
|
|
|
979
1046
|
}
|
|
980
1047
|
return nodes;
|
|
981
1048
|
}
|
|
982
|
-
//# sourceMappingURL=nodes.js.map
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OWNERSHIP: this module and its test file (`plugin-commands.test.ts`) are
|
|
3
|
+
* created here, by task 10, which is their sole author for the whole plan.
|
|
4
|
+
* Tasks 13 (the generic `blogwright <plugin> init` action), 16 (the generic
|
|
5
|
+
* `bootstrap`/`status`/`destroy` lifecycle verbs) and 17 (`blogwright plugin
|
|
6
|
+
* list`) all extend the SAME dispatch surface and the SAME test file rather
|
|
7
|
+
* than re-creating either - on the plan's dependency graph none of the three
|
|
8
|
+
* later tasks depends on either of the other two, so without one named
|
|
9
|
+
* owner here, three tasks would each try to create these two files from
|
|
10
|
+
* scratch and collide. Extend this module; never recreate it.
|
|
11
|
+
*
|
|
12
|
+
* `runPlugin` is generic dispatch for `blogwright <plugin> <action>`: the
|
|
13
|
+
* fall-through `cli.ts` reaches once the first positional is neither a
|
|
14
|
+
* built-in command nor `plugin` itself (`KNOWN_COMMANDS`,
|
|
15
|
+
* `known-commands.ts`). It:
|
|
16
|
+
*
|
|
17
|
+
* 1. Runs `discover` (over the `fs`/`loader` ports the caller supplies -
|
|
18
|
+
* see `runPlugin`'s `ports` parameter) to find the installed plugin
|
|
19
|
+
* claiming `command` as its namespace.
|
|
20
|
+
* 2. Matches the LONGEST declared action against the leading positionals,
|
|
21
|
+
* so a multi-word action such as `secret status` dispatches by
|
|
22
|
+
* declaration - never by hand-rolled positional shifting. `cli.ts`'s
|
|
23
|
+
* `runPds` branch shifted positionals to reach `secret status` and
|
|
24
|
+
* `secret delete`; task 29 deleted it, and this step is now the only
|
|
25
|
+
* thing that resolves a multi-word action anywhere in the CLI.
|
|
26
|
+
* 3. Resolves the environment exactly the way every built-in command
|
|
27
|
+
* already does: the first positional left over once the action is
|
|
28
|
+
* consumed, overridden by `--env`, defaulting to `production`.
|
|
29
|
+
* 4. Builds the ONE `OpsContext` this dispatch needs, now that the real
|
|
30
|
+
* environment is confirmed, validates the matched plugin's OWN config
|
|
31
|
+
* block off that context's raw `configDocument` (`resolvePluginConfig`,
|
|
32
|
+
* `plugins.ts` - the dispatched plugin's block and no other), adapts
|
|
33
|
+
* the context into the narrow `PluginContext` the SPI promises
|
|
34
|
+
* (`toPluginContext`, below) with the validated block on
|
|
35
|
+
* `pluginConfig`, and runs the matched command, mapping a normal return
|
|
36
|
+
* to exit code 0.
|
|
37
|
+
*
|
|
38
|
+
* Steps 1-3 run BEFORE any `OpsContext` is built - see `runPlugin`'s own
|
|
39
|
+
* doc comment for why an earlier, provisional-context version of this
|
|
40
|
+
* function was wrong, not merely wasteful.
|
|
41
|
+
*
|
|
42
|
+
* TASK 13 - the generic `init` action, and PRECEDENCE. Step 2 above matches
|
|
43
|
+
* a plugin's own `commands` FIRST; the generic `init` action (`runGenericInit`,
|
|
44
|
+
* below) is only ever reached once that match has already failed AND the
|
|
45
|
+
* leading word is exactly `init`. This is the whole of §CLI → `blogwright
|
|
46
|
+
* <plugin> init`'s precedence rule: a plugin declaring its own `init`
|
|
47
|
+
* command owns whatever `blogwright <plugin> init` does, full stop - pds's
|
|
48
|
+
* `init` creates the standard.site publication record
|
|
49
|
+
* (`packages/pds/src/commands.ts:118`) and writes no config block at all,
|
|
50
|
+
* and it must never be shadowed by a generic config writer. Nothing here
|
|
51
|
+
* requires a declared `init` command to write config; the generic action
|
|
52
|
+
* applies only where NO `init` command is declared. The other half of the
|
|
53
|
+
* rule - a plugin may not declare BOTH an `init` command and an `init?(io)`
|
|
54
|
+
* contributor, because the contributor would then never run - is a
|
|
55
|
+
* discovery-time rejection in `plugins.ts`'s collision pass, not something
|
|
56
|
+
* this dispatcher has to account for: by the time a plugin reaches here it
|
|
57
|
+
* has at most one of the two.
|
|
58
|
+
*
|
|
59
|
+
* The generic action itself needs none of the AWS-reaching machinery
|
|
60
|
+
* `makeContext` builds (accountId, clients, state) - only the two ports a
|
|
61
|
+
* plugin's `init?(io)` contributor is typed against (`fs`, `terminal`) and
|
|
62
|
+
* the resolved environment/repo root `runPlugin` already has before any
|
|
63
|
+
* `OpsContext` exists. Building a real context just to splice a text file
|
|
64
|
+
* would additionally require a runnable AWS session before an operator has
|
|
65
|
+
* even finished being asked their plugin's questions - so it deliberately
|
|
66
|
+
* does not.
|
|
67
|
+
*
|
|
68
|
+
* TASK 16 - the generic `bootstrap`/`status`/`destroy` lifecycle verbs, and
|
|
69
|
+
* PRECEDENCE. Like `init`, these three are only ever reached once step 2's
|
|
70
|
+
* `matchAction` has already failed to match the plugin's own `commands` -
|
|
71
|
+
* but the precedence differs by verb:
|
|
72
|
+
*
|
|
73
|
+
* - `bootstrap` and `destroy` are ALWAYS the generic verbs. A plugin may
|
|
74
|
+
* not import the CLI (§CLI → Plugin dispatch), and so cannot run the
|
|
75
|
+
* engine (`applyGraph`/`destroyGraph`, `graph.ts`) itself - there is no
|
|
76
|
+
* way for a plugin's own `bootstrap`/`destroy` command to do what these
|
|
77
|
+
* verbs need to do. A plugin declaring either as one of its own
|
|
78
|
+
* `commands` is therefore rejected at discovery, naming the plugin and
|
|
79
|
+
* the colliding action - `plugins.ts`'s `rejectDeclaredLifecycleCollisions`,
|
|
80
|
+
* beside `rejectDeclaredInitCollisions` in the same collision pass (see
|
|
81
|
+
* that module's DECISION note) - so `matchAction` never has a real
|
|
82
|
+
* `bootstrap`/`destroy` command to match against in the first place.
|
|
83
|
+
* - `status` is the generic verb ONLY UNLESS the plugin declares its own -
|
|
84
|
+
* `read()` lives on the plugin's own nodes, no engine call is needed,
|
|
85
|
+
* so there is nothing stopping a plugin from implementing `status`
|
|
86
|
+
* itself (pds's `secret status` is a precedent for a plugin owning its
|
|
87
|
+
* own status reporting). A declared `status` command is therefore left
|
|
88
|
+
* alone: `matchAction` already matches it in step 2, and the generic
|
|
89
|
+
* verb below is never reached for that plugin.
|
|
90
|
+
*
|
|
91
|
+
* All three are further gated on `plugin.nodes` being declared at all
|
|
92
|
+
* (`genericLifecycleCommand`, below): a plugin with no `nodes` contributor
|
|
93
|
+
* gains none of the three, and asking for one falls through to the same
|
|
94
|
+
* unknown-action refusal every other unmatched action gets - it is not a
|
|
95
|
+
* special case, because `genericLifecycleCommand` returns `undefined` for
|
|
96
|
+
* exactly that plugin, the same way an undeclared `init` contributor leaves
|
|
97
|
+
* `runGenericInit` unreached above.
|
|
98
|
+
*
|
|
99
|
+
* Each of the three runs the CLI's own engine - `applyGraph`, `destroyGraph`
|
|
100
|
+
* (`graph.ts`) and `readNodeStatus` (`commands.ts`) - over `plugin.nodes(ctx)`
|
|
101
|
+
* against a context built by `toPluginContext` (below), which by this task
|
|
102
|
+
* re-points `store`/`state`/`save()` at a `StateStore` scoped to the
|
|
103
|
+
* plugin's own name (`state/<env>.<plugin>.json`) rather than the site's.
|
|
104
|
+
* `destroy` additionally refuses without `--yes`, mirroring the site verb's
|
|
105
|
+
* own contract (`commands.ts`'s `destroy`), and deletes the scoped state
|
|
106
|
+
* object itself once `destroyGraph` has torn down every node - mirroring
|
|
107
|
+
* `commands.ts`'s own `destroy`/`previewTeardown`, both of which call
|
|
108
|
+
* `ctx.store.delete()` right after `destroyGraph`.
|
|
109
|
+
*
|
|
110
|
+
* TASK 17 - the built-in `plugin` namespace (`runPluginNamespace`, at the
|
|
111
|
+
* foot of this module) is a SECOND entry point, not an action of the generic
|
|
112
|
+
* dispatch above: `plugin` is reserved (`known-commands.ts`), so no installed
|
|
113
|
+
* plugin can claim it and `runPlugin` never sees it. It lives here because it
|
|
114
|
+
* is the same surface - the actions an operator types after `blogwright` -
|
|
115
|
+
* and shares this module's discovery call and its unknown-action refusal
|
|
116
|
+
* shape. See its own section comment for why `cli.ts` must dispatch it before
|
|
117
|
+
* `createContext`.
|
|
118
|
+
*/
|
|
119
|
+
import { type Plugin, type PluginContext, type Terminal } from 'blogwright-core';
|
|
120
|
+
import { type ContextOptions, type OpsContext } from './context.js';
|
|
121
|
+
import { type Logger } from './logger.js';
|
|
122
|
+
import type { PackageManager, Ports } from './ports.js';
|
|
123
|
+
/**
|
|
124
|
+
* The parsed flag values `main`'s single `parseArgs` call produces, as far
|
|
125
|
+
* as generic plugin dispatch reads them. `main` passes its own `values`
|
|
126
|
+
* object here directly - this interface only narrows what `runPlugin` reads
|
|
127
|
+
* off it, the same way `cli.ts`'s hand-rolled `PreviewValues` narrows the
|
|
128
|
+
* same shared object for itself. `PdsValues` was the other such narrowing
|
|
129
|
+
* until task 29 deleted it with the rest of the pds branch, which is why
|
|
130
|
+
* `identifier` below matters: this interface, and `serialiseFlags` under
|
|
131
|
+
* it, are now the only thing that carries `--identifier` to `pds login`.
|
|
132
|
+
*/
|
|
133
|
+
export interface PluginValues {
|
|
134
|
+
env?: string | undefined;
|
|
135
|
+
domain?: string | undefined;
|
|
136
|
+
config?: string | undefined;
|
|
137
|
+
endpoint?: string | undefined;
|
|
138
|
+
hash?: string | undefined;
|
|
139
|
+
id?: string | undefined;
|
|
140
|
+
identifier?: string | undefined;
|
|
141
|
+
plain: boolean;
|
|
142
|
+
refresh: boolean;
|
|
143
|
+
yes: boolean;
|
|
144
|
+
help: boolean;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Adapt an `OpsContext` into the narrow `PluginContext` a plugin command
|
|
148
|
+
* runs against. This is an ADAPTATION, not an assignment: an `OpsContext`
|
|
149
|
+
* carries thirteen of `PluginContext`'s sixteen members and none of
|
|
150
|
+
* `pluginConfig`, `siteState` or `record`, so a bare assignment is
|
|
151
|
+
* `TS2739`. This function supplies exactly those three - plus the
|
|
152
|
+
* two-member `ports` `PluginPorts` narrows the CLI's six-member `Ports`
|
|
153
|
+
* to, and the plugin's own scoped `store`/`state`/`save()` (below) - and
|
|
154
|
+
* passes every other member through unchanged. No cast, no `any`, anywhere
|
|
155
|
+
* in it.
|
|
156
|
+
*
|
|
157
|
+
* `pluginConfig` is supplied by the CALLER rather than read here, because
|
|
158
|
+
* this function is handed a plugin's NAME and not the plugin: `runPlugin`
|
|
159
|
+
* calls `resolvePluginConfig(plugin, ctx.configDocument)` (`plugins.ts`)
|
|
160
|
+
* first, so an invalid block fails BEFORE the scoped `store.load()` below
|
|
161
|
+
* makes the dispatch's first AWS call and long before the command does any
|
|
162
|
+
* work. The parameter is required, with no `{}` default: the dispatcher
|
|
163
|
+
* erases `TConfig` (it dispatches `Plugin<unknown>` and returns
|
|
164
|
+
* `PluginContext<unknown>`), so a forgotten argument could not be caught
|
|
165
|
+
* anywhere downstream - `pnpm typecheck` catching it here is the only
|
|
166
|
+
* check there is.
|
|
167
|
+
*
|
|
168
|
+
* `siteState` is `ops.state` passed through as the read-only view the SPI
|
|
169
|
+
* promises - a plugin reads the site's own recorded outputs through it (the
|
|
170
|
+
* analytics log-delivery node reads the site's CloudFront distribution
|
|
171
|
+
* through it), but never writes it. It is deliberately NOT the scoped load
|
|
172
|
+
* below: overwriting it would leave a plugin unable to see the site's own
|
|
173
|
+
* outputs at all.
|
|
174
|
+
*
|
|
175
|
+
* `store`, `state` and `save()` are the ONE thing this function gets that a
|
|
176
|
+
* bare assignment from `OpsContext` would not: a `StateStore` scoped to
|
|
177
|
+
* `pluginName` (`state/<env>.<pluginName>.json`, `StateStore`'s fourth
|
|
178
|
+
* constructor argument - `packages/core/src/state.ts`), its own freshly
|
|
179
|
+
* loaded `OpsState`, and a `save()` that persists THAT state through THAT
|
|
180
|
+
* store - never the site's own `state/<env>.json`. This is why the function
|
|
181
|
+
* is `async` where a straight field-for-field adaptation would not need to
|
|
182
|
+
* be: building the plugin's own `state` requires awaiting the scoped
|
|
183
|
+
* store's `load()`. Before this existed (tasks 10-15), `OpsContext`'s
|
|
184
|
+
* `store`/`state`/`save()` typechecked straight through as `PluginContext`'s
|
|
185
|
+
* of the same names with no error - the TYPES lined up even though the
|
|
186
|
+
* STORAGE did not - which is why nothing before this task may call a
|
|
187
|
+
* plugin's `nodes(ctx)`: doing so would have silently recorded a plugin's
|
|
188
|
+
* resources into the site's own state document instead of its own, and
|
|
189
|
+
* `record`, below, closes exactly that gap by writing into the scoped
|
|
190
|
+
* `state.resources` rather than the site's.
|
|
191
|
+
*/
|
|
192
|
+
export declare function toPluginContext(ops: OpsContext, pluginName: string, pluginConfig: unknown): Promise<PluginContext<unknown>>;
|
|
193
|
+
/**
|
|
194
|
+
* The generic lifecycle verbs `plugin` actually answers, as `{ action,
|
|
195
|
+
* summary }` pairs, for the two places that LIST a plugin's actions: the
|
|
196
|
+
* unknown-action refusal ({@link renderActions}, below) and `--help`
|
|
197
|
+
* (`cli.ts`'s `renderPluginSection`). Exported for the second of those;
|
|
198
|
+
* both must list exactly what {@link genericLifecycleCommand} would
|
|
199
|
+
* dispatch, or the listing advertises a verb the dispatcher refuses (or,
|
|
200
|
+
* worse, hides one that works - the state this function was added to fix,
|
|
201
|
+
* where a nodes-only plugin's refusal printed a heading and nothing at
|
|
202
|
+
* all).
|
|
203
|
+
*
|
|
204
|
+
* Gated on `plugin.nodes` exactly as `genericLifecycleCommand` is, so a
|
|
205
|
+
* plugin with no `nodes` contributor advertises none of the three. A verb
|
|
206
|
+
* the plugin declares ITSELF is omitted here rather than listed twice: only
|
|
207
|
+
* `status` can be declared (`bootstrap`/`destroy` are rejected at
|
|
208
|
+
* discovery, `plugins.ts`'s `rejectDeclaredLifecycleCollisions`), and its
|
|
209
|
+
* own command already appears in the caller's declared-command lines -
|
|
210
|
+
* where `matchAction`'s precedence means that is the one that actually
|
|
211
|
+
* runs.
|
|
212
|
+
*/
|
|
213
|
+
export declare function genericLifecycleActions(plugin: Plugin<unknown>): {
|
|
214
|
+
action: string;
|
|
215
|
+
summary: string;
|
|
216
|
+
}[];
|
|
217
|
+
/**
|
|
218
|
+
* Handle `blogwright <command> <action> [env] [args]` once `command` has
|
|
219
|
+
* failed the `KNOWN_COMMANDS` membership test - i.e. it is either an
|
|
220
|
+
* installed plugin's namespace or entirely unknown.
|
|
221
|
+
*
|
|
222
|
+
* `ports` - the `fs`/`loader` pair `discover` needs - is supplied by the
|
|
223
|
+
* caller (`cli.ts`, from a small factory `bin.ts` wires to the real
|
|
224
|
+
* adapters) rather than read off an `OpsContext` built here. An EARLIER
|
|
225
|
+
* version of this function built a throwaway `OpsContext` first (via
|
|
226
|
+
* `makeContext`, guessing `production` or `--env`'s value) purely to reach
|
|
227
|
+
* its `ports.fs`/`ports.loader` for discovery, then rebuilt a second
|
|
228
|
+
* context once the real environment was known. That guess was not merely
|
|
229
|
+
* wasteful - it was WRONG on a repo whose only config file is for a
|
|
230
|
+
* non-default environment: `blogwright <plugin> <action> staging` on a repo
|
|
231
|
+
* with `config/staging.jsonc` and neither `config/production.jsonc` nor
|
|
232
|
+
* `ops.config.jsonc` made the throwaway build's `loadConfig` call
|
|
233
|
+
* (`context.ts`) throw `no config found for environment "production"` -
|
|
234
|
+
* naming an environment the operator never asked for, before the real one
|
|
235
|
+
* (`staging`) was ever read off the positionals. That is worse than the
|
|
236
|
+
* silent fallback-to-production this dispatcher exists to avoid, because
|
|
237
|
+
* the message actively misleads.
|
|
238
|
+
*
|
|
239
|
+
* `discover` only ever needed `Pick<Ports, 'fs' | 'loader'>` - both of which
|
|
240
|
+
* `createContext` builds BEFORE it loads any config (`context.ts`), and
|
|
241
|
+
* `cli.ts`'s `init` branch already constructs a `FileSystem` directly with
|
|
242
|
+
* no context at all - so threading the same two ports in from the caller
|
|
243
|
+
* removes the guess completely: the environment is resolved from
|
|
244
|
+
* `command`'s matched action BEFORE any `OpsContext` - throwaway or real -
|
|
245
|
+
* is built, and exactly one `makeContext` call happens, with the confirmed
|
|
246
|
+
* environment, reusing the SAME `fs`/`loader` discovery already used rather
|
|
247
|
+
* than letting a second call default fresh ones.
|
|
248
|
+
*
|
|
249
|
+
* EXIT CODES, and a deliberate deviation tasks 13/16/17 must not assume away.
|
|
250
|
+
* This task's definition of done asks that "a plugin command's return value
|
|
251
|
+
* maps to the process exit code". It cannot: `PluginCommand.run` is declared
|
|
252
|
+
* `Promise<void>` (`blogwright-core`'s `plugin.ts`), and the change spec names
|
|
253
|
+
* no return-code channel. So there is no value to map. What this dispatcher
|
|
254
|
+
* owns it returns - 0 once `run` resolves, 1 for an unknown plugin and 1 for an
|
|
255
|
+
* unknown action - and a command that genuinely fails signals it by REJECTING,
|
|
256
|
+
* which propagates to `bin.ts`'s error path. Adding actions here (tasks 13, 16,
|
|
257
|
+
* 17) means following that contract: reject to fail, do not invent a numeric
|
|
258
|
+
* return the SPI has nowhere to carry.
|
|
259
|
+
*/
|
|
260
|
+
export declare function runPlugin(command: string, rest: string[], values: PluginValues, terminal: Terminal, logger: Logger, makeContext: (opts: ContextOptions) => Promise<OpsContext>, ports: Pick<Ports, 'fs' | 'loader'>): Promise<number>;
|
|
261
|
+
/**
|
|
262
|
+
* What `add` and `remove` need beyond the `fs`/`loader` pair `list` runs on.
|
|
263
|
+
* A single object rather than four more positional parameters, and every
|
|
264
|
+
* member is a FACTORY or a value rather than a live port, so a refusal path
|
|
265
|
+
* pays for none of them:
|
|
266
|
+
*
|
|
267
|
+
* - `values` - the same parsed flags `runPlugin` reads (`--yes`, `--env`,
|
|
268
|
+
* and the context overrides `remove`'s teardown branch forwards).
|
|
269
|
+
* - `makePackages` - the `PackageManager` port, built at the composition
|
|
270
|
+
* root. Called at most once per invocation, and never by `list` or by any
|
|
271
|
+
* refusal.
|
|
272
|
+
* - `cliVersion` - the running CLI's own version (`context.ts`'s
|
|
273
|
+
* `cliVersion`), resolved there because this module may not touch
|
|
274
|
+
* `node:fs`. Called only once `add` has decided it is going to install
|
|
275
|
+
* something.
|
|
276
|
+
* - `makeContext` - the ONE `OpsContext` `remove`'s "tear it down first"
|
|
277
|
+
* branch needs, and the only path in this namespace that builds one at
|
|
278
|
+
* all. Never called for `add`, for `list`, or for a `remove` that does
|
|
279
|
+
* not run a teardown.
|
|
280
|
+
*/
|
|
281
|
+
export interface PluginNamespaceDeps {
|
|
282
|
+
readonly values: PluginValues;
|
|
283
|
+
readonly makePackages: () => PackageManager;
|
|
284
|
+
readonly cliVersion: () => Promise<string>;
|
|
285
|
+
readonly makeContext: (opts: ContextOptions) => Promise<OpsContext>;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Handle `blogwright plugin <action>`. Dispatched by `cli.ts` ahead of any
|
|
289
|
+
* `OpsContext` - see this section's own comment above for why that placement
|
|
290
|
+
* is load-bearing rather than merely tidy.
|
|
291
|
+
*
|
|
292
|
+
* An absent or unrecognised action lists the namespace's actions and returns
|
|
293
|
+
* 1, the same shape `runPlugin` refuses an unknown action of an installed
|
|
294
|
+
* plugin in. `add` and `remove` share this function's name resolution and
|
|
295
|
+
* validation - one gate, so the two verbs cannot disagree about what
|
|
296
|
+
* `analytics` means or about what is a package name at all.
|
|
297
|
+
*/
|
|
298
|
+
export declare function runPluginNamespace(rest: readonly string[], terminal: Terminal, logger: Logger, ports: Pick<Ports, 'fs' | 'loader'>, deps: PluginNamespaceDeps): Promise<number>;
|