blogwright-core 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.
@@ -35,6 +35,12 @@ export declare class LogsClient {
35
35
  constructor(client: SigningClient);
36
36
  private call;
37
37
  ensureLogGroup(name: string, tags?: ResourceTags): Promise<void>;
38
+ /**
39
+ * Create a log stream inside a log group, treating an existing stream as success.
40
+ * `CreateLogStream` takes no tags, so this carries none: a stream is tagged only
41
+ * through the group that holds it.
42
+ */
43
+ ensureLogStream(logGroupName: string, logStreamName: string): Promise<void>;
38
44
  putRetentionPolicy(name: string, retentionInDays: number): Promise<void>;
39
45
  logGroupExists(name: string): Promise<boolean>;
40
46
  deleteLogGroup(name: string): Promise<void>;
package/dist/aws/logs.js CHANGED
@@ -33,6 +33,21 @@ export class LogsClient {
33
33
  throw err;
34
34
  }
35
35
  }
36
+ /**
37
+ * Create a log stream inside a log group, treating an existing stream as success.
38
+ * `CreateLogStream` takes no tags, so this carries none: a stream is tagged only
39
+ * through the group that holds it.
40
+ */
41
+ async ensureLogStream(logGroupName, logStreamName) {
42
+ try {
43
+ await this.call('CreateLogStream', { logGroupName, logStreamName });
44
+ }
45
+ catch (err) {
46
+ if (err instanceof AwsError && err.isAlreadyExists)
47
+ return;
48
+ throw err;
49
+ }
50
+ }
36
51
  async putRetentionPolicy(name, retentionInDays) {
37
52
  await this.call('PutRetentionPolicy', { logGroupName: name, retentionInDays });
38
53
  }
package/dist/config.d.ts CHANGED
@@ -30,9 +30,12 @@ export interface SeoConfig {
30
30
  * A *shape*, not a contract core enforces: `blogwright-pds` owns this key end
31
31
  * to end - it validates the block (`validatePdsConfig`) and derives the
32
32
  * `<siteName>/atproto` default `secretName` (`resolvePdsSecretName`). Core
33
- * parses the block through untouched. The type stays here only because
34
- * `OpsConfig.pds` is still declared, which is what lets the CLI's deploy-role
35
- * statement read it until that statement moves to the plugin.
33
+ * parses the block through untouched. The CLI's own resource graph no longer
34
+ * reads the block at all: its deploy-role statement for the secret is gone,
35
+ * and that grant is now the plugin's own named inline policy
36
+ * (`buildPdsNodes`, `packages/pds/src/nodes.ts`). The type stays here because
37
+ * `OpsConfig.pds` is still a declared member, and it is through
38
+ * `ctx.config.pds` that the plugin itself reads the block.
36
39
  */
37
40
  export interface PdsConfig {
38
41
  /** Publication display name (site.standard.publication `name`). */
package/dist/plugin.d.ts CHANGED
@@ -272,7 +272,7 @@ export interface ConfigBlockEntry {
272
272
  /**
273
273
  * Namespace pattern shared by `Plugin.name` and {@link PluginManifest}'s
274
274
  * `plugin` field, so the JSON Schema fragment
275
- * (`.specs/changes/2026-07-26-cli_plugin_system.md` §Type changes) and this
275
+ * (`.specs/changes/merged/2026-07-26-cli_plugin_system.md` §Type changes) and this
276
276
  * runtime check cannot drift apart. Lowercase alphanumerics and dashes.
277
277
  */
278
278
  export declare const PLUGIN_NAME_PATTERN: RegExp;
package/dist/plugin.js CHANGED
@@ -14,7 +14,7 @@
14
14
  /**
15
15
  * Namespace pattern shared by `Plugin.name` and {@link PluginManifest}'s
16
16
  * `plugin` field, so the JSON Schema fragment
17
- * (`.specs/changes/2026-07-26-cli_plugin_system.md` §Type changes) and this
17
+ * (`.specs/changes/merged/2026-07-26-cli_plugin_system.md` §Type changes) and this
18
18
  * runtime check cannot drift apart. Lowercase alphanumerics and dashes.
19
19
  */
20
20
  export const PLUGIN_NAME_PATTERN = /^[a-z0-9-]+$/;
package/dist/state.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /** S3-backed topology state and validation of its persisted boundary. */
1
2
  import type { S3Client } from './aws/s3.js';
2
3
  export type ResourceOutputs = Record<string, string | number | boolean | string[]>;
3
4
  export interface OpsState {
package/dist/state.js CHANGED
@@ -1,6 +1,43 @@
1
1
  export function emptyState(env) {
2
2
  return { version: 1, env, updatedAt: undefined, resources: {} };
3
3
  }
4
+ function isRecord(value) {
5
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
6
+ }
7
+ function isOutputValue(value) {
8
+ return (typeof value === 'string' ||
9
+ typeof value === 'number' ||
10
+ typeof value === 'boolean' ||
11
+ (Array.isArray(value) && value.every((item) => typeof item === 'string')));
12
+ }
13
+ function invalidStateField(value) {
14
+ if (!isRecord(value))
15
+ return 'state';
16
+ if (typeof value.version !== 'number')
17
+ return 'version';
18
+ if (typeof value.env !== 'string')
19
+ return 'env';
20
+ if (value.updatedAt !== undefined && typeof value.updatedAt !== 'string')
21
+ return 'updatedAt';
22
+ if (!isRecord(value.resources))
23
+ return 'resources';
24
+ for (const [nodeId, outputs] of Object.entries(value.resources)) {
25
+ const field = `resources[${JSON.stringify(nodeId)}]`;
26
+ if (!isRecord(outputs))
27
+ return field;
28
+ for (const [name, output] of Object.entries(outputs)) {
29
+ if (!isOutputValue(output))
30
+ return `${field}[${JSON.stringify(name)}]`;
31
+ }
32
+ }
33
+ return undefined;
34
+ }
35
+ function validateState(value, location) {
36
+ const field = invalidStateField(value);
37
+ if (field !== undefined) {
38
+ throw new Error(`${location} has invalid state field ${field} - refusing to proceed with empty state`);
39
+ }
40
+ }
4
41
  /** Namespace shape a plugin manifest declares - reused here so a scope can't smuggle a `/` or `..` into the key. */
5
42
  const SCOPE_PATTERN = /^[a-z0-9-]+$/;
6
43
  function stateKey(env, scope) {
@@ -53,12 +90,15 @@ export class StateStore {
53
90
  // corruption, not a fresh environment, and must hit the guard below.
54
91
  if (text === undefined)
55
92
  return emptyState(this.env);
93
+ let state;
56
94
  try {
57
- return JSON.parse(text);
95
+ state = JSON.parse(text);
58
96
  }
59
97
  catch (err) {
60
98
  throw new Error(`${this.key} in s3://${this.bucket} is not valid JSON - refusing to proceed with empty state`, { cause: err });
61
99
  }
100
+ validateState(state, `${this.key} in s3://${this.bucket}`);
101
+ return state;
62
102
  }
63
103
  async save(state) {
64
104
  state.updatedAt = new Date().toISOString();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blogwright-core",
3
- "version": "0.4.0-beta.2",
3
+ "version": "0.4.0-beta.4",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "files": [