blogwright-pds 0.3.3 → 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/dist/client-metadata.d.ts +3 -3
- package/dist/client-metadata.js +3 -3
- package/dist/commands.d.ts +3 -3
- package/dist/commands.js +10 -10
- package/dist/config.d.ts +49 -0
- package/dist/config.js +94 -0
- package/dist/content.d.ts +3 -3
- package/dist/content.js +2 -2
- package/dist/context.d.ts +36 -26
- package/dist/context.js +4 -3
- package/dist/index.d.ts +15 -0
- package/dist/index.js +15 -0
- package/dist/nodes.d.ts +72 -0
- package/dist/nodes.js +177 -0
- package/dist/oauth.d.ts +4 -4
- package/dist/oauth.js +12 -12
- package/dist/plugin.d.ts +72 -0
- package/dist/plugin.js +160 -0
- package/dist/rkey.d.ts +1 -1
- package/dist/rkey.js +2 -2
- package/dist/secret.d.ts +4 -4
- package/dist/secret.js +6 -6
- package/dist/sync.d.ts +17 -3
- package/dist/sync.js +22 -7
- package/dist/xrpc.d.ts +2 -2
- package/dist/xrpc.js +3 -3
- package/package.json +5 -2
|
@@ -8,8 +8,8 @@ import type { OAuthClientMetadataInput } from '@atproto/oauth-client-node';
|
|
|
8
8
|
import type { OpsConfig, PdsConfig } from 'blogwright-core';
|
|
9
9
|
/**
|
|
10
10
|
* Repo-relative paths of the committed OAuth client documents. The segments
|
|
11
|
-
* under the public dir are protocol-fixed
|
|
12
|
-
* the client metadata
|
|
11
|
+
* under the public dir are protocol-fixed - they must mirror the URL paths in
|
|
12
|
+
* the client metadata - so only the public dir itself is configurable.
|
|
13
13
|
*/
|
|
14
14
|
export declare function clientDocumentPaths(cfg: Pick<OpsConfig, 'paths'>): {
|
|
15
15
|
clientMetadata: string;
|
|
@@ -22,7 +22,7 @@ export declare function jwksUrl(domain: string): string;
|
|
|
22
22
|
export declare function callbackUrl(domain: string): string;
|
|
23
23
|
/** The client metadata document served at clientMetadataUrl(domain). */
|
|
24
24
|
export declare function clientMetadata(domain: string, pds: PdsConfig): OAuthClientMetadataInput;
|
|
25
|
-
/** The JWKS document served at jwksUrl(domain)
|
|
25
|
+
/** The JWKS document served at jwksUrl(domain) - public key material only. */
|
|
26
26
|
export declare function jwksDocument(publicJwk: Record<string, unknown>): {
|
|
27
27
|
keys: [typeof publicJwk];
|
|
28
28
|
};
|
package/dist/client-metadata.js
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
/**
|
|
8
8
|
* Repo-relative paths of the committed OAuth client documents. The segments
|
|
9
|
-
* under the public dir are protocol-fixed
|
|
10
|
-
* the client metadata
|
|
9
|
+
* under the public dir are protocol-fixed - they must mirror the URL paths in
|
|
10
|
+
* the client metadata - so only the public dir itself is configurable.
|
|
11
11
|
*/
|
|
12
12
|
export function clientDocumentPaths(cfg) {
|
|
13
13
|
return {
|
|
@@ -43,7 +43,7 @@ export function clientMetadata(domain, pds) {
|
|
|
43
43
|
jwks_uri: jwksUrl(domain),
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
|
-
/** The JWKS document served at jwksUrl(domain)
|
|
46
|
+
/** The JWKS document served at jwksUrl(domain) - public key material only. */
|
|
47
47
|
export function jwksDocument(publicJwk) {
|
|
48
48
|
if (publicJwk.d !== undefined)
|
|
49
49
|
throw new Error('jwksDocument received a private JWK (has "d")');
|
package/dist/commands.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { generateClientKey, login as oauthLogin, openPdsRepo, verifyClientAssets
|
|
|
3
3
|
import { type OpenRepo, type SyncSummary } from './sync.js';
|
|
4
4
|
/**
|
|
5
5
|
* Generate the OAuth confidential-client key: private JWK into the secret
|
|
6
|
-
* (clearing any session
|
|
6
|
+
* (clearing any session - client auth is bound to the key), public half into
|
|
7
7
|
* the two committed /oauth/ documents the site serves.
|
|
8
8
|
*/
|
|
9
9
|
export declare function keygen(ctx: PdsContext, repoRoot?: string, generateKey?: typeof generateClientKey): Promise<void>;
|
|
@@ -19,7 +19,7 @@ export declare function secretDelete(ctx: PdsContext, opts: {
|
|
|
19
19
|
}): Promise<void>;
|
|
20
20
|
/**
|
|
21
21
|
* One-time (idempotent) publication setup: create the site.standard.publication
|
|
22
|
-
* record
|
|
22
|
+
* record - or update it when the committed well-known file already names one - and
|
|
23
23
|
* write the two site files the user commits.
|
|
24
24
|
*/
|
|
25
25
|
export declare function init(ctx: PdsContext, repoRoot?: string, openRepo?: typeof openPdsRepo, verifyAssets?: typeof verifyClientAssets): Promise<void>;
|
|
@@ -27,7 +27,7 @@ export declare function init(ctx: PdsContext, repoRoot?: string, openRepo?: type
|
|
|
27
27
|
export declare function sync(ctx: PdsContext, repoRoot?: string, openRepo?: OpenRepo): Promise<void>;
|
|
28
28
|
/**
|
|
29
29
|
* Post-deploy hook: reconcile the PDS after a successful production deploy.
|
|
30
|
-
* Never fatal
|
|
30
|
+
* Never fatal - a PDS outage must not fail a good site deploy; the next deploy
|
|
31
31
|
* re-reconciles. No-op unless production, configured, and initialised.
|
|
32
32
|
*/
|
|
33
33
|
export declare function syncAfterDeploy(ctx: PdsContext, repoRoot?: string, doSync?: (ctx: PdsContext, repoRoot: string) => Promise<SyncSummary>): Promise<void>;
|
package/dist/commands.js
CHANGED
|
@@ -7,7 +7,7 @@ import { PUBLICATION_COLLECTION, publicationRecord, readWellKnownUri, requirePds
|
|
|
7
7
|
import { rkeyFromUri } from './xrpc.js';
|
|
8
8
|
/**
|
|
9
9
|
* Generate the OAuth confidential-client key: private JWK into the secret
|
|
10
|
-
* (clearing any session
|
|
10
|
+
* (clearing any session - client auth is bound to the key), public half into
|
|
11
11
|
* the two committed /oauth/ documents the site serves.
|
|
12
12
|
*/
|
|
13
13
|
export async function keygen(ctx, repoRoot, generateKey = generateClientKey) {
|
|
@@ -18,7 +18,7 @@ export async function keygen(ctx, repoRoot, generateKey = generateClientKey) {
|
|
|
18
18
|
const kid = `${ctx.config.siteName}-oauth-${new Date().toISOString().slice(0, 10)}`;
|
|
19
19
|
const clientKey = await generateKey(kid);
|
|
20
20
|
await updatePdsSecret(ctx.clients.secrets, pds.secretName, (secret) => ({ ...secret, clientKey, session: undefined }),
|
|
21
|
-
// keygen is the migration entry point
|
|
21
|
+
// keygen is the migration entry point - a legacy app-password value is replaced
|
|
22
22
|
{ replaceLegacy: true, tags: ctx.tags });
|
|
23
23
|
ctx.logger.ok(`stored private key "${kid}" in secret "${pds.secretName}"`);
|
|
24
24
|
const docPaths = clientDocumentPaths(ctx.config);
|
|
@@ -30,7 +30,7 @@ export async function keygen(ctx, repoRoot, generateKey = generateClientKey) {
|
|
|
30
30
|
await ctx.ports.fs.writeText(join(root, path), `${JSON.stringify(body, null, 2)}\n`);
|
|
31
31
|
ctx.logger.info(` wrote ${path}`);
|
|
32
32
|
}
|
|
33
|
-
ctx.logger.info(colors.bold(`Commit ${ctx.config.paths.publicDir}/oauth/* and release
|
|
33
|
+
ctx.logger.info(colors.bold(`Commit ${ctx.config.paths.publicDir}/oauth/* and release - then run \`blogwright pds login\`.`));
|
|
34
34
|
}
|
|
35
35
|
/** Interactive OAuth bootstrap; see oauth.ts#login for the flow. */
|
|
36
36
|
export async function login(ctx, opts, runLogin = oauthLogin) {
|
|
@@ -46,7 +46,7 @@ export async function secretStatus(ctx) {
|
|
|
46
46
|
const pds = requirePdsConfig(ctx);
|
|
47
47
|
const meta = await ctx.clients.secrets.describeSecret(pds.secretName);
|
|
48
48
|
if (!meta) {
|
|
49
|
-
ctx.logger.info(`no secret at "${pds.secretName}"
|
|
49
|
+
ctx.logger.info(`no secret at "${pds.secretName}" - create it with \`blogwright pds keygen\``);
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
52
|
ctx.logger.info(` name ${meta.name}`);
|
|
@@ -75,7 +75,7 @@ export async function secretDelete(ctx, opts) {
|
|
|
75
75
|
}
|
|
76
76
|
/**
|
|
77
77
|
* One-time (idempotent) publication setup: create the site.standard.publication
|
|
78
|
-
* record
|
|
78
|
+
* record - or update it when the committed well-known file already names one - and
|
|
79
79
|
* write the two site files the user commits.
|
|
80
80
|
*/
|
|
81
81
|
export async function init(ctx, repoRoot, openRepo = openPdsRepo, verifyAssets = verifyClientAssets) {
|
|
@@ -92,7 +92,7 @@ export async function init(ctx, repoRoot, openRepo = openPdsRepo, verifyAssets =
|
|
|
92
92
|
// would publish document records that point at a publication this account
|
|
93
93
|
// does not own, silently breaking standard.site verification.
|
|
94
94
|
throw new Error(`${wellKnownPath(ctx.config)} points at ${existingUri}, which belongs to a different ` +
|
|
95
|
-
`account than ${did}
|
|
95
|
+
`account than ${did} - delete the file to create a fresh publication, or log in ` +
|
|
96
96
|
`with the owning account`);
|
|
97
97
|
}
|
|
98
98
|
let publicationUri;
|
|
@@ -111,7 +111,7 @@ export async function init(ctx, repoRoot, openRepo = openPdsRepo, verifyAssets =
|
|
|
111
111
|
await ctx.ports.fs.writeText(join(root, ctx.config.paths.atprotoJson), `${JSON.stringify({ did, publicationUri }, null, 2)}\n`);
|
|
112
112
|
ctx.logger.info(` wrote ${wellKnown}`);
|
|
113
113
|
ctx.logger.info(` wrote ${ctx.config.paths.atprotoJson}`);
|
|
114
|
-
ctx.logger.info(colors.bold('Commit both files and deploy
|
|
114
|
+
ctx.logger.info(colors.bold('Commit both files and deploy - they verify the publication and the post link tags.'));
|
|
115
115
|
}
|
|
116
116
|
/** Reconcile PDS records against local content. Production only. */
|
|
117
117
|
export async function sync(ctx, repoRoot, openRepo = openPdsRepo) {
|
|
@@ -130,13 +130,13 @@ function logSummary(ctx, s) {
|
|
|
130
130
|
for (const slug of s.updated)
|
|
131
131
|
ctx.logger.info(` updated ${slug}`);
|
|
132
132
|
if (s.orphans.length > 0) {
|
|
133
|
-
ctx.logger.warn(`${s.orphans.length} PDS record(s) have no local post (rkeys: ${s.orphans.join(', ')})
|
|
133
|
+
ctx.logger.warn(`${s.orphans.length} PDS record(s) have no local post (rkeys: ${s.orphans.join(', ')}) - ` +
|
|
134
134
|
'not deleted; remove them manually if intended');
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
/**
|
|
138
138
|
* Post-deploy hook: reconcile the PDS after a successful production deploy.
|
|
139
|
-
* Never fatal
|
|
139
|
+
* Never fatal - a PDS outage must not fail a good site deploy; the next deploy
|
|
140
140
|
* re-reconciles. No-op unless production, configured, and initialised.
|
|
141
141
|
*/
|
|
142
142
|
export async function syncAfterDeploy(ctx, repoRoot, doSync = (c, r) => syncPds(c, r, openPdsRepo)) {
|
|
@@ -145,7 +145,7 @@ export async function syncAfterDeploy(ctx, repoRoot, doSync = (c, r) => syncPds(
|
|
|
145
145
|
const root = repoRoot ?? (await findRepoRoot(ctx.ports.fs));
|
|
146
146
|
const initialised = await readWellKnownUri(ctx.ports.fs, root, ctx.config).catch(() => undefined);
|
|
147
147
|
if (!initialised) {
|
|
148
|
-
ctx.logger.info('standard.site publishing not initialised (`blogwright pds init`)
|
|
148
|
+
ctx.logger.info('standard.site publishing not initialised (`blogwright pds init`) - skipping');
|
|
149
149
|
return;
|
|
150
150
|
}
|
|
151
151
|
try {
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This package owns the `pds` config key end to end: validating the block
|
|
3
|
+
* core's `parseConfig` hands off unvalidated, and deriving the
|
|
4
|
+
* `<siteName>/atproto` default `secretName`.
|
|
5
|
+
*
|
|
6
|
+
* `validatePdsConfig` and `resolvePdsSecretName` were lifted verbatim from
|
|
7
|
+
* `packages/core/src/config.ts` (the `cfg.pds` branch of `validateConfig` and
|
|
8
|
+
* the `secretName` line of `mergeConfig`) - same checks, same message
|
|
9
|
+
* strings, same `new URL(...)` try/catch ordering, so a non-URL resolver
|
|
10
|
+
* still reports "must be a URL" before an `http://` one reports "must be
|
|
11
|
+
* https". Core's copies are now gone: these are the only ones.
|
|
12
|
+
*
|
|
13
|
+
* One case core never had to answer is answered here: an ABSENT block. Core
|
|
14
|
+
* gated its checks behind `if (cfg.pds)`, so nothing validated a document
|
|
15
|
+
* with no `pds` key; the host now calls a plugin's validator with
|
|
16
|
+
* `undefined` in exactly that case, and {@link NO_PDS_SECTION_MESSAGE} is
|
|
17
|
+
* what this package returns to an operator for it.
|
|
18
|
+
*/
|
|
19
|
+
import type { PdsConfig } from 'blogwright-core';
|
|
20
|
+
/**
|
|
21
|
+
* A `pds` config block whose `secretName` has been resolved: never absent,
|
|
22
|
+
* where core's own `PdsConfig` declares it optional because core no longer
|
|
23
|
+
* defaults it. `requirePdsConfig` (`sync.ts`) returns this - built from
|
|
24
|
+
* {@link resolvePdsSecretName} - so every call site downstream keeps a total
|
|
25
|
+
* type on `secretName` with no cast and no `!`.
|
|
26
|
+
*/
|
|
27
|
+
export interface ResolvedPdsConfig extends PdsConfig {
|
|
28
|
+
secretName: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* What an ABSENT `pds` block reports. The same sentence `requirePdsConfig`
|
|
32
|
+
* (`sync.ts`) has always raised for a repo that has not written one, declared
|
|
33
|
+
* here and imported there so the two refusals cannot drift into two different
|
|
34
|
+
* sentences for one situation - whichever of them an operator happens to
|
|
35
|
+
* reach first.
|
|
36
|
+
*/
|
|
37
|
+
export declare const NO_PDS_SECTION_MESSAGE = "config has no \"pds\" section - add it to config/production.jsonc";
|
|
38
|
+
/**
|
|
39
|
+
* Validate a raw `pds` config block, boundary-checked as `unknown` because it
|
|
40
|
+
* comes off `parseConfigDocument`'s `raw` half (a plugin's block has no type
|
|
41
|
+
* until its own package narrows it). Throws with the same message strings
|
|
42
|
+
* core's `validateConfig` raises today for these checks.
|
|
43
|
+
*/
|
|
44
|
+
export declare function validatePdsConfig(raw: unknown): PdsConfig;
|
|
45
|
+
/**
|
|
46
|
+
* Resolve the secret name a `pds` block uses: the explicit `secretName` when
|
|
47
|
+
* present, otherwise `<siteName>/atproto`.
|
|
48
|
+
*/
|
|
49
|
+
export declare function resolvePdsSecretName(pds: PdsConfig, siteName: string): string;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This package owns the `pds` config key end to end: validating the block
|
|
3
|
+
* core's `parseConfig` hands off unvalidated, and deriving the
|
|
4
|
+
* `<siteName>/atproto` default `secretName`.
|
|
5
|
+
*
|
|
6
|
+
* `validatePdsConfig` and `resolvePdsSecretName` were lifted verbatim from
|
|
7
|
+
* `packages/core/src/config.ts` (the `cfg.pds` branch of `validateConfig` and
|
|
8
|
+
* the `secretName` line of `mergeConfig`) - same checks, same message
|
|
9
|
+
* strings, same `new URL(...)` try/catch ordering, so a non-URL resolver
|
|
10
|
+
* still reports "must be a URL" before an `http://` one reports "must be
|
|
11
|
+
* https". Core's copies are now gone: these are the only ones.
|
|
12
|
+
*
|
|
13
|
+
* One case core never had to answer is answered here: an ABSENT block. Core
|
|
14
|
+
* gated its checks behind `if (cfg.pds)`, so nothing validated a document
|
|
15
|
+
* with no `pds` key; the host now calls a plugin's validator with
|
|
16
|
+
* `undefined` in exactly that case, and {@link NO_PDS_SECTION_MESSAGE} is
|
|
17
|
+
* what this package returns to an operator for it.
|
|
18
|
+
*/
|
|
19
|
+
/** Characters permitted in a Secrets Manager secret name. */
|
|
20
|
+
const SECRET_NAME_PATTERN = /^[\w/+=.@-]+$/;
|
|
21
|
+
/**
|
|
22
|
+
* What an ABSENT `pds` block reports. The same sentence `requirePdsConfig`
|
|
23
|
+
* (`sync.ts`) has always raised for a repo that has not written one, declared
|
|
24
|
+
* here and imported there so the two refusals cannot drift into two different
|
|
25
|
+
* sentences for one situation - whichever of them an operator happens to
|
|
26
|
+
* reach first.
|
|
27
|
+
*/
|
|
28
|
+
export const NO_PDS_SECTION_MESSAGE = 'config has no "pds" section - add it to config/production.jsonc';
|
|
29
|
+
/** Template pds's default `secretName` is derived from - the one home for it in this package. */
|
|
30
|
+
function defaultSecretName(siteName) {
|
|
31
|
+
return `${siteName}/atproto`;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Validate a raw `pds` config block, boundary-checked as `unknown` because it
|
|
35
|
+
* comes off `parseConfigDocument`'s `raw` half (a plugin's block has no type
|
|
36
|
+
* until its own package narrows it). Throws with the same message strings
|
|
37
|
+
* core's `validateConfig` raises today for these checks.
|
|
38
|
+
*/
|
|
39
|
+
export function validatePdsConfig(raw) {
|
|
40
|
+
// ABSENT is not MALFORMED, and the two want different sentences. The host
|
|
41
|
+
// calls this with `undefined` whenever the document carries no `pds` key
|
|
42
|
+
// (`resolvePluginConfig`, `packages/cli/src/plugins.ts`) - deliberately, so
|
|
43
|
+
// a plugin with derivable defaults can supply them. This plugin has none:
|
|
44
|
+
// `name` is an operator's choice and nothing can invent it, so an absent
|
|
45
|
+
// block is a refusal. But it is the ORDINARY first-run refusal, not a
|
|
46
|
+
// defect in a block that was written, so it names the missing section and
|
|
47
|
+
// how to create it rather than a key inside a block that does not exist.
|
|
48
|
+
// Without this guard the first statement below dereferences `undefined` and
|
|
49
|
+
// the operator gets a bare `TypeError` instead (the defect task 29's
|
|
50
|
+
// changeset named as a known issue).
|
|
51
|
+
//
|
|
52
|
+
// `null` joins `undefined` because that is the behaviour a `"pds": null`
|
|
53
|
+
// document has always had: core's own check was gated behind `if (cfg.pds)`
|
|
54
|
+
// and skipped it, and `requirePdsConfig`'s `if (!pds)` then reported it as
|
|
55
|
+
// an absent section. Every other present value stays MALFORMED and falls
|
|
56
|
+
// through to the key-naming checks below, which is where a string, a number
|
|
57
|
+
// or an array already reported `config.pds.name is required`.
|
|
58
|
+
if (raw === undefined || raw === null)
|
|
59
|
+
throw new Error(NO_PDS_SECTION_MESSAGE);
|
|
60
|
+
const cfg = raw;
|
|
61
|
+
if (!cfg.name?.trim())
|
|
62
|
+
throw new Error('config.pds.name is required');
|
|
63
|
+
if (cfg.handleResolver !== undefined) {
|
|
64
|
+
let resolver;
|
|
65
|
+
try {
|
|
66
|
+
resolver = new URL(cfg.handleResolver);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
throw new Error(`config.pds.handleResolver must be a URL, got "${cfg.handleResolver}"`);
|
|
70
|
+
}
|
|
71
|
+
if (resolver.protocol !== 'https:') {
|
|
72
|
+
throw new Error(`config.pds.handleResolver must be https, got "${cfg.handleResolver}"`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// `secretName` is optional on `PdsConfig` now that core no longer defaults
|
|
76
|
+
// it, so the character class applies only to a value that is actually
|
|
77
|
+
// present. An absent one is not malformed - it selects the
|
|
78
|
+
// `<siteName>/atproto` default `resolvePdsSecretName` supplies, which
|
|
79
|
+
// trivially satisfies the class. The guard is a narrowing, not a behaviour
|
|
80
|
+
// change: the previous unguarded call handed `undefined` to `RegExp.test`,
|
|
81
|
+
// which coerced it to the string "undefined" and matched, so an absent
|
|
82
|
+
// `secretName` passed this check before the guard existed too.
|
|
83
|
+
if (cfg.secretName !== undefined && !SECRET_NAME_PATTERN.test(cfg.secretName)) {
|
|
84
|
+
throw new Error(`config.pds.secretName has invalid characters: "${cfg.secretName}"`);
|
|
85
|
+
}
|
|
86
|
+
return cfg;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Resolve the secret name a `pds` block uses: the explicit `secretName` when
|
|
90
|
+
* present, otherwise `<siteName>/atproto`.
|
|
91
|
+
*/
|
|
92
|
+
export function resolvePdsSecretName(pds, siteName) {
|
|
93
|
+
return pds.secretName ?? defaultSecretName(siteName);
|
|
94
|
+
}
|
package/dist/content.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export interface PostMeta {
|
|
|
4
4
|
/**
|
|
5
5
|
* Content-collection id: frontmatter `slug` when set, else the file path
|
|
6
6
|
* under the content dir minus its `.md`/`.mdx` extension and any trailing
|
|
7
|
-
* `/index` segment
|
|
7
|
+
* `/index` segment - matching the id Astro's glob loader produces.
|
|
8
8
|
*/
|
|
9
9
|
slug: string;
|
|
10
10
|
title: string;
|
|
@@ -14,14 +14,14 @@ export interface PostMeta {
|
|
|
14
14
|
/**
|
|
15
15
|
* Parse the flat frontmatter subset used by src/content.config.ts: scalar
|
|
16
16
|
* `key: value` lines where value is a bare token, a quoted string, a date, or a
|
|
17
|
-
* boolean. Deliberately not a YAML parser
|
|
17
|
+
* boolean. Deliberately not a YAML parser - the schema is flat by construction.
|
|
18
18
|
*/
|
|
19
19
|
export declare function parseFrontmatter(source: string, file: string): Record<string, string>;
|
|
20
20
|
/**
|
|
21
21
|
* Enumerate publishable posts from the content collection, mirroring the Astro
|
|
22
22
|
* glob-loader ids: `.md`/`.mdx` files, a frontmatter `slug` wins outright, a
|
|
23
23
|
* trailing `/index` segment is dropped, drafts excluded. (Astro additionally
|
|
24
|
-
* github-slugifies unusual path segments
|
|
24
|
+
* github-slugifies unusual path segments - keep file names lowercase-kebab, as
|
|
25
25
|
* the rkey derived here must match the id the site builds its link tags from.)
|
|
26
26
|
*/
|
|
27
27
|
export declare function listPublishablePosts(fs: FileSystem, repoRoot: string, contentDir?: string): Promise<PostMeta[]>;
|
package/dist/content.js
CHANGED
|
@@ -5,7 +5,7 @@ const REQUIRED_FIELDS = ['title', 'description', 'pubDate'];
|
|
|
5
5
|
/**
|
|
6
6
|
* Parse the flat frontmatter subset used by src/content.config.ts: scalar
|
|
7
7
|
* `key: value` lines where value is a bare token, a quoted string, a date, or a
|
|
8
|
-
* boolean. Deliberately not a YAML parser
|
|
8
|
+
* boolean. Deliberately not a YAML parser - the schema is flat by construction.
|
|
9
9
|
*/
|
|
10
10
|
export function parseFrontmatter(source, file) {
|
|
11
11
|
const match = source.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
|
|
@@ -34,7 +34,7 @@ export function parseFrontmatter(source, file) {
|
|
|
34
34
|
* Enumerate publishable posts from the content collection, mirroring the Astro
|
|
35
35
|
* glob-loader ids: `.md`/`.mdx` files, a frontmatter `slug` wins outright, a
|
|
36
36
|
* trailing `/index` segment is dropped, drafts excluded. (Astro additionally
|
|
37
|
-
* github-slugifies unusual path segments
|
|
37
|
+
* github-slugifies unusual path segments - keep file names lowercase-kebab, as
|
|
38
38
|
* the rkey derived here must match the id the site builds its link tags from.)
|
|
39
39
|
*/
|
|
40
40
|
export async function listPublishablePosts(fs, repoRoot, contentDir = DEFAULT_CONTENT_DIR) {
|
package/dist/context.d.ts
CHANGED
|
@@ -1,32 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The narrow slice of the host CLI's context this feature package depends on
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* The narrow slice of the host CLI's context this feature package depends on,
|
|
3
|
+
* expressed as a narrowing of core's `PluginContext`. The CLI's `OpsContext`
|
|
4
|
+
* satisfies it structurally, so the dispatch boundary is a plain assignment -
|
|
5
|
+
* this package never imports CLI types.
|
|
5
6
|
*/
|
|
6
|
-
import type {
|
|
7
|
-
/** Leveled logger surface the pds commands report through
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
7
|
+
import type { PdsConfig, PluginContext, PluginLogger, PluginPorts, SecretsManagerClient } from 'blogwright-core';
|
|
8
|
+
/** Leveled logger surface the pds commands report through - core's `PluginLogger`. */
|
|
9
|
+
export type PdsLogger = PluginLogger;
|
|
10
|
+
/** The ports the pds feature crosses: repo files and the operator's terminal - core's `PluginPorts`. */
|
|
11
|
+
export type PdsPorts = PluginPorts;
|
|
12
|
+
/**
|
|
13
|
+
* Everything a pds command needs from its host - config, secrets, ports,
|
|
14
|
+
* logging. A `Pick` over core's `PluginContext<PdsConfig>` of exactly the
|
|
15
|
+
* members pds uses today - `env`, `domain`, `config`, `ports` (already
|
|
16
|
+
* exactly the `fs`/`terminal` pair `PluginPorts` names), `logger` and
|
|
17
|
+
* optional `tags` - plus `clients`, narrowed here to the one client pds
|
|
18
|
+
* calls rather than picked verbatim: `PluginContext.clients` is the full
|
|
19
|
+
* `AwsClients`, and `test-support.ts` builds a context supplying only
|
|
20
|
+
* `secrets`.
|
|
21
|
+
*
|
|
22
|
+
* Everything else on `PluginContext` is left out on purpose. `preview`,
|
|
23
|
+
* `names` and `accountId` are host surface pds has never needed.
|
|
24
|
+
* `pluginConfig`, `state`, `store`, `siteState`, `record()` and `save()` are
|
|
25
|
+
* all built at a dispatch boundary that no pds command function runs behind:
|
|
26
|
+
* `deploy` calls `syncAfterDeploy(ctx)` with a plain `OpsContext`
|
|
27
|
+
* (`packages/cli/src/commands.ts`), which has none of them, so picking any
|
|
28
|
+
* of the six would break that call. The plugin's one resource node
|
|
29
|
+
* (`nodes.ts`) takes a full `PluginContext<PdsConfig>` instead, because only
|
|
30
|
+
* the lifecycle verbs have a dispatch boundary to build one.
|
|
31
|
+
*
|
|
32
|
+
* `Pick`, never `Omit`: an `Omit` of those six dispatch-boundary members
|
|
33
|
+
* from `PluginContext`'s sixteen would drag `preview`, `names` and
|
|
34
|
+
* `accountId` back in, and `createTestContext`
|
|
35
|
+
* (`packages/pds/src/test-support.ts`) would have to fabricate a whole
|
|
36
|
+
* `Names` to keep compiling.
|
|
37
|
+
*/
|
|
38
|
+
export interface PdsContext extends Pick<PluginContext<PdsConfig>, 'env' | 'domain' | 'config' | 'ports' | 'logger' | 'tags'> {
|
|
25
39
|
clients: {
|
|
26
40
|
secrets: SecretsManagerClient;
|
|
27
41
|
};
|
|
28
|
-
ports: PdsPorts;
|
|
29
|
-
logger: PdsLogger;
|
|
30
|
-
/** Resource tags the host applies to created AWS resources (the secret). */
|
|
31
|
-
tags?: Record<string, string> | undefined;
|
|
32
42
|
}
|
package/dist/context.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The narrow slice of the host CLI's context this feature package depends on
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* The narrow slice of the host CLI's context this feature package depends on,
|
|
3
|
+
* expressed as a narrowing of core's `PluginContext`. The CLI's `OpsContext`
|
|
4
|
+
* satisfies it structurally, so the dispatch boundary is a plain assignment -
|
|
5
|
+
* this package never imports CLI types.
|
|
5
6
|
*/
|
|
6
7
|
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
* Public surface of blogwright-pds: the pds command entry points the CLI
|
|
3
3
|
* dispatches to, plus the context types that name the dependency boundary.
|
|
4
4
|
* The rkey implementation is the separate `blogwright-pds/rkey` subpath.
|
|
5
|
+
*
|
|
6
|
+
* The default export is the package's `Plugin` declaration (`plugin.ts`) -
|
|
7
|
+
* the whole SPI surface in one object. It sits BESIDE the named exports, not
|
|
8
|
+
* instead of them, though the split is now lopsided: since task 29 deleted
|
|
9
|
+
* the CLI's `runPds` branch, the six command functions reach `blogwright pds
|
|
10
|
+
* <action>` through the default export's declared commands, not by name, and
|
|
11
|
+
* `syncAfterDeploy` is the one named export the CLI's shipped code still
|
|
12
|
+
* imports at runtime - the post-deploy sync, which has no lifecycle hook in
|
|
13
|
+
* the SPI to carry it (see the import comment in the CLI's `commands.ts`).
|
|
14
|
+
* The type exports below are load-bearing too, not dead surface: the CLI's
|
|
15
|
+
* `context.test.ts` imports `PdsContext` to assert `OpsContext` still
|
|
16
|
+
* satisfies it by plain assignment. The command functions stay exported as
|
|
17
|
+
* this package's own public API; `plugin.ts` builds its declarations from
|
|
18
|
+
* them.
|
|
5
19
|
*/
|
|
6
20
|
export * from './commands.js';
|
|
7
21
|
export type { PdsContext, PdsLogger, PdsPorts } from './context.js';
|
|
22
|
+
export { default } from './plugin.js';
|
package/dist/index.js
CHANGED
|
@@ -2,5 +2,20 @@
|
|
|
2
2
|
* Public surface of blogwright-pds: the pds command entry points the CLI
|
|
3
3
|
* dispatches to, plus the context types that name the dependency boundary.
|
|
4
4
|
* The rkey implementation is the separate `blogwright-pds/rkey` subpath.
|
|
5
|
+
*
|
|
6
|
+
* The default export is the package's `Plugin` declaration (`plugin.ts`) -
|
|
7
|
+
* the whole SPI surface in one object. It sits BESIDE the named exports, not
|
|
8
|
+
* instead of them, though the split is now lopsided: since task 29 deleted
|
|
9
|
+
* the CLI's `runPds` branch, the six command functions reach `blogwright pds
|
|
10
|
+
* <action>` through the default export's declared commands, not by name, and
|
|
11
|
+
* `syncAfterDeploy` is the one named export the CLI's shipped code still
|
|
12
|
+
* imports at runtime - the post-deploy sync, which has no lifecycle hook in
|
|
13
|
+
* the SPI to carry it (see the import comment in the CLI's `commands.ts`).
|
|
14
|
+
* The type exports below are load-bearing too, not dead surface: the CLI's
|
|
15
|
+
* `context.test.ts` imports `PdsContext` to assert `OpsContext` still
|
|
16
|
+
* satisfies it by plain assignment. The command functions stay exported as
|
|
17
|
+
* this package's own public API; `plugin.ts` builds its declarations from
|
|
18
|
+
* them.
|
|
5
19
|
*/
|
|
6
20
|
export * from './commands.js';
|
|
21
|
+
export { default } from './plugin.js';
|
package/dist/nodes.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The resource topography this plugin owns: one node attaching a
|
|
3
|
+
* `blogwright-pds`-named **inline policy** to the site's GitHub-OIDC deploy
|
|
4
|
+
* role, granting Secrets Manager access to the plugin's own secret.
|
|
5
|
+
*
|
|
6
|
+
* The grant lives here rather than in the site's resource graph because it is
|
|
7
|
+
* plugin topography: the site graph currently branches on `ctx.config.pds` and
|
|
8
|
+
* interpolates this plugin's secret name into an IAM ARN
|
|
9
|
+
* (`packages/cli/src/nodes.ts`), which is the CLI knowing something only this
|
|
10
|
+
* package should know. Moving it here is safe because IAM inline policies are
|
|
11
|
+
* *named*: the site's `<env>-deploy` document and this plugin's
|
|
12
|
+
* `blogwright-pds` document are independent objects on the same role, created,
|
|
13
|
+
* reconciled and removed with no read of each other. That is what makes the
|
|
14
|
+
* migration additive - both grants are live at once until the site's statement
|
|
15
|
+
* is removed a release later - and it is why `delete()` can safely remove this
|
|
16
|
+
* document without touching the role's own.
|
|
17
|
+
*
|
|
18
|
+
* The node's own state (the site's deploy role) is not something this plugin
|
|
19
|
+
* creates, so `read()` never reports the role - only whether the policy this
|
|
20
|
+
* plugin owns is attached to it.
|
|
21
|
+
*/
|
|
22
|
+
import type { PdsConfig, PluginContext, ResourceNode } from 'blogwright-core';
|
|
23
|
+
/**
|
|
24
|
+
* A node in this plugin's own graph. Instantiated on the context the
|
|
25
|
+
* lifecycle verbs build (`PluginContext<PdsConfig>`), not on the SPI's bare
|
|
26
|
+
* `ResourceNode` default (`PluginContext<never>`), so a caller - the plugin's
|
|
27
|
+
* `nodes(ctx)` member, or a test - can invoke a node's methods with the very
|
|
28
|
+
* context it was handed. `ResourceNode`'s methods are method-declared and
|
|
29
|
+
* therefore bivariant in their parameter, so this is still assignable to the
|
|
30
|
+
* `ResourceNode[]` `Plugin.nodes` declares.
|
|
31
|
+
*/
|
|
32
|
+
type PdsNode = ResourceNode<PluginContext<PdsConfig>>;
|
|
33
|
+
/**
|
|
34
|
+
* The nodes `blogwright-pds` contributes, for the plugin's `nodes(ctx)` member
|
|
35
|
+
* to return.
|
|
36
|
+
*
|
|
37
|
+
* Empty - a no-op `pds bootstrap`, not a failure - in three cases:
|
|
38
|
+
*
|
|
39
|
+
* 1. The site configures no `pds` block.
|
|
40
|
+
* 2. `config.githubRepo` is unset. A non-preview site graph only adds the
|
|
41
|
+
* deploy role when it is set (`packages/cli/src/nodes.ts`), so such a site
|
|
42
|
+
* is fully bootstrapped and simply has no role to attach to.
|
|
43
|
+
* 3. The target environment is the shared **preview** stack. That one is a
|
|
44
|
+
* privilege boundary, not an absence: the preview stack's role does exist
|
|
45
|
+
* (`buildNodes` adds `githubOidcRoleNode(true)` for it unconditionally),
|
|
46
|
+
* but its OIDC trust policy accepts the subject claim `repo:<owner>/<repo>:*`
|
|
47
|
+
* - **any ref** - where production accepts only the release-gated
|
|
48
|
+
* `repo:...:environment:production` (`oidcSubClaim`). The site's own graph
|
|
49
|
+
* withholds exactly this Secrets Manager statement from that role for that
|
|
50
|
+
* reason, and `resolvePdsSecretName` is environment-independent
|
|
51
|
+
* (`<siteName>/atproto`), so there is ONE PDS credential for the whole
|
|
52
|
+
* site: granting it here would let anyone who can push a branch read or
|
|
53
|
+
* rotate the site's ATProto session. `staging` is NOT a preview stack -
|
|
54
|
+
* the site already grants it this statement - so the skip is on the
|
|
55
|
+
* preview environment specifically, never on "not production".
|
|
56
|
+
*
|
|
57
|
+
* The preview check is `ctx.env === PREVIEW_ENV` and deliberately **not**
|
|
58
|
+
* `ctx.preview`, which would be dead code here: `runPlugin` builds its
|
|
59
|
+
* `ContextOptions` with no `preview` key at all
|
|
60
|
+
* (`packages/cli/src/plugin-commands.ts`), `createContext` defaults the flag to
|
|
61
|
+
* `false` (`context.ts`) and `toPluginContext` copies it, so `ctx.preview` is
|
|
62
|
+
* `false` for every plugin context the CLI builds - including the one
|
|
63
|
+
* `blogwright pds bootstrap --env preview` produces. `ctx.env` is what carries
|
|
64
|
+
* the stack's identity into a plugin.
|
|
65
|
+
*
|
|
66
|
+
* The `pds` block is read from `ctx.config`, not `ctx.pluginConfig`: core's
|
|
67
|
+
* `OpsConfig` still declares `pds` as a typed member (it keeps the type after
|
|
68
|
+
* the validation moves here), and the CLI does not populate `pluginConfig`
|
|
69
|
+
* from a plugin's own validator until later in this migration.
|
|
70
|
+
*/
|
|
71
|
+
export declare function buildPdsNodes(ctx: PluginContext<PdsConfig>): PdsNode[];
|
|
72
|
+
export {};
|