ec2-instance-running-scheduler 0.2.6 → 0.3.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.
@@ -20305,21 +20305,92 @@ var require_lib = __commonJS({
20305
20305
  "node_modules/fetch-retrier/lib/index.js"(exports2) {
20306
20306
  "use strict";
20307
20307
  Object.defineProperty(exports2, "__esModule", { value: true });
20308
- exports2.fetchRetrier = void 0;
20308
+ exports2.fetchRetrier = exports2.FetchRetrierUnreachableError = exports2.FetchRetrierNetworkError = exports2.FetchRetrierHttpError = exports2.FetchRetrierAlreadyAbortedError = exports2.FetchRetrierAbortError = void 0;
20309
+ var FetchRetrierAbortError = class _FetchRetrierAbortError extends Error {
20310
+ /**
20311
+ * @param message - Human-readable reason (default: `'Aborted'`)
20312
+ */
20313
+ constructor(message = "Aborted") {
20314
+ super(message);
20315
+ this.name = "FetchRetrierAbortError";
20316
+ Object.setPrototypeOf(this, _FetchRetrierAbortError.prototype);
20317
+ }
20318
+ };
20319
+ exports2.FetchRetrierAbortError = FetchRetrierAbortError;
20320
+ var FetchRetrierAlreadyAbortedError = class _FetchRetrierAlreadyAbortedError extends FetchRetrierAbortError {
20321
+ /**
20322
+ * @param message - Human-readable reason (default: `'Signal was already aborted'`)
20323
+ */
20324
+ constructor(message = "Signal was already aborted") {
20325
+ super(message);
20326
+ this.name = "FetchRetrierAlreadyAbortedError";
20327
+ Object.setPrototypeOf(this, _FetchRetrierAlreadyAbortedError.prototype);
20328
+ }
20329
+ };
20330
+ exports2.FetchRetrierAlreadyAbortedError = FetchRetrierAlreadyAbortedError;
20331
+ var FetchRetrierHttpError = class _FetchRetrierHttpError extends Error {
20332
+ /**
20333
+ * @param message - Error description
20334
+ * @param status - HTTP status code from the last non-OK response
20335
+ */
20336
+ constructor(message, status) {
20337
+ super(message);
20338
+ this.status = status;
20339
+ this.name = "FetchRetrierHttpError";
20340
+ Object.setPrototypeOf(this, _FetchRetrierHttpError.prototype);
20341
+ }
20342
+ };
20343
+ exports2.FetchRetrierHttpError = FetchRetrierHttpError;
20344
+ var FetchRetrierNetworkError = class _FetchRetrierNetworkError extends Error {
20345
+ /**
20346
+ * @param message - Human-readable reason (default: `'Network error'`)
20347
+ * @param cause - Original error from the underlying `fetch`, when available
20348
+ */
20349
+ constructor(message = "Network error", cause) {
20350
+ super(message);
20351
+ this.cause = cause;
20352
+ this.name = "FetchRetrierNetworkError";
20353
+ Object.setPrototypeOf(this, _FetchRetrierNetworkError.prototype);
20354
+ }
20355
+ };
20356
+ exports2.FetchRetrierNetworkError = FetchRetrierNetworkError;
20357
+ var FetchRetrierUnreachableError = class _FetchRetrierUnreachableError extends Error {
20358
+ /**
20359
+ * @param message - Human-readable reason (default: `'Unreachable'`)
20360
+ */
20361
+ constructor(message = "Unreachable") {
20362
+ super(message);
20363
+ this.name = "FetchRetrierUnreachableError";
20364
+ Object.setPrototypeOf(this, _FetchRetrierUnreachableError.prototype);
20365
+ }
20366
+ };
20367
+ exports2.FetchRetrierUnreachableError = FetchRetrierUnreachableError;
20309
20368
  var defaultShouldRetry = (res) => {
20310
20369
  return [429, 500, 502, 503, 504].includes(res.status);
20311
20370
  };
20312
20371
  var fetchRetrier = async (url, options) => {
20313
- const { headers, retries, timeoutMs, baseBackoffMs, shouldRetry = defaultShouldRetry } = options;
20372
+ const { headers, init, retries, timeoutMs, baseBackoffMs, signal: externalSignal, shouldRetry = defaultShouldRetry } = options;
20314
20373
  for (let attempt = 1; attempt <= retries; attempt++) {
20374
+ if (externalSignal?.aborted) {
20375
+ throw new FetchRetrierAlreadyAbortedError();
20376
+ }
20315
20377
  const controller = new AbortController();
20316
20378
  const timer = setTimeout(() => controller.abort(), timeoutMs);
20379
+ const onExternalAbort = () => {
20380
+ clearTimeout(timer);
20381
+ controller.abort();
20382
+ };
20383
+ if (externalSignal) {
20384
+ externalSignal.addEventListener("abort", onExternalAbort);
20385
+ }
20317
20386
  try {
20318
20387
  const res = await fetch(url, {
20319
- headers,
20388
+ ...init,
20389
+ ...headers !== void 0 ? { headers } : {},
20320
20390
  signal: controller.signal
20321
20391
  });
20322
20392
  clearTimeout(timer);
20393
+ externalSignal?.removeEventListener("abort", onExternalAbort);
20323
20394
  if (res.ok) {
20324
20395
  return res;
20325
20396
  }
@@ -20327,30 +20398,31 @@ var require_lib = __commonJS({
20327
20398
  const isContinue = shouldRetry(res, text);
20328
20399
  if (isContinue) {
20329
20400
  if (attempt === retries) {
20330
- throw new Error(`HTTP ${res.status}`);
20401
+ throw new FetchRetrierHttpError(`HTTP ${res.status}`, res.status);
20331
20402
  }
20332
20403
  await wait(fullJitter(baseBackoffMs, attempt));
20333
20404
  } else {
20334
- throw new Error(`Non-retriable HTTP error: ${res.status}`);
20405
+ throw new FetchRetrierHttpError(`Non-retriable HTTP error: ${res.status}`, res.status);
20335
20406
  }
20336
20407
  } catch (err) {
20337
20408
  clearTimeout(timer);
20409
+ externalSignal?.removeEventListener("abort", onExternalAbort);
20338
20410
  if (err instanceof Error && err.name === "AbortError") {
20339
20411
  if (attempt === retries)
20340
- throw err;
20412
+ throw err instanceof FetchRetrierAbortError ? err : new FetchRetrierAbortError();
20341
20413
  await wait(fullJitter(baseBackoffMs, attempt));
20342
20414
  continue;
20343
20415
  }
20344
20416
  if (err instanceof TypeError) {
20345
20417
  if (attempt === retries)
20346
- throw err;
20418
+ throw new FetchRetrierNetworkError("Network error", err);
20347
20419
  await wait(fullJitter(baseBackoffMs, attempt));
20348
20420
  continue;
20349
20421
  }
20350
20422
  throw err;
20351
20423
  }
20352
20424
  }
20353
- throw new Error("Unreachable");
20425
+ throw new FetchRetrierUnreachableError();
20354
20426
  };
20355
20427
  exports2.fetchRetrier = fetchRetrier;
20356
20428
  var wait = (ms) => {
@@ -20363,87 +20435,32 @@ var require_lib = __commonJS({
20363
20435
  }
20364
20436
  });
20365
20437
 
20366
- // node_modules/aws-lambda-secret-fetcher/lib/index.js
20438
+ // node_modules/strict-env-resolver/lib/index.js
20367
20439
  var require_lib2 = __commonJS({
20368
- "node_modules/aws-lambda-secret-fetcher/lib/index.js"(exports2) {
20440
+ "node_modules/strict-env-resolver/lib/index.js"(exports2) {
20369
20441
  "use strict";
20370
20442
  Object.defineProperty(exports2, "__esModule", { value: true });
20371
- exports2.secretFetcher = void 0;
20372
- var fetch_retrier_1 = require_lib();
20373
- var getSecretValue = async (name, options = {}) => {
20374
- const { timeoutMs = 2e3, retries = 3, baseBackoffMs = 300 } = options;
20375
- const url = `http://localhost:2773/secretsmanager/get?secretId=${encodeURIComponent(name)}`;
20376
- const requestOptions = {
20377
- headers: {
20378
- "X-Aws-Parameters-Secrets-Token": process.env.AWS_SESSION_TOKEN ?? ""
20379
- },
20380
- retries,
20381
- timeoutMs,
20382
- baseBackoffMs,
20383
- shouldRetry: (res, body) => {
20384
- if ([429, 500, 502, 503, 504].includes(res.status))
20385
- return true;
20386
- if (res.status === 400 && /not\s+ready.*traffic/i.test(body))
20387
- return true;
20388
- return false;
20389
- }
20390
- };
20391
- const response = await (0, fetch_retrier_1.fetchRetrier)(url, requestOptions);
20392
- const raw = await response.json();
20393
- if (!isSecretResponse(raw)) {
20394
- throw new Error("Invalid secret response format");
20395
- }
20396
- const data = raw;
20397
- const secretString = data.SecretString;
20398
- if (looksLikeJson(secretString)) {
20399
- return JSON.parse(secretString);
20400
- }
20401
- return secretString;
20402
- };
20403
- var isSecretResponse = (value) => {
20404
- if (typeof value !== "object" || value === null)
20405
- return false;
20406
- const v = value;
20407
- return typeof v.SecretString === "string" && typeof v.Name === "string" && typeof v.ARN === "string" && (v.VersionId === void 0 || typeof v.VersionId === "string");
20408
- };
20409
- var looksLikeJson = (str) => {
20410
- return typeof str === "string" && str.trim().startsWith("{");
20411
- };
20412
- exports2.secretFetcher = {
20413
- getSecretValue
20414
- };
20415
- }
20416
- });
20417
-
20418
- // node_modules/safe-env-getter/lib/index.js
20419
- var require_lib3 = __commonJS({
20420
- "node_modules/safe-env-getter/lib/index.js"(exports2) {
20421
- "use strict";
20422
- Object.defineProperty(exports2, "__esModule", { value: true });
20423
- exports2.SafeEnvGetter = exports2.SafeEnvType = exports2.SafeEnvGetterValidationError = exports2.SafeEnvGetterError = void 0;
20424
- var STRICT_INTEGER_PATTERN = /^-?\d+$/;
20425
- var parseStrictInteger = (raw) => {
20426
- const trimmed = raw.trim();
20427
- if (trimmed === "" || !STRICT_INTEGER_PATTERN.test(trimmed)) {
20428
- return void 0;
20429
- }
20430
- const n = Number(trimmed);
20431
- if (!Number.isFinite(n) || !Number.isInteger(n)) {
20443
+ exports2.StrictEnvResolver = exports2.StrictEnvType = exports2.StrictEnvValidationError = exports2.StrictEnvError = void 0;
20444
+ var TRUE_BOOLEAN_PATTERN = /^(1|true|yes|on)$/i;
20445
+ var parseNumber = (raw) => {
20446
+ const n = Number(raw);
20447
+ if (!Number.isFinite(n)) {
20432
20448
  return void 0;
20433
20449
  }
20434
20450
  return n;
20435
20451
  };
20436
- var SafeEnvGetterError = class extends Error {
20452
+ var parseBooleanEnvValue = (normalizedRaw) => TRUE_BOOLEAN_PATTERN.test(normalizedRaw);
20453
+ var StrictEnvError = class extends Error {
20437
20454
  /**
20438
20455
  * @param message - Error message.
20439
20456
  */
20440
20457
  constructor(message) {
20441
20458
  super(message);
20442
- this.name = "SafeEnvGetterError";
20459
+ this.name = "StrictEnvError";
20443
20460
  }
20444
20461
  };
20445
- exports2.SafeEnvGetterError = SafeEnvGetterError;
20446
- var SafeEnvGetterValidationError = class _SafeEnvGetterValidationError extends SafeEnvGetterError {
20462
+ exports2.StrictEnvError = StrictEnvError;
20463
+ var StrictEnvValidationError = class _StrictEnvValidationError extends StrictEnvError {
20447
20464
  /**
20448
20465
  * Formats validation errors into a human-readable error message.
20449
20466
  *
@@ -20457,126 +20474,206 @@ var require_lib3 = __commonJS({
20457
20474
  ${lines.join("\n")}`;
20458
20475
  }
20459
20476
  /**
20460
- * Creates a new validation error from one or more `SafeEnvError` entries.
20477
+ * Creates a new validation error from one or more `StrictEnvValidationEntry` entries.
20461
20478
  *
20462
20479
  * @param errors - One or more structured validation errors.
20463
20480
  */
20464
20481
  constructor(errors) {
20465
- const msg = _SafeEnvGetterValidationError.format(errors);
20482
+ const msg = _StrictEnvValidationError.format(errors);
20466
20483
  super(msg);
20467
- this.name = "SafeEnvGetterValidationError";
20484
+ this.name = "StrictEnvValidationError";
20468
20485
  this.errors = errors;
20469
20486
  this.keys = errors.map((e) => e.key);
20470
20487
  }
20471
20488
  };
20472
- exports2.SafeEnvGetterValidationError = SafeEnvGetterValidationError;
20473
- exports2.SafeEnvType = {
20474
- /** Spec for a string value (returned as-is). */
20489
+ exports2.StrictEnvValidationError = StrictEnvValidationError;
20490
+ exports2.StrictEnvType = {
20491
+ /** Spec for a string value (returned as-is unless `trim: true` is set in options). */
20475
20492
  String: { type: "string" },
20476
20493
  /**
20477
- * Spec for a strict decimal integer.
20478
- * Rejects whitespace-only, hex, `Infinity`, `NaN`, and non-integer forms.
20494
+ * Spec for a finite numeric value (`Number()` parsing; rejects `NaN` and `Infinity`).
20479
20495
  */
20480
20496
  Number: { type: "number" },
20481
- /** Spec for a boolean value (1/true/yes/on → true). */
20497
+ /** Spec for a boolean value (`1`/`true`/`yes`/`on``true`; other non-empty values → `false`). */
20482
20498
  Boolean: { type: "boolean" },
20483
20499
  /**
20484
20500
  * Returns a spec that restricts the value to one of the given choices.
20501
+ *
20485
20502
  * @param choices - Allowed string literals.
20486
- * @returns Enum spec for use with `getEnv`.
20503
+ * @returns Enum spec for use with `resolve` or `resolveAll`.
20487
20504
  */
20488
20505
  Enum: (choices) => ({ type: "enum", choices })
20489
20506
  };
20490
- function getEnv(key, spec = exports2.SafeEnvType.String, options) {
20491
- const raw = process.env[key];
20492
- const defaultValue = options?.default;
20507
+ var defaultTrimForSpec = (spec) => spec.type !== "string";
20508
+ var normalizeEnvRaw = (raw, trim) => {
20509
+ if (raw == null) {
20510
+ return void 0;
20511
+ }
20512
+ if (!trim) {
20513
+ return raw;
20514
+ }
20515
+ return raw.trim();
20516
+ };
20517
+ var resolveSchemaEntry = (entry) => ({
20518
+ spec: Array.isArray(entry) ? entry[0] : entry,
20519
+ options: Array.isArray(entry) ? entry[1] : void 0
20520
+ });
20521
+ var parseEnvValue = (key, spec, raw, defaultValue, options) => {
20522
+ const trim = options?.trim ?? defaultTrimForSpec(spec);
20523
+ const normalizedRaw = normalizeEnvRaw(raw, trim);
20493
20524
  const hasDefault = defaultValue !== void 0;
20494
- if (raw == null || raw === "") {
20495
- if (!hasDefault) {
20496
- throw new SafeEnvGetterValidationError([
20497
- { key, message: `Missing required environment variable: ${key}`, raw, kind: "missing" }
20498
- ]);
20525
+ if (normalizedRaw == null || normalizedRaw === "") {
20526
+ if (hasDefault) {
20527
+ return { ok: true, value: defaultValue };
20499
20528
  }
20500
- return defaultValue;
20529
+ return {
20530
+ ok: false,
20531
+ error: { key, message: `Missing required environment variable: ${key}`, raw, kind: "missing" }
20532
+ };
20501
20533
  }
20502
20534
  switch (spec.type) {
20503
20535
  case "number": {
20504
- const n = parseStrictInteger(raw);
20536
+ const n = parseNumber(normalizedRaw);
20505
20537
  if (n === void 0) {
20506
- throw new SafeEnvGetterValidationError([
20507
- { key, message: `Env ${key}: expected number, got "${raw}"`, raw, kind: "invalid_number" }
20508
- ]);
20538
+ return {
20539
+ ok: false,
20540
+ error: { key, message: `Env ${key}: expected number, got "${raw}"`, raw, kind: "invalid_number" }
20541
+ };
20509
20542
  }
20510
- return n;
20543
+ return { ok: true, value: n };
20511
20544
  }
20512
20545
  case "boolean":
20513
- return /^(1|true|yes|on)$/i.test(raw) ? true : false;
20546
+ return { ok: true, value: parseBooleanEnvValue(normalizedRaw) };
20514
20547
  case "enum":
20515
- if (!spec.choices.includes(raw)) {
20516
- throw new SafeEnvGetterValidationError([
20517
- { key, message: `Env ${key}: must be one of [${spec.choices.join(", ")}]`, raw, kind: "invalid_enum" }
20518
- ]);
20548
+ if (!spec.choices.includes(normalizedRaw)) {
20549
+ return {
20550
+ ok: false,
20551
+ error: {
20552
+ key,
20553
+ message: `Env ${key}: must be one of [${spec.choices.join(", ")}]`,
20554
+ raw,
20555
+ kind: "invalid_enum"
20556
+ }
20557
+ };
20519
20558
  }
20520
- return raw;
20559
+ return { ok: true, value: normalizedRaw };
20521
20560
  default:
20522
- return raw;
20561
+ return { ok: true, value: normalizedRaw };
20523
20562
  }
20524
- }
20525
- var getEnvs = (schema) => {
20563
+ };
20564
+ var resolve = (key, spec = exports2.StrictEnvType.String, options) => {
20565
+ const result = parseEnvValue(key, spec, process.env[key], options?.default, options);
20566
+ if (!result.ok) {
20567
+ throw new StrictEnvValidationError([result.error]);
20568
+ }
20569
+ return result.value;
20570
+ };
20571
+ var resolveAll = (schema) => {
20526
20572
  const envs = {};
20527
20573
  const errors = [];
20528
20574
  for (const key of Object.keys(schema)) {
20529
- const entry = schema[key];
20530
- const spec = Array.isArray(entry) ? entry[0] : entry;
20531
- const options = Array.isArray(entry) ? entry[1] : void 0;
20532
- const raw = process.env[key];
20533
- const defaultValue = options?.default;
20534
- const hasDefault = defaultValue !== void 0;
20535
- if (raw == null || raw === "") {
20536
- if (!hasDefault) {
20537
- errors.push({ key, message: `Missing required environment variable: ${key}`, raw, kind: "missing" });
20538
- continue;
20539
- }
20540
- envs[key] = defaultValue;
20541
- continue;
20542
- }
20543
- if (spec.type === "number") {
20544
- const n = parseStrictInteger(raw);
20545
- if (n === void 0) {
20546
- errors.push({ key, message: `Env ${key}: expected number, got "${raw}"`, raw, kind: "invalid_number" });
20547
- continue;
20548
- }
20549
- envs[key] = n;
20550
- continue;
20551
- }
20552
- if (spec.type === "boolean") {
20553
- envs[key] = /^(1|true|yes|on)$/i.test(raw) ? true : false;
20554
- continue;
20555
- }
20556
- if (spec.type === "enum") {
20557
- if (!spec.choices.includes(raw)) {
20558
- errors.push({
20559
- key,
20560
- message: `Env ${key}: must be one of [${spec.choices.join(", ")}]`,
20561
- raw,
20562
- kind: "invalid_enum"
20563
- });
20564
- continue;
20565
- }
20566
- envs[key] = raw;
20575
+ const { spec, options } = resolveSchemaEntry(schema[key]);
20576
+ const result = parseEnvValue(key, spec, process.env[key], options?.default, options);
20577
+ if (!result.ok) {
20578
+ errors.push(result.error);
20567
20579
  continue;
20568
20580
  }
20569
- envs[key] = raw;
20581
+ envs[key] = result.value;
20570
20582
  }
20571
20583
  if (errors.length > 0)
20572
- throw new SafeEnvGetterValidationError(errors);
20584
+ throw new StrictEnvValidationError(errors);
20573
20585
  return envs;
20574
20586
  };
20575
- exports2.SafeEnvGetter = {
20576
- /** Reads and parses a single environment variable. See {@link getEnv}. */
20577
- getEnv,
20578
- /** Reads and parses multiple environment variables. See {@link getEnvs}. */
20579
- getEnvs
20587
+ exports2.StrictEnvResolver = {
20588
+ /** Reads and parses a single environment variable. See {@link resolve}. */
20589
+ resolve,
20590
+ /** Reads and parses multiple environment variables in one pass. See {@link resolveAll}. */
20591
+ resolveAll
20592
+ };
20593
+ }
20594
+ });
20595
+
20596
+ // node_modules/aws-lambda-secret-fetcher/lib/index.js
20597
+ var require_lib3 = __commonJS({
20598
+ "node_modules/aws-lambda-secret-fetcher/lib/index.js"(exports2) {
20599
+ "use strict";
20600
+ Object.defineProperty(exports2, "__esModule", { value: true });
20601
+ exports2.secretFetcher = void 0;
20602
+ var fetch_retrier_1 = require_lib();
20603
+ var strict_env_resolver_1 = require_lib2();
20604
+ var getSecretValue = async (name, options = {}) => {
20605
+ const { extensionHttpPort, timeoutMs = 2e3, retries = 3, baseBackoffMs = 300 } = options;
20606
+ const port = resolveExtensionHttpPort(extensionHttpPort);
20607
+ const sessionToken = resolveAwsSessionToken();
20608
+ const url = `http://localhost:${port}/secretsmanager/get?secretId=${encodeURIComponent(name)}`;
20609
+ const requestOptions = {
20610
+ headers: {
20611
+ "X-Aws-Parameters-Secrets-Token": sessionToken
20612
+ },
20613
+ retries,
20614
+ timeoutMs,
20615
+ baseBackoffMs,
20616
+ shouldRetry: (res, body) => {
20617
+ if ([429, 500, 502, 503, 504].includes(res.status))
20618
+ return true;
20619
+ if (res.status === 400 && /not\s+ready.*traffic/i.test(body))
20620
+ return true;
20621
+ return false;
20622
+ }
20623
+ };
20624
+ const response = await (0, fetch_retrier_1.fetchRetrier)(url, requestOptions);
20625
+ const raw = await response.json();
20626
+ if (!isSecretResponse(raw)) {
20627
+ throw new Error("Invalid secret response format");
20628
+ }
20629
+ const data = raw;
20630
+ const secretString = data.SecretString;
20631
+ if (looksLikeJson(secretString)) {
20632
+ return JSON.parse(secretString);
20633
+ }
20634
+ return secretString;
20635
+ };
20636
+ var AWS_SESSION_TOKEN_GUIDANCE = "AWS_SESSION_TOKEN is not set. This library only works inside an AWS Lambda execution environment where the runtime provides AWS_SESSION_TOKEN for the Parameters and Secrets Extension. Attach the extension layer and run your code as a Lambda function handler.";
20637
+ var MIN_TCP_PORT = 1;
20638
+ var MAX_TCP_PORT = 65535;
20639
+ var resolveAwsSessionToken = () => {
20640
+ try {
20641
+ return strict_env_resolver_1.StrictEnvResolver.resolve("AWS_SESSION_TOKEN", strict_env_resolver_1.StrictEnvType.String, { trim: true });
20642
+ } catch (e) {
20643
+ if (e instanceof strict_env_resolver_1.StrictEnvValidationError && e.errors.length === 1 && e.errors[0]?.key === "AWS_SESSION_TOKEN" && e.errors[0]?.kind === "missing") {
20644
+ throw new Error(AWS_SESSION_TOKEN_GUIDANCE);
20645
+ }
20646
+ throw e;
20647
+ }
20648
+ };
20649
+ var resolveExtensionHttpPort = (overridePort) => {
20650
+ const port = overridePort === void 0 ? strict_env_resolver_1.StrictEnvResolver.resolve("PARAMETERS_SECRETS_EXTENSION_HTTP_PORT", strict_env_resolver_1.StrictEnvType.Number, { default: 2773 }) : parseExtensionHttpPortOverride(overridePort);
20651
+ return assertValidTcpPort(port);
20652
+ };
20653
+ var parseExtensionHttpPortOverride = (overridePort) => {
20654
+ const candidate = String(overridePort).trim();
20655
+ if (!/^\d+$/.test(candidate)) {
20656
+ throw new Error("Invalid extension HTTP port: must be a number");
20657
+ }
20658
+ return Number.parseInt(candidate, 10);
20659
+ };
20660
+ var assertValidTcpPort = (port) => {
20661
+ if (port < MIN_TCP_PORT || port > MAX_TCP_PORT) {
20662
+ throw new Error("Invalid extension HTTP port: must be between 1 and 65535");
20663
+ }
20664
+ return String(port);
20665
+ };
20666
+ var isSecretResponse = (value) => {
20667
+ if (typeof value !== "object" || value === null)
20668
+ return false;
20669
+ const v = value;
20670
+ return typeof v.SecretString === "string" && typeof v.Name === "string" && typeof v.ARN === "string" && (v.VersionId === void 0 || typeof v.VersionId === "string");
20671
+ };
20672
+ var looksLikeJson = (str) => {
20673
+ return typeof str === "string" && str.trim().startsWith("{");
20674
+ };
20675
+ exports2.secretFetcher = {
20676
+ getSecretValue
20580
20677
  };
20581
20678
  }
20582
20679
  });
@@ -24106,24 +24203,17 @@ var DEFAULT_CONFIG = {
24106
24203
  var import_client_ec2 = require("@aws-sdk/client-ec2");
24107
24204
  var import_client_resource_groups_tagging_api = require("@aws-sdk/client-resource-groups-tagging-api");
24108
24205
  var import_web_api = __toESM(require_dist5());
24109
- var import_aws_lambda_secret_fetcher = __toESM(require_lib2());
24110
- var import_safe_env_getter2 = __toESM(require_lib3());
24111
-
24112
- // src/funcs/running-scheduler-polling-env.ts
24113
- var import_safe_env_getter = __toESM(require_lib3());
24114
-
24115
- // src/funcs/running-scheduler-polling-config.ts
24116
- var PROCESS_RESOURCE_MAX_LOOP_COUNT_ENV = "PROCESS_RESOURCE_MAX_LOOP_COUNT";
24117
- var PROCESS_RESOURCE_MAX_ELAPSED_SECONDS_ENV = "PROCESS_RESOURCE_MAX_ELAPSED_SECONDS";
24206
+ var import_aws_lambda_secret_fetcher = __toESM(require_lib3());
24207
+ var import_strict_env_resolver2 = __toESM(require_lib2());
24118
24208
 
24119
24209
  // src/funcs/running-scheduler-predicates.ts
24120
- var DEFAULT_RESOURCE_POLLING_LIMITS = {
24210
+ var DEFAULT_RESOURCE_WAIT_LIMITS = {
24121
24211
  maxLoopCount: 90,
24122
24212
  maxElapsedSeconds: 1800
24123
24213
  };
24124
24214
  var isDesiredStableState = (mode, currentState) => mode === "Start" && currentState === "running" || mode === "Stop" && currentState === "stopped";
24125
24215
  var isTransitioningState = (mode, currentState) => mode === "Start" && currentState === "pending" || mode === "Stop" && (currentState === "stopping" || currentState === "shutting-down");
24126
- var getPollingAbortReason = (loopCount, startedAtMs, nowMs, limits) => {
24216
+ var getWaitAbortReason = (loopCount, startedAtMs, nowMs, limits) => {
24127
24217
  if (loopCount >= limits.maxLoopCount) {
24128
24218
  return "MaxLoopCountExceeded";
24129
24219
  }
@@ -24133,18 +24223,25 @@ var getPollingAbortReason = (loopCount, startedAtMs, nowMs, limits) => {
24133
24223
  }
24134
24224
  return void 0;
24135
24225
  };
24136
- var formatResourcePollingFailure = (reason, context) => {
24226
+ var formatResourceWaitFailure = (reason, context) => {
24137
24227
  const common = `identifier=${context.identifier} mode=${context.mode} currentState=${context.currentState} loopCount=${context.loopCount}`;
24138
24228
  if (reason === "MaxLoopCountExceeded") {
24139
- return `ResourcePollingFailed:MaxLoopCountExceeded: ${common} maxLoopCount=${context.limits.maxLoopCount}`;
24229
+ return `ResourceWaitFailed:MaxLoopCountExceeded: ${common} maxLoopCount=${context.limits.maxLoopCount}`;
24140
24230
  }
24141
24231
  if (reason === "MaxElapsedTimeExceeded") {
24142
- return `ResourcePollingFailed:MaxElapsedTimeExceeded: ${common} maxElapsedSeconds=${context.limits.maxElapsedSeconds}`;
24232
+ return `ResourceWaitFailed:MaxElapsedTimeExceeded: ${common} maxElapsedSeconds=${context.limits.maxElapsedSeconds}`;
24143
24233
  }
24144
- return `ResourcePollingFailed:UnexpectedInstanceState: ${common}`;
24234
+ return `ResourceWaitFailed:UnexpectedInstanceState: ${common}`;
24145
24235
  };
24146
24236
 
24147
- // src/funcs/running-scheduler-polling-env.ts
24237
+ // src/funcs/running-scheduler-wait-env.ts
24238
+ var import_strict_env_resolver = __toESM(require_lib2());
24239
+
24240
+ // src/funcs/running-scheduler-wait-config.ts
24241
+ var PROCESS_RESOURCE_MAX_LOOP_COUNT_ENV = "PROCESS_RESOURCE_MAX_LOOP_COUNT";
24242
+ var PROCESS_RESOURCE_MAX_ELAPSED_SECONDS_ENV = "PROCESS_RESOURCE_MAX_ELAPSED_SECONDS";
24243
+
24244
+ // src/funcs/running-scheduler-wait-env.ts
24148
24245
  var assertPositiveEnvInt = (key, value) => {
24149
24246
  if (value <= 0) {
24150
24247
  const raw = process.env[key];
@@ -24152,15 +24249,15 @@ var assertPositiveEnvInt = (key, value) => {
24152
24249
  }
24153
24250
  return value;
24154
24251
  };
24155
- var parseResourcePollingLimitsFromEnv = () => {
24156
- const parsed = import_safe_env_getter.SafeEnvGetter.getEnvs({
24252
+ var parseResourceWaitLimitsFromEnv = () => {
24253
+ const parsed = import_strict_env_resolver.StrictEnvResolver.resolveAll({
24157
24254
  [PROCESS_RESOURCE_MAX_LOOP_COUNT_ENV]: [
24158
- import_safe_env_getter.SafeEnvType.Number,
24159
- { default: DEFAULT_RESOURCE_POLLING_LIMITS.maxLoopCount }
24255
+ import_strict_env_resolver.StrictEnvType.Number,
24256
+ { default: DEFAULT_RESOURCE_WAIT_LIMITS.maxLoopCount }
24160
24257
  ],
24161
24258
  [PROCESS_RESOURCE_MAX_ELAPSED_SECONDS_ENV]: [
24162
- import_safe_env_getter.SafeEnvType.Number,
24163
- { default: DEFAULT_RESOURCE_POLLING_LIMITS.maxElapsedSeconds }
24259
+ import_strict_env_resolver.StrictEnvType.Number,
24260
+ { default: DEFAULT_RESOURCE_WAIT_LIMITS.maxElapsedSeconds }
24164
24261
  ]
24165
24262
  });
24166
24263
  return {
@@ -24181,11 +24278,16 @@ var STATE_LIST = [
24181
24278
  { name: "STOPPED", emoji: "\u{1F634}", state: "stopped" }
24182
24279
  ];
24183
24280
  var STATUS_CHANGE_WAIT_SECONDS = 20;
24281
+ var SLACK_SECRET_FETCH_OPTIONS = {
24282
+ timeoutMs: 3e3,
24283
+ retries: 5,
24284
+ baseBackoffMs: 300
24285
+ };
24184
24286
  var getStateDisplay = (current) => {
24185
24287
  const found = STATE_LIST.find((s) => s.state === current);
24186
24288
  return found ? { emoji: found.emoji, name: found.name } : void 0;
24187
24289
  };
24188
- var processOneResource = async (ctx, targetResource, params, resourceIndex, pollingLimits) => {
24290
+ var processOneResource = async (ctx, targetResource, params, resourceIndex, waitLimits) => {
24189
24291
  const parts = targetResource.split("/");
24190
24292
  const identifier = parts[parts.length - 1] ?? "unknown";
24191
24293
  const arnParts = targetResource.split(":");
@@ -24198,32 +24300,32 @@ var processOneResource = async (ctx, targetResource, params, resourceIndex, poll
24198
24300
  region,
24199
24301
  account,
24200
24302
  mode: params.Mode,
24201
- maxLoopCount: pollingLimits.maxLoopCount,
24202
- maxElapsedSeconds: pollingLimits.maxElapsedSeconds
24303
+ maxLoopCount: waitLimits.maxLoopCount,
24304
+ maxElapsedSeconds: waitLimits.maxElapsedSeconds
24203
24305
  });
24204
- const startedAtMs = await ctx.step(`${stepPrefix}-polling-started-at`, async () => Date.now());
24306
+ const startedAtMs = await ctx.step(`${stepPrefix}-wait-started-at`, async () => Date.now());
24205
24307
  let loopCount = 0;
24206
24308
  let currentState = "";
24207
24309
  do {
24208
24310
  const abortReason = await ctx.step(
24209
- `${stepPrefix}-poll-limit-check-${loopCount}`,
24210
- async () => getPollingAbortReason(loopCount, startedAtMs, Date.now(), pollingLimits)
24311
+ `${stepPrefix}-wait-limit-check-${loopCount}`,
24312
+ async () => getWaitAbortReason(loopCount, startedAtMs, Date.now(), waitLimits)
24211
24313
  );
24212
24314
  if (abortReason) {
24213
- const message = formatResourcePollingFailure(abortReason, {
24315
+ const message = formatResourceWaitFailure(abortReason, {
24214
24316
  identifier,
24215
24317
  mode: params.Mode,
24216
24318
  currentState: currentState || "unknown",
24217
24319
  loopCount,
24218
- limits: pollingLimits
24320
+ limits: waitLimits
24219
24321
  });
24220
- ctx.logger.error("processOneResource: polling limit exceeded", {
24322
+ ctx.logger.error("processOneResource: wait limit exceeded", {
24221
24323
  identifier,
24222
24324
  abortReason,
24223
24325
  loopCount,
24224
24326
  currentState,
24225
- maxLoopCount: pollingLimits.maxLoopCount,
24226
- maxElapsedSeconds: pollingLimits.maxElapsedSeconds
24327
+ maxLoopCount: waitLimits.maxLoopCount,
24328
+ maxElapsedSeconds: waitLimits.maxElapsedSeconds
24227
24329
  });
24228
24330
  throw new Error(message);
24229
24331
  }
@@ -24280,12 +24382,12 @@ var processOneResource = async (ctx, targetResource, params, resourceIndex, poll
24280
24382
  loopCount += 1;
24281
24383
  continue;
24282
24384
  }
24283
- const message = formatResourcePollingFailure("UnexpectedInstanceState", {
24385
+ const message = formatResourceWaitFailure("UnexpectedInstanceState", {
24284
24386
  identifier,
24285
24387
  mode,
24286
24388
  currentState,
24287
24389
  loopCount,
24288
- limits: pollingLimits
24390
+ limits: waitLimits
24289
24391
  });
24290
24392
  ctx.logger.error("processOneResource: unexpected state", {
24291
24393
  identifier,
@@ -24320,11 +24422,11 @@ var handler = withDurableExecution(async (event, ctx) => {
24320
24422
  if (!params?.TagKey || !params?.TagValues || !params?.Mode) {
24321
24423
  throw new Error("Invalid event: Params.TagKey, Params.TagValues, Params.Mode are required.");
24322
24424
  }
24323
- const pollingLimits = parseResourcePollingLimitsFromEnv();
24324
- const slackSecretName = import_safe_env_getter2.SafeEnvGetter.getEnv("SLACK_SECRET_NAME");
24425
+ const waitLimits = parseResourceWaitLimitsFromEnv();
24426
+ const slackSecretName = import_strict_env_resolver2.StrictEnvResolver.resolve("SLACK_SECRET_NAME", import_strict_env_resolver2.StrictEnvType.String);
24325
24427
  const slackSecretValue = await ctx.step("fetch-slack-secret", async () => {
24326
24428
  ctx.logger.info("running-scheduler: fetching Slack secret", { secretName: slackSecretName });
24327
- return import_aws_lambda_secret_fetcher.secretFetcher.getSecretValue(slackSecretName);
24429
+ return import_aws_lambda_secret_fetcher.secretFetcher.getSecretValue(slackSecretName, SLACK_SECRET_FETCH_OPTIONS);
24328
24430
  });
24329
24431
  ctx.logger.info("running-scheduler: Slack secret loaded");
24330
24432
  if (!slackSecretValue?.token || !slackSecretValue?.channel) {
@@ -24352,10 +24454,19 @@ var handler = withDurableExecution(async (event, ctx) => {
24352
24454
  instanceCount: targetResources.length
24353
24455
  });
24354
24456
  const slackParentMessageResult = await ctx.step("post-slack-messages", async () => {
24355
- return client.chat.postMessage({
24356
- channel,
24357
- text: `${params.Mode === "Start" ? "\u{1F606} Starts" : "\u{1F971} Stops"} the scheduled EC2 Instance.`
24358
- });
24457
+ try {
24458
+ return await client.chat.postMessage({
24459
+ channel,
24460
+ text: `${params.Mode === "Start" ? "\u{1F606} Starts" : "\u{1F971} Stops"} the scheduled EC2 Instance.`
24461
+ });
24462
+ } catch (error) {
24463
+ ctx.logger.error("running-scheduler: Slack post failed", {
24464
+ step: "post-slack-messages",
24465
+ channel,
24466
+ errorMessage: error instanceof Error ? error.message : String(error)
24467
+ });
24468
+ throw error;
24469
+ }
24359
24470
  });
24360
24471
  ctx.logger.info("running-scheduler: parent Slack message posted", {
24361
24472
  threadTs: slackParentMessageResult?.ts ?? null
@@ -24372,7 +24483,7 @@ var handler = withDurableExecution(async (event, ctx) => {
24372
24483
  // ),
24373
24484
  async (mapCtx, targetResource, index) => {
24374
24485
  return mapCtx.runInChildContext(`resource-${index}`, async (childCtx) => {
24375
- const result = await processOneResource(childCtx, targetResource, params, index, pollingLimits);
24486
+ const result = await processOneResource(childCtx, targetResource, params, index, waitLimits);
24376
24487
  childCtx.logger.info("running-scheduler: posting thread Slack message", {
24377
24488
  index,
24378
24489
  identifier: result.identifier,
@@ -24380,22 +24491,32 @@ var handler = withDurableExecution(async (event, ctx) => {
24380
24491
  });
24381
24492
  await childCtx.step("post-slack-child-messages", async () => {
24382
24493
  const display = getStateDisplay(result.status);
24383
- return client.chat.postMessage({
24384
- channel,
24385
- thread_ts: slackParentMessageResult?.ts,
24386
- attachments: [
24387
- {
24388
- color: "#36a64f",
24389
- pretext: `${display?.emoji} The status of the EC2 Instance ${result.identifier} changed to ${display?.name} due to the schedule.`,
24390
- fields: [
24391
- { title: "Account", value: result.account, short: true },
24392
- { title: "Region", value: result.region, short: true },
24393
- { title: "Identifier", value: result.identifier, short: true },
24394
- { title: "Status", value: display?.name ?? "Unknown", short: true }
24395
- ]
24396
- }
24397
- ]
24398
- });
24494
+ try {
24495
+ return await client.chat.postMessage({
24496
+ channel,
24497
+ thread_ts: slackParentMessageResult?.ts,
24498
+ attachments: [
24499
+ {
24500
+ color: "#36a64f",
24501
+ pretext: `${display?.emoji} The status of the EC2 Instance ${result.identifier} changed to ${display?.name} due to the schedule.`,
24502
+ fields: [
24503
+ { title: "Account", value: result.account, short: true },
24504
+ { title: "Region", value: result.region, short: true },
24505
+ { title: "Identifier", value: result.identifier, short: true },
24506
+ { title: "Status", value: display?.name ?? "Unknown", short: true }
24507
+ ]
24508
+ }
24509
+ ]
24510
+ });
24511
+ } catch (error) {
24512
+ childCtx.logger.error("running-scheduler: Slack post failed", {
24513
+ step: "post-slack-child-messages",
24514
+ channel,
24515
+ identifier: result.identifier,
24516
+ errorMessage: error instanceof Error ? error.message : String(error)
24517
+ });
24518
+ throw error;
24519
+ }
24399
24520
  });
24400
24521
  return result;
24401
24522
  });