@steve31415/baselib 3.30.0 → 3.31.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 CHANGED
@@ -29,7 +29,9 @@ ERROR log (`~/plasticine-way/docs/SECURITY.md`, "Resend API key").
29
29
 
30
30
  Bins: `check-test-owners` — the fleet's structural test-coverage gate; every
31
31
  app runs it from `npm run verify`. `pw-deploy` / `pw-rollback` — the fleet
32
- deploy (`~/plasticine-way/docs/OPERATIONS.md`, "Deployment safety").
32
+ deploy (`~/plasticine-way/docs/OPERATIONS.md`, "Deployment safety"); an
33
+ app's hooks module (`DeployHooks` in `src/deploy/types.ts`) extends both,
34
+ `postRollback` being the one hook `pw-rollback` runs.
33
35
  `pw-sql` / `pw-logs` — read-only production queries (database, Axiom), the
34
36
  first step of any production investigation (same doc, "Investigating
35
37
  production state").
@@ -7,7 +7,7 @@ import { hostname } from 'node:os';
7
7
  import { must, mustJson } from './exec.js';
8
8
  import { openEvidence, PhaseTimer } from './evidence.js';
9
9
  import { acquireLock, listReleaseRecords, placeHold, releaseLock } from './gcs.js';
10
- import { DEFAULT_ACCOUNT, loadConfig } from './config.js';
10
+ import { DEFAULT_ACCOUNT, loadConfig, loadHooks } from './config.js';
11
11
  import { pickRevisionForSha, releaseEntriesFrom, selectRollbackTarget, readServingState, SHA40, } from './plan.js';
12
12
  export async function runRollback(options) {
13
13
  const log = options.log ?? ((m) => console.error(`[pw-rollback] ${m}`));
@@ -19,6 +19,7 @@ export async function runRollback(options) {
19
19
  const requested = argv.find((a) => SHA40.test(a));
20
20
  const timer = new PhaseTimer(log);
21
21
  let evidenceDir = '';
22
+ let rolledBackTo = '';
22
23
  let lock = null;
23
24
  try {
24
25
  const config = await loadConfig(options.repoRoot);
@@ -151,6 +152,26 @@ export async function runRollback(options) {
151
152
  throw new Error(`${service.name}: post-rollback smoke failed (${lastDetail})`);
152
153
  log(`${service.name}: smoke passed`);
153
154
  }
155
+ // The hooks module is loaded only now: a broken one must not stand
156
+ // between an incident and the traffic move.
157
+ timer.enter('hooks');
158
+ rolledBackTo = target;
159
+ const hooks = await loadHooks(options.repoRoot, config);
160
+ if (hooks.postRollback) {
161
+ const ctx = {
162
+ sha: target,
163
+ baseSha: servingShas.length === 1 ? servingShas[0] : null,
164
+ config,
165
+ repoRoot: options.repoRoot,
166
+ contextDir: '',
167
+ evidenceDir,
168
+ imageDigest: targets[0].imageDigest,
169
+ serving,
170
+ exec: runner,
171
+ log,
172
+ };
173
+ await hooks.postRollback(ctx);
174
+ }
154
175
  timer.close();
155
176
  log(`rolled back to ${target.slice(0, 12)}`);
156
177
  log(`hold placed on ${servingShas.map((sha) => sha.slice(0, 12)).join(', ')} — ` +
@@ -162,6 +183,10 @@ export async function runRollback(options) {
162
183
  const message = error instanceof Error ? error.message : String(error);
163
184
  console.error(message);
164
185
  console.error(`rollback failed in phase: ${timer.current}`);
186
+ if (rolledBackTo) {
187
+ console.error(`traffic IS on ${rolledBackTo.slice(0, 12)} and the hold is placed; only the app's ` +
188
+ 'postRollback hook failed — finish what it does by hand');
189
+ }
165
190
  if (evidenceDir)
166
191
  console.error(`evidence: ${evidenceDir}`);
167
192
  return 1;
@@ -120,4 +120,11 @@ export interface DeployHooks {
120
120
  smoke?: (ctx: HookContext) => Promise<void>;
121
121
  /** Runs after everything else succeeded (e.g. notes's release state). */
122
122
  postSuccess?: (ctx: HookContext) => Promise<void>;
123
+ /** pw-rollback only: runs after traffic is back on the target build, the
124
+ * hold is placed and the smoke passed — for what a traffic move does not
125
+ * carry (e.g. podcasts' render Job image). `sha` and `imageDigest` are the
126
+ * rollback target's, `serving` is the state rolled back from, and
127
+ * `contextDir` is empty (nothing is built). A throw fails the command but
128
+ * undoes nothing. */
129
+ postRollback?: (ctx: HookContext) => Promise<void>;
123
130
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steve31415/baselib",
3
- "version": "3.30.0",
3
+ "version": "3.31.0",
4
4
  "description": "Plasticine new-world shared platform library: logging, auth, service-to-service auth, db, HTTP, sync, app updates",
5
5
  "type": "module",
6
6
  "license": "MIT",