@awsless/cli 0.0.46-local.1 → 0.0.46-local.11

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
@@ -102748,7 +102748,7 @@ var init_find_cache_dir = __esm(() => {
102748
102748
  ({ env: env3, cwd } = process4);
102749
102749
  });
102750
102750
 
102751
- // ../open-search/dist/chunk-7I7EB23J.js
102751
+ // ../open-search/dist/chunk-M36HBITL.js
102752
102752
  import { mkdir as mkdir6, rename as rename4, rm as rm7, stat as stat5 } from "fs/promises";
102753
102753
  import { join as join27, resolve as resolve3 } from "path";
102754
102754
  import { mkdir as mkdir22, readdir as readdir4, rename as rename22, rm as rm22, stat as stat22 } from "fs/promises";
@@ -102842,7 +102842,7 @@ var import_decompress, import_decompress2, getArchiveName = (version2) => {
102842
102842
  }
102843
102843
  return findJavaHome(dir);
102844
102844
  }, VERSION_2_8_0;
102845
- var init_chunk_7I7EB23J = __esm(() => {
102845
+ var init_chunk_M36HBITL = __esm(() => {
102846
102846
  init_find_cache_dir();
102847
102847
  init_find_cache_dir();
102848
102848
  import_decompress = __toESM(require_decompress(), 1);
@@ -102856,7 +102856,8 @@ var init_chunk_7I7EB23J = __esm(() => {
102856
102856
  "http.port": port,
102857
102857
  "path.data": `${cache3}/data`,
102858
102858
  "path.logs": `${cache3}/logs`,
102859
- "plugins.security.disabled": true
102859
+ "plugins.security.disabled": true,
102860
+ "cluster.routing.allocation.disk.threshold_enabled": false
102860
102861
  })
102861
102862
  };
102862
102863
  });
@@ -103462,7 +103463,7 @@ var import_sleep_await, mock, searchClient = (options = {}, service = "es") => {
103462
103463
  return new Schema2((input) => [...input].map((item) => struct.encode(item)), (encoded) => new Set(encoded.map((item) => struct.decode(item))), struct.mapping);
103463
103464
  }, string2 = (props = {}) => new Schema2((value) => value, (value) => value, { type: "keyword", ...props }), uuid = (props = {}) => new Schema2((value) => value, (value) => value, { type: "keyword", ...props });
103464
103465
  var init_dist2 = __esm(() => {
103465
- init_chunk_7I7EB23J();
103466
+ init_chunk_M36HBITL();
103466
103467
  init_dist();
103467
103468
  import_sleep_await = __toESM(require_dist3(), 1);
103468
103469
  BulkError = class extends Error {
@@ -105671,7 +105672,7 @@ var require_readdirp = __commonJS((exports, module) => {
105671
105672
  var sysPath = __require("path");
105672
105673
  var { promisify: promisify2 } = __require("util");
105673
105674
  var picomatch = require_picomatch3();
105674
- var readdir6 = promisify2(fs5.readdir);
105675
+ var readdir7 = promisify2(fs5.readdir);
105675
105676
  var stat7 = promisify2(fs5.stat);
105676
105677
  var lstat4 = promisify2(fs5.lstat);
105677
105678
  var realpath2 = promisify2(fs5.realpath);
@@ -105804,7 +105805,7 @@ var require_readdirp = __commonJS((exports, module) => {
105804
105805
  async _exploreDir(path6, depth) {
105805
105806
  let files;
105806
105807
  try {
105807
- files = await readdir6(path6, this._rdOptions);
105808
+ files = await readdir7(path6, this._rdOptions);
105808
105809
  } catch (error3) {
105809
105810
  this._onError(error3);
105810
105811
  }
@@ -150256,6 +150257,16 @@ var bootstrapAwsless = async (props) => {
150256
150257
  // src/config/load/load.ts
150257
150258
  import { basename as basename2, dirname as dirname5, join as join13 } from "path";
150258
150259
 
150260
+ // src/feature/config/schema.ts
150261
+ var ConfigNameSchema = exports_external.string().regex(/^[a-z0-9-]+$/, "Invalid config name");
150262
+ var ConfigsSchema = exports_external.array(ConfigNameSchema).optional().describe("Define the config values for your app.");
150263
+ var ConfigFileSchema = exports_external.record(ConfigNameSchema, exports_external.union([
150264
+ exports_external.string().describe("A committed default value for the local dev environment."),
150265
+ exports_external.object({
150266
+ secret: exports_external.literal(true).describe("Pulled from ssm into memory on local dev boot, never stored locally.")
150267
+ }).describe("A secret config value.")
150268
+ ])).describe("The config values of your app.");
150269
+
150259
150270
  // src/config/schema/config-ref.ts
150260
150271
  var ConfigRefSchema = exports_external.string().regex(/^config:[a-z0-9-]+$/, "Invalid config reference");
150261
150272
  var isConfigRef = (value) => {
@@ -150324,10 +150335,6 @@ var AuthDefaultSchema = exports_external.record(ResourceIdSchema, exports_extern
150324
150335
  }).default({}).describe("Specifies the validity duration for every JWT token.")
150325
150336
  })).default({}).describe("Define the authenticatable users in your app.");
150326
150337
 
150327
- // src/feature/config/schema.ts
150328
- var ConfigNameSchema = exports_external.string().regex(/^[a-z0-9-]+$/, "Invalid config name");
150329
- var ConfigsSchema = exports_external.array(ConfigNameSchema).optional().describe("Define the config values for your app.");
150330
-
150331
150338
  // src/feature/domain/schema.ts
150332
150339
  var DomainNameSchema = exports_external.string().regex(/[a-z\-\_\.]/g, "Invalid domain name").describe("Enter a fully qualified domain name, for example, www.example.com. You can optionally include a trailing dot. If you omit the trailing dot, Amazon Route 53 assumes that the domain name that you specify is fully qualified. This means that Route 53 treats www.example.com (without a trailing dot) and www.example.com. (with a trailing dot) as identical.");
150333
150340
  var DNSTypeSchema = exports_external.enum(["A", "AAAA", "CAA", "CNAME", "DS", "MX", "NAPTR", "NS", "PTR", "SOA", "SPF", "SRV", "TXT"]).describe("The DNS record type.");
@@ -151267,7 +151274,7 @@ var IndexSchema = exports_external.object({
151267
151274
  strict: exports_external.boolean().optional().describe("Reject documents with fields that are missing from the schema."),
151268
151275
  mappings: MappingsSchema.optional(),
151269
151276
  settings: exports_external.record(exports_external.string(), exports_external.unknown()).optional().describe("The OpenSearch index settings, applied when the index is created.")
151270
- }).refine((props) => !(props.schema && props.mappings), {
151277
+ }).strict().refine((props) => !(props.schema && props.mappings), {
151271
151278
  message: 'Define either "schema" or "mappings" for an index, not both.'
151272
151279
  }).refine((props) => !(props.strict && !props.schema), {
151273
151280
  message: 'The "strict" option only works together with "schema".'
@@ -151372,6 +151379,22 @@ var AppSchema = exports_external.object({
151372
151379
  test: TestDefaultSchema
151373
151380
  }).strict();
151374
151381
 
151382
+ // src/config/schema/local-entry.ts
151383
+ import { stat as stat4 } from "fs/promises";
151384
+ var LocalEntrySchema = exports_external.union([
151385
+ RelativePathSchema.refine(async (path2) => {
151386
+ try {
151387
+ const s2 = await stat4(path2);
151388
+ return s2.isFile() || s2.isDirectory();
151389
+ } catch (error3) {
151390
+ return false;
151391
+ }
151392
+ }, `File or directory doesn't exist`),
151393
+ exports_external.object({
151394
+ nocheck: RelativePathSchema.describe("Specifies a local file or directory without checking if the file or directory exists.")
151395
+ }).transform((v3) => v3.nocheck)
151396
+ ]);
151397
+
151375
151398
  // src/feature/cache/schema.ts
151376
151399
  var StorageSchema = SizeSchema.refine(sizeMin(gibibytes(1)), "Minimum storage size is 1 GB").refine(sizeMax(gibibytes(5000)), "Maximum storage size is 5000 GB");
151377
151400
  var MinimumStorageSchema = StorageSchema.describe("The lower limit for data storage the cache is set to use. You can specify a size value from 1 GB to 5000 GB.");
@@ -151453,22 +151476,6 @@ example: "5 minutes"`),
151453
151476
  payload: exports_external.unknown().optional().describe("The JSON payload that will be passed to the consumer.")
151454
151477
  })).optional().describe(`Define the cron jobs in your stack.`);
151455
151478
 
151456
- // src/config/schema/local-entry.ts
151457
- import { stat as stat4 } from "fs/promises";
151458
- var LocalEntrySchema = exports_external.union([
151459
- RelativePathSchema.refine(async (path2) => {
151460
- try {
151461
- const s2 = await stat4(path2);
151462
- return s2.isFile() || s2.isDirectory();
151463
- } catch (error3) {
151464
- return false;
151465
- }
151466
- }, `File or directory doesn't exist`),
151467
- exports_external.object({
151468
- nocheck: RelativePathSchema.describe("Specifies a local file or directory without checking if the file or directory exists.")
151469
- }).transform((v3) => v3.nocheck)
151470
- ]);
151471
-
151472
151479
  // src/feature/site/schema.ts
151473
151480
  var SitesSchema = exports_external.record(ResourceIdSchema, exports_external.object({
151474
151481
  router: ResourceIdSchema.describe("The router id to link your site with."),
@@ -151710,6 +151717,7 @@ var StackSchema = exports_external.object({
151710
151717
  pubsub: PubSubSchema,
151711
151718
  searchs: SearchsSchema,
151712
151719
  sites: SitesSchema,
151720
+ seed: LocalEntrySchema.optional().describe("The file that seeds your local dev environment with data. It runs with the full local environment on the first dev boot & through the dashboard reseed button."),
151713
151721
  tests: TestsSchema,
151714
151722
  images: ImagesSchema,
151715
151723
  icons: IconsSchema,
@@ -152480,8 +152488,32 @@ var loadAppConfig = async (options) => {
152480
152488
  debug("Validate app config file");
152481
152489
  const app = await validateConfig(AppSchema, appConfig.file, appConfig.data);
152482
152490
  app.stage = options.stage;
152491
+ await loadConfigFile(app, root3, options.stage);
152483
152492
  return app;
152484
152493
  };
152494
+ var loadConfigFile = async (app, root3, stage) => {
152495
+ const ext2 = ["config.json", "config.jsonc", "config.json5"];
152496
+ const files = await glob(ext2, { cwd: root3 });
152497
+ const file2 = ext2.find((name) => files.includes(name));
152498
+ if (!file2) {
152499
+ return;
152500
+ }
152501
+ debug("Load config file:", color2.info(file2));
152502
+ const config = await readConfigWithStage(join13(root3, file2), stage);
152503
+ const entries2 = await validateConfig(ConfigFileSchema, config.file, config.data);
152504
+ const defaults2 = {};
152505
+ const secrets = [];
152506
+ for (const [name, value] of Object.entries(entries2)) {
152507
+ if (typeof value === "string") {
152508
+ defaults2[name] = value;
152509
+ } else {
152510
+ secrets.push(name);
152511
+ }
152512
+ }
152513
+ app.configs = [...new Set([...app.configs ?? [], ...Object.keys(entries2)])].sort();
152514
+ app.configDefaults = defaults2;
152515
+ app.configSecrets = secrets;
152516
+ };
152485
152517
  var loadStackConfigs = async (options) => {
152486
152518
  debug("Load stacks config files");
152487
152519
  const ext2 = "{json,jsonc,json5}";
@@ -153511,7 +153543,7 @@ var bundleFeature = defineFeature({
153511
153543
  ctx.registerBuild("bundle", name, buildBundle({
153512
153544
  name,
153513
153545
  handlers,
153514
- minify: defaults2.minify,
153546
+ minify: ctx.dev ? false : defaults2.minify,
153515
153547
  external: [...defaults2.external ?? [], ...layerPackages]
153516
153548
  }));
153517
153549
  const sourceHash = new Output(envDeps, async (resolve3) => {
@@ -154769,6 +154801,9 @@ var createSsmServer = (props) => {
154769
154801
  let closeServer;
154770
154802
  let log;
154771
154803
  const warned2 = new Set;
154804
+ let defaults2 = {};
154805
+ let pulled = {};
154806
+ let secrets = new Set;
154772
154807
  const loadValues = async () => {
154773
154808
  try {
154774
154809
  return JSON.parse(await readFile12(props.file, "utf8"));
@@ -154780,6 +154815,11 @@ var createSsmServer = (props) => {
154780
154815
  connect(logFn) {
154781
154816
  log = logFn;
154782
154817
  },
154818
+ setValues(next) {
154819
+ defaults2 = next.defaults;
154820
+ pulled = next.pulled;
154821
+ secrets = next.secrets;
154822
+ },
154783
154823
  async listen(port = 0) {
154784
154824
  server = createServer2((req, res) => {
154785
154825
  const chunks = [];
@@ -154799,12 +154839,12 @@ var createSsmServer = (props) => {
154799
154839
  const parameters = [];
154800
154840
  for (const name of Names ?? []) {
154801
154841
  const key = name.split("/").at(-1);
154802
- const value = values[key];
154842
+ const value = values[key] ?? defaults2[key] ?? pulled[key];
154803
154843
  if (typeof value === "string") {
154804
154844
  parameters.push({ Name: name, Type: "SecureString", Value: value });
154805
154845
  } else if (!warned2.has(key)) {
154806
154846
  warned2.add(key);
154807
- log?.(`The "${key}" config has no local value yet. Set it on the dashboard or in ${props.file}`);
154847
+ log?.(secrets.has(key) ? `The "${key}" config secret has no value - the ssm pull didn't provide one. Set it with "awsless config set ${key}" or override it on the dashboard.` : `The "${key}" config has no local value yet. Set it on the dashboard or add a default in config.jsonc`);
154808
154848
  }
154809
154849
  }
154810
154850
  res.writeHead(200, { "content-type": "application/x-amz-json-1.1" });
@@ -154972,25 +155012,73 @@ var configFeature = defineFeature({
154972
155012
  },
154973
155013
  async onDev(ctx) {
154974
155014
  const names = new Set(ctx.appConfig.configs ?? []);
155015
+ for (const stack of ctx.stackConfigs) {
155016
+ for (const props of Object.values(stack.functions ?? {})) {
155017
+ for (const name of props.sandbox?.configs ?? []) {
155018
+ names.add(name);
155019
+ }
155020
+ }
155021
+ for (const site of Object.values(stack.sites ?? {})) {
155022
+ for (const name of site.build?.configs ?? []) {
155023
+ names.add(name);
155024
+ }
155025
+ for (const name of site.ssr?.sandbox?.configs ?? []) {
155026
+ names.add(name);
155027
+ }
155028
+ }
155029
+ }
154975
155030
  if (names.size === 0) {
154976
155031
  return;
154977
155032
  }
155033
+ const secrets = new Set(ctx.appConfig.configSecrets ?? []);
154978
155034
  for (const name of names) {
154979
155035
  ctx.addEnv(`CONFIG_${constantCase(name)}`, name);
154980
- ctx.registerResource({ kind: "config", id: name });
155036
+ ctx.registerResource({ kind: "config", id: name, detail: secrets.has(name) ? "secret" : "" });
154981
155037
  }
154982
155038
  const file2 = join18(directories.output, "local", "config.json");
154983
- const server = createSsmServer({ file: file2 });
154984
- const port = await server.listen();
155039
+ const { server, port } = await ctx.keep("shim:ssm", file2, async () => {
155040
+ const server2 = createSsmServer({ file: file2 });
155041
+ const port2 = await server2.listen();
155042
+ return { value: { server: server2, port: port2 }, stop: () => server2.stop() };
155043
+ });
154985
155044
  ctx.addEnv("AWS_ENDPOINT_URL_SSM", `http://127.0.0.1:${port}`);
155045
+ let pulled = {};
155046
+ if (secrets.size > 0) {
155047
+ pulled = await ctx.keep("config:pull", [...secrets].sort().join(","), async () => {
155048
+ const values = {};
155049
+ try {
155050
+ ctx.log(`Pulling ${secrets.size} config secret${secrets.size === 1 ? "" : "s"} from SSM...`);
155051
+ const credentials2 = await getCredentials(ctx.appConfig.profile);
155052
+ const store = new SsmStore({ credentials: credentials2, appConfig: ctx.appConfig });
155053
+ let timer;
155054
+ const all5 = await Promise.race([
155055
+ store.list(),
155056
+ new Promise((_3, reject) => {
155057
+ timer = setTimeout(() => reject(new Error("the pull timed out after 15s")), 15000);
155058
+ })
155059
+ ]).finally(() => clearTimeout(timer));
155060
+ for (const name of secrets) {
155061
+ if (typeof all5[name] === "string") {
155062
+ values[name] = all5[name];
155063
+ }
155064
+ }
155065
+ } catch (error3) {
155066
+ debug("Config secret pull failed", error3);
155067
+ ctx.log(`Couldn't pull the config secrets from SSM (${error3 instanceof Error ? error3.message : String(error3)}) - secret-backed features will fail when called.`);
155068
+ }
155069
+ return { value: values, stop: () => {} };
155070
+ });
155071
+ }
155072
+ server.setValues({
155073
+ defaults: ctx.appConfig.configDefaults ?? {},
155074
+ pulled,
155075
+ secrets
155076
+ });
154986
155077
  ctx.restartOnChange(file2);
154987
155078
  ctx.registerServer({
154988
155079
  name: "config",
154989
155080
  start({ log }) {
154990
155081
  server.connect(log);
154991
- },
154992
- stop() {
154993
- return server.stop();
154994
155082
  }
154995
155083
  });
154996
155084
  }
@@ -155945,7 +156033,7 @@ var functionFeature = defineFeature({
155945
156033
  onStack(ctx) {
155946
156034
  for (const [id, props] of Object.entries(ctx.stackConfig.functions ?? {})) {
155947
156035
  const routeKey = formatRouteKey(ctx.stack.name, "function", id);
155948
- if (!isStandaloneFunction(props)) {
156036
+ if (!isStandaloneFunction(props) || ctx.dev) {
155949
156037
  registerBundleFunction(ctx, routeKey, props);
155950
156038
  continue;
155951
156039
  }
@@ -157405,12 +157493,15 @@ var queueFeature = defineFeature({
157405
157493
  queueRoutes.set(name, formatRouteKey(stackName, "queue", id));
157406
157494
  return { stackName, id, name };
157407
157495
  });
157408
- const server = createSqsServer({
157409
- region: ctx.appConfig.region,
157410
- accountId: "000000000000",
157411
- queues: queueRoutes
157496
+ const { server, port } = await ctx.keep("shim:sqs", queueRoutes, async () => {
157497
+ const server2 = createSqsServer({
157498
+ region: ctx.appConfig.region,
157499
+ accountId: "000000000000",
157500
+ queues: queueRoutes
157501
+ });
157502
+ const port2 = await server2.listen();
157503
+ return { value: { server: server2, port: port2 }, stop: () => server2.stop() };
157412
157504
  });
157413
- const port = await server.listen();
157414
157505
  ctx.addEnv("AWS_ENDPOINT_URL_SQS", `http://127.0.0.1:${port}`);
157415
157506
  for (const { stackName, id, name } of named) {
157416
157507
  ctx.addEnv(`QUEUE_${constantCase(stackName)}_${constantCase(id)}_URL`, `http://127.0.0.1:${port}/000000000000/${name}`);
@@ -157426,9 +157517,6 @@ var queueFeature = defineFeature({
157426
157517
  name: "sqs",
157427
157518
  start({ dispatch, reportFailure }) {
157428
157519
  server.connect(dispatch, reportFailure);
157429
- },
157430
- stop() {
157431
- return server.stop();
157432
157520
  }
157433
157521
  });
157434
157522
  },
@@ -157580,6 +157668,8 @@ var formatWebEvent = (request, route, body, url, sourceIp) => {
157580
157668
  headers[name] = value;
157581
157669
  }
157582
157670
  headers[ROUTE_HEADER] = route.routeKey;
157671
+ headers["cloudfront-viewer-address"] ??= `${sourceIp}:0`;
157672
+ headers["cloudfront-viewer-country"] ??= "US";
157583
157673
  for (const [name, value] of Object.entries(route.params)) {
157584
157674
  headers[`x-param-${name.toLowerCase()}`] = value;
157585
157675
  }
@@ -157690,7 +157780,11 @@ var startDevRouter = async (props) => {
157690
157780
  body: request.body,
157691
157781
  redirect: "manual"
157692
157782
  });
157693
- return new Response(proxied.body, { status: proxied.status, headers: proxied.headers });
157783
+ return new Response(proxied.body, {
157784
+ status: proxied.status,
157785
+ statusText: proxied.statusText,
157786
+ headers: proxied.headers
157787
+ });
157694
157788
  }
157695
157789
  if (!route.routeKey) {
157696
157790
  return new Response(`No route matched: ${url.pathname}`, { status: 404 });
@@ -158658,7 +158752,7 @@ var siteFeature = defineFeature({
158658
158752
  });
158659
158753
  const addRoutes = ctx.shared.entry("router", "addRoutes", props.router);
158660
158754
  const routeKey = props.path.endsWith("/") ? `${props.path}*` : `${props.path}/*`;
158661
- if (props.build) {
158755
+ if (props.build && !(ctx.dev && props.dev)) {
158662
158756
  const buildProps = props.build;
158663
158757
  ctx.registerBuild("site", name, async (build2) => {
158664
158758
  const fingerprint = await generateCacheKey(buildProps.cacheKey);
@@ -158705,7 +158799,7 @@ ${(errors3.trim() || output.trim()).slice(-2000)}`);
158705
158799
  });
158706
158800
  });
158707
158801
  }
158708
- if (props.ssr && isStandaloneFunction(props.ssr)) {
158802
+ if (props.ssr && ctx.dev && props.dev) {} else if (props.ssr && isStandaloneFunction(props.ssr) && !ctx.dev) {
158709
158803
  const fn = createLambdaFunction(ctx, `${id}-ssr`, props.ssr);
158710
158804
  if (ctx.shared.has("router", "endpoint", props.router)) {
158711
158805
  fn.setEnvironment(`ROUTER_${constantCase(props.router)}_ENDPOINT`, ctx.shared.entry("router", "endpoint", props.router));
@@ -158989,7 +159083,83 @@ import { toSeconds as toSeconds9 } from "@awsless/duration";
158989
159083
  import { CreateTableCommand as CreateTableCommand2, DeleteTableCommand } from "@aws-sdk/client-dynamodb";
158990
159084
  import { PutCommand } from "@aws-sdk/lib-dynamodb";
158991
159085
  import { readFile as readFile15 } from "fs/promises";
159086
+ import { join as join37 } from "path";
159087
+
159088
+ // src/dev/reset.ts
159089
+ var import_ioredis4 = __toESM(require_built3(), 1);
159090
+ import { DeleteCommand, ScanCommand } from "@aws-sdk/lib-dynamodb";
159091
+ import { Client as Client6 } from "@opensearch-project/opensearch";
159092
+ import { readdir as readdir6, rm as rm9 } from "fs/promises";
158992
159093
  import { join as join36 } from "path";
159094
+ var wiping = false;
159095
+ var isWiping = () => wiping;
159096
+ var createDataReset = (props) => {
159097
+ return async () => {
159098
+ wiping = true;
159099
+ try {
159100
+ const dynamo = props.pool.peek("dynamo");
159101
+ if (dynamo) {
159102
+ const client = dynamo.server.getDocumentClient();
159103
+ for (const [name, fingerprint] of dynamo.tableFingerprints) {
159104
+ const keys = JSON.parse(fingerprint).KeySchema.map((key) => key.AttributeName);
159105
+ let cursor;
159106
+ do {
159107
+ const result = await client.send(new ScanCommand({ TableName: name, ExclusiveStartKey: cursor }));
159108
+ for (const item of result.Items ?? []) {
159109
+ await client.send(new DeleteCommand({
159110
+ TableName: name,
159111
+ Key: Object.fromEntries(keys.map((key) => [key, item[key]]))
159112
+ }));
159113
+ }
159114
+ cursor = result.LastEvaluatedKey;
159115
+ } while (cursor);
159116
+ }
159117
+ }
159118
+ for (const stack of props.stackConfigs) {
159119
+ for (const id of Object.keys(stack.caches ?? {})) {
159120
+ const port = props.pool.peek(`cache:${stack.name}:${id}`);
159121
+ if (!port) {
159122
+ continue;
159123
+ }
159124
+ const redis = new import_ioredis4.Redis({ host: "127.0.0.1", port, lazyConnect: true });
159125
+ await redis.connect();
159126
+ await redis.flushall();
159127
+ redis.disconnect();
159128
+ }
159129
+ }
159130
+ const search2 = props.pool.peek("opensearch");
159131
+ if (search2) {
159132
+ const client = new Client6({ node: `http://localhost:${search2.port}` });
159133
+ for (const stack of props.stackConfigs) {
159134
+ for (const [id, searchProps] of Object.entries(stack.searchs ?? {})) {
159135
+ const index = formatSearchIndexName(stack.name, id);
159136
+ try {
159137
+ await client.indices.delete({ index });
159138
+ } catch (_3) {}
159139
+ await applySearchIndex(client, {
159140
+ index,
159141
+ mappings: resolveSearchMappings(searchProps),
159142
+ settings: searchProps.settings
159143
+ });
159144
+ }
159145
+ }
159146
+ }
159147
+ const storeRoot = join36(directories.output, "local", "store");
159148
+ try {
159149
+ for (const bucket of await readdir6(storeRoot)) {
159150
+ for (const entry of await readdir6(join36(storeRoot, bucket))) {
159151
+ if (entry === "image" || entry === "icon") {
159152
+ continue;
159153
+ }
159154
+ await rm9(join36(storeRoot, bucket, entry), { recursive: true, force: true });
159155
+ }
159156
+ }
159157
+ } catch (_3) {}
159158
+ } finally {
159159
+ wiping = false;
159160
+ }
159161
+ };
159162
+ };
158993
159163
 
158994
159164
  // src/feature/table/util.ts
158995
159165
  var formatTableKeys = (props) => {
@@ -159049,7 +159219,7 @@ var createTableInput = (name, props) => {
159049
159219
  };
159050
159220
  };
159051
159221
  var loadSeed = async (stackName, id) => {
159052
- const file2 = join36(directories.output, "local", "seed", "table", stackName, `${id}.json`);
159222
+ const file2 = join37(directories.output, "local", "seed", "table", stackName, `${id}.json`);
159053
159223
  try {
159054
159224
  const items = JSON.parse(await readFile15(file2, "utf8"));
159055
159225
  return Array.isArray(items) ? items : [];
@@ -159123,6 +159293,9 @@ var tableOnDev = async (ctx) => {
159123
159293
  const routeKey = formatRouteKey(stackName, "table", id);
159124
159294
  const eventSourceARN = `arn:aws:dynamodb:${ctx.appConfig.region}:000000000000:table/${name}/stream/local`;
159125
159295
  const unsubscribe = server.onStreamRecord(name, (record) => {
159296
+ if (isWiping()) {
159297
+ return;
159298
+ }
159126
159299
  const event = { Records: [{ ...record, eventSourceARN }] };
159127
159300
  dispatch(event).catch((error3) => {
159128
159301
  reportFailure({
@@ -159168,7 +159341,7 @@ var tableFeature = defineFeature({
159168
159341
  const sort2 = props.sort ? typeValue(props.sort) : "undefined";
159169
159342
  const indexes = props.indexes && Object.keys(props.indexes).length > 0 ? `{ ${Object.entries(props.indexes).map(([indexName, index]) => {
159170
159343
  const indexSort = index.sort ? `; sort: ${typeValue(index.sort)}` : "";
159171
- return `${indexName}: { hash: ${typeValue(index.hash)}${indexSort} }`;
159344
+ return `'${indexName}': { hash: ${typeValue(index.hash)}${indexSort} }`;
159172
159345
  }).join("; ")} }` : "undefined";
159173
159346
  list2.addType(name, `{
159174
159347
  readonly name: '${tableName}'
@@ -159415,16 +159588,16 @@ var taskFeature = defineFeature({
159415
159588
  routeKey: formatRouteKey(stackName, "task", id)
159416
159589
  });
159417
159590
  }
159418
- const server = createSchedulerServer();
159419
- const port = await server.listen();
159591
+ const { server, port } = await ctx.keep("shim:scheduler", null, async () => {
159592
+ const server2 = createSchedulerServer();
159593
+ const port2 = await server2.listen();
159594
+ return { value: { server: server2, port: port2 }, stop: () => server2.stop() };
159595
+ });
159420
159596
  ctx.addEnv("AWS_ENDPOINT_URL_SCHEDULER", `http://127.0.0.1:${port}`);
159421
159597
  ctx.registerServer({
159422
159598
  name: "scheduler",
159423
159599
  start({ dispatch, reportFailure }) {
159424
159600
  server.connect(dispatch, reportFailure);
159425
- },
159426
- stop() {
159427
- return server.stop();
159428
159601
  }
159429
159602
  });
159430
159603
  },
@@ -159608,16 +159781,16 @@ var topicFeature = defineFeature({
159608
159781
  });
159609
159782
  }
159610
159783
  }
159611
- const server = createSnsServer();
159612
- const port = await server.listen();
159784
+ const { server, port } = await ctx.keep("shim:sns", null, async () => {
159785
+ const server2 = createSnsServer();
159786
+ const port2 = await server2.listen();
159787
+ return { value: { server: server2, port: port2 }, stop: () => server2.stop() };
159788
+ });
159613
159789
  ctx.addEnv("AWS_ENDPOINT_URL_SNS", `http://127.0.0.1:${port}`);
159614
159790
  ctx.registerServer({
159615
159791
  name: "sns",
159616
159792
  start({ dispatch, reportFailure }) {
159617
159793
  server.connect(dispatch, reportFailure);
159618
- },
159619
- stop() {
159620
- return server.stop();
159621
159794
  }
159622
159795
  });
159623
159796
  },
@@ -159959,12 +160132,12 @@ import { toDays as toDays9 } from "@awsless/duration";
159959
160132
  import { formatRouteEnvName as formatRouteEnvName5 } from "awsless";
159960
160133
  import { createHash as createHash18 } from "crypto";
159961
160134
  import { readFile as readFile16 } from "fs/promises";
159962
- import { dirname as dirname18, join as join38 } from "path";
160135
+ import { dirname as dirname18, join as join39 } from "path";
159963
160136
  import { fileURLToPath as fileURLToPath13 } from "url";
159964
160137
 
159965
160138
  // src/feature/image/dev.ts
159966
160139
  import { cp } from "fs/promises";
159967
- import { isAbsolute as isAbsolute2, join as join37 } from "path";
160140
+ import { isAbsolute as isAbsolute2, join as join38 } from "path";
159968
160141
  var imageOnDev = async (ctx) => {
159969
160142
  const images = ctx.stackConfigs.flatMap((stack) => Object.keys(stack.images ?? {}));
159970
160143
  if (images.length === 0) {
@@ -160002,8 +160175,8 @@ var imageOnDev = async (ctx) => {
160002
160175
  }
160003
160176
  if (props.origin.static) {
160004
160177
  ctx.addEnv(`${routeKey}:IMAGE_ORIGIN_S3`, "true");
160005
- const source = isAbsolute2(props.origin.static) ? props.origin.static : join37(directories.root, props.origin.static);
160006
- await cp(source, join37(directories.output, "local", "store", bucket, folder, "origin"), {
160178
+ const source = isAbsolute2(props.origin.static) ? props.origin.static : join38(directories.root, props.origin.static);
160179
+ await cp(source, join38(directories.output, "local", "store", bucket, folder, "origin"), {
160007
160180
  recursive: true
160008
160181
  });
160009
160182
  }
@@ -160033,7 +160206,7 @@ var imageFeature = defineFeature({
160033
160206
  throw new FileError("app.json", "The image feature requires an arm64 function bundle.");
160034
160207
  }
160035
160208
  const group4 = new Group(ctx.base, "image", "layer");
160036
- const path6 = join38(dirname18(fileURLToPath13(import.meta.url)), "/layers/sharp-arm.zip");
160209
+ const path6 = join39(dirname18(fileURLToPath13(import.meta.url)), "/layers/sharp-arm.zip");
160037
160210
  const layerId = formatGlobalResourceName({
160038
160211
  appName: ctx.appConfig.name,
160039
160212
  resourceType: "layer",
@@ -160113,7 +160286,7 @@ var imageFeature = defineFeature({
160113
160286
  const serverRouteKey = formatRouteKey(ctx.stack.name, "image", id);
160114
160287
  bundle.addHandler({
160115
160288
  routeKey: serverRouteKey,
160116
- file: join38(dirname18(fileURLToPath13(import.meta.url)), "/handlers/image.js"),
160289
+ file: join39(dirname18(fileURLToPath13(import.meta.url)), "/handlers/image.js"),
160117
160290
  exportName: "default",
160118
160291
  external: ["sharp"]
160119
160292
  });
@@ -160151,8 +160324,8 @@ var imageFeature = defineFeature({
160151
160324
  new aws2.s3.BucketObject(group4, `static-${file2}`, {
160152
160325
  bucket: bucket.name,
160153
160326
  key: `${folder}origin/${file2}`,
160154
- source: join38(props.origin.static, file2),
160155
- sourceHash: $hash(join38(props.origin.static, file2))
160327
+ source: join39(props.origin.static, file2),
160328
+ sourceHash: $hash(join39(props.origin.static, file2))
160156
160329
  }, {
160157
160330
  replaceOnChanges: ["bucket", "key"]
160158
160331
  });
@@ -160168,12 +160341,12 @@ var imageFeature = defineFeature({
160168
160341
  // src/feature/icon/index.ts
160169
160342
  import { toDays as toDays10 } from "@awsless/duration";
160170
160343
  import { formatRouteEnvName as formatRouteEnvName6 } from "awsless";
160171
- import { dirname as dirname19, join as join40 } from "path";
160344
+ import { dirname as dirname19, join as join41 } from "path";
160172
160345
  import { fileURLToPath as fileURLToPath14 } from "url";
160173
160346
 
160174
160347
  // src/feature/icon/dev.ts
160175
160348
  import { cp as cp2 } from "fs/promises";
160176
- import { isAbsolute as isAbsolute3, join as join39 } from "path";
160349
+ import { isAbsolute as isAbsolute3, join as join40 } from "path";
160177
160350
  var iconOnDev = async (ctx) => {
160178
160351
  const icons = ctx.stackConfigs.flatMap((stack) => Object.keys(stack.icons ?? {}));
160179
160352
  if (icons.length === 0) {
@@ -160211,8 +160384,8 @@ var iconOnDev = async (ctx) => {
160211
160384
  }
160212
160385
  if (props.origin.static) {
160213
160386
  ctx.addEnv(`${routeKey}:ICON_ORIGIN_S3`, "true");
160214
- const source = isAbsolute3(props.origin.static) ? props.origin.static : join39(directories.root, props.origin.static);
160215
- await cp2(source, join39(directories.output, "local", "store", bucket, folder, "origin"), {
160387
+ const source = isAbsolute3(props.origin.static) ? props.origin.static : join40(directories.root, props.origin.static);
160388
+ await cp2(source, join40(directories.output, "local", "store", bucket, folder, "origin"), {
160216
160389
  recursive: true
160217
160390
  });
160218
160391
  }
@@ -160259,7 +160432,7 @@ var iconFeature = defineFeature({
160259
160432
  const serverRouteKey = formatRouteKey(ctx.stack.name, "icon", id);
160260
160433
  bundle.addHandler({
160261
160434
  routeKey: serverRouteKey,
160262
- file: join40(dirname19(fileURLToPath14(import.meta.url)), "/handlers/icon.js"),
160435
+ file: join41(dirname19(fileURLToPath14(import.meta.url)), "/handlers/icon.js"),
160263
160436
  exportName: "default"
160264
160437
  });
160265
160438
  addRoutes({
@@ -160299,8 +160472,8 @@ var iconFeature = defineFeature({
160299
160472
  new aws2.s3.BucketObject(group4, `static-${file2}`, {
160300
160473
  bucket: bucket.name,
160301
160474
  key: `${folder}origin/${file2}`,
160302
- source: join40(props.origin.static, file2),
160303
- sourceHash: $hash(join40(props.origin.static, file2))
160475
+ source: join41(props.origin.static, file2),
160476
+ sourceHash: $hash(join41(props.origin.static, file2))
160304
160477
  }, {
160305
160478
  replaceOnChanges: ["bucket", "key"]
160306
160479
  });
@@ -160327,10 +160500,10 @@ var import_deepmerge4 = __toESM(require_cjs2(), 1);
160327
160500
  // src/feature/job/build/executable.ts
160328
160501
  import { createHash as createHash19 } from "crypto";
160329
160502
  import { readFile as readFile17, writeFile as writeFile8 } from "fs/promises";
160330
- import { join as join41, resolve as resolve4 } from "path";
160503
+ import { join as join42, resolve as resolve4 } from "path";
160331
160504
  var buildJobExecutable = async (input, outputPath, architecture) => {
160332
- const filePath = join41(outputPath, "program");
160333
- const wrapperPath = join41(outputPath, "wrapper.ts");
160505
+ const filePath = join42(outputPath, "program");
160506
+ const wrapperPath = join42(outputPath, "wrapper.ts");
160334
160507
  const handlerPath = resolve4(input);
160335
160508
  await writeFile8(wrapperPath, `import { parse } from '@awsless/json'
160336
160509
  import { getObject } from '@awsless/s3'
@@ -160843,7 +161016,7 @@ import { stringify as stringify3 } from "@awsless/json";
160843
161016
  import { generateFileHash as generateFileHash4 } from "@awsless/ts-file-cache";
160844
161017
  var import_deepmerge6 = __toESM(require_cjs2(), 1);
160845
161018
  import { createHash as createHash21 } from "crypto";
160846
- import { join as join42 } from "path";
161019
+ import { join as join43 } from "path";
160847
161020
  var createFargateTask = (parentGroup, ctx, ns, id, local) => {
160848
161021
  const group4 = new Group(parentGroup, "instance", ns);
160849
161022
  const name = formatLocalResourceName({
@@ -161048,7 +161221,7 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
161048
161221
  healthCheck: props.healthCheck ? {
161049
161222
  command: [
161050
161223
  "CMD-SHELL",
161051
- `curl -f http://${join42("localhost", props.healthCheck.path)} || exit 1`
161224
+ `curl -f http://${join43("localhost", props.healthCheck.path)} || exit 1`
161052
161225
  ],
161053
161226
  interval: toSeconds11(props.healthCheck.interval),
161054
161227
  retries: props.healthCheck.retries,
@@ -163191,6 +163364,7 @@ var createApp = (props) => {
163191
163364
  feature.onBefore?.({
163192
163365
  ...props,
163193
163366
  import: props.import ?? false,
163367
+ dev: props.dev ?? false,
163194
163368
  app,
163195
163369
  appId,
163196
163370
  base,
@@ -163205,6 +163379,7 @@ var createApp = (props) => {
163205
163379
  feature.onApp?.({
163206
163380
  ...props,
163207
163381
  import: props.import ?? false,
163382
+ dev: props.dev ?? false,
163208
163383
  app,
163209
163384
  appId,
163210
163385
  base,
@@ -163266,6 +163441,7 @@ var createApp = (props) => {
163266
163441
  feature.onStack?.({
163267
163442
  ...props,
163268
163443
  import: props.import ?? false,
163444
+ dev: props.dev ?? false,
163269
163445
  stackConfig,
163270
163446
  app,
163271
163447
  appId,
@@ -163803,12 +163979,12 @@ import { DynamoDBClient as DynamoDBClient3 } from "@awsless/dynamodb";
163803
163979
  // src/cli/ui/complex/run-tests.ts
163804
163980
  var import_wildstring3 = __toESM(require_wildstring(), 1);
163805
163981
  import { mkdir as mkdir7, readFile as readFile18, writeFile as writeFile9 } from "fs/promises";
163806
- import { join as join44 } from "path";
163982
+ import { join as join45 } from "path";
163807
163983
  import { parse as parse7, stringify as stringify4 } from "@awsless/json";
163808
163984
  import { generateFolderHash, loadWorkspace as loadWorkspace2 } from "@awsless/ts-file-cache";
163809
163985
 
163810
163986
  // src/test/start.ts
163811
- import { dirname as dirname20, join as join43 } from "path";
163987
+ import { dirname as dirname20, join as join44 } from "path";
163812
163988
  import { fileURLToPath as fileURLToPath15 } from "url";
163813
163989
  import { configDefaults } from "vitest/config";
163814
163990
  import { startVitest } from "vitest/node";
@@ -163876,7 +164052,7 @@ var startTest = async (props) => {
163876
164052
  reporters: [new NullReporter],
163877
164053
  env: props.env,
163878
164054
  setupFiles: [
163879
- join43(__dirname4, "test-global-setup.js")
164055
+ join44(__dirname4, "test-global-setup.js")
163880
164056
  ]
163881
164057
  }, {
163882
164058
  logLevel: "silent",
@@ -164032,7 +164208,7 @@ var logTestErrors = (event) => {
164032
164208
  };
164033
164209
  var runTest = async (stack, dir, filters, workspace, opts) => {
164034
164210
  await mkdir7(directories.test, { recursive: true });
164035
- const file2 = join44(directories.test, `${stack}.json`);
164211
+ const file2 = join45(directories.test, `${stack}.json`);
164036
164212
  const fingerprint = await generateFolderHash(workspace, dir);
164037
164213
  if (!process.env.NO_CACHE) {
164038
164214
  const exists5 = await fileExist(file2);
@@ -164905,12 +165081,12 @@ var auth = (program3) => {
164905
165081
 
164906
165082
  // src/cli/command/bind.ts
164907
165083
  import { readFile as readFile19 } from "fs/promises";
164908
- import { join as join45 } from "path";
165084
+ import { join as join46 } from "path";
164909
165085
  var bind = (program3) => {
164910
165086
  program3.command("bind").argument("[command...]", "The command to execute").option("--config <string...>", "List of config values that will be accessable", (v3) => v3.split(",")).option("--local", "Bind against the running local dev environment instead of the deployed app").description(`Bind your site environment variables to a command`).action(async (commands5 = [], opts) => {
164911
165087
  await layout("bind", async ({ appConfig, stackConfigs }) => {
164912
165088
  if (opts.local) {
164913
- const file2 = join45(directories.output, "local", "env.json");
165089
+ const file2 = join46(directories.output, "local", "env.json");
164914
165090
  let env5;
164915
165091
  try {
164916
165092
  env5 = JSON.parse(await readFile19(file2, "utf8"));
@@ -165044,7 +165220,7 @@ var {
165044
165220
  isIBMi
165045
165221
  } = require_constants7();
165046
165222
  var stat7 = promisify2(fs5.stat);
165047
- var readdir6 = promisify2(fs5.readdir);
165223
+ var readdir7 = promisify2(fs5.readdir);
165048
165224
  var arrify = (value = []) => Array.isArray(value) ? value : [value];
165049
165225
  var flatten = (list3, result = []) => {
165050
165226
  list3.forEach((item) => {
@@ -165116,7 +165292,7 @@ class DirEntry {
165116
165292
  return;
165117
165293
  const dir = this.path;
165118
165294
  try {
165119
- await readdir6(dir);
165295
+ await readdir7(dir);
165120
165296
  } catch (err) {
165121
165297
  if (this._removeWatcher) {
165122
165298
  this._removeWatcher(sysPath.dirname(dir), sysPath.basename(dir));
@@ -165683,7 +165859,7 @@ var watchConfig = async (options, resolve5, reject) => {
165683
165859
  await loadAppConfig(options);
165684
165860
  debug("Start watching...");
165685
165861
  const ext2 = "{json,jsonc,json5}";
165686
- const watcher = $watch([`app.${ext2}`, `**/stack.${ext2}`, `**/*.stack.${ext2}`], {
165862
+ const watcher = $watch([`app.${ext2}`, `config.${ext2}`, `**/stack.${ext2}`, `**/*.stack.${ext2}`], {
165687
165863
  cwd: directories.root,
165688
165864
  ignored: ["**/node_modules/**", "**/dist/**"],
165689
165865
  awaitWriteFinish: true
@@ -165703,18 +165879,18 @@ var watchConfig = async (options, resolve5, reject) => {
165703
165879
 
165704
165880
  // src/dev/index.ts
165705
165881
  import { loadWorkspace as loadWorkspace3 } from "@awsless/ts-file-cache";
165706
- import { mkdir as mkdir10, rm as rm11, writeFile as writeFile13 } from "fs/promises";
165707
- import { join as join51 } from "path";
165882
+ import { mkdir as mkdir10, rm as rm12, writeFile as writeFile13 } from "fs/promises";
165883
+ import { join as join53 } from "path";
165708
165884
 
165709
165885
  // src/dev/context.ts
165710
165886
  import { DynamoDBServer } from "@awsless/dynamodb-server";
165711
- import { join as join47 } from "path";
165887
+ import { join as join48 } from "path";
165712
165888
 
165713
165889
  // src/dev/servers/s3.ts
165714
165890
  import { createHash as createHash22 } from "crypto";
165715
165891
  import { createServer as createServer7 } from "http";
165716
- import { mkdir as mkdir8, readdir as readdir7, readFile as readFile20, rm as rm9, stat as stat8, writeFile as writeFile10 } from "fs/promises";
165717
- import { dirname as dirname21, join as join46, relative as relative9, sep as sep4 } from "path";
165892
+ import { mkdir as mkdir8, readdir as readdir8, readFile as readFile20, rm as rm10, stat as stat8, writeFile as writeFile10 } from "fs/promises";
165893
+ import { dirname as dirname21, join as join47, relative as relative9, sep as sep4 } from "path";
165718
165894
  var readBody2 = (req) => {
165719
165895
  return new Promise((resolve5, reject) => {
165720
165896
  const chunks = [];
@@ -165784,8 +165960,8 @@ var createS3Server = (props) => {
165784
165960
  res.end(xmlError("InvalidRequest", "Missing bucket name"));
165785
165961
  return;
165786
165962
  }
165787
- const bucketDir = join46(props.root, bucket);
165788
- const file2 = join46(bucketDir, ...keyParts);
165963
+ const bucketDir = join47(props.root, bucket);
165964
+ const file2 = join47(bucketDir, ...keyParts);
165789
165965
  if (relative9(bucketDir, file2).startsWith("..")) {
165790
165966
  res.writeHead(400, { "content-type": "application/xml" });
165791
165967
  res.end(xmlError("InvalidRequest", "Invalid object key"));
@@ -165797,12 +165973,12 @@ var createS3Server = (props) => {
165797
165973
  const walk2 = async (dir) => {
165798
165974
  let entries2;
165799
165975
  try {
165800
- entries2 = await readdir7(dir, { withFileTypes: true });
165976
+ entries2 = await readdir8(dir, { withFileTypes: true });
165801
165977
  } catch (_4) {
165802
165978
  return;
165803
165979
  }
165804
165980
  for (const entry of entries2) {
165805
- const path6 = join46(dir, entry.name);
165981
+ const path6 = join47(dir, entry.name);
165806
165982
  if (entry.isDirectory()) {
165807
165983
  await walk2(path6);
165808
165984
  } else {
@@ -165853,7 +166029,7 @@ var createS3Server = (props) => {
165853
166029
  return;
165854
166030
  }
165855
166031
  if (req.method === "DELETE") {
165856
- await rm9(file2, { force: true });
166032
+ await rm10(file2, { force: true });
165857
166033
  res.writeHead(204);
165858
166034
  res.end();
165859
166035
  notify("ObjectRemoved:Delete", bucket, key, 0, "");
@@ -165941,7 +166117,7 @@ var createDevContext = (props) => {
165941
166117
  const value = await props.pool.keep("store", null, async () => {
165942
166118
  const rules = [];
165943
166119
  const server = createS3Server({
165944
- root: join47(directories.output, "local", "store"),
166120
+ root: join48(directories.output, "local", "store"),
165945
166121
  region: props.appConfig.region,
165946
166122
  rules
165947
166123
  });
@@ -166010,19 +166186,67 @@ var createDevContext = (props) => {
166010
166186
  };
166011
166187
  };
166012
166188
 
166189
+ // src/dev/seed.ts
166190
+ import { spawn as spawn5 } from "child_process";
166191
+ import { isAbsolute as isAbsolute4, join as join49 } from "path";
166192
+ var createSeedRunner = (props) => {
166193
+ const seeds = props.stackConfigs.filter((stack) => stack.seed).map((stack) => ({
166194
+ name: stack.name,
166195
+ file: isAbsolute4(stack.seed) ? stack.seed : join49(directories.root, stack.seed)
166196
+ }));
166197
+ let running;
166198
+ const run = () => {
166199
+ running ??= (async () => {
166200
+ try {
166201
+ for (const entry of seeds) {
166202
+ debug(`Seeding the ${entry.name} stack`);
166203
+ await new Promise((resolve5, reject) => {
166204
+ const child = spawn5("bun", [entry.file], {
166205
+ cwd: directories.root,
166206
+ stdio: ["ignore", "pipe", "pipe"],
166207
+ env: { ...process.env, ...props.env }
166208
+ });
166209
+ const output = [];
166210
+ const capture = (chunk3) => output.push(chunk3.toString());
166211
+ child.stdout?.on("data", capture);
166212
+ child.stderr?.on("data", capture);
166213
+ child.on("error", reject);
166214
+ child.on("exit", (code) => {
166215
+ const logs = output.join("").trim();
166216
+ if (logs) {
166217
+ debug(logs);
166218
+ }
166219
+ if (code === 0) {
166220
+ resolve5();
166221
+ } else {
166222
+ reject(new Error(`The seed of the "${entry.name}" stack exited with code ${code}:
166223
+ ${logs}`));
166224
+ }
166225
+ });
166226
+ });
166227
+ }
166228
+ } finally {
166229
+ running = undefined;
166230
+ }
166231
+ })();
166232
+ return running;
166233
+ };
166234
+ return { count: seeds.length, run };
166235
+ };
166236
+
166013
166237
  // src/dev/dashboard/index.ts
166014
- var import_ioredis4 = __toESM(require_built3(), 1);
166238
+ var import_ioredis5 = __toESM(require_built3(), 1);
166015
166239
  import { DynamoDBClient as DynamoDBClient4 } from "@aws-sdk/client-dynamodb";
166016
- import { DynamoDBDocumentClient, ScanCommand } from "@aws-sdk/lib-dynamodb";
166240
+ import { DynamoDBDocumentClient, ScanCommand as ScanCommand2 } from "@aws-sdk/lib-dynamodb";
166017
166241
  import { randomUUID as randomUUID5 } from "crypto";
166018
- import { readdir as readdir8, readFile as readFile21, stat as stat9, writeFile as writeFile12 } from "fs/promises";
166242
+ import { readdir as readdir9, readFile as readFile21, stat as stat9, writeFile as writeFile12 } from "fs/promises";
166019
166243
  import { createServer as createServer8 } from "http";
166020
- import { join as join49, relative as relative10, sep as sep5 } from "path";
166244
+ import { join as join51, relative as relative10, sep as sep5 } from "path";
166021
166245
 
166022
166246
  // src/dev/worker.ts
166023
- import { spawn as spawn5 } from "child_process";
166247
+ import { spawn as spawn6 } from "child_process";
166024
166248
  import { writeFile as writeFile11 } from "fs/promises";
166025
- import { join as join48 } from "path";
166249
+ import { join as join50 } from "path";
166026
166250
  class WorkerError extends Error {
166027
166251
  name;
166028
166252
  constructor(name, message4, stack) {
@@ -166115,10 +166339,10 @@ var createBundleWorker = (props) => {
166115
166339
  throw new Error("The bundle worker never became ready.");
166116
166340
  };
166117
166341
  const start = async () => {
166118
- const entry = join48(props.buildDir, "worker.mjs");
166342
+ const entry = join50(props.buildDir, "worker.mjs");
166119
166343
  await writeFile11(entry, WORKER_ENTRY);
166120
166344
  port = await findFreePort();
166121
- child = spawn5("node", ["--enable-source-maps", entry], {
166345
+ child = spawn6("node", ["--enable-source-maps", entry], {
166122
166346
  cwd: props.buildDir,
166123
166347
  stdio: ["ignore", "pipe", "pipe"],
166124
166348
  env: {
@@ -166273,6 +166497,8 @@ var dashboardHtml = `<!doctype html>
166273
166497
  cursor: pointer;
166274
166498
  }
166275
166499
  nav button .count { margin-left: auto; }
166500
+ nav button.reseed { margin-top: auto; color: var(--muted); }
166501
+ nav button.reseed:disabled { cursor: default; }
166276
166502
  .icon {
166277
166503
  width: 15px;
166278
166504
  height: 15px;
@@ -166322,12 +166548,16 @@ var dashboardHtml = `<!doctype html>
166322
166548
  .filters input { flex: 1; }
166323
166549
  .filters input::placeholder { color: var(--muted); }
166324
166550
  .filters .count { color: var(--muted); align-self: center; white-space: nowrap; }
166325
- .rows { display: flex; flex-direction: column; }
166551
+ /* The stack & name columns share one grid across every row, so the
166552
+ names line up no matter how long each stack name is. */
166553
+ .rows { display: grid; grid-template-columns: fit-content(280px) 1fr auto; }
166554
+ .rows > .empty { grid-column: 1 / -1; }
166326
166555
  .row {
166327
- display: flex;
166556
+ display: grid;
166557
+ grid-template-columns: subgrid;
166558
+ grid-column: 1 / -1;
166328
166559
  gap: 12px;
166329
166560
  align-items: baseline;
166330
- width: 100%;
166331
166561
  text-align: left;
166332
166562
  background: none;
166333
166563
  border: none;
@@ -166345,14 +166575,20 @@ var dashboardHtml = `<!doctype html>
166345
166575
  text-overflow: ellipsis;
166346
166576
  white-space: nowrap;
166347
166577
  }
166348
- .row .id { font-weight: bold; }
166578
+ .row .id {
166579
+ font-weight: bold;
166580
+ min-width: 0;
166581
+ overflow: hidden;
166582
+ text-overflow: ellipsis;
166583
+ white-space: nowrap;
166584
+ }
166349
166585
  .row .info {
166350
166586
  color: var(--muted);
166351
- margin-left: auto;
166587
+ justify-self: end;
166352
166588
  overflow: hidden;
166353
166589
  text-overflow: ellipsis;
166354
166590
  white-space: nowrap;
166355
- max-width: 45%;
166591
+ max-width: 100%;
166356
166592
  }
166357
166593
  textarea {
166358
166594
  width: 100%;
@@ -166469,6 +166705,7 @@ const ICONS = {
166469
166705
  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"/>'),
166470
166706
  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"/>'),
166471
166707
  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"/>'),
166708
+ 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"/>'),
166472
166709
  }
166473
166710
 
166474
166711
  const icon = kind => {
@@ -166644,6 +166881,19 @@ const rpcPanel = (main, r) => {
166644
166881
  }
166645
166882
 
166646
166883
  const query = $('select', {}, r.queries.map(name => $('option', { value: name, textContent: name })))
166884
+
166885
+ // The token rides the same "authentication" header the rpc client
166886
+ // sends, & sticks around per api so a page reload keeps it.
166887
+ const authKey = 'rpc-auth-token:' + r.routeKey
166888
+ const auth = $('input', {
166889
+ type: 'text',
166890
+ placeholder: 'Auth token (optional)',
166891
+ value: localStorage.getItem(authKey) ?? '',
166892
+ autocomplete: 'off',
166893
+ spellcheck: false,
166894
+ })
166895
+ auth.oninput = () => localStorage.setItem(authKey, auth.value)
166896
+
166647
166897
  const payload = $('textarea', { value: '{}', spellcheck: false })
166648
166898
  const status = $('span', { className: 'status' })
166649
166899
  const result = $('pre', { className: 'result', hidden: true })
@@ -166662,7 +166912,10 @@ const rpcPanel = (main, r) => {
166662
166912
  version: '2.0',
166663
166913
  rawPath: '/',
166664
166914
  requestContext: { http: { method: 'POST', userAgent: 'awsless dev dashboard', sourceIp: '127.0.0.1' } },
166665
- headers: { 'content-type': 'application/json' },
166915
+ headers: {
166916
+ 'content-type': 'application/json',
166917
+ ...(auth.value.trim() ? { authentication: auth.value.trim() } : {}),
166918
+ },
166666
166919
  body: JSON.stringify([{ name: query.value, ...(parsed === undefined ? {} : { payload: parsed }) }]),
166667
166920
  }
166668
166921
 
@@ -166686,7 +166939,7 @@ const rpcPanel = (main, r) => {
166686
166939
  }
166687
166940
 
166688
166941
  main.append(
166689
- $('div', { className: 'filters' }, [query]),
166942
+ $('div', { className: 'filters' }, [query, auth]),
166690
166943
  payload,
166691
166944
  $('div', { className: 'actions' }, [run, status]),
166692
166945
  result,
@@ -166938,12 +167191,25 @@ const configPanel = async (main) => {
166938
167191
  const names = [...new Set(state.resources.filter(r => r.kind === 'config').map(r => r.id))].sort()
166939
167192
  const data = await api('/api/config')
166940
167193
  const values = data.values ?? {}
167194
+ const defaults = data.defaults ?? {}
167195
+ const secrets = new Set(data.secrets ?? [])
166941
167196
 
166942
167197
  const inputs = new Map()
166943
167198
  const form = $('div', { className: 'config-form' })
166944
167199
 
166945
167200
  for (const name of names) {
166946
- const input = $('input', { value: values[name] ?? '', placeholder: 'not set', spellcheck: false })
167201
+ // An empty input falls through to the committed default or the
167202
+ // pulled ssm secret - the placeholder shows which one carries.
167203
+ const placeholder = secrets.has(name)
167204
+ ? 'pulled from ssm'
167205
+ : defaults[name] !== undefined ? 'default: ' + defaults[name] : 'not set'
167206
+
167207
+ const input = $('input', {
167208
+ value: values[name] ?? '',
167209
+ placeholder,
167210
+ spellcheck: false,
167211
+ ...(secrets.has(name) ? { type: 'password' } : {}),
167212
+ })
166947
167213
  inputs.set(name, input)
166948
167214
  form.append($('label', { className: 'field' }, [$('span', { className: 'name' }, name), input]))
166949
167215
  }
@@ -167122,6 +167388,46 @@ const render = () => {
167122
167388
  nav.append(button)
167123
167389
  }
167124
167390
 
167391
+ if (state.seeds) {
167392
+ const reseed = $('button', { className: 'reseed' }, [icon('seed'), 'Reset & seed'])
167393
+ let armed = false
167394
+ let disarm
167395
+
167396
+ reseed.onclick = async () => {
167397
+ // The reset wipes all local data, so it asks for a second
167398
+ // click before running.
167399
+ if (!armed) {
167400
+ armed = true
167401
+ reseed.lastChild.textContent = 'Click to confirm'
167402
+ disarm = setTimeout(() => {
167403
+ armed = false
167404
+ reseed.lastChild.textContent = 'Reset & seed'
167405
+ }, 3000)
167406
+ return
167407
+ }
167408
+
167409
+ clearTimeout(disarm)
167410
+ armed = false
167411
+ reseed.disabled = true
167412
+ reseed.lastChild.textContent = 'Seeding...'
167413
+
167414
+ try {
167415
+ const res = await fetch('/api/seed', { method: 'POST' })
167416
+ const data = await res.json()
167417
+ reseed.lastChild.textContent = data.ok ? 'Done' : 'Failed'
167418
+ } catch {
167419
+ reseed.lastChild.textContent = 'Failed'
167420
+ }
167421
+
167422
+ setTimeout(() => {
167423
+ reseed.disabled = false
167424
+ reseed.lastChild.textContent = 'Reset & seed'
167425
+ }, 2000)
167426
+ }
167427
+
167428
+ nav.append(reseed)
167429
+ }
167430
+
167125
167431
  const main = document.getElementById('main')
167126
167432
  main.innerHTML = ''
167127
167433
 
@@ -167201,7 +167507,8 @@ var createDashboardServer = (props) => {
167201
167507
  app: props.app,
167202
167508
  routerPorts: props.routerPorts,
167203
167509
  resources: props.resources,
167204
- routes: props.routes
167510
+ routes: props.routes,
167511
+ seeds: Boolean(props.runSeeds)
167205
167512
  }).replaceAll("<", "\\u003c");
167206
167513
  return { status: 200, body: dashboardHtml.replace("__STATE__", state), type: "text/html" };
167207
167514
  }
@@ -167244,7 +167551,7 @@ var createDashboardServer = (props) => {
167244
167551
  if (url.pathname === "/api/table") {
167245
167552
  const name = url.searchParams.get("name");
167246
167553
  try {
167247
- const result = await getDocumentClient().send(new ScanCommand({ TableName: name, Limit: 100 }));
167554
+ const result = await getDocumentClient().send(new ScanCommand2({ TableName: name, Limit: 100 }));
167248
167555
  return { status: 200, body: JSON.stringify({ items: result.Items ?? [], count: result.Count ?? 0 }) };
167249
167556
  } catch (error3) {
167250
167557
  throw new Error(`Scanning "${name}" via ${props.env.AWS_ENDPOINT_URL_DYNAMODB ?? "no dynamodb endpoint"} failed: ${error3 instanceof Error ? error3.message : String(error3)}`);
@@ -167252,7 +167559,7 @@ var createDashboardServer = (props) => {
167252
167559
  }
167253
167560
  if (url.pathname === "/api/cache") {
167254
167561
  const [host, port] = (url.searchParams.get("target") ?? "").split(":");
167255
- const redis = new import_ioredis4.Redis({ host, port: Number(port), lazyConnect: true });
167562
+ const redis = new import_ioredis5.Redis({ host, port: Number(port), lazyConnect: true });
167256
167563
  try {
167257
167564
  await redis.connect();
167258
167565
  const keyspace = await redis.info("keyspace");
@@ -167299,12 +167606,12 @@ var createDashboardServer = (props) => {
167299
167606
  const walk2 = async (dir) => {
167300
167607
  let entries2;
167301
167608
  try {
167302
- entries2 = await readdir8(dir, { withFileTypes: true });
167609
+ entries2 = await readdir9(dir, { withFileTypes: true });
167303
167610
  } catch (_4) {
167304
167611
  return;
167305
167612
  }
167306
167613
  for (const entry of entries2) {
167307
- const path6 = join49(dir, entry.name);
167614
+ const path6 = join51(dir, entry.name);
167308
167615
  if (entry.isDirectory()) {
167309
167616
  await walk2(path6);
167310
167617
  } else {
@@ -167319,6 +167626,20 @@ var createDashboardServer = (props) => {
167319
167626
  await walk2(props.storeRoot);
167320
167627
  return { status: 200, body: JSON.stringify({ files }) };
167321
167628
  }
167629
+ if (url.pathname === "/api/seed" && req.method === "POST") {
167630
+ if (!props.runSeeds) {
167631
+ return { status: 400, body: JSON.stringify({ error: "No seed files configured." }) };
167632
+ }
167633
+ try {
167634
+ await props.runSeeds();
167635
+ return { status: 200, body: JSON.stringify({ ok: true }) };
167636
+ } catch (error3) {
167637
+ return {
167638
+ status: 500,
167639
+ body: JSON.stringify({ error: error3 instanceof Error ? error3.message : String(error3) })
167640
+ };
167641
+ }
167642
+ }
167322
167643
  if (url.pathname === "/api/config") {
167323
167644
  if (req.method === "PUT") {
167324
167645
  const values2 = JSON.parse((await readBody3(req)).toString() || "{}");
@@ -167330,7 +167651,14 @@ var createDashboardServer = (props) => {
167330
167651
  try {
167331
167652
  values = JSON.parse(await readFile21(props.configFile, "utf8"));
167332
167653
  } catch (_4) {}
167333
- return { status: 200, body: JSON.stringify({ values }) };
167654
+ return {
167655
+ status: 200,
167656
+ body: JSON.stringify({
167657
+ values,
167658
+ defaults: props.configMeta?.defaults ?? {},
167659
+ secrets: props.configMeta?.secrets ?? []
167660
+ })
167661
+ };
167334
167662
  }
167335
167663
  return { status: 404, body: JSON.stringify({ error: `Unknown dashboard path: ${url.pathname}` }) };
167336
167664
  };
@@ -167552,22 +167880,22 @@ var createLambdaServer = (props) => {
167552
167880
  };
167553
167881
 
167554
167882
  // src/dev/sdk.ts
167555
- import { readdir as readdir9, readFile as readFile22, mkdir as mkdir9, rm as rm10, symlink } from "fs/promises";
167883
+ import { readdir as readdir10, readFile as readFile22, mkdir as mkdir9, rm as rm11, symlink } from "fs/promises";
167556
167884
  import { createRequire } from "module";
167557
- import { dirname as dirname22, join as join50 } from "path";
167885
+ import { dirname as dirname22, join as join52 } from "path";
167558
167886
  var linkSdkPackages = async (buildDir, onWarn) => {
167559
- const filesDir = join50(buildDir, "files");
167887
+ const filesDir = join52(buildDir, "files");
167560
167888
  const packages = new Set;
167561
- for (const file2 of await readdir9(filesDir)) {
167889
+ for (const file2 of await readdir10(filesDir)) {
167562
167890
  if (!file2.endsWith(".mjs")) {
167563
167891
  continue;
167564
167892
  }
167565
- const code = await readFile22(join50(filesDir, file2), "utf8");
167893
+ const code = await readFile22(join52(filesDir, file2), "utf8");
167566
167894
  for (const match3 of code.matchAll(/(?:from\s*|import\()\s*["'](@aws-sdk\/[a-z0-9-]+|sharp)["']/g)) {
167567
167895
  packages.add(match3[1]);
167568
167896
  }
167569
167897
  }
167570
- const projectRequire = createRequire(join50(directories.root, "noop.js"));
167898
+ const projectRequire = createRequire(join52(directories.root, "noop.js"));
167571
167899
  const ownRequire = createRequire(import.meta.url);
167572
167900
  for (const name of packages) {
167573
167901
  try {
@@ -167581,84 +167909,112 @@ var linkSdkPackages = async (buildDir, onWarn) => {
167581
167909
  onWarn?.(`The bundle imports "${name}", which is neither a project dependency nor shipped with the cli. Add it to your project dependencies.`);
167582
167910
  continue;
167583
167911
  }
167584
- const target2 = join50(buildDir, "node_modules", name);
167912
+ const target2 = join52(buildDir, "node_modules", name);
167585
167913
  await mkdir9(dirname22(target2), { recursive: true });
167586
- await rm10(target2, { recursive: true, force: true });
167914
+ await rm11(target2, { recursive: true, force: true });
167587
167915
  await symlink(source, target2, "dir");
167588
167916
  }
167589
167917
  };
167590
167918
 
167591
167919
  // src/dev/index.ts
167920
+ var breakdown = (timings) => {
167921
+ const slow = timings.filter(([, ms]) => ms >= 100).sort((a4, b6) => b6[1] - a4[1]);
167922
+ return slow.map(([name, ms]) => `${name} ${ms < 1000 ? `${Math.round(ms)}ms` : `${(ms / 1000).toFixed(1)}s`}`).join(", ");
167923
+ };
167592
167924
  var LOCAL_ACCOUNT_ID = "000000000000";
167593
167925
  var startDev = async (props) => {
167594
167926
  const log = props.onLog ?? (() => {});
167927
+ const phase = props.phase ?? (async ({ start }, fn) => (log(start), fn(() => {})));
167595
167928
  const { appConfig, stackConfigs } = props;
167596
167929
  const { builders, appId } = createApp({
167597
167930
  appConfig,
167598
167931
  stackConfigs,
167599
- accountId: LOCAL_ACCOUNT_ID
167932
+ accountId: LOCAL_ACCOUNT_ID,
167933
+ dev: true
167600
167934
  });
167601
- await mkdir10(join51(directories.output, "local"), { recursive: true });
167935
+ await mkdir10(join53(directories.output, "local"), { recursive: true });
167602
167936
  const routerPorts = {};
167603
167937
  Object.keys(appConfig.router ?? {}).forEach((id, index) => {
167604
167938
  routerPorts[id] = props.port + 1 + index;
167605
167939
  });
167940
+ const firstBoot = props.pool.peek("session") === undefined;
167606
167941
  props.pool.begin();
167942
+ await props.pool.keep("session", null, async () => ({ value: true, stop: () => {} }));
167607
167943
  const dev = createDevContext({ appConfig, stackConfigs, appId, routerPorts, log, pool: props.pool });
167608
- log("Preparing the local resources...");
167609
- for (const feature of features) {
167610
- await feature.onDev?.(dev.context);
167611
- }
167612
- await props.pool.sweep();
167613
- const blocked = await props.pool.keep("blocked", null, async () => {
167614
- const server = createBlockedServer({ onLog: (message4) => debug(message4) });
167615
- const port = await server.listen();
167616
- return { value: { server, port }, stop: () => server.stop() };
167617
- });
167618
- const blockedPort = blocked.port;
167619
167944
  const bundleName = getBundleFunctionName(appConfig.name);
167620
167945
  const buildDir = getBuildPath("bundle", bundleName, ".");
167621
- const pooledLambda = await props.pool.keep("lambda", bundleName, async () => {
167622
- const server = createLambdaServer({ functionName: bundleName });
167623
- const port = await server.listen();
167624
- return { value: { server, port }, stop: () => server.stop() };
167625
- });
167626
- const lambda = pooledLambda.server;
167627
- const lambdaPort = pooledLambda.port;
167628
- const env4 = {
167629
- APP: appConfig.name,
167630
- APP_ID: appId,
167631
- AWSLESS_ENV: "local",
167632
- AWS_REGION: appConfig.region,
167633
- AWS_ACCOUNT_ID: LOCAL_ACCOUNT_ID,
167634
- AWS_ACCESS_KEY_ID: "local",
167635
- AWS_SECRET_ACCESS_KEY: "local",
167636
- AWS_LAMBDA_FUNCTION_NAME: bundleName,
167637
- AWS_LAMBDA_FUNCTION_VERSION: "local",
167638
- AWS_ENDPOINT_URL: `http://127.0.0.1:${blockedPort}`,
167639
- AWS_ENDPOINT_URL_LAMBDA: `http://127.0.0.1:${lambdaPort}`,
167640
- ...dev.env
167641
- };
167642
- for (const [id, port] of Object.entries(routerPorts)) {
167643
- env4[`ROUTER_${constantCase(id)}_ENDPOINT`] = `localhost:${port}`;
167644
- }
167645
- await writeFile13(join51(directories.output, "local", "env.json"), JSON.stringify(env4, null, "\t") + `
167946
+ const { env: env4, lambda } = await phase({ start: "Preparing the local resources...", done: "Prepared the local resources" }, async (detail) => {
167947
+ const timings = [];
167948
+ for (const feature of features) {
167949
+ if (!feature.onDev) {
167950
+ continue;
167951
+ }
167952
+ const started = Date.now();
167953
+ await feature.onDev(dev.context);
167954
+ timings.push([feature.name, Date.now() - started]);
167955
+ }
167956
+ detail(breakdown(timings));
167957
+ props.pool.retain("blocked");
167958
+ props.pool.retain("lambda");
167959
+ await props.pool.sweep();
167960
+ const blocked = await props.pool.keep("blocked", null, async () => {
167961
+ const server = createBlockedServer({ onLog: (message4) => debug(message4) });
167962
+ const port = await server.listen();
167963
+ return { value: { server, port }, stop: () => server.stop() };
167964
+ });
167965
+ const blockedPort = blocked.port;
167966
+ const pooledLambda = await props.pool.keep("lambda", bundleName, async () => {
167967
+ const server = createLambdaServer({ functionName: bundleName });
167968
+ const port = await server.listen();
167969
+ return { value: { server, port }, stop: () => server.stop() };
167970
+ });
167971
+ const lambda2 = pooledLambda.server;
167972
+ const lambdaPort = pooledLambda.port;
167973
+ const env5 = {
167974
+ APP: appConfig.name,
167975
+ APP_ID: appId,
167976
+ AWSLESS_ENV: "local",
167977
+ AWS_REGION: appConfig.region,
167978
+ AWS_ACCOUNT_ID: LOCAL_ACCOUNT_ID,
167979
+ AWS_ACCESS_KEY_ID: "local",
167980
+ AWS_SECRET_ACCESS_KEY: "local",
167981
+ AWS_LAMBDA_FUNCTION_NAME: bundleName,
167982
+ AWS_LAMBDA_FUNCTION_VERSION: "local",
167983
+ AWS_ENDPOINT_URL: `http://127.0.0.1:${blockedPort}`,
167984
+ AWS_ENDPOINT_URL_LAMBDA: `http://127.0.0.1:${lambdaPort}`,
167985
+ ...dev.env
167986
+ };
167987
+ for (const [id, port] of Object.entries(routerPorts)) {
167988
+ env5[`ROUTER_${constantCase(id)}_ENDPOINT`] = `localhost:${port}`;
167989
+ }
167990
+ await writeFile13(join53(directories.output, "local", "env.json"), JSON.stringify(env5, null, "\t") + `
167646
167991
  `);
167992
+ return { env: env5, lambda: lambda2 };
167993
+ });
167647
167994
  const sorted = [...builders].sort((a4, b6) => Number(a4.type === "bundle") - Number(b6.type === "bundle"));
167648
- const workspace = await loadWorkspace3(directories.root);
167995
+ let workspace;
167649
167996
  const buildAll = async () => {
167997
+ let changed = false;
167650
167998
  for (const builder of sorted) {
167651
- await build(builder.type, builder.name, builder.builder, { workspace });
167999
+ const meta = await build(builder.type, builder.name, builder.builder, { workspace });
168000
+ if (!meta?.cached) {
168001
+ changed = true;
168002
+ }
168003
+ debug(`Build ${builder.type}:${builder.name}`, meta?.cached ? "cached" : String(meta?.buildTime));
167652
168004
  }
167653
168005
  await writeFile13(getBuildPath("bundle", bundleName, "files/awsless-env.mjs"), `export default {}
167654
168006
  `);
167655
168007
  await linkSdkPackages(buildDir, log);
168008
+ return changed;
167656
168009
  };
167657
- log("Building the bundle...");
167658
- await buildAll();
168010
+ await phase({ start: "Building the bundle...", done: "Built the bundle" }, async () => {
168011
+ workspace = await loadWorkspace3(directories.root);
168012
+ await buildAll();
168013
+ });
167659
168014
  const worker = createBundleWorker({ buildDir, env: env4, functionName: bundleName });
167660
168015
  let dirty = false;
167661
168016
  let fresh;
168017
+ let restartNeeded = false;
167662
168018
  const ensureFresh = async () => {
167663
168019
  if (!dirty) {
167664
168020
  return;
@@ -167667,9 +168023,16 @@ var startDev = async (props) => {
167667
168023
  dirty = false;
167668
168024
  const started = Date.now();
167669
168025
  try {
167670
- await buildAll();
167671
- await worker.restart();
167672
- log(`Reloaded the bundle in ${Date.now() - started}ms`);
168026
+ const changed = await buildAll();
168027
+ const built = Date.now();
168028
+ if (changed || restartNeeded) {
168029
+ restartNeeded = true;
168030
+ await worker.restart();
168031
+ restartNeeded = false;
168032
+ log(`Reloaded the bundle in ${Date.now() - started}ms (build ${built - started}ms, worker ${Date.now() - built}ms)`);
168033
+ } else {
168034
+ debug(`Rebuild found no changes in ${Date.now() - started}ms`);
168035
+ }
167673
168036
  } catch (error3) {
167674
168037
  dirty = true;
167675
168038
  throw error3;
@@ -167689,17 +168052,33 @@ var startDev = async (props) => {
167689
168052
  log
167690
168053
  });
167691
168054
  lambda.connect(dispatch, reportFailure);
167692
- log("Starting the local servers...");
167693
- for (const server of dev.servers) {
167694
- await server.start({ dispatch, log, reportFailure, env: env4 });
167695
- }
167696
- log("Starting the bundle worker...");
167697
- try {
167698
- await worker.start();
167699
- } catch (error3) {
167700
- log(`The bundle worker failed to start: ${error3 instanceof Error ? error3.message : String(error3)}`);
167701
- log("Fix the error - the next invoke retries.");
167702
- dirty = true;
168055
+ await phase({ start: "Starting the local servers...", done: "Started the local servers" }, async (detail) => {
168056
+ const timings = [];
168057
+ for (const server of dev.servers) {
168058
+ const started = Date.now();
168059
+ await server.start({ dispatch, log, reportFailure, env: env4 });
168060
+ timings.push([server.name, Date.now() - started]);
168061
+ }
168062
+ detail(breakdown(timings));
168063
+ });
168064
+ await phase({ start: "Starting the bundle worker...", done: "Started the bundle worker" }, async () => {
168065
+ try {
168066
+ await worker.start();
168067
+ } catch (error3) {
168068
+ log(`The bundle worker failed to start: ${error3 instanceof Error ? error3.message : String(error3)}`);
168069
+ log("Fix the error - the next invoke retries.");
168070
+ dirty = true;
168071
+ restartNeeded = true;
168072
+ }
168073
+ });
168074
+ const seeder = createSeedRunner({ stackConfigs, env: env4 });
168075
+ const resetData = createDataReset({ pool: props.pool, appConfig, stackConfigs });
168076
+ if (firstBoot && seeder.count > 0 && !dirty) {
168077
+ try {
168078
+ await phase({ start: "Seeding the local data...", done: "Seeded the local data" }, () => seeder.run());
168079
+ } catch (error3) {
168080
+ log(`Seeding failed: ${error3 instanceof Error ? error3.message : String(error3)}`);
168081
+ }
167703
168082
  }
167704
168083
  const routers = [];
167705
168084
  for (const [id, port] of Object.entries(routerPorts)) {
@@ -167711,14 +168090,22 @@ var startDev = async (props) => {
167711
168090
  }
167712
168091
  const dashboardPort = props.port;
167713
168092
  const dashboard = createDashboardServer({
168093
+ runSeeds: seeder.count > 0 ? async () => {
168094
+ await resetData();
168095
+ await seeder.run();
168096
+ } : undefined,
167714
168097
  app: appConfig.name,
167715
168098
  region: appConfig.region,
167716
168099
  routerPorts,
167717
168100
  resources: dev.resources,
167718
168101
  routes: dev.routes,
167719
168102
  env: env4,
167720
- storeRoot: join51(directories.output, "local", "store"),
167721
- configFile: join51(directories.output, "local", "config.json"),
168103
+ storeRoot: join53(directories.output, "local", "store"),
168104
+ configFile: join53(directories.output, "local", "config.json"),
168105
+ configMeta: {
168106
+ defaults: appConfig.configDefaults ?? {},
168107
+ secrets: appConfig.configSecrets ?? []
168108
+ },
167722
168109
  events: dev.events
167723
168110
  });
167724
168111
  dashboard.connect(dispatch);
@@ -167728,8 +168115,15 @@ var startDev = async (props) => {
167728
168115
  ignoreInitial: true,
167729
168116
  awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 }
167730
168117
  });
168118
+ let rebuildTimer;
167731
168119
  watcher.on("all", () => {
167732
168120
  dirty = true;
168121
+ clearTimeout(rebuildTimer);
168122
+ rebuildTimer = setTimeout(() => {
168123
+ ensureFresh().catch((error3) => {
168124
+ debug("Background rebuild failed", error3);
168125
+ });
168126
+ }, 150);
167733
168127
  });
167734
168128
  const restartWatcher = dev.restartPaths.length > 0 ? $watch(dev.restartPaths, { ignoreInitial: true }) : undefined;
167735
168129
  restartWatcher?.on("all", () => {
@@ -167740,7 +168134,8 @@ var startDev = async (props) => {
167740
168134
  dashboardPort,
167741
168135
  routerPorts,
167742
168136
  async stop() {
167743
- await rm11(join51(directories.output, "local", "env.json"), { force: true });
168137
+ await rm12(join53(directories.output, "local", "env.json"), { force: true });
168138
+ clearTimeout(rebuildTimer);
167744
168139
  await watcher.close();
167745
168140
  await restartWatcher?.close();
167746
168141
  for (const router of routers) {
@@ -167804,7 +168199,7 @@ var createServerPool = () => {
167804
168199
 
167805
168200
  // src/type-gen/generate.ts
167806
168201
  import { mkdir as mkdir11, writeFile as writeFile14 } from "fs/promises";
167807
- import { dirname as dirname23, join as join52, relative as relative11 } from "path";
168202
+ import { dirname as dirname23, join as join54, relative as relative11 } from "path";
167808
168203
  var generateTypes = async (props) => {
167809
168204
  const files = [];
167810
168205
  await Promise.all(features.map((feature) => {
@@ -167812,7 +168207,7 @@ var generateTypes = async (props) => {
167812
168207
  ...props,
167813
168208
  async write(file2, data, include = false) {
167814
168209
  const code = data?.toString("utf8");
167815
- const path6 = join52(directories.types, file2);
168210
+ const path6 = join54(directories.types, file2);
167816
168211
  if (code) {
167817
168212
  if (include) {
167818
168213
  files.push(relative11(directories.root, path6));
@@ -167826,7 +168221,7 @@ var generateTypes = async (props) => {
167826
168221
  if (files.length) {
167827
168222
  const code = files.map((file2) => `/// <reference path='${file2}' />`).join(`
167828
168223
  `);
167829
- await writeFile14(join52(directories.root, `awsless.d.ts`), code);
168224
+ await writeFile14(join54(directories.root, `awsless.d.ts`), code);
167830
168225
  }
167831
168226
  };
167832
168227
 
@@ -167856,25 +168251,34 @@ var dev = (program3) => {
167856
168251
  };
167857
168252
  const start = async (appConfig = props.appConfig, stackConfigs = props.stackConfigs) => {
167858
168253
  let logger = (message4) => logs_exports.step(message4);
167859
- instance = await logs_exports.task({
167860
- initialMessage: "Starting the local dev environment...",
167861
- successMessage: "Local dev environment ready.",
167862
- errorMessage: "Failed to start the local dev environment.",
167863
- task: ({ updateMessage }) => {
167864
- logger = updateMessage;
167865
- return startDev({
167866
- appConfig,
167867
- stackConfigs,
167868
- port,
167869
- pool,
167870
- onLog(message4) {
167871
- logger(message4);
167872
- }
167873
- }).finally(() => {
167874
- logger = (message4) => logs_exports.step(message4);
167875
- });
168254
+ const phase = async (titles, fn) => {
168255
+ const time2 = createTimer();
168256
+ let detail = "";
168257
+ return logs_exports.task({
168258
+ initialMessage: titles.start,
168259
+ errorMessage: `${titles.start.replace(/\.\.\.$/, "")} failed.`,
168260
+ task: ({ updateMessage, updateSuccessMessage }) => {
168261
+ logger = updateMessage;
168262
+ return fn((text2) => detail = text2).then((result) => {
168263
+ updateSuccessMessage(`${titles.done} in ${time2()}${detail ? color2.dim(` (${detail})`) : ""}`);
168264
+ return result;
168265
+ }).finally(() => {
168266
+ logger = (message4) => logs_exports.step(message4);
168267
+ });
168268
+ }
168269
+ });
168270
+ };
168271
+ instance = await startDev({
168272
+ appConfig,
168273
+ stackConfigs,
168274
+ port,
168275
+ pool,
168276
+ phase,
168277
+ onLog(message4) {
168278
+ logger(message4);
167876
168279
  }
167877
168280
  });
168281
+ logs_exports.success("Local dev environment ready.");
167878
168282
  logs_exports.list("Endpoints", {
167879
168283
  Dashboard: color2.info(`http://localhost:${instance.dashboardPort}`),
167880
168284
  ...Object.fromEntries(Object.entries(instance.routerPorts).map(([id, routerPort]) => [
@@ -167971,7 +168375,7 @@ import { DynamoDBClient as DynamoDBClient6, dynamoDBClient } from "@awsless/dyna
167971
168375
 
167972
168376
  // ../../node_modules/.pnpm/@aws-sdk+client-iot-data-plane@3.1023.0_aws-crt@1.30.0_supports-color@8.1.1_/node_modules/@aws-sdk/client-iot-data-plane/dist-es/IoTDataPlaneClient.js
167973
168377
  var import_core50 = __toESM(require_dist_cjs2(), 1);
167974
- var import_schema67 = __toESM(require_schema(), 1);
168378
+ var import_schema68 = __toESM(require_schema(), 1);
167975
168379
 
167976
168380
  // ../../node_modules/.pnpm/@aws-sdk+client-iot-data-plane@3.1023.0_aws-crt@1.30.0_supports-color@8.1.1_/node_modules/@aws-sdk/client-iot-data-plane/dist-es/auth/httpAuthSchemeProvider.js
167977
168381
  var import_httpAuthSchemes4 = __toESM(require_httpAuthSchemes(), 1);
@@ -168177,7 +168581,7 @@ var defaultEndpointResolver3 = (endpointParams, context = {}) => {
168177
168581
  customEndpointFunctions.aws = awsEndpointFunctions;
168178
168582
 
168179
168583
  // ../../node_modules/.pnpm/@aws-sdk+client-iot-data-plane@3.1023.0_aws-crt@1.30.0_supports-color@8.1.1_/node_modules/@aws-sdk/client-iot-data-plane/dist-es/schemas/schemas_0.js
168180
- var import_schema66 = __toESM(require_schema(), 1);
168584
+ var import_schema67 = __toESM(require_schema(), 1);
168181
168585
 
168182
168586
  // ../../node_modules/.pnpm/@aws-sdk+client-iot-data-plane@3.1023.0_aws-crt@1.30.0_supports-color@8.1.1_/node_modules/@aws-sdk/client-iot-data-plane/dist-es/models/IoTDataPlaneServiceException.js
168183
168587
  class IoTDataPlaneServiceException extends ServiceException {
@@ -168407,10 +168811,10 @@ var _xamcd = "x-amz-mqtt5-correlation-data";
168407
168811
  var _xampfi = "x-amz-mqtt5-payload-format-indicator";
168408
168812
  var _xamup = "x-amz-mqtt5-user-properties";
168409
168813
  var n03 = "com.amazonaws.iotdataplane";
168410
- var _s_registry3 = import_schema66.TypeRegistry.for(_s3);
168814
+ var _s_registry3 = import_schema67.TypeRegistry.for(_s3);
168411
168815
  var IoTDataPlaneServiceException$ = [-3, _s3, "IoTDataPlaneServiceException", 0, [], []];
168412
168816
  _s_registry3.registerError(IoTDataPlaneServiceException$, IoTDataPlaneServiceException);
168413
- var n0_registry3 = import_schema66.TypeRegistry.for(n03);
168817
+ var n0_registry3 = import_schema67.TypeRegistry.for(n03);
168414
168818
  var ConflictException$ = [
168415
168819
  -3,
168416
168820
  n03,
@@ -168846,7 +169250,7 @@ class IoTDataPlaneClient extends Client {
168846
169250
  const _config_7 = resolveHttpAuthSchemeConfig3(_config_6);
168847
169251
  const _config_8 = resolveRuntimeExtensions3(_config_7, configuration?.extensions || []);
168848
169252
  this.config = _config_8;
168849
- this.middlewareStack.use(import_schema67.getSchemaSerdePlugin(this.config));
169253
+ this.middlewareStack.use(import_schema68.getSchemaSerdePlugin(this.config));
168850
169254
  this.middlewareStack.use(getUserAgentPlugin(this.config));
168851
169255
  this.middlewareStack.use(getRetryPlugin(this.config));
168852
169256
  this.middlewareStack.use(getContentLengthPlugin(this.config));
@@ -183447,12 +183851,12 @@ var state = (program3) => {
183447
183851
 
183448
183852
  // src/cli/command/test.ts
183449
183853
  import { mkdir as mkdir12, writeFile as writeFile15 } from "fs/promises";
183450
- import { join as join54 } from "path";
183854
+ import { join as join56 } from "path";
183451
183855
 
183452
183856
  // src/test/manifest.ts
183453
- import { isAbsolute as isAbsolute4, join as join53 } from "path";
183857
+ import { isAbsolute as isAbsolute5, join as join55 } from "path";
183454
183858
  var absolute = (file2) => {
183455
- return isAbsolute4(file2) ? file2 : join53(directories.root, file2);
183859
+ return isAbsolute5(file2) ? file2 : join55(directories.root, file2);
183456
183860
  };
183457
183861
  var createTestManifest = (appConfig, stackConfigs) => {
183458
183862
  const manifest = {
@@ -183548,7 +183952,7 @@ var test2 = (program3) => {
183548
183952
  return "No tests found.";
183549
183953
  }
183550
183954
  const manifest = createTestManifest(props.appConfig, props.stackConfigs);
183551
- const manifestFile = join54(directories.output, "test", "manifest.json");
183955
+ const manifestFile = join56(directories.output, "test", "manifest.json");
183552
183956
  let redis;
183553
183957
  let killSearch;
183554
183958
  manifest.servers = {};
@@ -183575,7 +183979,7 @@ var test2 = (program3) => {
183575
183979
  await redis.ping();
183576
183980
  manifest.servers.redis = { host: "127.0.0.1", port: await redis.getPort() };
183577
183981
  }
183578
- await mkdir12(join54(directories.output, "test"), { recursive: true });
183982
+ await mkdir12(join56(directories.output, "test"), { recursive: true });
183579
183983
  await writeFile15(manifestFile, JSON.stringify(manifest));
183580
183984
  process.env.AWSLESS_TEST_MANIFEST = manifestFile;
183581
183985
  let passed = false;