@awsless/cli 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -6,60 +6,39 @@ var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- function __accessProp(key) {
10
- return this[key];
11
- }
12
- var __toESMCache_node;
13
- var __toESMCache_esm;
14
9
  var __toESM = (mod, isNodeMode, target) => {
15
- var canCache = mod != null && typeof mod === "object";
16
- if (canCache) {
17
- var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
18
- var cached = cache.get(mod);
19
- if (cached)
20
- return cached;
21
- }
22
10
  target = mod != null ? __create(__getProtoOf(mod)) : {};
23
11
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
24
12
  for (let key of __getOwnPropNames(mod))
25
13
  if (!__hasOwnProp.call(to, key))
26
14
  __defProp(to, key, {
27
- get: __accessProp.bind(mod, key),
15
+ get: () => mod[key],
28
16
  enumerable: true
29
17
  });
30
- if (canCache)
31
- cache.set(mod, to);
32
18
  return to;
33
19
  };
20
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
34
21
  var __toCommonJS = (from) => {
35
- var entry = (__moduleCache ??= new WeakMap).get(from), desc;
22
+ var entry = __moduleCache.get(from), desc;
36
23
  if (entry)
37
24
  return entry;
38
25
  entry = __defProp({}, "__esModule", { value: true });
39
- if (from && typeof from === "object" || typeof from === "function") {
40
- for (var key of __getOwnPropNames(from))
41
- if (!__hasOwnProp.call(entry, key))
42
- __defProp(entry, key, {
43
- get: __accessProp.bind(from, key),
44
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
45
- });
46
- }
26
+ if (from && typeof from === "object" || typeof from === "function")
27
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
28
+ get: () => from[key],
29
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
30
+ }));
47
31
  __moduleCache.set(from, entry);
48
32
  return entry;
49
33
  };
50
- var __moduleCache;
51
34
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
52
- var __returnValue = (v) => v;
53
- function __exportSetter(name, newValue) {
54
- this[name] = __returnValue.bind(null, newValue);
55
- }
56
35
  var __export = (target, all) => {
57
36
  for (var name in all)
58
37
  __defProp(target, name, {
59
38
  get: all[name],
60
39
  enumerable: true,
61
40
  configurable: true,
62
- set: __exportSetter.bind(all, name)
41
+ set: (newValue) => all[name] = () => newValue
63
42
  });
64
43
  };
65
44
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -853,7 +832,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
853
832
  this._exitCallback = (err) => {
854
833
  if (err.code !== "commander.executeSubCommandAsync") {
855
834
  throw err;
856
- }
835
+ } else {}
857
836
  };
858
837
  }
859
838
  return this;
@@ -3090,7 +3069,7 @@ var require_cell = __commonJS((exports, module) => {
3090
3069
  let content = utils.truncate(this.content, 10, this.truncate);
3091
3070
  if (!lineNum) {
3092
3071
  info(`${this.y}-${this.x}: ${this.rowSpan - lineNum}x${this.colSpan} Cell ${content}`);
3093
- }
3072
+ } else {}
3094
3073
  let padLen = Math.max(this.height - this.lines.length, 0);
3095
3074
  let padTop;
3096
3075
  switch (this.vAlign) {
@@ -26859,7 +26838,7 @@ var require_node_gyp_build_optional_packages = __commonJS((exports, module) => {
26859
26838
 
26860
26839
  // ../../node_modules/.pnpm/msgpackr-extract@3.0.4/node_modules/msgpackr-extract/index.js
26861
26840
  var require_msgpackr_extract = __commonJS((exports, module) => {
26862
- var __dirname = "/Users/work/Code/awsless/node_modules/.pnpm/msgpackr-extract@3.0.4/node_modules/msgpackr-extract";
26841
+ var __dirname = "/Users/ivandasilveira/Sites/@awsless/node_modules/.pnpm/msgpackr-extract@3.0.4/node_modules/msgpackr-extract";
26863
26842
  module.exports = require_node_gyp_build_optional_packages()(__dirname);
26864
26843
  });
26865
26844
 
@@ -39727,7 +39706,7 @@ var require_src3 = __commonJS((exports) => {
39727
39706
 
39728
39707
  // ../../node_modules/.pnpm/@grpc+grpc-js@1.12.6/node_modules/@grpc/grpc-js/build/src/channelz.js
39729
39708
  var require_channelz = __commonJS((exports) => {
39730
- var __dirname = "/Users/work/Code/awsless/node_modules/.pnpm/@grpc+grpc-js@1.12.6/node_modules/@grpc/grpc-js/build/src";
39709
+ var __dirname = "/Users/ivandasilveira/Sites/@awsless/node_modules/.pnpm/@grpc+grpc-js@1.12.6/node_modules/@grpc/grpc-js/build/src";
39731
39710
  Object.defineProperty(exports, "__esModule", { value: true });
39732
39711
  exports.registerChannelzSocket = exports.registerChannelzServer = exports.registerChannelzSubchannel = exports.registerChannelzChannel = exports.ChannelzCallTrackerStub = exports.ChannelzCallTracker = exports.ChannelzChildrenTrackerStub = exports.ChannelzChildrenTracker = exports.ChannelzTrace = exports.ChannelzTraceStub = undefined;
39733
39712
  exports.unregisterChannelzRef = unregisterChannelzRef;
@@ -57231,7 +57210,7 @@ var require_zipEntries = __commonJS((exports, module) => {
57231
57210
  if (this.centralDirRecords !== this.files.length) {
57232
57211
  if (this.centralDirRecords !== 0 && this.files.length === 0) {
57233
57212
  throw new Error("Corrupted zip or bug: expected " + this.centralDirRecords + " records in central dir, got " + this.files.length);
57234
- }
57213
+ } else {}
57235
57214
  }
57236
57215
  },
57237
57216
  readEndOfCentral: function() {
@@ -74452,7 +74431,7 @@ WD9f
74452
74431
 
74453
74432
  // ../../node_modules/.pnpm/ioredis@5.10.1_supports-color@8.1.1/node_modules/ioredis/built/utils/index.js
74454
74433
  var require_utils5 = __commonJS((exports) => {
74455
- var __dirname = "/Users/work/Code/awsless/node_modules/.pnpm/ioredis@5.10.1_supports-color@8.1.1/node_modules/ioredis/built/utils";
74434
+ var __dirname = "/Users/ivandasilveira/Sites/@awsless/node_modules/.pnpm/ioredis@5.10.1_supports-color@8.1.1/node_modules/ioredis/built/utils";
74456
74435
  Object.defineProperty(exports, "__esModule", { value: true });
74457
74436
  exports.noop = exports.defaults = exports.Debug = exports.getPackageMeta = exports.zipMap = exports.CONNECTION_CLOSED_ERROR_MSG = exports.shuffle = exports.sample = exports.resolveTLSProfile = exports.parseURL = exports.optimizeErrorStack = exports.toArg = exports.convertMapToArray = exports.convertObjectToArray = exports.timeout = exports.packObject = exports.isInt = exports.wrapMultiResult = exports.convertBufferToString = undefined;
74458
74437
  var fs_1 = __require("fs");
@@ -74738,7 +74717,7 @@ var require_argumentParsers = __commonJS((exports) => {
74738
74717
 
74739
74718
  // ../../node_modules/.pnpm/ioredis@5.10.1_supports-color@8.1.1/node_modules/ioredis/built/Command.js
74740
74719
  var require_Command = __commonJS((exports) => {
74741
- var __dirname = "/Users/work/Code/awsless/node_modules/.pnpm/ioredis@5.10.1_supports-color@8.1.1/node_modules/ioredis/built";
74720
+ var __dirname = "/Users/ivandasilveira/Sites/@awsless/node_modules/.pnpm/ioredis@5.10.1_supports-color@8.1.1/node_modules/ioredis/built";
74742
74721
  Object.defineProperty(exports, "__esModule", { value: true });
74743
74722
  var commands_1 = require_built();
74744
74723
  var calculateSlot = require_lib4();
@@ -95385,7 +95364,7 @@ var require_db_util = __commonJS((exports) => {
95385
95364
 
95386
95365
  // ../../node_modules/.pnpm/redis-memory-server@0.16.1_supports-color@8.1.1/node_modules/redis-memory-server/lib/util/RedisInstance.js
95387
95366
  var require_RedisInstance = __commonJS((exports) => {
95388
- var __dirname = "/Users/work/Code/awsless/node_modules/.pnpm/redis-memory-server@0.16.1_supports-color@8.1.1/node_modules/redis-memory-server/lib/util";
95367
+ var __dirname = "/Users/ivandasilveira/Sites/@awsless/node_modules/.pnpm/redis-memory-server@0.16.1_supports-color@8.1.1/node_modules/redis-memory-server/lib/util";
95389
95368
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P3, generator) {
95390
95369
  function adopt(value) {
95391
95370
  return value instanceof P3 ? value : new P3(function(resolve3) {
@@ -99153,15 +99132,15 @@ var require_pack = __commonJS((exports, module) => {
99153
99132
  process.nextTick(callback);
99154
99133
  return new Void;
99155
99134
  }
99156
- var sink2 = new Sink(this);
99157
- this._stream = sink2;
99158
- eos(sink2, function(err) {
99135
+ var sink3 = new Sink(this);
99136
+ this._stream = sink3;
99137
+ eos(sink3, function(err) {
99159
99138
  self2._stream = null;
99160
99139
  if (err) {
99161
99140
  self2.destroy();
99162
99141
  return callback(err);
99163
99142
  }
99164
- if (sink2.written !== header.size) {
99143
+ if (sink3.written !== header.size) {
99165
99144
  self2.destroy();
99166
99145
  return callback(new Error("size mismatch"));
99167
99146
  }
@@ -99170,7 +99149,7 @@ var require_pack = __commonJS((exports, module) => {
99170
99149
  self2.finalize();
99171
99150
  callback();
99172
99151
  });
99173
- return sink2;
99152
+ return sink3;
99174
99153
  };
99175
99154
  Pack.prototype.finalize = function() {
99176
99155
  if (this._stream) {
@@ -101299,7 +101278,7 @@ var require_unbzip2_stream = __commonJS((exports, module) => {
101299
101278
  stream2.queue(d5);
101300
101279
  if (d5 !== null) {
101301
101280
  outlength += d5.length;
101302
- }
101281
+ } else {}
101303
101282
  });
101304
101283
  } catch (e4) {
101305
101284
  stream2.emit("error", e4);
@@ -103168,7 +103147,15 @@ var import_decompress, import_sleep_await, mock, searchClient = (options = {}, s
103168
103147
  return Object.entries(settings).map(([key, value]) => {
103169
103148
  return ["-E", `${key}=${value}`];
103170
103149
  }).flat();
103171
- }, launch = ({ path: path6, host, port, version: version2, debug: debug4 }) => {
103150
+ }, launch = ({
103151
+ path: path6,
103152
+ host,
103153
+ port,
103154
+ version: version2,
103155
+ debug: debug4,
103156
+ onExit: onDied,
103157
+ onOutput
103158
+ }) => {
103172
103159
  return new Promise(async (resolve22, reject) => {
103173
103160
  const cache3 = join32(path6, "cache", String(port));
103174
103161
  const cleanUp = async () => {
@@ -103206,7 +103193,9 @@ ${output.join("")}`);
103206
103193
  done();
103207
103194
  }
103208
103195
  };
103196
+ let stopping = false;
103209
103197
  const kill = async () => {
103198
+ stopping = true;
103210
103199
  if (child.exitCode === null && !child.killed) {
103211
103200
  await new Promise((resolve32) => {
103212
103201
  child.once(`exit`, () => {
@@ -103235,6 +103224,23 @@ ${output.join("")}`);
103235
103224
  };
103236
103225
  const done = async () => {
103237
103226
  off();
103227
+ child.once("exit", (code, signal) => {
103228
+ if (!stopping) {
103229
+ onDied?.(code, signal);
103230
+ }
103231
+ });
103232
+ if (onOutput) {
103233
+ const capture = (chunk3) => {
103234
+ for (const line of chunk3.toString().split(`
103235
+ `)) {
103236
+ if (line.trim() !== "") {
103237
+ onOutput(line);
103238
+ }
103239
+ }
103240
+ };
103241
+ child.stdout.on("data", capture);
103242
+ child.stderr.on("data", capture);
103243
+ }
103238
103244
  resolve22(kill);
103239
103245
  };
103240
103246
  const fail = async (error3) => {
@@ -108402,7 +108408,6 @@ import { join, normalize, relative } from "path";
108402
108408
  var root = process.cwd();
108403
108409
  var directories = {
108404
108410
  root,
108405
- buildFolder: "build",
108406
108411
  get output() {
108407
108412
  return join(this.root, ".awsless");
108408
108413
  },
@@ -108413,7 +108418,7 @@ var directories = {
108413
108418
  return join(this.output, "state");
108414
108419
  },
108415
108420
  get build() {
108416
- return join(this.output, this.buildFolder);
108421
+ return join(this.output, "build");
108417
108422
  },
108418
108423
  get types() {
108419
108424
  return join(this.output, "types");
@@ -108428,9 +108433,6 @@ var directories = {
108428
108433
  var setRoot = (path = root) => {
108429
108434
  directories.root = path;
108430
108435
  };
108431
- var useDevBuildDir = () => {
108432
- directories.buildFolder = "build-dev";
108433
- };
108434
108436
  var findRootDir = async (path, configFiles, level = 5) => {
108435
108437
  if (!level) {
108436
108438
  throw new TypeError("No awsless project found");
@@ -108467,6 +108469,10 @@ var clearDebugLog = () => {
108467
108469
  ready = true;
108468
108470
  } catch (_) {}
108469
108471
  };
108472
+ var sink;
108473
+ var setDebugSink = (listener) => {
108474
+ sink = listener;
108475
+ };
108470
108476
  var write = (type, message) => {
108471
108477
  try {
108472
108478
  if (!ready) {
@@ -108474,6 +108480,7 @@ var write = (type, message) => {
108474
108480
  }
108475
108481
  appendFileSync(debugLogFile, `${new Date().toISOString()} [${type}] ${message}
108476
108482
  `);
108483
+ sink?.(type, message);
108477
108484
  } catch (_) {}
108478
108485
  };
108479
108486
  var format = (parts) => {
@@ -114483,7 +114490,7 @@ var getSanitizedDevTypeScriptVersion = (version2 = "") => {
114483
114490
  };
114484
114491
 
114485
114492
  // ../../node_modules/.pnpm/@aws-sdk+util-user-agent-node@3.973.14_aws-crt@1.30.0_supports-color@8.1.1_/node_modules/@aws-sdk/util-user-agent-node/dist-es/getTypeScriptUserAgentPair.js
114486
- var __dirname = "/Users/work/Code/awsless/node_modules/.pnpm/@aws-sdk+util-user-agent-node@3.973.14_aws-crt@1.30.0_supports-color@8.1.1_/node_modules/@aws-sdk/util-user-agent-node/dist-es";
114493
+ var __dirname = "/Users/ivandasilveira/Sites/@awsless/node_modules/.pnpm/@aws-sdk+util-user-agent-node@3.973.14_aws-crt@1.30.0_supports-color@8.1.1_/node_modules/@aws-sdk/util-user-agent-node/dist-es";
114487
114494
  var tscVersion;
114488
114495
  var TS_PACKAGE_JSON = join7("node_modules", "typescript", "package.json");
114489
114496
  var getTypeScriptUserAgentPair = async () => {
@@ -131560,17 +131567,6 @@ var $forEachNodeInTopologicalOrder = sort.forEachNodeInTopologicalOrder;
131560
131567
  var $topologicalSort = sort.topologicalSort;
131561
131568
  var $topologicalGenerations = sort.topologicalGenerations;
131562
131569
  var $forEachTopologicalGeneration = sort.forEachTopologicalGeneration;
131563
- // ../../node_modules/.pnpm/uuid@11.1.0/node_modules/uuid/dist/esm/sha1.js
131564
- import { createHash as createHash3 } from "crypto";
131565
- function sha1(bytes) {
131566
- if (Array.isArray(bytes)) {
131567
- bytes = Buffer.from(bytes);
131568
- } else if (typeof bytes === "string") {
131569
- bytes = Buffer.from(bytes, "utf8");
131570
- }
131571
- return createHash3("sha1").update(bytes).digest();
131572
- }
131573
- var sha1_default = sha1;
131574
131570
 
131575
131571
  // ../../node_modules/.pnpm/uuid@11.1.0/node_modules/uuid/dist/esm/regex.js
131576
131572
  var regex_default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/i;
@@ -131636,6 +131632,18 @@ function v35(version2, hash, value, namespace, buf, offset) {
131636
131632
  return unsafeStringify(bytes);
131637
131633
  }
131638
131634
 
131635
+ // ../../node_modules/.pnpm/uuid@11.1.0/node_modules/uuid/dist/esm/sha1.js
131636
+ import { createHash as createHash3 } from "crypto";
131637
+ function sha1(bytes) {
131638
+ if (Array.isArray(bytes)) {
131639
+ bytes = Buffer.from(bytes);
131640
+ } else if (typeof bytes === "string") {
131641
+ bytes = Buffer.from(bytes, "utf8");
131642
+ }
131643
+ return createHash3("sha1").update(bytes).digest();
131644
+ }
131645
+ var sha1_default = sha1;
131646
+
131639
131647
  // ../../node_modules/.pnpm/uuid@11.1.0/node_modules/uuid/dist/esm/v5.js
131640
131648
  function v5(value, namespace, buf, offset) {
131641
131649
  return v35(80, sha1_default, value, namespace, buf, offset);
@@ -132327,13 +132335,13 @@ var consoleSink = (group4, ...args) => {
132327
132335
  console.log(`${group4}:`, ...args);
132328
132336
  console.log();
132329
132337
  };
132330
- var sink;
132338
+ var sink2;
132331
132339
  var enableDebug = (customSink) => {
132332
- sink = customSink ?? consoleSink;
132340
+ sink2 = customSink ?? consoleSink;
132333
132341
  };
132334
132342
  var createDebugger = (group4) => {
132335
132343
  return (...args) => {
132336
- sink?.(group4, ...args);
132344
+ sink2?.(group4, ...args);
132337
132345
  };
132338
132346
  };
132339
132347
  var AlreadyLockedError = class extends Error {
@@ -141101,6 +141109,8 @@ if (!nativeAccelerationDisabled) {
141101
141109
  try {
141102
141110
  if (true)
141103
141111
  extractor = require_msgpackr_extract();
141112
+ else
141113
+ ;
141104
141114
  if (extractor)
141105
141115
  setExtractor(extractor.extractStrings);
141106
141116
  } catch (error3) {}
@@ -143961,14 +143971,6 @@ var createOpenSearchProvider = ({ credentials: credentials2, region }) => {
143961
143971
  async updateResource(props) {
143962
143972
  return apply(props.proposedState);
143963
143973
  },
143964
- async planResourceChange(props) {
143965
- const prior = props.priorState;
143966
- const proposed = props.proposedState;
143967
- return {
143968
- state: props.proposedState,
143969
- requiresReplacement: !!prior && (prior.endpoint !== proposed?.endpoint || prior.index !== proposed?.index)
143970
- };
143971
- },
143972
143974
  async deleteResource(props) {
143973
143975
  const state = inputSchema.parse(props.state);
143974
143976
  await getClient(state.endpoint).indices.delete({ index: state.index }, { ignore: [404] });
@@ -151138,7 +151140,7 @@ var JobDefaultSchema = exports_external.object({
151138
151140
 
151139
151141
  // src/feature/search/schema.ts
151140
151142
  var VersionSchema = exports_external.union([
151141
- exports_external.enum(["3.7", "3.5", "3.3", "3.1", "2.19", "2.17", "2.15", "2.13", "2.11", "2.9", "2.7", "2.5", "2.3", "1.3"]),
151143
+ exports_external.enum(["2.13", "2.11", "2.9", "2.7", "2.5", "2.3", "1.3"]),
151142
151144
  exports_external.string()
151143
151145
  ]).describe("Specify the OpenSearch engine version.");
151144
151146
  var TypeSchema = exports_external.union([
@@ -151180,7 +151182,7 @@ var StorageSizeSchema = SizeSchema.refine(sizeMin(gibibytes(10)), "Minimum stora
151180
151182
  var SearchDefaultSchema = exports_external.object({
151181
151183
  type: TypeSchema.default("t3.small"),
151182
151184
  count: CountSchema.default(1),
151183
- version: VersionSchema.default("3.7"),
151185
+ version: VersionSchema.default("2.13"),
151184
151186
  storage: StorageSizeSchema.default("10 GB")
151185
151187
  }).strict().default({}).describe("Configure the shared OpenSearch domain that backs every search index in your app.");
151186
151188
  var IndexNameSchema = exports_external.string().regex(/^[a-z0-9][a-z0-9\-_.]*$/, 'Index names must be lowercase & start with a letter or number, like "users" or "game-events".');
@@ -154054,6 +154056,7 @@ var __export3 = (target2, all5) => {
154054
154056
  var RedisServer = class {
154055
154057
  client;
154056
154058
  process;
154059
+ stopping = false;
154057
154060
  async start(port, version2 = "7.2.4", args = []) {
154058
154061
  if (this.process) {
154059
154062
  throw new Error(`Redis server is already listening on port: ${await this.process.getPort()}`);
@@ -154061,6 +154064,7 @@ var RedisServer = class {
154061
154064
  if (port && (port < 0 || port >= 65536)) {
154062
154065
  throw new RangeError(`Port should be >= 0 and < 65536. Received ${port}.`);
154063
154066
  }
154067
+ this.stopping = false;
154064
154068
  this.process = await import_redis_memory_server.RedisMemoryServer.create({
154065
154069
  instance: {
154066
154070
  port,
@@ -154069,8 +154073,30 @@ var RedisServer = class {
154069
154073
  binary: { version: version2 }
154070
154074
  });
154071
154075
  }
154076
+ onExit(handler) {
154077
+ const child = this.process?.instanceInfoSync?.childProcess;
154078
+ child?.once("exit", (code, signal) => {
154079
+ if (!this.stopping) {
154080
+ handler(code, signal);
154081
+ }
154082
+ });
154083
+ }
154084
+ onOutput(handler) {
154085
+ const child = this.process?.instanceInfoSync?.childProcess;
154086
+ const capture = (chunk3) => {
154087
+ for (const line of chunk3.toString().split(`
154088
+ `)) {
154089
+ if (line.trim() !== "") {
154090
+ handler(line);
154091
+ }
154092
+ }
154093
+ };
154094
+ child?.stdout?.on("data", capture);
154095
+ child?.stderr?.on("data", capture);
154096
+ }
154072
154097
  async kill() {
154073
154098
  if (this.process) {
154099
+ this.stopping = true;
154074
154100
  await this.client?.disconnect();
154075
154101
  await this.process.stop();
154076
154102
  this.process = undefined;
@@ -154852,12 +154878,31 @@ var cacheOnDev = async (ctx) => {
154852
154878
  return;
154853
154879
  }
154854
154880
  for (const { stackName, id } of caches) {
154855
- const port = await ctx.keep(`cache:${stackName}:${id}`, null, async () => {
154881
+ const { port, sink: sink3 } = await ctx.keep(`cache:${stackName}:${id}`, null, async () => {
154856
154882
  const server = new RedisServer;
154857
154883
  await server.start();
154858
154884
  await server.ping();
154859
- return { value: await server.getPort(), stop: () => server.kill() };
154885
+ const sink4 = { tail: [] };
154886
+ server.onExit((code, signal) => {
154887
+ sink4.crashed = code !== null ? `exited with code ${code}` : `killed by ${signal}`;
154888
+ sink4.health?.("down", sink4.crashed);
154889
+ });
154890
+ server.onOutput((line) => {
154891
+ sink4.tail.push(line);
154892
+ while (sink4.tail.length > 20) {
154893
+ sink4.tail.shift();
154894
+ }
154895
+ sink4.log?.(line);
154896
+ });
154897
+ return { value: { port: await server.getPort(), sink: sink4 }, stop: () => server.kill() };
154860
154898
  });
154899
+ const channel = `cache:${stackName}:${id}`;
154900
+ sink3.health = (status2, detail) => ctx.reportHealth(`cache ${stackName}/${id}`, status2, detail);
154901
+ sink3.health(sink3.crashed ? "down" : "up", sink3.crashed);
154902
+ sink3.log = (line) => ctx.emitEvent(channel, { date: Date.now(), line });
154903
+ for (const line of sink3.tail) {
154904
+ ctx.emitEvent(channel, { date: Date.now(), line });
154905
+ }
154861
154906
  const prefix = `CACHE_${constantCase(stackName)}_${constantCase(id)}`;
154862
154907
  ctx.addEnv(`${prefix}_HOST`, "localhost");
154863
154908
  ctx.addEnv(`${prefix}_PORT`, String(port));
@@ -154867,7 +154912,8 @@ var cacheOnDev = async (ctx) => {
154867
154912
  kind: "cache",
154868
154913
  stack: stackName,
154869
154914
  id,
154870
- detail: `localhost:${port}`
154915
+ detail: `localhost:${port}`,
154916
+ channel
154871
154917
  });
154872
154918
  }
154873
154919
  };
@@ -155655,8 +155701,7 @@ var createDnsValidatedCertificate = (group4, id, props) => {
155655
155701
  }, {
155656
155702
  ...props.dependsOn ? { dependsOn: props.dependsOn } : {},
155657
155703
  ...props.provider ? { provider: props.provider } : {},
155658
- replaceOnChanges: ["certificateArn", "validationRecordFqdns"],
155659
- createBeforeReplace: true
155704
+ replaceOnChanges: ["certificateArn", "validationRecordFqdns"]
155660
155705
  });
155661
155706
  return validation;
155662
155707
  };
@@ -157300,13 +157345,18 @@ var parseQueryPublish = (body) => {
157300
157345
  MessageAttributes: attributes
157301
157346
  };
157302
157347
  };
157303
- var createSnsServer = () => {
157348
+ var createSnsServer = (props) => {
157304
157349
  let server;
157305
157350
  let closeServer;
157306
157351
  let dispatch;
157307
157352
  let reportFailure;
157308
157353
  const publish2 = (input) => {
157309
157354
  const messageId = randomUUID4();
157355
+ for (const capture of props?.captures ?? []) {
157356
+ if (capture(input)) {
157357
+ return messageId;
157358
+ }
157359
+ }
157310
157360
  const attributes = {};
157311
157361
  for (const [name, attribute] of Object.entries(input.MessageAttributes ?? {})) {
157312
157362
  attributes[name] = {
@@ -157414,22 +157464,29 @@ var pubsubOnDev = async (ctx) => {
157414
157464
  declared.add(event);
157415
157465
  }
157416
157466
  }
157417
- const { redisPort, sns, snsPort, wsPort } = await ctx.keep(`pubsub-core:${id}`, null, async () => {
157467
+ const { redisPort, sns, snsPort, wsPort, redisSink } = await ctx.keep(`pubsub-core:${id}`, null, async () => {
157418
157468
  const redis = new RedisServer;
157419
157469
  await redis.start();
157420
157470
  await redis.ping();
157471
+ const redisSink2 = {};
157472
+ redis.onExit((code, signal) => {
157473
+ redisSink2.crashed = code !== null ? `exited with code ${code}` : `killed by ${signal}`;
157474
+ redisSink2.health?.("down", redisSink2.crashed);
157475
+ });
157421
157476
  const redisPort2 = await redis.getPort();
157422
157477
  const sns2 = createSnsServer();
157423
157478
  const snsPort2 = await sns2.listen();
157424
157479
  const wsPort2 = await findFreePort();
157425
157480
  return {
157426
- value: { redisPort: redisPort2, sns: sns2, snsPort: snsPort2, wsPort: wsPort2 },
157481
+ value: { redisPort: redisPort2, sns: sns2, snsPort: snsPort2, wsPort: wsPort2, redisSink: redisSink2 },
157427
157482
  stop: async () => {
157428
157483
  await sns2.stop();
157429
157484
  await redis.kill();
157430
157485
  }
157431
157486
  };
157432
157487
  });
157488
+ redisSink.health = (status2, detail) => ctx.reportHealth(`pubsub ${id} redis`, status2, detail);
157489
+ redisSink.health(redisSink.crashed ? "down" : "up", redisSink.crashed);
157433
157490
  ctx.retain(`pubsub-ws:${id}`);
157434
157491
  const channel = `pubsub:${id}`;
157435
157492
  const publisherRoute = formatRouteKey("base", "pubsub", `${id}-publisher`);
@@ -157511,7 +157568,10 @@ var pubsubOnDev = async (ctx) => {
157511
157568
  }
157512
157569
  return dispatch(event);
157513
157570
  }, reportFailure);
157514
- await ctx.keep(`pubsub-ws:${id}`, { wsPort, redisPort, snsPort }, async () => {
157571
+ const ws = await ctx.keep(`pubsub-ws:${id}`, { wsPort, redisPort, snsPort }, async () => {
157572
+ const sink3 = {
157573
+ stopping: false
157574
+ };
157515
157575
  const runtime = join24(dirname12(fileURLToPath8(import.meta.url)), "handlers", "pubsub-server.js");
157516
157576
  const child = spawn2(process.execPath, [runtime], {
157517
157577
  stdio: ["ignore", "ignore", "pipe"],
@@ -157536,9 +157596,23 @@ var pubsubOnDev = async (ctx) => {
157536
157596
  }
157537
157597
  });
157538
157598
  child.stderr?.on("data", (chunk3) => process.stderr.write(chunk3));
157599
+ child.on("exit", (code, signal) => {
157600
+ if (!sink3.stopping) {
157601
+ sink3.health?.("down", code !== null ? `exited with code ${code}` : `killed by ${signal}`);
157602
+ }
157603
+ });
157539
157604
  await waitForHealth(wsPort, 30000);
157540
- return { value: child, stop: () => stopChild(child) };
157605
+ return {
157606
+ value: { child, sink: sink3 },
157607
+ stop: async () => {
157608
+ sink3.stopping = true;
157609
+ await stopChild(child);
157610
+ }
157611
+ };
157541
157612
  });
157613
+ ws.sink.health = (status2, detail) => ctx.reportHealth(`pubsub ${id}`, status2, detail);
157614
+ ws.sink.stopping = false;
157615
+ ws.sink.health(ws.child.exitCode === null ? "up" : "down");
157542
157616
  },
157543
157617
  async stop() {
157544
157618
  feed?.disconnect();
@@ -158756,19 +158830,37 @@ var searchOnDev = async (ctx) => {
158756
158830
  if (indexes.length === 0) {
158757
158831
  return;
158758
158832
  }
158759
- const { port } = await ctx.keep("opensearch", null, async () => {
158833
+ const { port, sink: sink3 } = await ctx.keep("opensearch", null, async () => {
158760
158834
  const port2 = await findFreePort();
158761
158835
  const path6 = await download(VERSION_3_5_0_MIN);
158836
+ const sink4 = { tail: [] };
158762
158837
  const kill = await launch({
158763
158838
  path: path6,
158764
158839
  port: port2,
158765
158840
  host: "localhost",
158766
158841
  version: VERSION_3_5_0_MIN,
158767
- debug: false
158842
+ debug: false,
158843
+ onExit(code, signal) {
158844
+ sink4.crashed = code !== null ? `exited with code ${code}` : `killed by ${signal}`;
158845
+ sink4.health?.("down", sink4.crashed);
158846
+ },
158847
+ onOutput(line) {
158848
+ sink4.tail.push(line);
158849
+ while (sink4.tail.length > 20) {
158850
+ sink4.tail.shift();
158851
+ }
158852
+ sink4.log?.(line);
158853
+ }
158768
158854
  });
158769
158855
  await waitForSearch(port2, 60000);
158770
- return { value: { port: port2 }, stop: kill };
158856
+ return { value: { port: port2, sink: sink4 }, stop: kill };
158771
158857
  });
158858
+ sink3.health = (status2, detail) => ctx.reportHealth("search", status2, detail);
158859
+ sink3.health(sink3.crashed ? "down" : "up", sink3.crashed);
158860
+ sink3.log = (line) => ctx.emitEvent("search", { date: Date.now(), line });
158861
+ for (const line of sink3.tail) {
158862
+ ctx.emitEvent("search", { date: Date.now(), line });
158863
+ }
158772
158864
  ctx.addEnv("SEARCH_DOMAIN", `localhost:${port}`);
158773
158865
  const client = new Client5({ node: `http://localhost:${port}` });
158774
158866
  for (const { stackName, id, props } of indexes) {
@@ -158781,7 +158873,8 @@ var searchOnDev = async (ctx) => {
158781
158873
  kind: "search",
158782
158874
  stack: stackName,
158783
158875
  id,
158784
- detail: `localhost:${port}`
158876
+ detail: `localhost:${port}`,
158877
+ channel: "search"
158785
158878
  });
158786
158879
  }
158787
158880
  };
@@ -158870,8 +158963,7 @@ var searchFeature = defineFeature({
158870
158963
  })
158871
158964
  }, {
158872
158965
  retainOnDelete: ctx.appConfig.removal === "retain",
158873
- import: ctx.import ? name : undefined,
158874
- replaceOnChanges: ["engineVersion", "domainName"]
158966
+ import: ctx.import ? name : undefined
158875
158967
  });
158876
158968
  ctx.addEnv("SEARCH_DOMAIN", openSearch.endpointV2);
158877
158969
  ctx.addPermission({
@@ -158885,8 +158977,7 @@ var searchFeature = defineFeature({
158885
158977
  mappings: JSON.stringify(resolveSearchMappings(indexProps) ?? {}),
158886
158978
  settings: JSON.stringify(indexProps.settings ?? {})
158887
158979
  }, {
158888
- retainOnDelete: ctx.appConfig.removal === "retain",
158889
- replaceOnChanges: ["endpoint", "index"]
158980
+ retainOnDelete: ctx.appConfig.removal === "retain"
158890
158981
  });
158891
158982
  }
158892
158983
  }
@@ -159014,7 +159105,7 @@ var siteOnDev = async (ctx) => {
159014
159105
  const value = await ctx.keep(key, { command: command3, port, env: env4 }, async () => {
159015
159106
  const [bin, ...args] = command3.split(" ");
159016
159107
  const cwd2 = join33(directories.root, dirname16(stackConfig.file));
159017
- const sink2 = {
159108
+ const sink3 = {
159018
159109
  emit: () => {},
159019
159110
  stopping: false
159020
159111
  };
@@ -159032,7 +159123,7 @@ var siteOnDev = async (ctx) => {
159032
159123
  continue;
159033
159124
  }
159034
159125
  tail.push(line);
159035
- sink2.emit({ date: Date.now(), line });
159126
+ sink3.emit({ date: Date.now(), line });
159036
159127
  }
159037
159128
  while (tail.length > 20) {
159038
159129
  tail.shift();
@@ -159040,24 +159131,30 @@ var siteOnDev = async (ctx) => {
159040
159131
  };
159041
159132
  child.stdout?.on("data", capture);
159042
159133
  child.stderr?.on("data", capture);
159043
- child.on("exit", (code) => {
159044
- if (!sink2.stopping && code !== null && code !== 0) {
159134
+ child.on("exit", (code, signal) => {
159135
+ if (sink3.stopping) {
159136
+ return;
159137
+ }
159138
+ if (code !== null && code !== 0) {
159045
159139
  logs_exports.error(`The site "${id}" dev server exited with code ${code}:
159046
159140
  ${tail.join(`
159047
159141
  `)}`);
159048
- sink2.emit({ date: Date.now(), line: `Exited with code ${code}` });
159142
+ sink3.emit({ date: Date.now(), line: `Exited with code ${code}` });
159049
159143
  }
159144
+ sink3.health?.("down", code !== null ? `exited with code ${code}` : `killed by ${signal}`);
159050
159145
  });
159051
159146
  return {
159052
- value: { port, tail, sink: sink2, child },
159147
+ value: { port, tail, sink: sink3, child },
159053
159148
  stop: async () => {
159054
- sink2.stopping = true;
159149
+ sink3.stopping = true;
159055
159150
  await stopChild(child);
159056
159151
  }
159057
159152
  };
159058
159153
  });
159059
159154
  value.sink.emit = (data) => ctx.emitEvent(channel, data);
159155
+ value.sink.health = (status2, detail) => ctx.reportHealth(`site ${id}`, status2, detail);
159060
159156
  value.sink.stopping = false;
159157
+ value.sink.health(value.child.exitCode === null ? "up" : "down");
159061
159158
  for (const line of value.tail) {
159062
159159
  ctx.emitEvent(channel, { date: Date.now(), line });
159063
159160
  }
@@ -160173,18 +160270,7 @@ var topicFeature = defineFeature({
160173
160270
  });
160174
160271
  }
160175
160272
  }
160176
- const { server, port } = await ctx.keep("shim:sns", null, async () => {
160177
- const server2 = createSnsServer();
160178
- const port2 = await server2.listen();
160179
- return { value: { server: server2, port: port2 }, stop: () => server2.stop() };
160180
- });
160181
- ctx.addEnv("AWS_ENDPOINT_URL_SNS", `http://127.0.0.1:${port}`);
160182
- ctx.registerServer({
160183
- name: "sns",
160184
- start({ dispatch, reportFailure }) {
160185
- server.connect(dispatch, reportFailure);
160186
- }
160187
- });
160273
+ await ctx.useSns();
160188
160274
  },
160189
160275
  async onTypeGen(ctx) {
160190
160276
  const gen = new TypeFile("awsless");
@@ -160382,6 +160468,7 @@ var vpcFeature = defineFeature({
160382
160468
  });
160383
160469
 
160384
160470
  // src/feature/alert/index.ts
160471
+ import { randomUUID as randomUUID7 } from "crypto";
160385
160472
  var typeGenCode10 = `
160386
160473
  import type { PublishOptions } from '@awsless/sns'
160387
160474
  import type { Mock } from 'vitest'
@@ -160400,6 +160487,44 @@ type TestMockEntry = MockBuilder & Mock<(payload: unknown) => unknown>
160400
160487
  `;
160401
160488
  var alertFeature = defineFeature({
160402
160489
  name: "alert",
160490
+ async onDev(ctx) {
160491
+ const ids = Object.keys(ctx.appConfig.alerts ?? {});
160492
+ if (ids.length === 0) {
160493
+ return;
160494
+ }
160495
+ const { captures, alerts } = await ctx.useSns();
160496
+ const names = new Map(ids.map((id) => [
160497
+ formatGlobalResourceName({
160498
+ appName: ctx.appConfig.name,
160499
+ resourceType: "alert",
160500
+ resourceName: id
160501
+ }),
160502
+ id
160503
+ ]));
160504
+ captures.push((input) => {
160505
+ const id = names.get(input.TopicArn?.split(":").at(-1) ?? "");
160506
+ if (!id) {
160507
+ return false;
160508
+ }
160509
+ alerts.unshift({
160510
+ id: randomUUID7(),
160511
+ date: Date.now(),
160512
+ alert: id,
160513
+ subject: input.Subject,
160514
+ message: input.Message
160515
+ });
160516
+ ctx.emitEvent("problems", {
160517
+ date: Date.now(),
160518
+ kind: "alert",
160519
+ title: input.Subject ?? id,
160520
+ detail: id
160521
+ });
160522
+ return true;
160523
+ });
160524
+ for (const [name, id] of names) {
160525
+ ctx.registerResource({ kind: "alert", id, detail: name });
160526
+ }
160527
+ },
160403
160528
  async onTypeGen(ctx) {
160404
160529
  const gen = new TypeFile("awsless");
160405
160530
  const resources = new TypeObject(1);
@@ -161457,6 +161582,7 @@ var instanceOnDev = async (ctx) => {
161457
161582
  async start({ env: env4 }) {
161458
161583
  const tail = [];
161459
161584
  boot = () => {
161585
+ ctx.reportHealth(`instance ${id}`, "up");
161460
161586
  child = spawn5("bun", [file2], {
161461
161587
  cwd: cwd2,
161462
161588
  stdio: ["ignore", "pipe", "pipe"],
@@ -161485,13 +161611,17 @@ var instanceOnDev = async (ctx) => {
161485
161611
  };
161486
161612
  child.stdout?.on("data", capture);
161487
161613
  child.stderr?.on("data", capture);
161488
- child.on("exit", (code) => {
161489
- if (!stopping && code !== null && code !== 0) {
161614
+ child.on("exit", (code, signal) => {
161615
+ if (stopping) {
161616
+ return;
161617
+ }
161618
+ if (code !== null && code !== 0) {
161490
161619
  logs_exports.error(`The instance "${id}" exited with code ${code}:
161491
161620
  ${tail.join(`
161492
161621
  `)}`);
161493
161622
  ctx.emitEvent(channel, { date: Date.now(), line: `Exited with code ${code}`, error: true });
161494
161623
  }
161624
+ ctx.reportHealth(`instance ${id}`, "down", code !== null ? `exited with code ${code}` : `killed by ${signal}`);
161495
161625
  });
161496
161626
  };
161497
161627
  boot();
@@ -164584,16 +164714,11 @@ var startTest = async (props) => {
164584
164714
  }
164585
164715
  for (const error3 of module.errors()) {
164586
164716
  const stack = error3.stacks?.[0];
164587
- const { code, syscall, address, port } = error3;
164588
164717
  errors3.push({
164589
164718
  type: error3.name,
164590
164719
  message: error3.message,
164591
164720
  location: stack ? { line: stack.line, column: stack.column } : undefined,
164592
- stack: error3.stack,
164593
- code,
164594
- syscall,
164595
- address,
164596
- port
164721
+ stack: error3.stack
164597
164722
  });
164598
164723
  }
164599
164724
  }
@@ -164689,11 +164814,6 @@ var logTestErrors = (event) => {
164689
164814
  }
164690
164815
  }
164691
164816
  };
164692
- var formatModuleError = (error3) => {
164693
- const syscall = [error3.syscall, error3.address, error3.port && `port ${error3.port}`].filter(Boolean).join(" ");
164694
- return [error3.message, syscall && `(${syscall})`, `
164695
- `, error3.stack ?? "(no stack captured)"].filter(Boolean).join(" ");
164696
- };
164697
164817
  var runTest = async (stack, dir, filters, workspace, opts) => {
164698
164818
  await mkdir8(directories.test, { recursive: true });
164699
164819
  const file2 = join49(directories.test, `${stack}.json`);
@@ -164721,26 +164841,15 @@ var runTest = async (stack, dir, filters, workspace, opts) => {
164721
164841
  initialMessage: `Run tests for the ${color2.info(stack)} stack`,
164722
164842
  errorMessage: `Running tests for the ${color2.info(stack)} stack failed`,
164723
164843
  async task(ctx) {
164724
- const transient = (error3) => ["ECONNREFUSED", "ECONNRESET", "ETIMEDOUT", "EPIPE"].includes(error3.code ?? "") || ["ECONNREFUSED", "ECONNRESET", "ETIMEDOUT", "EPIPE"].some((code) => error3.message.includes(code));
164725
- let result2 = await startTest({
164844
+ const result2 = await startTest({
164726
164845
  dir,
164727
164846
  filters,
164728
164847
  env: opts.env
164729
164848
  });
164730
- if (result2.errors.length > 0 && result2.errors.every(transient)) {
164731
- for (const error3 of result2.errors) {
164732
- debug(`Transient module error in ${stack} tests, retrying once: ${formatModuleError(error3)}`);
164733
- }
164734
- await new Promise((resolve5) => setTimeout(resolve5, 1000));
164735
- result2 = await startTest({
164736
- dir,
164737
- filters,
164738
- env: opts.env
164739
- });
164740
- }
164741
164849
  if (result2.errors.length > 0) {
164742
164850
  for (const error3 of result2.errors) {
164743
- debug(`Module error in ${stack} tests: ${formatModuleError(error3)}`);
164851
+ debug(`Module error in ${stack} tests: ${error3.message}
164852
+ ${error3.stack ?? "(no stack captured)"}`);
164744
164853
  }
164745
164854
  throw result2.errors.map((error3) => new ExpectedError(error3.type && error3.type !== "Error" ? `${error3.type}: ${error3.message}` : error3.message));
164746
164855
  }
@@ -166580,7 +166689,7 @@ var createS3Server = (props) => {
166580
166689
  };
166581
166690
 
166582
166691
  // src/dev/servers/sqs.ts
166583
- import { createHash as createHash23, randomUUID as randomUUID7 } from "crypto";
166692
+ import { createHash as createHash23, randomUUID as randomUUID8 } from "crypto";
166584
166693
  import { createServer as createServer8 } from "http";
166585
166694
  var md5 = (value) => createHash23("md5").update(value).digest("hex");
166586
166695
  var md5OfAttributes = (attributes) => {
@@ -166655,7 +166764,7 @@ var createSqsServer = (props) => {
166655
166764
  Records: [
166656
166765
  {
166657
166766
  messageId,
166658
- receiptHandle: randomUUID7(),
166767
+ receiptHandle: randomUUID8(),
166659
166768
  body: input.MessageBody,
166660
166769
  attributes: {
166661
166770
  ApproximateReceiveCount: "1",
@@ -166723,7 +166832,7 @@ var createSqsServer = (props) => {
166723
166832
  if (!queue) {
166724
166833
  throw new Error(`Unknown local queue: ${input.QueueUrl}`);
166725
166834
  }
166726
- const messageId = randomUUID7();
166835
+ const messageId = randomUUID8();
166727
166836
  deliver(queue, input, messageId);
166728
166837
  return sendResult(input, messageId);
166729
166838
  },
@@ -166734,7 +166843,7 @@ var createSqsServer = (props) => {
166734
166843
  }
166735
166844
  return {
166736
166845
  Successful: (input.Entries ?? []).map((entry) => {
166737
- const messageId = randomUUID7();
166846
+ const messageId = randomUUID8();
166738
166847
  deliver(queue, entry, messageId);
166739
166848
  return {
166740
166849
  Id: entry.Id,
@@ -166758,7 +166867,7 @@ var createSqsServer = (props) => {
166758
166867
  const batch2 = visible.slice(0, Math.max(1, input.MaxNumberOfMessages ?? 1));
166759
166868
  return {
166760
166869
  Messages: batch2.map((message4) => {
166761
- message4.receipt = randomUUID7();
166870
+ message4.receipt = randomUUID8();
166762
166871
  message4.receiveCount += 1;
166763
166872
  message4.firstReceivedAt ??= now;
166764
166873
  message4.visibleAt = now + (input.VisibilityTimeout ?? 30) * 1000;
@@ -166915,6 +167024,12 @@ var createDevContext = (props) => {
166915
167024
  const servers = [];
166916
167025
  const restartPaths = [];
166917
167026
  const resources = [];
167027
+ const health = new Map;
167028
+ const reportHealth = (id, status2, detail) => {
167029
+ const entry = { id, status: status2, detail, date: Date.now() };
167030
+ health.set(id, entry);
167031
+ events.emit("health", entry);
167032
+ };
166918
167033
  const listeners2 = new Map;
166919
167034
  const replays = new Map;
166920
167035
  const events = {
@@ -167012,12 +167127,37 @@ var createDevContext = (props) => {
167012
167127
  })();
167013
167128
  return sqs;
167014
167129
  };
167130
+ let sns;
167131
+ const useSns = () => {
167132
+ sns ??= (async () => {
167133
+ const value = await props.pool.keep("shim:sns", null, async () => {
167134
+ const captures = [];
167135
+ const server = createSnsServer({ captures });
167136
+ const port = await server.listen();
167137
+ return {
167138
+ value: { server, port, captures, alerts: [] },
167139
+ stop: () => server.stop()
167140
+ };
167141
+ });
167142
+ value.captures.splice(0, value.captures.length);
167143
+ env4["AWS_ENDPOINT_URL_SNS"] = `http://127.0.0.1:${value.port}`;
167144
+ servers.push({
167145
+ name: "sns",
167146
+ start({ dispatch, reportFailure }) {
167147
+ value.server.connect(dispatch, reportFailure);
167148
+ }
167149
+ });
167150
+ return { port: value.port, captures: value.captures, alerts: value.alerts };
167151
+ })();
167152
+ return sns;
167153
+ };
167015
167154
  return {
167016
167155
  env: env4,
167017
167156
  routes,
167018
167157
  servers,
167019
167158
  restartPaths,
167020
167159
  resources,
167160
+ health,
167021
167161
  events,
167022
167162
  context: {
167023
167163
  appConfig: props.appConfig,
@@ -167036,6 +167176,7 @@ var createDevContext = (props) => {
167036
167176
  useDynamo,
167037
167177
  useStore,
167038
167178
  useSqs,
167179
+ useSns,
167039
167180
  addEnv(name, value) {
167040
167181
  if (name in env4 && env4[name] !== value) {
167041
167182
  throw new Error(`The env var "${name}" is defined multiple times with different values, while all bundled functions share the same env.`);
@@ -167055,7 +167196,8 @@ var createDevContext = (props) => {
167055
167196
  resources.push(resource);
167056
167197
  },
167057
167198
  log: props.log,
167058
- emitEvent: events.emit
167199
+ emitEvent: events.emit,
167200
+ reportHealth
167059
167201
  }
167060
167202
  };
167061
167203
  };
@@ -167112,7 +167254,7 @@ ${logs}`));
167112
167254
  var import_ioredis5 = __toESM(require_built3(), 1);
167113
167255
  import { DynamoDBClient as DynamoDBClient5 } from "@aws-sdk/client-dynamodb";
167114
167256
  import { DynamoDBDocumentClient, ScanCommand as ScanCommand2 } from "@aws-sdk/lib-dynamodb";
167115
- import { randomUUID as randomUUID8 } from "crypto";
167257
+ import { randomUUID as randomUUID9 } from "crypto";
167116
167258
  import { readdir as readdir9, readFile as readFile22, stat as stat8, writeFile as writeFile14 } from "fs/promises";
167117
167259
  import { createServer as createServer9 } from "http";
167118
167260
  import { join as join55, relative as relative11, sep as sep5 } from "path";
@@ -167321,6 +167463,7 @@ var createBundleWorker = (props) => {
167321
167463
  return run;
167322
167464
  };
167323
167465
  return {
167466
+ size: () => workers.length,
167324
167467
  start: () => serialize(doStart),
167325
167468
  stop: () => serialize(doStop),
167326
167469
  restart: () => serialize(async () => {
@@ -167364,7 +167507,7 @@ var dashboardHtml = `<!doctype html>
167364
167507
  --border: #262b36;
167365
167508
  --text: #d7dae0;
167366
167509
  --muted: #7d8590;
167367
- --accent: #f5a623;
167510
+ --accent: #ff9000;
167368
167511
  --good: #3fb950;
167369
167512
  --bad: #f85149;
167370
167513
  font-size: 14px;
@@ -167426,18 +167569,34 @@ var dashboardHtml = `<!doctype html>
167426
167569
  .logs .entry { padding: 4px 0; }
167427
167570
  .logs .entry + .entry, .logs .line + .entry, .logs .entry + .line { border-top: 1px solid var(--border); }
167428
167571
  .logs .entry .meta { display: flex; gap: 8px; margin-bottom: 2px; }
167429
- .config-form { display: flex; flex-direction: column; gap: 8px; max-width: 520px; }
167430
- .config-form .field { display: grid; grid-template-columns: 180px 1fr; gap: 12px; align-items: center; }
167431
- .config-form .name { color: var(--muted); overflow-wrap: anywhere; }
167432
- .config-form input {
167572
+ /* Every group is its own card, with a shared grid inside: the name
167573
+ column grows to the longest name (capped), so nothing truncates &
167574
+ the inputs line up. */
167575
+ .config-form { display: flex; flex-direction: column; gap: 14px; max-width: 720px; margin-top: 14px; }
167576
+ .config-group {
167433
167577
  background: var(--panel);
167578
+ border: 1px solid var(--border);
167579
+ border-radius: 8px;
167580
+ padding: 14px 16px;
167581
+ }
167582
+ .config-group h3 { margin: 0 0 12px; }
167583
+ .config-fields {
167584
+ display: grid;
167585
+ grid-template-columns: fit-content(340px) 1fr;
167586
+ gap: 10px 16px;
167587
+ align-items: center;
167588
+ }
167589
+ .config-fields .field { display: contents; }
167590
+ .config-fields .name { color: var(--muted); overflow-wrap: anywhere; }
167591
+ .config-fields input {
167592
+ background: var(--bg);
167434
167593
  color: var(--text);
167435
167594
  border: 1px solid var(--border);
167436
167595
  border-radius: 6px;
167437
167596
  padding: 6px 10px;
167438
167597
  font: inherit;
167439
167598
  }
167440
- .config-form input::placeholder { color: var(--muted); }
167599
+ .config-fields input::placeholder { color: var(--muted); }
167441
167600
  .groups { display: flex; flex-wrap: wrap; gap: 4px 16px; }
167442
167601
  .groups .group { display: flex; align-items: center; gap: 4px; cursor: pointer; }
167443
167602
  nav {
@@ -167448,10 +167607,13 @@ var dashboardHtml = `<!doctype html>
167448
167607
  nav h1 {
167449
167608
  font-size: 15px;
167450
167609
  margin: 4px 8px 16px;
167451
- color: var(--accent);
167452
167610
  cursor: pointer;
167453
167611
  }
167454
- nav h1 span { color: var(--muted); font-weight: normal; }
167612
+ /* The same two-tone logo as the cli: bold AWS in the brand orange,
167613
+ LESS dimmed, the command muted. */
167614
+ nav h1 .logo { color: var(--accent); font-weight: bold; }
167615
+ nav h1 .logo .dim { color: #a35d00; }
167616
+ nav h1 .cmd { color: var(--muted); font-weight: normal; }
167455
167617
  nav h3 {
167456
167618
  font-size: 10px;
167457
167619
  text-transform: uppercase;
@@ -167544,6 +167706,24 @@ var dashboardHtml = `<!doctype html>
167544
167706
  cursor: pointer;
167545
167707
  }
167546
167708
  .row:hover { background: var(--panel); }
167709
+ a.row { text-decoration: none; }
167710
+ .health { display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0 4px; }
167711
+ .health .chip {
167712
+ display: inline-flex;
167713
+ align-items: center;
167714
+ gap: 7px;
167715
+ background: var(--panel);
167716
+ border: 1px solid var(--border);
167717
+ border-radius: 999px;
167718
+ padding: 4px 12px;
167719
+ font-size: 12px;
167720
+ }
167721
+ .health .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--good); flex-shrink: 0; }
167722
+ .health .chip.down { border-color: var(--bad); }
167723
+ .health .chip.down .dot { background: var(--bad); }
167724
+ .health .chip .detail-text { color: var(--muted); }
167725
+ .row.problem .id { color: var(--bad); }
167726
+ .empty.good { color: var(--good); }
167547
167727
  .row .stack {
167548
167728
  color: var(--muted);
167549
167729
  min-width: 90px;
@@ -167675,9 +167855,10 @@ const GROUPS = [
167675
167855
  ['store', 'Stores'],
167676
167856
  ['config', 'Config'],
167677
167857
  ['auth', 'Auth'],
167858
+ ['alert', 'Alerts'],
167678
167859
  ['email', 'Emails'],
167679
167860
  ['worker', 'Logs'],
167680
- ['route', 'Routes'],
167861
+ ['route', 'Routers'],
167681
167862
  ]
167682
167863
 
167683
167864
  // The nav renders the features in scannable sections - a section only
@@ -167687,7 +167868,7 @@ const SECTIONS = [
167687
167868
  ['Compute', ['function', 'cron', 'task', 'instance']],
167688
167869
  ['Messaging', ['queue', 'topic', 'subscriber', 'pubsub']],
167689
167870
  ['Storage', ['table', 'cache', 'search', 'store']],
167690
- ['App', ['config', 'auth', 'email', 'worker']],
167871
+ ['App', ['config', 'auth', 'alert', 'email', 'worker']],
167691
167872
  ]
167692
167873
 
167693
167874
  const svg = inner =>
@@ -167710,12 +167891,13 @@ const ICONS = {
167710
167891
  cache: svg('<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/>'),
167711
167892
  search: svg('<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>'),
167712
167893
  store: svg('<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>'),
167894
+ alert: svg('<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/>'),
167713
167895
  email: svg('<rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/>'),
167714
167896
  config: svg('<line x1="4" y1="21" x2="4" y2="14"/><line x1="4" y1="10" x2="4" y2="3"/><line x1="12" y1="21" x2="12" y2="12"/><line x1="12" y1="8" x2="12" y2="3"/><line x1="20" y1="21" x2="20" y2="16"/><line x1="20" y1="12" x2="20" y2="3"/><line x1="1" y1="14" x2="7" y2="14"/><line x1="9" y1="8" x2="15" y2="8"/><line x1="17" y1="16" x2="23" y2="16"/>'),
167715
167897
  route: svg('<circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>'),
167716
167898
  site: svg('<rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/>'),
167717
167899
  seed: svg('<path d="M12 22V8"/><path d="M12 8C12 5 9 2 5 2c0 4 3 6 7 6z"/><path d="M12 12c0-3 3-6 7-6 0 4-3 6-7 6z"/>'),
167718
- auth: svg('<rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>'),
167900
+ auth: svg('<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>'),
167719
167901
  worker: svg('<polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/>'),
167720
167902
  }
167721
167903
 
@@ -167728,7 +167910,7 @@ const icon = kind => {
167728
167910
  let state
167729
167911
  let cleanupPanel
167730
167912
  const view = { kind: null, resource: null }
167731
- const filter = { query: '', stack: '' }
167913
+ const filter = { query: '', stack: '', router: '' }
167732
167914
 
167733
167915
  const label = r => (r.stack ? r.stack + ' / ' + r.id : r.id)
167734
167916
  const groupTitle = kind => GROUPS.find(([k]) => k === kind)?.[1] ?? kind
@@ -167744,6 +167926,7 @@ const filterParams = () => {
167744
167926
  const params = new URLSearchParams()
167745
167927
  if (filter.query) params.set('q', filter.query)
167746
167928
  if (filter.stack) params.set('stack', filter.stack)
167929
+ if (filter.router) params.set('router', filter.router)
167747
167930
  return params.toString()
167748
167931
  }
167749
167932
 
@@ -167764,6 +167947,7 @@ const selectKind = kind => {
167764
167947
  view.resource = null
167765
167948
  filter.query = ''
167766
167949
  filter.stack = ''
167950
+ filter.router = ''
167767
167951
  history.pushState(null, '', listUrl(kind))
167768
167952
  render()
167769
167953
  }
@@ -167789,6 +167973,7 @@ const applyLocation = () => {
167789
167973
  view.resource = null
167790
167974
  filter.query = params.get('q') ?? ''
167791
167975
  filter.stack = params.get('stack') ?? ''
167976
+ filter.router = params.get('router') ?? ''
167792
167977
 
167793
167978
  if (view.kind && segments.length >= 3) {
167794
167979
  const router = params.get('router')
@@ -168175,6 +168360,11 @@ const cachePanel = async (main, r) => {
168175
168360
  const holder = $('div', {}, $('p', { className: 'empty' }, 'Scanning...'))
168176
168361
  main.append(holder)
168177
168362
 
168363
+ // The redis server's own output, streamed live.
168364
+ if (r.channel) {
168365
+ cleanupPanel = attachLogFeed(main, r.channel, undefined, 'Server logs')
168366
+ }
168367
+
168178
168368
  try {
168179
168369
  const data = await api('/api/cache?target=' + encodeURIComponent(r.detail))
168180
168370
  holder.innerHTML = ''
@@ -168255,6 +168445,11 @@ const searchPanel = async (main, r) => {
168255
168445
  result,
168256
168446
  )
168257
168447
 
168448
+ // The opensearch server's own output, streamed live.
168449
+ if (r.channel) {
168450
+ cleanupPanel = attachLogFeed(main, r.channel, undefined, 'Server logs')
168451
+ }
168452
+
168258
168453
  try {
168259
168454
  const { data } = await proxy('/_cat/indices?format=json&h=index,health,docs.count,store.size&s=index')
168260
168455
  const rows = data.filter(entry => !entry.index.startsWith('.'))
@@ -168309,6 +168504,59 @@ const storePanel = async (main, r) => {
168309
168504
 
168310
168505
  // Every email sent through Email.send during this session - captured
168311
168506
  // by the local ses shim instead of being delivered.
168507
+ const alertPanel = async (main) => {
168508
+ const holder = $('div', {})
168509
+ main.append(holder)
168510
+
168511
+ const render = async () => {
168512
+ const data = await api('/api/alerts')
168513
+ const alerts = data.alerts ?? []
168514
+ holder.innerHTML = ''
168515
+
168516
+ const refresh = $('button', { className: 'primary', textContent: 'Refresh' })
168517
+ refresh.onclick = render
168518
+ holder.append($('div', { className: 'actions' }, [refresh]))
168519
+
168520
+ if (alerts.length === 0) {
168521
+ holder.append($('p', { className: 'empty' }, 'No alerts sent yet.'))
168522
+ return
168523
+ }
168524
+
168525
+ const rows = $('div', { className: 'rows' })
168526
+
168527
+ for (const alert of alerts) {
168528
+ const row = $('button', { className: 'row' }, [
168529
+ $('span', { className: 'stack' }, new Date(alert.date).toLocaleTimeString()),
168530
+ $('span', { className: 'id' }, alert.subject ?? '(no subject)'),
168531
+ $('span', { className: 'info' }, alert.alert),
168532
+ ])
168533
+ row.onclick = () => {
168534
+ holder.innerHTML = ''
168535
+
168536
+ const back = $('button', { className: 'back', textContent: '\u2190 Alerts' })
168537
+ back.onclick = render
168538
+
168539
+ let message = alert.message ?? '(no payload)'
168540
+ try {
168541
+ message = JSON.stringify(JSON.parse(alert.message), null, 2)
168542
+ } catch (_) {}
168543
+
168544
+ holder.append(
168545
+ back,
168546
+ $('h2', {}, alert.subject ?? '(no subject)'),
168547
+ $('p', { className: 'detail' }, alert.alert + ' \xB7 ' + new Date(alert.date).toLocaleString()),
168548
+ $('pre', { className: 'result' }, message),
168549
+ )
168550
+ }
168551
+ rows.append(row)
168552
+ }
168553
+
168554
+ holder.append(rows)
168555
+ }
168556
+
168557
+ render()
168558
+ }
168559
+
168312
168560
  const emailPanel = async (main) => {
168313
168561
  const holder = $('div', {})
168314
168562
  main.append(holder)
@@ -168371,7 +168619,34 @@ const configPanel = async (main) => {
168371
168619
  const inputs = new Map()
168372
168620
  const form = $('div', { className: 'config-form' })
168373
168621
 
168622
+ // Configs group by the first segment of their name: alphapo-api-key
168623
+ // & alphapo-host read as one "alphapo" block with the prefix
168624
+ // stripped from the rows. A prefix needs at least two members to
168625
+ // form a group - the loners keep their full name under "other".
168626
+ const byPrefix = new Map()
168627
+
168374
168628
  for (const name of names) {
168629
+ const prefix = name.split('-')[0]
168630
+ if (!byPrefix.has(prefix)) byPrefix.set(prefix, [])
168631
+ byPrefix.get(prefix).push(name)
168632
+ }
168633
+
168634
+ const sections = []
168635
+ const other = []
168636
+
168637
+ for (const [prefix, members] of byPrefix) {
168638
+ if (members.length > 1) {
168639
+ sections.push([prefix, members])
168640
+ } else {
168641
+ other.push(members[0])
168642
+ }
168643
+ }
168644
+
168645
+ if (other.length > 0) {
168646
+ sections.push(['other', other])
168647
+ }
168648
+
168649
+ const addField = (grid, name) => {
168375
168650
  // An empty input falls through to the value pulled from ssm on
168376
168651
  // boot - the placeholder shows whether the pull provided one.
168377
168652
  const input = $('input', {
@@ -168380,7 +168655,21 @@ const configPanel = async (main) => {
168380
168655
  spellcheck: false,
168381
168656
  })
168382
168657
  inputs.set(name, input)
168383
- form.append($('label', { className: 'field' }, [$('span', { className: 'name' }, name), input]))
168658
+ grid.append($('label', { className: 'field' }, [$('span', { className: 'name' }, name), input]))
168659
+ }
168660
+
168661
+ for (const [prefix, members] of sections) {
168662
+ const grid = $('div', { className: 'config-fields' })
168663
+
168664
+ for (const name of members) {
168665
+ addField(grid, name)
168666
+ }
168667
+
168668
+ // A lone unlabeled group skips its header - the card alone
168669
+ // carries the grouping.
168670
+ const header = sections.length > 1 || other.length === 0 ? [$('h3', {}, prefix)] : []
168671
+
168672
+ form.append($('div', { className: 'config-group' }, [...header, grid]))
168384
168673
  }
168385
168674
 
168386
168675
  if (names.length === 0) {
@@ -168549,7 +168838,9 @@ const authPanel = async (main, r) => {
168549
168838
 
168550
168839
  const renderResource = main => {
168551
168840
  const r = view.resource
168552
- const back = $('button', { className: 'back' }, '\u2190 ' + groupTitle(r.kind))
168841
+ // A route's back button lands on its router's route list, one level
168842
+ // below the Routers tab itself.
168843
+ const back = $('button', { className: 'back' }, '\u2190 ' + (r.kind === 'route' ? 'Routes' : groupTitle(r.kind)))
168553
168844
 
168554
168845
  back.onclick = () => {
168555
168846
  view.resource = null
@@ -168596,7 +168887,49 @@ const renderList = main => {
168596
168887
  const items = state.resources.filter(r => r.kind === view.kind)
168597
168888
  const stacks = [...new Set(items.map(r => r.stack).filter(Boolean))].sort()
168598
168889
 
168599
- main.append($('h2', {}, groupTitle(view.kind)))
168890
+ // The routes view drills down per router: every router runs as its
168891
+ // own local server (like its own domain in production), so a flat
168892
+ // list mixing every router's routes would only confuse.
168893
+ if (view.kind === 'route' && !filter.router) {
168894
+ main.append($('h2', {}, 'Routers'))
168895
+
168896
+ const rows = $('div', { className: 'rows' })
168897
+
168898
+ for (const [id, port] of Object.entries(state.routerPorts)) {
168899
+ const count = items.filter(r => r.router === id).length
168900
+ const row = $('button', { className: 'row' }, [
168901
+ $('span', { className: 'stack' }, id),
168902
+ $('span', { className: 'id' }, 'http://localhost:' + port),
168903
+ $('span', { className: 'info' }, count + (count === 1 ? ' route' : ' routes')),
168904
+ ])
168905
+ row.onclick = () => {
168906
+ filter.router = id
168907
+ history.pushState(null, '', listUrl('route'))
168908
+ render()
168909
+ }
168910
+ rows.append(row)
168911
+ }
168912
+
168913
+ main.append(rows)
168914
+ return
168915
+ }
168916
+
168917
+ if (view.kind === 'route') {
168918
+ const back = $('button', { className: 'back' }, '\u2190 Routers')
168919
+ back.onclick = () => {
168920
+ filter.router = ''
168921
+ history.pushState(null, '', listUrl('route'))
168922
+ render()
168923
+ }
168924
+
168925
+ main.append(
168926
+ back,
168927
+ $('h2', {}, 'Routes'),
168928
+ $('p', { className: 'detail' }, filter.router + ' \xB7 http://localhost:' + state.routerPorts[filter.router]),
168929
+ )
168930
+ } else {
168931
+ main.append($('h2', {}, groupTitle(view.kind)))
168932
+ }
168600
168933
 
168601
168934
  // The config page is a single form over every defined value,
168602
168935
  // instead of a resource list.
@@ -168609,15 +168942,27 @@ const renderList = main => {
168609
168942
  return emailPanel(main)
168610
168943
  }
168611
168944
 
168945
+ // The alert page lists every captured alert of the session.
168946
+ if (view.kind === 'alert') {
168947
+ return alertPanel(main)
168948
+ }
168949
+
168612
168950
  // The worker page streams the bundle worker output directly,
168613
168951
  // instead of listing its single resource.
168614
168952
  if (view.kind === 'worker') {
168615
- cleanupPanel = attachLogFeed(main, 'worker', undefined, null)
168953
+ const workerFeed = attachLogFeed(main, 'worker', undefined, 'Worker output')
168954
+ const debugFeed = attachLogFeed(main, 'debug', undefined, 'Dev server')
168955
+
168956
+ cleanupPanel = () => {
168957
+ workerFeed?.()
168958
+ debugFeed?.()
168959
+ }
168616
168960
  return
168617
168961
  }
168618
168962
 
168619
168963
  const matches = r => {
168620
168964
  if (filter.stack && r.stack !== filter.stack) return false
168965
+ if (filter.router && r.router !== filter.router) return false
168621
168966
  if (!filter.query) return true
168622
168967
 
168623
168968
  return (r.stack + ' ' + r.id + ' ' + (r.detail ?? ''))
@@ -168691,11 +169036,14 @@ const render = () => {
168691
169036
  cleanupPanel = undefined
168692
169037
 
168693
169038
  const nav = document.getElementById('nav')
168694
- nav.innerHTML = '<h1>awsless <span>dev</span></h1>'
169039
+ nav.innerHTML = '<h1><span class="logo">AWS<span class="dim">LESS</span></span> <span class="cmd">dev</span></h1>'
168695
169040
  nav.querySelector('h1').onclick = showHome
168696
169041
 
168697
169042
  const navButton = kind => {
168698
- const count = state.resources.filter(r => r.kind === kind).length
169043
+ // The Routers tab lists routers, so its count follows suit.
169044
+ const count = kind === 'route'
169045
+ ? Object.keys(state.routerPorts).length
169046
+ : state.resources.filter(r => r.kind === kind).length
168699
169047
  if (count === 0) return null
168700
169048
 
168701
169049
  // The email outbox is a single feed, so a count of registered
@@ -168703,7 +169051,7 @@ const render = () => {
168703
169051
  const button = $('button', { className: view.kind === kind ? 'active' : '' }, [
168704
169052
  icon(kind),
168705
169053
  groupTitle(kind),
168706
- kind === 'email' || kind === 'worker' ? '' : $('span', { className: 'count' }, String(count)),
169054
+ kind === 'email' || kind === 'worker' || kind === 'alert' ? '' : $('span', { className: 'count' }, String(count)),
168707
169055
  ])
168708
169056
  button.onclick = () => selectKind(kind)
168709
169057
  return button
@@ -168775,7 +169123,192 @@ const render = () => {
168775
169123
  } else if (view.kind) {
168776
169124
  renderList(main)
168777
169125
  } else {
168778
- main.append($('p', { className: 'empty' }, 'Select a feature to browse its resources.'))
169126
+ renderHome(main)
169127
+ }
169128
+ }
169129
+
169130
+ // ------------------------------------------------------------------
169131
+ // Homepage: the session at a glance - endpoints, problems & the live
169132
+ // activity feed of everything running through the bundle.
169133
+
169134
+ const timeAgo = ms => {
169135
+ const s = Math.max(0, Math.round(ms / 1000))
169136
+ if (s < 60) return s + 's'
169137
+ if (s < 3600) return Math.round(s / 60) + 'm'
169138
+ return Math.floor(s / 3600) + 'h ' + (Math.round(s / 60) % 60) + 'm'
169139
+ }
169140
+
169141
+ const renderHome = main => {
169142
+ const session = state.session ?? {}
169143
+
169144
+ const uptime = $('span', {}, '')
169145
+ const updateUptime = () => {
169146
+ uptime.textContent = session.startedAt ? 'up ' + timeAgo(Date.now() - session.startedAt) : ''
169147
+ }
169148
+ updateUptime()
169149
+ const uptimeTimer = setInterval(updateUptime, 1000)
169150
+
169151
+ main.append(
169152
+ $('h2', {}, state.app),
169153
+ $('p', { className: 'detail' }, [
169154
+ uptime,
169155
+ session.workers ? ' \\u00b7 ' + session.workers + ' workers' : '',
169156
+ ]),
169157
+ )
169158
+
169159
+ // ----------------------------------------------------------------
169160
+ // Health: one chip per moving part, live.
169161
+
169162
+ const chips = $('div', { className: 'health' })
169163
+ const chipEls = new Map()
169164
+
169165
+ const renderChip = entry => {
169166
+ let chip = chipEls.get(entry.id)
169167
+
169168
+ if (!chip) {
169169
+ chip = $('span', { className: 'chip' })
169170
+ chipEls.set(entry.id, chip)
169171
+ chips.append(chip)
169172
+ }
169173
+
169174
+ chip.className = 'chip' + (entry.status === 'down' ? ' down' : '')
169175
+ chip.innerHTML = ''
169176
+ chip.append(
169177
+ $('span', { className: 'dot' }),
169178
+ entry.id,
169179
+ entry.detail ? $('span', { className: 'detail-text' }, '\xB7 ' + entry.detail) : '',
169180
+ )
169181
+ }
169182
+
169183
+ for (const entry of state.health ?? []) renderChip(entry)
169184
+
169185
+ const healthSource = new EventSource('/api/events?channel=health')
169186
+ healthSource.onmessage = message => {
169187
+ try {
169188
+ renderChip(JSON.parse(message.data))
169189
+ } catch (_) {}
169190
+ }
169191
+
169192
+ if ((state.health ?? []).length > 0) {
169193
+ main.append(chips)
169194
+ }
169195
+
169196
+ // ----------------------------------------------------------------
169197
+ // Endpoints
169198
+
169199
+ main.append($('h3', {}, 'Endpoints'))
169200
+
169201
+ const endpoints = $('div', { className: 'rows' })
169202
+
169203
+ for (const [id, port] of Object.entries(state.routerPorts ?? {})) {
169204
+ const url = 'http://localhost:' + port
169205
+ const row = $('a', { className: 'row', href: url, target: '_blank' }, [
169206
+ $('span', { className: 'stack' }, 'router ' + id),
169207
+ $('span', { className: 'id' }, url),
169208
+ $('span', { className: 'info' }, ''),
169209
+ ])
169210
+ endpoints.append(row)
169211
+ }
169212
+
169213
+ main.append(endpoints)
169214
+
169215
+ // ----------------------------------------------------------------
169216
+ // Problems: failures, route errors & fired alerts, newest first.
169217
+
169218
+ main.append($('h3', {}, 'Problems'))
169219
+
169220
+ const problems = $('div', { className: 'rows' })
169221
+ const calm = $('p', { className: 'empty good' }, 'No problems - everything runs clean.')
169222
+ main.append(calm, problems)
169223
+
169224
+ const problemRows = []
169225
+
169226
+ const problemRow = data => {
169227
+ calm.hidden = true
169228
+
169229
+ const row = $('button', { className: 'row problem' }, [
169230
+ $('span', { className: 'stack' }, new Date(data.date).toLocaleTimeString() + ' \\u00b7 ' + data.kind),
169231
+ $('span', { className: 'id' }, data.title ?? ''),
169232
+ $('span', { className: 'info' }, (data.detail ?? '').slice(0, 120)),
169233
+ ])
169234
+
169235
+ row.onclick = () => {
169236
+ if (data.kind === 'alert') {
169237
+ selectKind('alert')
169238
+ return
169239
+ }
169240
+
169241
+ const resource = state.resources.find(r => r.routeKey === data.title)
169242
+ if (resource) selectResource(resource)
169243
+ }
169244
+
169245
+ problems.prepend(row)
169246
+ problemRows.push(row)
169247
+
169248
+ while (problemRows.length > 20) {
169249
+ problemRows.shift().remove()
169250
+ }
169251
+ }
169252
+
169253
+ const problemSource = new EventSource('/api/events?channel=problems')
169254
+ problemSource.onmessage = message => {
169255
+ try {
169256
+ problemRow(JSON.parse(message.data))
169257
+ } catch (_) {}
169258
+ }
169259
+
169260
+ // ----------------------------------------------------------------
169261
+ // Activity: every dispatch through the bundle, newest first.
169262
+
169263
+ main.append($('h3', {}, 'Activity'))
169264
+
169265
+ const activity = $('div', { className: 'rows' })
169266
+ const idle = $('p', { className: 'empty' }, 'Nothing ran yet.')
169267
+ main.append(idle, activity)
169268
+
169269
+ const activityRows = []
169270
+
169271
+ const activityRow = data => {
169272
+ idle.hidden = true
169273
+
169274
+ const resource = state.resources.find(r => r.routeKey === data.route)
169275
+ const row = $('button', { className: 'row' + (data.ok ? '' : ' problem') }, [
169276
+ $('span', { className: 'stack' }, new Date(data.date).toLocaleTimeString()),
169277
+ $('span', { className: 'id' }, (data.ok ? '' : '\\u2717 ') + data.route),
169278
+ $('span', { className: 'info' }, data.ok ? data.ms + 'ms' : (data.error ?? 'failed').slice(0, 80)),
169279
+ ])
169280
+
169281
+ if (resource) {
169282
+ row.onclick = () => selectResource(resource)
169283
+ }
169284
+
169285
+ activity.prepend(row)
169286
+ activityRows.push(row)
169287
+
169288
+ while (activityRows.length > 50) {
169289
+ activityRows.shift().remove()
169290
+ }
169291
+ }
169292
+
169293
+ const activitySource = new EventSource('/api/events?channel=activity')
169294
+ activitySource.onmessage = message => {
169295
+ try {
169296
+ activityRow(JSON.parse(message.data))
169297
+ } catch (_) {}
169298
+ }
169299
+
169300
+ // ----------------------------------------------------------------
169301
+ // Logs: the handlers' own console output, live - the full feed
169302
+ // (incl. the dev server stream) lives on the Logs tab.
169303
+
169304
+ const logsFeed = attachLogFeed(main, 'worker', undefined, 'Logs')
169305
+
169306
+ cleanupPanel = () => {
169307
+ clearInterval(uptimeTimer)
169308
+ healthSource.close()
169309
+ problemSource.close()
169310
+ activitySource.close()
169311
+ logsFeed?.()
168779
169312
  }
168780
169313
  }
168781
169314
 
@@ -168796,14 +169329,15 @@ try {
168796
169329
  document.title = state.app + ' \xB7 awsless dev'
168797
169330
  state.resources = [
168798
169331
  ...state.resources,
168799
- // Proxy routes carry no route key & are already listed by
168800
- // the feature that owns them.
168801
- ...state.routes.filter(route => route.routeKey).map(route => ({
169332
+ // The full route table per router, like the deployed router: both
169333
+ // bundle routes & proxy mounts (site dev servers, the pubsub
169334
+ // websocket). Only the raw per-file static asset routes stay out.
169335
+ ...state.routes.filter(route => !route.rawKey).map(route => ({
168802
169336
  kind: 'route',
168803
169337
  id: route.pattern,
168804
- stack: route.routeKey.split(':')[0],
169338
+ stack: route.routeKey ? route.routeKey.split(':')[0] : undefined,
168805
169339
  router: route.routerId,
168806
- detail: route.routeKey,
169340
+ detail: route.routeKey ?? ('proxy \u2192 ' + route.proxy),
168807
169341
  url: 'http://localhost:' + state.routerPorts[route.routerId] + route.pattern.split('{')[0].replace(/\\*$/, ''),
168808
169342
  })),
168809
169343
  ]
@@ -168863,7 +169397,9 @@ var createDashboardServer = (props) => {
168863
169397
  routerPorts: props.routerPorts,
168864
169398
  resources: props.resources,
168865
169399
  routes: props.routes,
168866
- seeds: Boolean(props.runSeeds)
169400
+ seeds: Boolean(props.runSeeds),
169401
+ session: props.getSession?.() ?? null,
169402
+ health: props.getHealth?.() ?? []
168867
169403
  }).replaceAll("<", "\\u003c");
168868
169404
  return { status: 200, body: dashboardHtml.replace("__STATE__", () => state), type: "text/html" };
168869
169405
  }
@@ -168881,7 +169417,7 @@ var createDashboardServer = (props) => {
168881
169417
  EventVersion: "1.0",
168882
169418
  Sns: {
168883
169419
  Type: "Notification",
168884
- MessageId: randomUUID8(),
169420
+ MessageId: randomUUID9(),
168885
169421
  TopicArn: `arn:aws:sns:${props.region}:000000000000:${topic}`,
168886
169422
  Message: typeof message4 === "string" ? message4 : JSON.stringify(message4 ?? {}),
168887
169423
  Timestamp: new Date().toISOString(),
@@ -169032,6 +169568,9 @@ var createDashboardServer = (props) => {
169032
169568
  if (url.pathname === "/api/emails") {
169033
169569
  return { status: 200, body: JSON.stringify({ emails: props.getEmails?.() ?? [] }) };
169034
169570
  }
169571
+ if (url.pathname === "/api/alerts") {
169572
+ return { status: 200, body: JSON.stringify({ alerts: props.getAlerts?.() ?? [] }) };
169573
+ }
169035
169574
  if (url.pathname.startsWith("/api/auth") && props.auth) {
169036
169575
  const auth2 = props.auth;
169037
169576
  try {
@@ -169145,13 +169684,13 @@ var createDashboardServer = (props) => {
169145
169684
  // src/dev/failure.ts
169146
169685
  import { stringify as stringify5 } from "@awsless/json";
169147
169686
  import { formatRouteKey as formatRouteKey2, formatRoutePayload as formatRoutePayload2 } from "awsless";
169148
- import { randomUUID as randomUUID9 } from "crypto";
169687
+ import { randomUUID as randomUUID10 } from "crypto";
169149
169688
  var CONSUMER_ROUTE = formatRouteKey2("base", "on-failure", "consumer");
169150
169689
  var createFailureReporter = (props) => {
169151
169690
  const format4 = (report) => {
169152
169691
  const error3 = report.error;
169153
169692
  const base = {
169154
- id: randomUUID9(),
169693
+ id: randomUUID10(),
169155
169694
  date: new Date,
169156
169695
  payload: report.event
169157
169696
  };
@@ -169191,6 +169730,7 @@ var createFailureReporter = (props) => {
169191
169730
  return (report) => {
169192
169731
  const message4 = report.error instanceof Error ? report.error.message : String(report.error);
169193
169732
  props.log(`${report.routeKey ?? report.queue?.name ?? "async invoke"} failed: ${message4}`);
169733
+ props.onReport?.(report);
169194
169734
  if (!props.enabled) {
169195
169735
  return;
169196
169736
  }
@@ -169383,7 +169923,7 @@ var startDev = async (props) => {
169383
169923
  const log = props.onLog ?? (() => {});
169384
169924
  const phase = props.phase ?? (async ({ start }, fn) => (log(start), fn(() => {})));
169385
169925
  const { appConfig, stackConfigs } = props;
169386
- useDevBuildDir();
169926
+ const startedAt = Date.now();
169387
169927
  const { builders, appId, ready: ready2 } = createApp({
169388
169928
  appConfig,
169389
169929
  stackConfigs,
@@ -169400,6 +169940,9 @@ var startDev = async (props) => {
169400
169940
  props.pool.begin();
169401
169941
  await props.pool.keep("session", null, async () => ({ value: true, stop: () => {} }));
169402
169942
  const dev = createDevContext({ appConfig, stackConfigs, appId, routerPorts, log, pool: props.pool });
169943
+ setDebugSink((type2, message4) => {
169944
+ dev.events.emit("debug", { date: Date.now(), line: message4, error: type2 === "error" });
169945
+ });
169403
169946
  const bundleName = getBundleFunctionName(appConfig.name);
169404
169947
  const buildDir = getBuildPath("bundle", bundleName, ".");
169405
169948
  const { env: env4, lambda } = await phase({ start: "Preparing the local resources...", done: "Prepared the local resources" }, async (detail) => {
@@ -169511,12 +170054,14 @@ var startDev = async (props) => {
169511
170054
  restartNeeded = true;
169512
170055
  await worker.restart();
169513
170056
  restartNeeded = false;
170057
+ dev.context.reportHealth("workers", "up", String(worker.size()));
169514
170058
  log(`Reloaded the bundle in ${Date.now() - started}ms (build ${built - started}ms, worker ${Date.now() - built}ms)`);
169515
170059
  } else if (!changed) {
169516
170060
  debug(`Rebuild found no changes in ${Date.now() - started}ms`);
169517
170061
  }
169518
170062
  } catch (error3) {
169519
170063
  dirty = true;
170064
+ dev.context.reportHealth("workers", "down", error3 instanceof Error ? error3.message : String(error3));
169520
170065
  throw error3;
169521
170066
  } finally {
169522
170067
  fresh = undefined;
@@ -169524,14 +170069,62 @@ var startDev = async (props) => {
169524
170069
  })();
169525
170070
  await fresh;
169526
170071
  };
170072
+ const describeDispatch = (event) => {
170073
+ const payload = event;
170074
+ const route = payload?.["$awsless-route"];
170075
+ if (typeof route === "string") {
170076
+ return route;
170077
+ }
170078
+ const record = payload?.Records?.[0];
170079
+ if (record?.eventSource === "aws:sqs") {
170080
+ return "queue " + String(record.eventSourceARN ?? "").split(":").at(-1);
170081
+ }
170082
+ if (record?.EventSource === "aws:sns") {
170083
+ return "topic " + String(record.Sns?.TopicArn ?? "").split(":").at(-1);
170084
+ }
170085
+ if (record?.eventSource === "aws:s3") {
170086
+ return "store " + String(record.s3?.bucket?.name ?? "");
170087
+ }
170088
+ if (String(record?.eventSourceARN ?? "").includes(":dynamodb:")) {
170089
+ return "stream " + (String(record.eventSourceARN).split("table/")[1]?.split("/")[0] ?? "table");
170090
+ }
170091
+ const header = payload?.headers?.[ROUTE_HEADER];
170092
+ if (typeof header === "string") {
170093
+ return header;
170094
+ }
170095
+ return "invoke";
170096
+ };
169527
170097
  const dispatch = async (event) => {
169528
170098
  await ensureFresh();
169529
- return worker.dispatch(event);
170099
+ const started = Date.now();
170100
+ const route = describeDispatch(event);
170101
+ try {
170102
+ const result = await worker.dispatch(event);
170103
+ dev.events.emit("activity", { date: started, route, ms: Date.now() - started, ok: true });
170104
+ return result;
170105
+ } catch (error3) {
170106
+ dev.events.emit("activity", {
170107
+ date: started,
170108
+ route,
170109
+ ms: Date.now() - started,
170110
+ ok: false,
170111
+ error: error3 instanceof Error ? error3.message : String(error3)
170112
+ });
170113
+ throw error3;
170114
+ }
169530
170115
  };
169531
170116
  const reportFailure = createFailureReporter({
169532
170117
  enabled: Boolean(appConfig.onFailure),
169533
170118
  dispatch,
169534
- log
170119
+ log,
170120
+ onReport(report) {
170121
+ dev.events.emit("problems", {
170122
+ date: Date.now(),
170123
+ kind: report.kind,
170124
+ title: report.routeKey ?? report.queue?.name ?? "async invoke",
170125
+ detail: report.error instanceof Error ? report.error.message : String(report.error)
170126
+ });
170127
+ }
169535
170128
  });
169536
170129
  lambda.connect(dispatch, reportFailure);
169537
170130
  await phase({ start: "Starting the local servers...", done: "Started the local servers" }, async (detail) => {
@@ -169546,9 +170139,11 @@ var startDev = async (props) => {
169546
170139
  await phase({ start: "Starting the bundle worker...", done: "Started the bundle worker" }, async (detail) => {
169547
170140
  try {
169548
170141
  await worker.start();
170142
+ dev.context.reportHealth("workers", "up", String(worker.size()));
169549
170143
  } catch (error3) {
169550
170144
  detail("FAILED - the next invoke retries");
169551
170145
  log(`The bundle worker failed to start: ${error3 instanceof Error ? error3.message : String(error3)}`);
170146
+ dev.context.reportHealth("workers", "down", error3 instanceof Error ? error3.message : String(error3));
169552
170147
  dirty = true;
169553
170148
  restartNeeded = true;
169554
170149
  }
@@ -169583,6 +170178,12 @@ var startDev = async (props) => {
169583
170178
  process.stderr.write(`Route ${routeKey} failed: ${detail}
169584
170179
  `);
169585
170180
  emitWorkerLine(`Route ${routeKey} failed: ${detail}`, true, routeKey);
170181
+ dev.events.emit("problems", {
170182
+ date: Date.now(),
170183
+ kind: "route",
170184
+ title: routeKey,
170185
+ detail: error3 instanceof Error ? error3.message : String(error3)
170186
+ });
169586
170187
  }
169587
170188
  }));
169588
170189
  }
@@ -169601,6 +170202,9 @@ var startDev = async (props) => {
169601
170202
  storeRoot: join57(directories.output, "local", "store"),
169602
170203
  configFile: join57(directories.output, "local", "config.json"),
169603
170204
  getEmails: () => props.pool.peek("shim:ses-email")?.server.list() ?? [],
170205
+ getAlerts: () => props.pool.peek("shim:sns")?.alerts ?? [],
170206
+ getSession: () => ({ startedAt, workers: worker.size() }),
170207
+ getHealth: () => [...dev.health.values()],
169604
170208
  configPulled: Object.keys(props.pool.peek("config:pull") ?? {}),
169605
170209
  auth: createAuthAdmin({
169606
170210
  appConfig,
@@ -169630,9 +170234,13 @@ var startDev = async (props) => {
169630
170234
  if (stopping) {
169631
170235
  return;
169632
170236
  }
169633
- worker.restart().then(() => log("Restarted the bundle worker.")).catch((error3) => {
170237
+ worker.restart().then(() => {
170238
+ dev.context.reportHealth("workers", "up", String(worker.size()));
170239
+ log("Restarted the bundle worker.");
170240
+ }).catch((error3) => {
169634
170241
  restartNeeded = true;
169635
170242
  dirty = true;
170243
+ dev.context.reportHealth("workers", "down", error3 instanceof Error ? error3.message : String(error3));
169636
170244
  log(`The bundle worker failed to restart: ${error3 instanceof Error ? error3.message : String(error3)}`);
169637
170245
  });
169638
170246
  });
@@ -177005,7 +177613,7 @@ ${job.error.stack.split(`
177005
177613
  stream2.shift();
177006
177614
  } else if (stream2.length > 0) {
177007
177615
  this._BOMseen = true;
177008
- }
177616
+ } else {}
177009
177617
  }
177010
177618
  return codePointsToString(stream2);
177011
177619
  }
@@ -185685,7 +186293,7 @@ var formatLog = (level, date2, origin, message4) => {
185685
186293
  import { DeleteObjectsCommand, ListObjectsV2Command, S3Client as S3Client5 } from "@aws-sdk/client-s3";
185686
186294
 
185687
186295
  // src/formation/cloudfront.ts
185688
- import { randomUUID as randomUUID10 } from "crypto";
186296
+ import { randomUUID as randomUUID11 } from "crypto";
185689
186297
  var createInvalidationForDistributionTenants = async (client, props) => {
185690
186298
  let cursor;
185691
186299
  do {
@@ -185705,7 +186313,7 @@ var createInvalidationForDistributionTenants = async (client, props) => {
185705
186313
  Quantity: props.paths.length,
185706
186314
  Items: props.paths
185707
186315
  },
185708
- CallerReference: randomUUID10()
186316
+ CallerReference: randomUUID11()
185709
186317
  }
185710
186318
  }));
185711
186319
  }