@exabugs/dynamodb-client 1.19.0 → 1.20.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.
Files changed (33) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +1 -1
  3. package/bin/dynamodb-system-change.js +6 -0
  4. package/dist/server/handler.cjs +3 -3
  5. package/dist/server/media-handler.cjs +2 -2
  6. package/dist/server/media-process-handler-dlq.cjs +2 -2
  7. package/dist/server/media-process-handler.cjs +2 -2
  8. package/dist/tools/dynamodb-system-change/cli.d.ts +35 -0
  9. package/dist/tools/dynamodb-system-change/cli.d.ts.map +1 -0
  10. package/dist/tools/dynamodb-system-change/cli.js +226 -0
  11. package/dist/tools/dynamodb-system-change/cli.js.map +1 -0
  12. package/dist/tools/dynamodb-system-change/details.d.ts +42 -0
  13. package/dist/tools/dynamodb-system-change/details.d.ts.map +1 -0
  14. package/dist/tools/dynamodb-system-change/details.js +59 -0
  15. package/dist/tools/dynamodb-system-change/details.js.map +1 -0
  16. package/dist/tools/dynamodb-system-change/index.d.ts +13 -0
  17. package/dist/tools/dynamodb-system-change/index.d.ts.map +1 -0
  18. package/dist/tools/dynamodb-system-change/index.js +9 -0
  19. package/dist/tools/dynamodb-system-change/index.js.map +1 -0
  20. package/dist/tools/dynamodb-system-change/releases.d.ts +34 -0
  21. package/dist/tools/dynamodb-system-change/releases.d.ts.map +1 -0
  22. package/dist/tools/dynamodb-system-change/releases.js +108 -0
  23. package/dist/tools/dynamodb-system-change/releases.js.map +1 -0
  24. package/dist/tools/dynamodb-system-change/run.d.ts +2 -0
  25. package/dist/tools/dynamodb-system-change/run.d.ts.map +1 -0
  26. package/dist/tools/dynamodb-system-change/run.js +6 -0
  27. package/dist/tools/dynamodb-system-change/run.js.map +1 -0
  28. package/dist/tools/dynamodb-system-change/send.d.ts +43 -0
  29. package/dist/tools/dynamodb-system-change/send.d.ts.map +1 -0
  30. package/dist/tools/dynamodb-system-change/send.js +97 -0
  31. package/dist/tools/dynamodb-system-change/send.js.map +1 -0
  32. package/docs/SYSTEM_CHANGES.md +67 -0
  33. package/package.json +7 -2
package/CHANGELOG.md CHANGED
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.20.0] - 2026-10-24
11
+
12
+ ### Added
13
+
14
+ - **CLI `dynamodb-system-change start|finish`**(ADR 0006 の段階 5 = (c)): terraform の apply・destroy の開始と終了を、記録 Lambda(`<app>-<env>-system-change-recorder`)に送る。asanowa・astro の `terraform-wrapper.sh` の記録の部分(開始・終了の呼び出し、`details` の組み立て、`parse-lambda-releases.py`)を Node にした。bash のラッパーは各アプリに残り、記録だけを委ねる。経路は従来と同じ AWS CLI の `aws lambda invoke`、送る本文も同じ。**記録の失敗・引数の誤りでも終了コード 0**(apply を止めない)。start は変更 ID(ULID)を標準出力に出し、ラッパーが finish の `--change-id` に渡す。両アプリの差(アプリ名、git の場所と未コミットの変更を調べる場所、`lambdaReleases` の切り詰め)は引数で渡す。使い方は `docs/SYSTEM_CHANGES.md` の「記録の CLI」
15
+ - `@exabugs/dynamodb-client/system-changes/cli`: `extractLambdaReleases`(`parse-lambda-releases.py` の移植。出力は同じ)、`buildFinishDetails` / `limitReleases` / `resourceCountsOf`、`main` / `parseRecordArgs`、型
16
+
10
17
  ## [1.19.0] - 2026-10-24
11
18
 
12
19
  ### Added
package/README.md CHANGED
@@ -198,7 +198,7 @@ const article = await articles.findOne({ title: 'Hello DynamoDB' });
198
198
  - **[疎な検索の警告と対応](docs/SCAN_WARNING.md)** - 検索を打ち切らず、警告(SPARSE_SCAN)で気づいてインデックス・スキーマ・ロジックを見直す
199
199
  - **[Records クライアントのヘルパー](docs/RECORDS_CLIENT.md)** - Lambda から Records を呼ぶ定型(`createRecordsClient` / `withCollection` / `lazyCollection`)
200
200
  - **[運用通知](docs/NOTIFY.md)** - ログ・アラームを、Slack・メールで管理者へ通知する部品(`@exabugs/dynamodb-client/notify`)
201
- - **[システム変更履歴](docs/SYSTEM_CHANGES.md)** - インフラの変更・リリースを `systemChanges` に記録し、Slack に送る部品と記録 Lambda のハンドラ(`@exabugs/dynamodb-client/system-changes`)
201
+ - **[システム変更履歴](docs/SYSTEM_CHANGES.md)** - インフラの変更・リリースを `systemChanges` に記録し、Slack に送る部品と記録 Lambda のハンドラ(`@exabugs/dynamodb-client/system-changes`)、terraform のラッパーから記録する CLI(`dynamodb-system-change`)
202
202
  - **[Deployment](docs/DEPLOYMENT.md)** - Production deployment guide
203
203
  - **[Terraform Modules](terraform/README.md)** - Infrastructure as Code
204
204
 
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * terraform の apply・destroy のシステム変更履歴への記録(docs/SYSTEM_CHANGES.md の「記録の CLI」)
5
+ */
6
+ import '../dist/tools/dynamodb-system-change/run.js';
@@ -1,5 +1,5 @@
1
- // @exabugs/dynamodb-client v1.19.0
2
- // Built: 2026-10-24T07:44:43.731Z
1
+ // @exabugs/dynamodb-client v1.20.0
2
+ // Built: 2026-10-24T11:55:31.940Z
3
3
  "use strict";
4
4
  var __create = Object.create;
5
5
  var __defProp = Object.defineProperty;
@@ -38980,7 +38980,7 @@ async function handler(event) {
38980
38980
  return createCorsResponse(HTTP_STATUS.OK);
38981
38981
  }
38982
38982
  if (event.requestContext.http.method === "GET" && event.requestContext.http.path === "/version") {
38983
- const version = "1.19.0";
38983
+ const version = "1.20.0";
38984
38984
  return createSuccessResponse({ version, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, requestId);
38985
38985
  }
38986
38986
  if (event.requestContext.http.method !== "POST") {
@@ -1,5 +1,5 @@
1
- // @exabugs/dynamodb-client v1.19.0
2
- // Built: 2026-10-24T07:44:43.731Z
1
+ // @exabugs/dynamodb-client v1.20.0
2
+ // Built: 2026-10-24T11:55:31.940Z
3
3
  "use strict";
4
4
  var __create = Object.create;
5
5
  var __defProp = Object.defineProperty;
@@ -1,5 +1,5 @@
1
- // @exabugs/dynamodb-client v1.19.0
2
- // Built: 2026-10-24T07:44:43.731Z
1
+ // @exabugs/dynamodb-client v1.20.0
2
+ // Built: 2026-10-24T11:55:31.940Z
3
3
  "use strict";
4
4
  var __create = Object.create;
5
5
  var __defProp = Object.defineProperty;
@@ -1,5 +1,5 @@
1
- // @exabugs/dynamodb-client v1.19.0
2
- // Built: 2026-10-24T07:44:43.731Z
1
+ // @exabugs/dynamodb-client v1.20.0
2
+ // Built: 2026-10-24T11:55:31.940Z
3
3
  "use strict";
4
4
  var __create = Object.create;
5
5
  var __defProp = Object.defineProperty;
@@ -0,0 +1,35 @@
1
+ import { type ReleasesLimit } from './details.js';
2
+ import { type RunCommand } from './send.js';
3
+ export interface CliIo {
4
+ out: (s: string) => void;
5
+ /** 標準エラー(改行は呼び出し側が付ける。バイト列も受ける) */
6
+ err: (s: string | Buffer) => void;
7
+ }
8
+ export interface CliDeps {
9
+ env?: NodeJS.ProcessEnv;
10
+ run?: RunCommand;
11
+ newChangeId?: () => string;
12
+ actor?: () => string;
13
+ }
14
+ export declare const USAGE = "\u4F7F\u3044\u65B9:\n dynamodb-system-change start --app <app> --env <env> --command <cmd> [\u5171\u901A]\n \u2192 \u5909\u66F4 ID\uFF08ULID\uFF09\u3092\u6A19\u6E96\u51FA\u529B\u306B\u51FA\u3059\uFF08SKIP_CHANGE_RECORD=1 \u3067\u306F\u4F55\u3082\u51FA\u3055\u306A\u3044\uFF09\n dynamodb-system-change finish --app <app> --env <env> --command <cmd> --exit-code <n>\n [--change-id <id>] [--log <file>] [\u5171\u901A]\n\n --app <app> \u30A2\u30D7\u30EA\u540D\uFF08details.app\u3002\u65E2\u5B9A\u306E\u95A2\u6570\u540D\u30FB\u5BFE\u8C61\u306B\u4F7F\u3046\uFF09\n --env <env> \u74B0\u5883\uFF08details.env\uFF09\n --command <cmd> \u30E9\u30C3\u30D1\u30FC\u306E\u30B3\u30DE\u30F3\u30C9\uFF08details.command\u3002destroy \u3067\u306F\u30EA\u30EA\u30FC\u30B9\u3092\u53D6\u308A\u51FA\u3055\u306A\u3044\uFF09\n --exit-code <n> terraform \u306E\u7D42\u4E86\u30B3\u30FC\u30C9\uFF080 = succeeded\u3001\u305D\u308C\u4EE5\u5916 = failed\u30020 \u4EE5\u5916\u306F details.exit_code\uFF09\n --change-id <id> start \u304C\u51FA\u3057\u305F\u5909\u66F4 ID\uFF08\u7A7A\u306A\u3089 changeId \u306A\u3057\u3067\u8A18\u9332\uFF09\n --log <file> plan / apply \u306E\u30ED\u30B0\uFF08\u4EF6\u6570\u3068 Lambda \u306E\u30EA\u30EA\u30FC\u30B9\u3092\u53D6\u308A\u51FA\u3059\uFF09\n\u5171\u901A:\n --repo <dir> git \u3092\u5B9F\u884C\u3059\u308B\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\uFF08\u65E2\u5B9A: \u30AB\u30EC\u30F3\u30C8\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\uFF09\n --dirty-path <path>... \u672A\u30B3\u30DF\u30C3\u30C8\u306E\u5909\u66F4\u3092\u8ABF\u3079\u308B\u5834\u6240\uFF08\u3042\u308C\u3070 commit \u306B -dirty\u3002\u65E2\u5B9A: \u4F5C\u696D\u30C4\u30EA\u30FC\u5168\u4F53\uFF09\n --function <name> \u8A18\u9332 Lambda\uFF08\u65E2\u5B9A: <app>-<env>-system-change-recorder\uFF09\n --target <target> \u5BFE\u8C61\uFF08\u65E2\u5B9A: terraform:<app>\uFF09\n --region <region> \u65E2\u5B9A: AWS_REGION\u3001\u7121\u3051\u308C\u3070 ap-northeast-1\n --releases-max-items <n|none> lambdaReleases \u306E\u4EF6\u6570\u306E\u4E0A\u9650\uFF08\u65E2\u5B9A: none\uFF09\n --releases-max-bytes <n|none> lambdaReleases \u306E JSON \u306E\u30D0\u30A4\u30C8\u6570\u306E\u4E0A\u9650\uFF08\u65E2\u5B9A: 3000\uFF09\n\n\u74B0\u5883\u5909\u6570: RECORD_DRY_RUN=1\uFF08\u547C\u3073\u51FA\u3055\u305A\u3001\u9001\u308B\u5185\u5BB9\u3092\u6A19\u6E96\u30A8\u30E9\u30FC\u306B\u51FA\u3059\uFF09\u3001SKIP_CHANGE_RECORD=1\uFF08\u8A18\u9332\u3057\u306A\u3044\uFF09\n\u7D42\u4E86\u30B3\u30FC\u30C9: start / finish \u306F\u5E38\u306B 0\uFF08\u8A18\u9332\u306E\u5931\u6557\u3067 apply \u3092\u6B62\u3081\u306A\u3044\uFF09\u3002--help \u306F 0\u3001\u30B5\u30D6\u30B3\u30DE\u30F3\u30C9\u306E\u8AA4\u308A\u306F 2";
15
+ export interface RecordArgs {
16
+ app: string;
17
+ env: string;
18
+ command: string;
19
+ repo?: string;
20
+ dirtyPaths: string[];
21
+ functionName: string;
22
+ target: string;
23
+ region?: string;
24
+ limit: ReleasesLimit;
25
+ exitCode?: number;
26
+ changeId?: string;
27
+ log?: string;
28
+ }
29
+ /** 引数を解釈する(誤りは Error)。`--change-id` と `--log` は空の値を許す(ラッパーの変数が空のとき) */
30
+ export declare function parseRecordArgs(sub: 'start' | 'finish', argv: string[]): RecordArgs;
31
+ /** 終了の details(件数・exit_code・lambdaReleases)。destroy ではリリースを取り出さない */
32
+ export declare function finishDetailsOf(args: RecordArgs): Record<string, unknown>;
33
+ /** CLI 本体。終了コードを返す(process.exitCode は run.ts が設定する) */
34
+ export declare function main(argv: string[], io: CliIo, deps?: CliDeps): number;
35
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../../src/tools/dynamodb-system-change/cli.ts"],"names":[],"mappings":"AAcA,OAAO,EAA8B,KAAK,aAAa,EAAsB,MAAM,cAAc,CAAC;AAElG,OAAO,EACL,KAAK,UAAU,EAKhB,MAAM,WAAW,CAAC;AAEnB,MAAM,WAAW,KAAK;IACpB,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACzB,mCAAmC;IACnC,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,OAAO;IACtB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,MAAM,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,KAAK,wxFAsBsD,CAAC;AAEzE,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,aAAa,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAyBD,uEAAuE;AACvE,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAsDnF;AAYD,qEAAqE;AACrE,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgBzE;AA8DD,uDAAuD;AACvD,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,GAAE,OAAY,GAAG,MAAM,CAW1E"}
@@ -0,0 +1,226 @@
1
+ /**
2
+ * `dynamodb-system-change start|finish`: terraform の apply・destroy の開始と終了を、システム変更履歴に記録する
3
+ * (ADR 0006 の段階 5 = (c)。docs/SYSTEM_CHANGES.md の「記録の CLI」)。
4
+ *
5
+ * 各アプリの terraform-wrapper.sh(bash)は残し、記録に送る部分(開始・終了の呼び出し、details の組み立て、
6
+ * Lambda のリリースの取り出し)だけをこの CLI に委ねる。**記録の失敗で apply を止めない**: start / finish は、
7
+ * 引数の誤りを含めて、常に終了コード 0 で終わる(警告は標準エラー)。
8
+ *
9
+ * 受け渡し: start は変更 ID(ULID)を標準出力に 1 行で出す(SKIP_CHANGE_RECORD=1 では何も出さない)。
10
+ * ラッパーはそれをシェルの変数に持ち、finish の `--change-id` に渡す(現行の CHANGE_ID と同じ)。
11
+ */
12
+ import { readFileSync } from 'node:fs';
13
+ import { ulid } from 'ulid';
14
+ import { DEFAULT_RELEASES_MAX_BYTES, buildFinishDetails } from './details.js';
15
+ import { extractLambdaReleases } from './releases.js';
16
+ import { actorOf, commitOf, sendSystemChange, } from './send.js';
17
+ export const USAGE = `使い方:
18
+ dynamodb-system-change start --app <app> --env <env> --command <cmd> [共通]
19
+ → 変更 ID(ULID)を標準出力に出す(SKIP_CHANGE_RECORD=1 では何も出さない)
20
+ dynamodb-system-change finish --app <app> --env <env> --command <cmd> --exit-code <n>
21
+ [--change-id <id>] [--log <file>] [共通]
22
+
23
+ --app <app> アプリ名(details.app。既定の関数名・対象に使う)
24
+ --env <env> 環境(details.env)
25
+ --command <cmd> ラッパーのコマンド(details.command。destroy ではリリースを取り出さない)
26
+ --exit-code <n> terraform の終了コード(0 = succeeded、それ以外 = failed。0 以外は details.exit_code)
27
+ --change-id <id> start が出した変更 ID(空なら changeId なしで記録)
28
+ --log <file> plan / apply のログ(件数と Lambda のリリースを取り出す)
29
+ 共通:
30
+ --repo <dir> git を実行するディレクトリ(既定: カレントディレクトリ)
31
+ --dirty-path <path>... 未コミットの変更を調べる場所(あれば commit に -dirty。既定: 作業ツリー全体)
32
+ --function <name> 記録 Lambda(既定: <app>-<env>-system-change-recorder)
33
+ --target <target> 対象(既定: terraform:<app>)
34
+ --region <region> 既定: AWS_REGION、無ければ ap-northeast-1
35
+ --releases-max-items <n|none> lambdaReleases の件数の上限(既定: none)
36
+ --releases-max-bytes <n|none> lambdaReleases の JSON のバイト数の上限(既定: ${DEFAULT_RELEASES_MAX_BYTES})
37
+
38
+ 環境変数: RECORD_DRY_RUN=1(呼び出さず、送る内容を標準エラーに出す)、SKIP_CHANGE_RECORD=1(記録しない)
39
+ 終了コード: start / finish は常に 0(記録の失敗で apply を止めない)。--help は 0、サブコマンドの誤りは 2`;
40
+ const SINGLE = {
41
+ '--app': 'app',
42
+ '--env': 'env',
43
+ '--command': 'command',
44
+ '--repo': 'repo',
45
+ '--function': 'functionName',
46
+ '--target': 'target',
47
+ '--region': 'region',
48
+ '--exit-code': 'exitCode',
49
+ '--change-id': 'changeId',
50
+ '--log': 'log',
51
+ '--releases-max-items': 'maxItems',
52
+ '--releases-max-bytes': 'maxBytes',
53
+ };
54
+ function limitValue(name, v, fallback) {
55
+ if (v === undefined)
56
+ return fallback;
57
+ if (v === 'none')
58
+ return undefined;
59
+ if (!/^\d+$/.test(v))
60
+ throw new Error(`${name} は 0 以上の整数か none`);
61
+ return Number(v);
62
+ }
63
+ /** 引数を解釈する(誤りは Error)。`--change-id` と `--log` は空の値を許す(ラッパーの変数が空のとき) */
64
+ export function parseRecordArgs(sub, argv) {
65
+ const v = {};
66
+ const dirtyPaths = [];
67
+ let inDirty = false;
68
+ for (let i = 0; i < argv.length; i++) {
69
+ const a = argv[i];
70
+ if (a in SINGLE) {
71
+ const value = argv[++i];
72
+ const key = SINGLE[a];
73
+ const allowEmpty = key === 'changeId' || key === 'log';
74
+ if (value === undefined || (!allowEmpty && (value === '' || value.startsWith('--')))) {
75
+ throw new Error(`${a} に値が要る`);
76
+ }
77
+ v[key] = value;
78
+ inDirty = false;
79
+ }
80
+ else if (a === '--dirty-path') {
81
+ inDirty = true;
82
+ }
83
+ else if (a.startsWith('--')) {
84
+ throw new Error(`未知の引数: ${a}`);
85
+ }
86
+ else if (inDirty) {
87
+ dirtyPaths.push(a);
88
+ }
89
+ else {
90
+ throw new Error(`位置が不正な引数: ${a}`);
91
+ }
92
+ }
93
+ for (const k of ['app', 'env', 'command']) {
94
+ if (!v[k])
95
+ throw new Error(`--${k} が要る`);
96
+ }
97
+ const app = v.app;
98
+ const env = v.env;
99
+ let exitCode;
100
+ if (sub === 'finish') {
101
+ if (v.exitCode === undefined || !/^\d+$/.test(v.exitCode)) {
102
+ throw new Error('--exit-code に 0 以上の整数が要る');
103
+ }
104
+ exitCode = Number(v.exitCode);
105
+ }
106
+ return {
107
+ app,
108
+ env,
109
+ command: v.command,
110
+ repo: v.repo,
111
+ dirtyPaths,
112
+ functionName: v.functionName ?? `${app}-${env}-system-change-recorder`,
113
+ target: v.target ?? `terraform:${app}`,
114
+ region: v.region,
115
+ limit: {
116
+ maxItems: limitValue('--releases-max-items', v.maxItems),
117
+ maxBytes: limitValue('--releases-max-bytes', v.maxBytes, DEFAULT_RELEASES_MAX_BYTES),
118
+ },
119
+ exitCode,
120
+ changeId: v.changeId || undefined,
121
+ log: v.log || undefined,
122
+ };
123
+ }
124
+ /** ログを読む(読めなければ undefined。不正な UTF-8 は置き換える) */
125
+ function readLog(path) {
126
+ if (!path)
127
+ return undefined;
128
+ try {
129
+ return readFileSync(path).toString('utf8');
130
+ }
131
+ catch {
132
+ return undefined;
133
+ }
134
+ }
135
+ /** 終了の details(件数・exit_code・lambdaReleases)。destroy ではリリースを取り出さない */
136
+ export function finishDetailsOf(args) {
137
+ const exitCode = args.exitCode ?? 0;
138
+ const logText = readLog(args.log);
139
+ let releases = [];
140
+ if (args.command !== 'destroy' && logText !== undefined) {
141
+ try {
142
+ releases = extractLambdaReleases(logText);
143
+ }
144
+ catch {
145
+ releases = []; // 現行: 取り出しに失敗したら []
146
+ }
147
+ }
148
+ try {
149
+ return { ...buildFinishDetails({ exitCode, logText, releases, limit: args.limit }) };
150
+ }
151
+ catch {
152
+ return {}; // 現行: 組み立てに失敗したら {}
153
+ }
154
+ }
155
+ function record(args, result, extra, io, deps) {
156
+ const env = deps.env ?? process.env;
157
+ let payload;
158
+ try {
159
+ payload = {
160
+ kind: 'infra',
161
+ result,
162
+ target: args.target,
163
+ commit: commitOf(args.repo, args.dirtyPaths, deps.run),
164
+ actor: (deps.actor ?? actorOf)(),
165
+ // 開始と終了の両方に付ける(記録 Lambda は details を深くマージするので、同じキーが重なっても害はない)
166
+ details: { ...extra, app: args.app, env: args.env, command: args.command },
167
+ ...(args.changeId ? { changeId: args.changeId } : {}),
168
+ };
169
+ }
170
+ catch {
171
+ io.err('[change-record] ペイロードの作成に失敗しました(記録をスキップ)\n');
172
+ return;
173
+ }
174
+ sendSystemChange(payload, {
175
+ functionName: args.functionName,
176
+ region: args.region || env.AWS_REGION || 'ap-northeast-1',
177
+ dryRun: env.RECORD_DRY_RUN === '1',
178
+ warn: io.err,
179
+ run: deps.run,
180
+ });
181
+ }
182
+ /** start / finish の本体。記録の失敗・引数の誤りでも 0 を返す */
183
+ function runRecord(sub, argv, io, deps) {
184
+ const env = deps.env ?? process.env;
185
+ if (env.SKIP_CHANGE_RECORD === '1')
186
+ return 0;
187
+ let args;
188
+ try {
189
+ args = parseRecordArgs(sub, argv);
190
+ }
191
+ catch (e) {
192
+ io.err(`[change-record] 引数の誤り(記録をスキップ): ${e.message}\n`);
193
+ return 0;
194
+ }
195
+ try {
196
+ if (sub === 'start') {
197
+ const changeId = (deps.newChangeId ?? ulid)();
198
+ // 変更 ID は、記録の成否によらず出す(終了の記録に同じ ID を付ける。現行と同じ)
199
+ io.out(`${changeId}\n`);
200
+ record({ ...args, changeId }, 'started', {}, io, deps);
201
+ }
202
+ else {
203
+ const result = args.exitCode === 0 ? 'succeeded' : 'failed';
204
+ record(args, result, finishDetailsOf(args), io, deps);
205
+ }
206
+ }
207
+ catch (e) {
208
+ io.err(`[change-record] 記録に失敗しました(処理は続行): ${e.message}\n`);
209
+ }
210
+ return 0;
211
+ }
212
+ /** CLI 本体。終了コードを返す(process.exitCode は run.ts が設定する) */
213
+ export function main(argv, io, deps = {}) {
214
+ const [sub, ...rest] = argv;
215
+ // --help はサブコマンドの位置だけで見る(start / finish の引数の値の -h を拾わない)。
216
+ // USAGE は標準エラーに出す(start の標準出力は変更 ID の受け渡しに使うため、汚さない)
217
+ if (sub === '--help' || sub === '-h') {
218
+ io.err(`${USAGE}\n`);
219
+ return 0;
220
+ }
221
+ if (sub === 'start' || sub === 'finish')
222
+ return runRecord(sub, rest, io, deps);
223
+ io.err(`サブコマンドは start か finish(got: ${sub ?? 'なし'})\n\n${USAGE}\n`);
224
+ return 2;
225
+ }
226
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../../src/tools/dynamodb-system-change/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * 終了(succeeded / failed)の記録の `details` を組み立てる(両アプリの terraform-wrapper.sh の finish_change の
3
+ * Python を移植したもの)。純粋な関数だけ(ファイルの読み取りは呼び出し側)。
4
+ */
5
+ import { type ExtractedLambdaRelease } from './releases.js';
6
+ /** `lambdaReleases` の切り詰めの既定(asanowa の現行: 3000 バイトまで。件数の上限なし) */
7
+ export declare const DEFAULT_RELEASES_MAX_BYTES = 3000;
8
+ export interface ReleasesLimit {
9
+ /** 件数の上限(undefined は無制限)。astro の現行は 30 */
10
+ maxItems?: number;
11
+ /** `lambdaReleases` の JSON(区切りの空白なし、UTF-8)のバイト数の上限(undefined は無制限)。asanowa の現行は 3000 */
12
+ maxBytes?: number;
13
+ }
14
+ export interface FinishDetails {
15
+ add?: number;
16
+ change?: number;
17
+ destroy?: number;
18
+ exit_code?: number;
19
+ lambdaReleases?: ExtractedLambdaRelease[];
20
+ lambdaReleasesOmitted?: number;
21
+ }
22
+ /** ログの最後の完了の行から、件数を取り出す(無ければ undefined) */
23
+ export declare function resourceCountsOf(logText: string): Pick<FinishDetails, 'add' | 'change' | 'destroy'> | undefined;
24
+ /**
25
+ * 失敗を先に並べ(失敗は落とさない)、上限まで残す。入らなかった件数を omitted に返す。
26
+ * 記録 Lambda の details の上限(4096 バイト)に収めるため
27
+ */
28
+ export declare function limitReleases(releases: readonly ExtractedLambdaRelease[], limit?: ReleasesLimit): {
29
+ items: ExtractedLambdaRelease[];
30
+ omitted: number;
31
+ };
32
+ /**
33
+ * 終了の details(件数・exit_code・lambdaReleases・lambdaReleasesOmitted。キーの順も現行と同じ)。
34
+ * 件数は、成功(exitCode 0)でログがあるときだけ
35
+ */
36
+ export declare function buildFinishDetails(input: {
37
+ exitCode: number;
38
+ logText?: string;
39
+ releases: readonly ExtractedLambdaRelease[];
40
+ limit?: ReleasesLimit;
41
+ }): FinishDetails;
42
+ //# sourceMappingURL=details.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"details.d.ts","sourceRoot":"","sources":["../../../src/tools/dynamodb-system-change/details.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,KAAK,sBAAsB,EAAa,MAAM,eAAe,CAAC;AAEvE,iEAAiE;AACjE,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C,MAAM,WAAW,aAAa;IAC5B,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wFAAwF;IACxF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,sBAAsB,EAAE,CAAC;IAC1C,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAMD,2CAA2C;AAC3C,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,GACd,IAAI,CAAC,aAAa,EAAE,KAAK,GAAG,QAAQ,GAAG,SAAS,CAAC,GAAG,SAAS,CAM/D;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,SAAS,sBAAsB,EAAE,EAC3C,KAAK,GAAE,aAAwD,GAC9D;IAAE,KAAK,EAAE,sBAAsB,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAiBtD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,SAAS,sBAAsB,EAAE,CAAC;IAC5C,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB,GAAG,aAAa,CAUhB"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * 終了(succeeded / failed)の記録の `details` を組み立てる(両アプリの terraform-wrapper.sh の finish_change の
3
+ * Python を移植したもの)。純粋な関数だけ(ファイルの読み取りは呼び出し側)。
4
+ */
5
+ import { stripAnsi } from './releases.js';
6
+ /** `lambdaReleases` の切り詰めの既定(asanowa の現行: 3000 バイトまで。件数の上限なし) */
7
+ export const DEFAULT_RELEASES_MAX_BYTES = 3000;
8
+ // `Apply complete! Resources: 1 added, 2 changed, 3 destroyed.` と `Destroy complete! Resources: 3 destroyed.`
9
+ const COMPLETE = /(?:Apply|Destroy) complete! Resources: (\d+) (?:added|destroyed)(?:, (\d+) changed, (\d+) destroyed)?/g;
10
+ /** ログの最後の完了の行から、件数を取り出す(無ければ undefined) */
11
+ export function resourceCountsOf(logText) {
12
+ const ms = [...stripAnsi(logText).matchAll(COMPLETE)];
13
+ const m = ms[ms.length - 1];
14
+ if (!m)
15
+ return undefined;
16
+ if (m[2] === undefined)
17
+ return { add: 0, change: 0, destroy: Number(m[1]) }; // Destroy の短い形
18
+ return { add: Number(m[1]), change: Number(m[2]), destroy: Number(m[3]) };
19
+ }
20
+ /**
21
+ * 失敗を先に並べ(失敗は落とさない)、上限まで残す。入らなかった件数を omitted に返す。
22
+ * 記録 Lambda の details の上限(4096 バイト)に収めるため
23
+ */
24
+ export function limitReleases(releases, limit = { maxBytes: DEFAULT_RELEASES_MAX_BYTES }) {
25
+ const sorted = [
26
+ ...releases.filter((d) => d.result === 'failed'),
27
+ ...releases.filter((d) => d.result !== 'failed'),
28
+ ];
29
+ const items = [];
30
+ for (const d of sorted) {
31
+ if (limit.maxItems !== undefined && items.length >= limit.maxItems)
32
+ break;
33
+ if (limit.maxBytes !== undefined &&
34
+ Buffer.byteLength(JSON.stringify([...items, d]), 'utf8') > limit.maxBytes) {
35
+ break;
36
+ }
37
+ items.push({ target: d.target, result: d.result, change: d.change });
38
+ }
39
+ return { items, omitted: sorted.length - items.length };
40
+ }
41
+ /**
42
+ * 終了の details(件数・exit_code・lambdaReleases・lambdaReleasesOmitted。キーの順も現行と同じ)。
43
+ * 件数は、成功(exitCode 0)でログがあるときだけ
44
+ */
45
+ export function buildFinishDetails(input) {
46
+ const out = {};
47
+ if (input.exitCode === 0 && input.logText !== undefined) {
48
+ Object.assign(out, resourceCountsOf(input.logText));
49
+ }
50
+ if (input.exitCode !== 0)
51
+ out.exit_code = input.exitCode;
52
+ const { items, omitted } = limitReleases(input.releases, input.limit);
53
+ if (items.length > 0)
54
+ out.lambdaReleases = items;
55
+ if (omitted > 0)
56
+ out.lambdaReleasesOmitted = omitted;
57
+ return out;
58
+ }
59
+ //# sourceMappingURL=details.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"details.js","sourceRoot":"","sources":["../../../src/tools/dynamodb-system-change/details.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `dynamodb-system-change`(terraform の apply・destroy のシステム変更履歴への記録の CLI。ADR 0006 の段階 5)の部品。
3
+ * 使い方は docs/SYSTEM_CHANGES.md の「記録の CLI」
4
+ */
5
+ export { USAGE, finishDetailsOf, main, parseRecordArgs } from './cli.js';
6
+ export type { CliDeps, CliIo, RecordArgs } from './cli.js';
7
+ export { DEFAULT_RELEASES_MAX_BYTES, buildFinishDetails, limitReleases, resourceCountsOf, } from './details.js';
8
+ export type { FinishDetails, ReleasesLimit } from './details.js';
9
+ export { EXTRACT_MAX_ITEMS, extractLambdaReleases, splitLines, stripAnsi } from './releases.js';
10
+ export type { ExtractedLambdaRelease } from './releases.js';
11
+ export { actorOf, commitOf, pythonJsonDumps, runCommand, sendSystemChange } from './send.js';
12
+ export type { RunCommand, SendOptions, SystemChangePayload } from './send.js';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/dynamodb-system-change/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AACzE,YAAY,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3D,OAAO,EACL,0BAA0B,EAC1B,kBAAkB,EAClB,aAAa,EACb,gBAAgB,GACjB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAChG,YAAY,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7F,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `dynamodb-system-change`(terraform の apply・destroy のシステム変更履歴への記録の CLI。ADR 0006 の段階 5)の部品。
3
+ * 使い方は docs/SYSTEM_CHANGES.md の「記録の CLI」
4
+ */
5
+ export { USAGE, finishDetailsOf, main, parseRecordArgs } from './cli.js';
6
+ export { DEFAULT_RELEASES_MAX_BYTES, buildFinishDetails, limitReleases, resourceCountsOf, } from './details.js';
7
+ export { EXTRACT_MAX_ITEMS, extractLambdaReleases, splitLines, stripAnsi } from './releases.js';
8
+ export { actorOf, commitOf, pythonJsonDumps, runCommand, sendSystemChange } from './send.js';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tools/dynamodb-system-change/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEzE,OAAO,EACL,0BAA0B,EAC1B,kBAAkB,EAClB,aAAa,EACb,gBAAgB,GACjB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAEhG,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * terraform の plan / apply のログから、Lambda のコードのリリース(作成・置き換え・コードの更新)の成功・失敗を取り出す。
3
+ *
4
+ * asanowa(ADR 0044)・astro(ADR-0011)の `infra/scripts/parse-lambda-releases.py` を Node に移植したもの
5
+ * (両アプリの版は、ロジックが同一。違いは docstring だけ)。出力は Python 版と同じ(テストで固定)。
6
+ *
7
+ * 取り出す条件(Terraform 1.x と AWS プロバイダ 6.x の実際の出力で確認済み):
8
+ * - 計画の `# <アドレス> will be created / must be replaced / will be replaced, as requested / will be updated in-place`
9
+ * のうち、aws_lambda_function で、作成・置き換え、または更新で `source_code_hash` の差分があるもの(= コードが変わる)。
10
+ * 設定だけの更新(環境変数・同時実行数など)は、対象外
11
+ * - 成功: `<アドレス>: Modifications complete after … [id=<関数名>]`(更新)/ `Creation complete after … [id=<関数名>]`(作成)
12
+ * - 失敗: `Error: creating Lambda Function (<名前>)` / `Error: updating Lambda Function (<名前>) code` /
13
+ * `Error: waiting for Lambda Function (<名前>) code update|create`(設定の更新の失敗は、対象外)
14
+ */
15
+ /** 取り出した 1 件(`details.lambdaReleases` の 1 件と同じ形) */
16
+ export interface ExtractedLambdaRelease {
17
+ target: string;
18
+ result: 'succeeded' | 'failed';
19
+ change: 'create' | 'replace' | 'code';
20
+ }
21
+ /** 取り出す上限(Python 版の MAX_ITEMS) */
22
+ export declare const EXTRACT_MAX_ITEMS = 50;
23
+ /** 色コード(Python 版の ANSI) */
24
+ export declare const ANSI_PATTERN: RegExp;
25
+ /**
26
+ * Python の `str.splitlines()` と同じ区切りで行に分ける(\r\n、\n、\r、\v、\f、\x1c〜\x1e、\x85、U+2028、U+2029)。
27
+ * 末尾の区切りの後ろに空の行を作らない
28
+ */
29
+ export declare function splitLines(text: string): string[];
30
+ /** 色コードを除く */
31
+ export declare function stripAnsi(text: string): string;
32
+ /** ログから Lambda のリリースを取り出す(該当が無ければ空の配列。最大 EXTRACT_MAX_ITEMS 件) */
33
+ export declare function extractLambdaReleases(text: string): ExtractedLambdaRelease[];
34
+ //# sourceMappingURL=releases.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"releases.d.ts","sourceRoot":"","sources":["../../../src/tools/dynamodb-system-change/releases.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,oDAAoD;AACpD,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,WAAW,GAAG,QAAQ,CAAC;IAC/B,MAAM,EAAE,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;CACvC;AAED,kCAAkC;AAClC,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC,2BAA2B;AAC3B,eAAO,MAAM,YAAY,QAAoB,CAAC;AAsB9C;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAIjD;AAED,cAAc;AACd,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAoCD,iEAAiE;AACjE,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,sBAAsB,EAAE,CA2B5E"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * terraform の plan / apply のログから、Lambda のコードのリリース(作成・置き換え・コードの更新)の成功・失敗を取り出す。
3
+ *
4
+ * asanowa(ADR 0044)・astro(ADR-0011)の `infra/scripts/parse-lambda-releases.py` を Node に移植したもの
5
+ * (両アプリの版は、ロジックが同一。違いは docstring だけ)。出力は Python 版と同じ(テストで固定)。
6
+ *
7
+ * 取り出す条件(Terraform 1.x と AWS プロバイダ 6.x の実際の出力で確認済み):
8
+ * - 計画の `# <アドレス> will be created / must be replaced / will be replaced, as requested / will be updated in-place`
9
+ * のうち、aws_lambda_function で、作成・置き換え、または更新で `source_code_hash` の差分があるもの(= コードが変わる)。
10
+ * 設定だけの更新(環境変数・同時実行数など)は、対象外
11
+ * - 成功: `<アドレス>: Modifications complete after … [id=<関数名>]`(更新)/ `Creation complete after … [id=<関数名>]`(作成)
12
+ * - 失敗: `Error: creating Lambda Function (<名前>)` / `Error: updating Lambda Function (<名前>) code` /
13
+ * `Error: waiting for Lambda Function (<名前>) code update|create`(設定の更新の失敗は、対象外)
14
+ */
15
+ /** 取り出す上限(Python 版の MAX_ITEMS) */
16
+ export const EXTRACT_MAX_ITEMS = 50;
17
+ /** 色コード(Python 版の ANSI) */
18
+ export const ANSI_PATTERN = /\x1b\[[0-9;]*m/g;
19
+ const ADDR = String.raw `\S*aws_lambda_function\.[A-Za-z0-9_]+(?:\[[^\]\n]*\])?`;
20
+ // 見出しの言い方: will be created / will be updated in-place / must be replaced / will be replaced, as requested(-replace)/
21
+ // is tainted, so must be replaced / will be destroyed
22
+ const HEADER = new RegExp(String.raw `^\s*# (` +
23
+ ADDR +
24
+ String.raw `) (will be created|will be updated in-place|will be destroyed|` +
25
+ String.raw `(?:is tainted, so )?must be replaced|will be replaced, as requested)`);
26
+ const ANY_HEADER = /^\s*# \S+ (?:will|must|is) /;
27
+ const DONE = new RegExp('^(' + ADDR + String.raw `): (Modifications|Creation) complete after \S+ \[id=([^\]\s]+)\]`);
28
+ const FAILED = [
29
+ [/Error: creating Lambda Function \(([^)\s]+)\)/, 'create'],
30
+ [/Error: updating Lambda Function \(([^)\s]+)\) code/, 'code'],
31
+ [/Error: waiting for Lambda Function \(([^)\s]+)\) code update/, 'code'],
32
+ [/Error: waiting for Lambda Function \(([^)\s]+)\) create/, 'create'],
33
+ ];
34
+ /**
35
+ * Python の `str.splitlines()` と同じ区切りで行に分ける(\r\n、\n、\r、\v、\f、\x1c〜\x1e、\x85、U+2028、U+2029)。
36
+ * 末尾の区切りの後ろに空の行を作らない
37
+ */
38
+ export function splitLines(text) {
39
+ const lines = text.split(/\r\n|[\n\r\v\f\x1c\x1d\x1e\x85\u2028\u2029]/);
40
+ if (lines.length > 0 && lines[lines.length - 1] === '')
41
+ lines.pop();
42
+ return lines;
43
+ }
44
+ /** 色コードを除く */
45
+ export function stripAnsi(text) {
46
+ return text.replace(ANSI_PATTERN, '');
47
+ }
48
+ /** 計画の見出しから「コードが変わる Lambda のアドレス → 変更の種類」を作る */
49
+ function codeChangesOf(lines) {
50
+ const changes = new Map();
51
+ let i = 0;
52
+ while (i < lines.length) {
53
+ const m = HEADER.exec(lines[i]);
54
+ if (!m) {
55
+ i += 1;
56
+ continue;
57
+ }
58
+ const [, addr, action] = m;
59
+ let j = i + 1;
60
+ while (j < lines.length &&
61
+ !ANY_HEADER.test(lines[j]) &&
62
+ !lines[j].startsWith('Plan:') &&
63
+ !lines[j].startsWith('Do you want')) {
64
+ j += 1;
65
+ }
66
+ const block = lines.slice(i + 1, j);
67
+ if (action === 'will be created')
68
+ changes.set(addr, 'create');
69
+ else if (action.includes('replaced'))
70
+ changes.set(addr, 'replace');
71
+ else if (action === 'will be updated in-place' &&
72
+ block.some((l) => l.includes('source_code_hash'))) {
73
+ changes.set(addr, 'code');
74
+ }
75
+ i = j;
76
+ }
77
+ return changes;
78
+ }
79
+ /** ログから Lambda のリリースを取り出す(該当が無ければ空の配列。最大 EXTRACT_MAX_ITEMS 件) */
80
+ export function extractLambdaReleases(text) {
81
+ const lines = splitLines(text).map(stripAnsi);
82
+ const codeChanges = codeChangesOf(lines);
83
+ const out = [];
84
+ const seen = new Set();
85
+ for (const raw of lines) {
86
+ const line = raw.replace(/^[│ ]+/, '').trimEnd();
87
+ const done = DONE.exec(line);
88
+ const change = done ? codeChanges.get(done[1]) : undefined;
89
+ if (done && change) {
90
+ const name = done[3];
91
+ if (!seen.has(name)) {
92
+ seen.add(name);
93
+ out.push({ target: name, result: 'succeeded', change });
94
+ }
95
+ continue;
96
+ }
97
+ for (const [rx, failedChange] of FAILED) {
98
+ const m = rx.exec(line);
99
+ if (m && !seen.has(m[1])) {
100
+ seen.add(m[1]);
101
+ out.push({ target: m[1], result: 'failed', change: failedChange });
102
+ break;
103
+ }
104
+ }
105
+ }
106
+ return out.slice(0, EXTRACT_MAX_ITEMS);
107
+ }
108
+ //# sourceMappingURL=releases.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"releases.js","sourceRoot":"","sources":["../../../src/tools/dynamodb-system-change/releases.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/tools/dynamodb-system-change/run.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ import { main } from './cli.js';
2
+ process.exitCode = main(process.argv.slice(2), {
3
+ out: (s) => process.stdout.write(s),
4
+ err: (s) => process.stderr.write(s),
5
+ });
6
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../../src/tools/dynamodb-system-change/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAEhC,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;IAC7C,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACnC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;CACpC,CAAC,CAAC"}
@@ -0,0 +1,43 @@
1
+ /** 外部コマンドの実行(テストで差し替える) */
2
+ export type RunCommand = (command: string, args: string[], options?: {
3
+ cwd?: string;
4
+ }) => {
5
+ status: number | null;
6
+ stdout: Buffer;
7
+ stderr: Buffer;
8
+ error?: Error;
9
+ };
10
+ export declare const runCommand: RunCommand;
11
+ /** 記録 Lambda に送る本文(記録 Lambda の入力。キーの順は現行と同じ) */
12
+ export interface SystemChangePayload {
13
+ kind: string;
14
+ result: 'started' | 'succeeded' | 'failed';
15
+ target: string;
16
+ commit: string;
17
+ actor: string;
18
+ details: Record<string, unknown>;
19
+ changeId?: string;
20
+ }
21
+ /**
22
+ * Python の `json.dumps(x, ensure_ascii=False)` と同じ文字列(区切りは `, ` と `: `)。
23
+ * 送る内容(--payload)と、RECORD_DRY_RUN の表示を、現行と一字一句同じにするため
24
+ */
25
+ export declare function pythonJsonDumps(value: unknown): string;
26
+ /**
27
+ * コミット(`git rev-parse --short HEAD`。取れなければ unknown)。dirtyPaths に未コミットの変更があれば `-dirty`
28
+ * (`git diff-index --quiet HEAD -- <paths>` が 0 以外。git が無い・失敗も -dirty。現行と同じ)
29
+ */
30
+ export declare function commitOf(repo: string | undefined, dirtyPaths: string[], run?: RunCommand): string;
31
+ /** 実行者(`$(whoami)@$(hostname -s)`) */
32
+ export declare function actorOf(): string;
33
+ export interface SendOptions {
34
+ functionName: string;
35
+ region: string;
36
+ dryRun: boolean;
37
+ /** 警告の出力先(標準エラー。バイト列も受ける) */
38
+ warn: (message: string | Buffer) => void;
39
+ run?: RunCommand;
40
+ }
41
+ /** 記録 Lambda を呼ぶ。失敗は警告だけ(例外にしない)。記録 Lambda が無い環境は黙ってスキップ */
42
+ export declare function sendSystemChange(payload: SystemChangePayload, options: SendOptions): void;
43
+ //# sourceMappingURL=send.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send.d.ts","sourceRoot":"","sources":["../../../src/tools/dynamodb-system-change/send.ts"],"names":[],"mappings":"AAUA,2BAA2B;AAC3B,MAAM,MAAM,UAAU,GAAG,CACvB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,KACvB;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC;AAE9E,eAAO,MAAM,UAAU,EAAE,UAQxB,CAAC;AAEF,gDAAgD;AAChD,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAOtD;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,GAAG,aAAa,GAAG,MAAM,CAOjG;AAED,sCAAsC;AACtC,wBAAgB,OAAO,IAAI,MAAM,CAEhC;AAED,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;IAChB,6BAA6B;IAC7B,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC;IACzC,GAAG,CAAC,EAAE,UAAU,CAAC;CAClB;AAYD,4DAA4D;AAC5D,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI,CAsCzF"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * 記録 Lambda(`<app>-<env>-system-change-recorder`)への送信(両アプリの terraform-wrapper.sh の record_change を
3
+ * 移植したもの)。
4
+ *
5
+ * 経路は現行のまま: AWS CLI の `aws lambda invoke`(認証は AWS CLI の認証情報。`AWS_PROFILE` などは環境変数で
6
+ * 引き継ぐ)。記録の失敗は、警告を出すだけで、例外にしない(apply・destroy を止めない)。
7
+ */
8
+ import { spawnSync } from 'node:child_process';
9
+ import { hostname, userInfo } from 'node:os';
10
+ export const runCommand = (command, args, options) => {
11
+ const r = spawnSync(command, args, { cwd: options?.cwd, stdio: ['ignore', 'pipe', 'pipe'] });
12
+ return {
13
+ status: r.status,
14
+ stdout: r.stdout ?? Buffer.alloc(0),
15
+ stderr: r.stderr ?? Buffer.alloc(0),
16
+ error: r.error,
17
+ };
18
+ };
19
+ /**
20
+ * Python の `json.dumps(x, ensure_ascii=False)` と同じ文字列(区切りは `, ` と `: `)。
21
+ * 送る内容(--payload)と、RECORD_DRY_RUN の表示を、現行と一字一句同じにするため
22
+ */
23
+ export function pythonJsonDumps(value) {
24
+ if (Array.isArray(value))
25
+ return `[${value.map(pythonJsonDumps).join(', ')}]`;
26
+ if (value !== null && typeof value === 'object') {
27
+ const entries = Object.entries(value).filter(([, v]) => v !== undefined);
28
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}: ${pythonJsonDumps(v)}`).join(', ')}}`;
29
+ }
30
+ return JSON.stringify(value);
31
+ }
32
+ /**
33
+ * コミット(`git rev-parse --short HEAD`。取れなければ unknown)。dirtyPaths に未コミットの変更があれば `-dirty`
34
+ * (`git diff-index --quiet HEAD -- <paths>` が 0 以外。git が無い・失敗も -dirty。現行と同じ)
35
+ */
36
+ export function commitOf(repo, dirtyPaths, run = runCommand) {
37
+ const head = run('git', ['rev-parse', '--short', 'HEAD'], { cwd: repo });
38
+ let commit = head.status === 0 && !head.error ? head.stdout.toString('utf8').replace(/\n+$/, '') : 'unknown';
39
+ const diff = run('git', ['diff-index', '--quiet', 'HEAD', '--', ...dirtyPaths], { cwd: repo });
40
+ if (diff.status !== 0 || diff.error)
41
+ commit = `${commit}-dirty`;
42
+ return commit;
43
+ }
44
+ /** 実行者(`$(whoami)@$(hostname -s)`) */
45
+ export function actorOf() {
46
+ return `${userInfo().username}@${hostname().split('.')[0]}`;
47
+ }
48
+ const PREFIX = '[change-record]';
49
+ const NL = Buffer.from('\n');
50
+ /** 末尾の改行(0x0a)を除く(bash の `$(...)` と同じ) */
51
+ function trimTrailingNewlines(buf) {
52
+ let end = buf.length;
53
+ while (end > 0 && buf[end - 1] === 0x0a)
54
+ end -= 1;
55
+ return buf.subarray(0, end);
56
+ }
57
+ /** 記録 Lambda を呼ぶ。失敗は警告だけ(例外にしない)。記録 Lambda が無い環境は黙ってスキップ */
58
+ export function sendSystemChange(payload, options) {
59
+ const body = pythonJsonDumps(payload);
60
+ if (options.dryRun) {
61
+ options.warn(`${PREFIX}[dry-run] ${options.functionName} <- ${body}\n`);
62
+ return;
63
+ }
64
+ const run = options.run ?? runCommand;
65
+ const r = run('aws', [
66
+ 'lambda',
67
+ 'invoke',
68
+ '--function-name',
69
+ options.functionName,
70
+ '--region',
71
+ options.region,
72
+ '--cli-binary-format',
73
+ 'raw-in-base64-out',
74
+ '--cli-connect-timeout',
75
+ '5',
76
+ '--cli-read-timeout',
77
+ '30',
78
+ '--payload',
79
+ body,
80
+ '/dev/null',
81
+ ]);
82
+ // 現行は `out="$(… 2>&1)"`(標準出力と標準エラーをまとめ、末尾の改行を除く)
83
+ const errText = r.error ? Buffer.from(`${r.error.message}\n`) : Buffer.alloc(0);
84
+ const out = trimTrailingNewlines(Buffer.concat([r.stdout, r.stderr, errText]));
85
+ const text = out.toString('utf8');
86
+ if (r.status !== 0 || r.error) {
87
+ if (text.includes('ResourceNotFoundException'))
88
+ return; // 記録 Lambda が無い環境は、スキップ
89
+ // 現行は `$(echo "$out" | head -c 200)`(バイトで 200。末尾の改行は $(...) が除く)
90
+ const head = trimTrailingNewlines(Buffer.concat([out, NL]).subarray(0, 200));
91
+ options.warn(Buffer.concat([Buffer.from(`${PREFIX} 記録に失敗しました(処理は続行): `), head, NL]));
92
+ }
93
+ else if (text.includes('"FunctionError"')) {
94
+ options.warn(`${PREFIX} 記録 Lambda がエラーを返しました(処理は続行)\n`);
95
+ }
96
+ }
97
+ //# sourceMappingURL=send.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send.js","sourceRoot":"","sources":["../../../src/tools/dynamodb-system-change/send.ts"],"names":[],"mappings":"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"}
@@ -104,10 +104,77 @@ export const handler = createSystemChangeHandler({
104
104
  - 管理画面の一覧・詳細は `SystemChangesList` / `SystemChangesShow`(`@exabugs/dynamodb-client/integrations/react-admin`。1.19.0。[react-admin-integration.md](react-admin-integration.md#運用の画面alertssystemchanges))
105
105
  - 投稿は notify の `postSlackText`(`createSlackChannel` と同じ送信。URL の接頭辞の確認、URL を出さないエラー)
106
106
 
107
+ ## 記録の CLI(`dynamodb-system-change`。1.20.0)
108
+
109
+ terraform の apply・destroy の開始と終了を、記録 Lambda(`<app>-<env>-system-change-recorder`)に送る CLI([ADR 0006](adr/0006-library-owns-table-operations.md) の段階 5 = (c))。asanowa・astro の `terraform-wrapper.sh` の「記録に送る部分」(開始・終了の呼び出し、`details` の組み立て、`parse-lambda-releases.py`)を Node にした。**bash のラッパーは各アプリに残し**(state のバックエンド、plan の保存と確認、環境の検査はアプリごとに違う)、記録だけをこの CLI に委ねる。
110
+
111
+ - 経路は従来のまま: AWS CLI の `aws lambda invoke`(`--cli-connect-timeout 5 --cli-read-timeout 30`。認証は AWS CLI の認証情報で、`AWS_PROFILE` などは環境変数で引き継ぐ)。送る本文は従来の Python の `json.dumps` と同じ文字列
112
+ - **記録の失敗で apply を止めない**: `start` / `finish` は、記録の失敗・引数の誤りでも終了コード 0(警告は標準エラーに `[change-record] …`)。記録 Lambda が無い環境(`ResourceNotFoundException`)は黙ってスキップ。サブコマンドの誤りだけ 2
113
+ - 受け渡し: `start` は変更 ID(ULID)を標準出力に 1 行で出す(記録の成否によらず。`SKIP_CHANGE_RECORD=1` では何も出さない)。ラッパーはシェルの変数に持ち、`finish --change-id` に渡す
114
+ - 環境変数は従来と同じ: `RECORD_DRY_RUN=1`(呼ばずに、送る内容を表示)、`SKIP_CHANGE_RECORD=1`(記録しない)、`AWS_REGION`(無ければ `ap-northeast-1`)
115
+
116
+ ```text
117
+ dynamodb-system-change start --app <app> --env <env> --command <cmd> [共通]
118
+ dynamodb-system-change finish --app <app> --env <env> --command <cmd> --exit-code <n> [--change-id <id>] [--log <file>] [共通]
119
+ 共通: [--repo <dir>] [--function <name>] [--target <target>] [--region <region>]
120
+ [--releases-max-items <n|none>] [--releases-max-bytes <n|none>] [--dirty-path <path>...]
121
+ ```
122
+
123
+ | 引数 | 内容 |
124
+ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
125
+ | `--app` / `--env` | `details.app` / `details.env`。既定の関数名 `<app>-<env>-system-change-recorder` と対象 `terraform:<app>` にも使う |
126
+ | `--command` | `details.command`(`apply` / `apply-auto` / `destroy` など)。`destroy` では Lambda のリリースを取り出さない |
127
+ | `--exit-code` | terraform の終了コード。0 は `succeeded`、それ以外は `failed` と `details.exit_code` |
128
+ | `--log` | plan / apply のログ。成功なら最後の完了の行から `add` / `change` / `destroy`、Lambda のリリースから `lambdaReleases` |
129
+ | `--repo` | git を実行するディレクトリ(既定: カレント)。`commit` は `git rev-parse --short HEAD`(取れなければ `unknown`) |
130
+ | `--dirty-path` | 未コミットの変更を調べる場所(`git diff-index --quiet HEAD -- <path>...`)。あれば `commit` に `-dirty`。最後に書く |
131
+ | `--releases-max-items` | `lambdaReleases` の件数の上限(既定 `none`) |
132
+ | `--releases-max-bytes` | `lambdaReleases` の JSON のバイト数の上限(既定 3000。details の上限 4096 に収める)。失敗を先に残し、省いた件数は `lambdaReleasesOmitted` |
133
+
134
+ `actor` は `<ユーザー名>@<ホスト名の先頭>`(従来の `$(whoami)@$(hostname -s)`)。
135
+
136
+ ### ラッパーからの呼び方
137
+
138
+ `set -euo pipefail` のラッパーでも止まらないよう、呼び出しには `|| true` を付ける(CLI が入っていない・node が無いときも apply を進める)。CLI が無いときは、記録が欠けたことに気づけるよう警告を出す。記録の部分は次のとおり(`$CHANGE_LOG` と `trap` の扱い、plan の確認はアプリのまま。`<…>` はアプリごとの値):
139
+
140
+ ```bash
141
+ SYSTEM_CHANGE="<node_modules の .bin>/dynamodb-system-change" # npx は標準出力に余計な行を出しうるので使わない
142
+ CHANGE_ARGS=(--app "<アプリ名>" --env "$ENV" --command "$COMMAND" [--repo "<git を実行する場所>"] --dirty-path <調べる場所>...)
143
+
144
+ begin_change() {
145
+ [ -x "$SYSTEM_CHANGE" ] || echo "[change-record] CLI がありません(記録をスキップ)" >&2
146
+ CHANGE_ID="$("$SYSTEM_CHANGE" start "${CHANGE_ARGS[@]}" || true)"
147
+ CHANGE_OPEN=1
148
+ }
149
+
150
+ finish_change() {
151
+ local rc=$?
152
+ trap - EXIT
153
+ if [ "$CHANGE_OPEN" = "1" ]; then
154
+ CHANGE_OPEN=0
155
+ "$SYSTEM_CHANGE" finish "${CHANGE_ARGS[@]}" --change-id "$CHANGE_ID" --exit-code "$rc" --log "$CHANGE_LOG" || true
156
+ fi
157
+ rm -f "$CHANGE_LOG"
158
+ return "$rc"
159
+ }
160
+ ```
161
+
162
+ アプリごとの `CHANGE_ARGS`(従来の記録と同じ commit・details になる。`lambdaReleases` の上限は、両アプリとも既定の 3000 バイトを使う):
163
+
164
+ - **asanowa**: `--app "$APP" --env "$ENV" --command "$COMMAND" --dirty-path "$INFRA_ROOT" "$INFRA_ROOT/../functions" "$INFRA_ROOT/../packages"`(`--repo` は不要。ラッパーが `cd` した先で git を実行する)
165
+ - **astro**: `--app "$PROJECT" --env "$ENV" --command "$COMMAND" --repo "$REPO_ROOT" --dirty-path infra apps functions packages`。上限は asanowa の既定(3000 バイト)に揃える(従来の「30 件まで」はやめる。件数の上限は付けない。64 文字の名前が並ぶと 30 件 → 約 27 件になり、超えた分は `lambdaReleasesOmitted` に残る)
166
+
167
+ `--releases-max-items` / `--releases-max-bytes` は、既定と違う上限が要るときだけ使う。
168
+
169
+ 置き換えた後は、`record_change`・`new_change_id`・`finish_change` の Python と `parse-lambda-releases.py` を消せる。両アプリの `test-terraform-wrapper.sh` は、偽の `aws` を PATH に置く方式なので、そのまま使える(1.20.0 の開発時に、両アプリの検査がこの形のラッパーで全て通ることを確かめた)。
170
+
171
+ 部品(`@exabugs/dynamodb-client/system-changes/cli`): `extractLambdaReleases(logText)`(`parse-lambda-releases.py` の移植。出力は同じ)、`buildFinishDetails` / `limitReleases` / `resourceCountsOf`、`main(argv, io, deps?)`。
172
+
107
173
  ## テスト
108
174
 
109
175
  - 単体: `src/system-changes/__tests__/`。ハンドラは、本物の `DynamoClient` に `fetch` だけを差し替えて固定する(`connect()` の呼び忘れを見逃さない)
110
176
  - 統合(DynamoDB Local。CI には入っていない): `src/system-changes/__tests__/system-changes.integration.test.ts`(実行方法は PUBLISHING.md)
177
+ - CLI: `__tests__/tools/dynamodb-system-change-*.test.ts`。Lambda のリリースの取り出しは、両アプリの本物のログ(`__tests__/tools/testdata/terraform-logs`。アカウント ID・URL・Cognito のユーザープール ID・ドメインは伏せた)で Python 版と同じ出力を固定する。送り方は偽の `aws` / `git`、止めないことは `set -euo pipefail` の bash のラッパーから CLI をプロセスとして呼んで確かめる
111
178
 
112
179
  ## アプリの移行(公開後の別作業)
113
180
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exabugs/dynamodb-client",
3
- "version": "1.19.0",
3
+ "version": "1.20.0",
4
4
  "description": "DynamoDB Single-Table Client SDK with MongoDB-like API, Shadow Records, and Lambda implementation for serverless applications",
5
5
  "author": "exabugs",
6
6
  "license": "MIT",
@@ -8,7 +8,8 @@
8
8
  "main": "./dist/index.js",
9
9
  "types": "./dist/index.d.ts",
10
10
  "bin": {
11
- "dynamodb-access-check": "./bin/dynamodb-access-check.js"
11
+ "dynamodb-access-check": "./bin/dynamodb-access-check.js",
12
+ "dynamodb-system-change": "./bin/dynamodb-system-change.js"
12
13
  },
13
14
  "keywords": [
14
15
  "dynamodb",
@@ -129,6 +130,10 @@
129
130
  "./system-changes": {
130
131
  "import": "./dist/system-changes/index.js",
131
132
  "types": "./dist/system-changes/index.d.ts"
133
+ },
134
+ "./system-changes/cli": {
135
+ "import": "./dist/tools/dynamodb-system-change/index.js",
136
+ "types": "./dist/tools/dynamodb-system-change/index.d.ts"
132
137
  }
133
138
  },
134
139
  "peerDependencies": {