@awsless/cli 0.1.5 → 0.1.7

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
@@ -108402,6 +108402,7 @@ import { join, normalize, relative } from "path";
108402
108402
  var root = process.cwd();
108403
108403
  var directories = {
108404
108404
  root,
108405
+ buildFolder: "build",
108405
108406
  get output() {
108406
108407
  return join(this.root, ".awsless");
108407
108408
  },
@@ -108412,7 +108413,7 @@ var directories = {
108412
108413
  return join(this.output, "state");
108413
108414
  },
108414
108415
  get build() {
108415
- return join(this.output, "build");
108416
+ return join(this.output, this.buildFolder);
108416
108417
  },
108417
108418
  get types() {
108418
108419
  return join(this.output, "types");
@@ -108427,6 +108428,9 @@ var directories = {
108427
108428
  var setRoot = (path = root) => {
108428
108429
  directories.root = path;
108429
108430
  };
108431
+ var useDevBuildDir = () => {
108432
+ directories.buildFolder = "build-dev";
108433
+ };
108430
108434
  var findRootDir = async (path, configFiles, level = 5) => {
108431
108435
  if (!level) {
108432
108436
  throw new TypeError("No awsless project found");
@@ -143957,6 +143961,14 @@ var createOpenSearchProvider = ({ credentials: credentials2, region }) => {
143957
143961
  async updateResource(props) {
143958
143962
  return apply(props.proposedState);
143959
143963
  },
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
+ },
143960
143972
  async deleteResource(props) {
143961
143973
  const state = inputSchema.parse(props.state);
143962
143974
  await getClient(state.endpoint).indices.delete({ index: state.index }, { ignore: [404] });
@@ -151126,7 +151138,7 @@ var JobDefaultSchema = exports_external.object({
151126
151138
 
151127
151139
  // src/feature/search/schema.ts
151128
151140
  var VersionSchema = exports_external.union([
151129
- exports_external.enum(["2.13", "2.11", "2.9", "2.7", "2.5", "2.3", "1.3"]),
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"]),
151130
151142
  exports_external.string()
151131
151143
  ]).describe("Specify the OpenSearch engine version.");
151132
151144
  var TypeSchema = exports_external.union([
@@ -151168,7 +151180,7 @@ var StorageSizeSchema = SizeSchema.refine(sizeMin(gibibytes(10)), "Minimum stora
151168
151180
  var SearchDefaultSchema = exports_external.object({
151169
151181
  type: TypeSchema.default("t3.small"),
151170
151182
  count: CountSchema.default(1),
151171
- version: VersionSchema.default("2.13"),
151183
+ version: VersionSchema.default("3.7"),
151172
151184
  storage: StorageSizeSchema.default("10 GB")
151173
151185
  }).strict().default({}).describe("Configure the shared OpenSearch domain that backs every search index in your app.");
151174
151186
  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".');
@@ -158858,7 +158870,8 @@ var searchFeature = defineFeature({
158858
158870
  })
158859
158871
  }, {
158860
158872
  retainOnDelete: ctx.appConfig.removal === "retain",
158861
- import: ctx.import ? name : undefined
158873
+ import: ctx.import ? name : undefined,
158874
+ replaceOnChanges: ["engineVersion", "domainName"]
158862
158875
  });
158863
158876
  ctx.addEnv("SEARCH_DOMAIN", openSearch.endpointV2);
158864
158877
  ctx.addPermission({
@@ -158872,7 +158885,8 @@ var searchFeature = defineFeature({
158872
158885
  mappings: JSON.stringify(resolveSearchMappings(indexProps) ?? {}),
158873
158886
  settings: JSON.stringify(indexProps.settings ?? {})
158874
158887
  }, {
158875
- retainOnDelete: ctx.appConfig.removal === "retain"
158888
+ retainOnDelete: ctx.appConfig.removal === "retain",
158889
+ replaceOnChanges: ["endpoint", "index"]
158876
158890
  });
158877
158891
  }
158878
158892
  }
@@ -164570,11 +164584,16 @@ var startTest = async (props) => {
164570
164584
  }
164571
164585
  for (const error3 of module.errors()) {
164572
164586
  const stack = error3.stacks?.[0];
164587
+ const { code, syscall, address, port } = error3;
164573
164588
  errors3.push({
164574
164589
  type: error3.name,
164575
164590
  message: error3.message,
164576
164591
  location: stack ? { line: stack.line, column: stack.column } : undefined,
164577
- stack: error3.stack
164592
+ stack: error3.stack,
164593
+ code,
164594
+ syscall,
164595
+ address,
164596
+ port
164578
164597
  });
164579
164598
  }
164580
164599
  }
@@ -164670,6 +164689,11 @@ var logTestErrors = (event) => {
164670
164689
  }
164671
164690
  }
164672
164691
  };
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
+ };
164673
164697
  var runTest = async (stack, dir, filters, workspace, opts) => {
164674
164698
  await mkdir8(directories.test, { recursive: true });
164675
164699
  const file2 = join49(directories.test, `${stack}.json`);
@@ -164697,15 +164721,26 @@ var runTest = async (stack, dir, filters, workspace, opts) => {
164697
164721
  initialMessage: `Run tests for the ${color2.info(stack)} stack`,
164698
164722
  errorMessage: `Running tests for the ${color2.info(stack)} stack failed`,
164699
164723
  async task(ctx) {
164700
- const result2 = await startTest({
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({
164701
164726
  dir,
164702
164727
  filters,
164703
164728
  env: opts.env
164704
164729
  });
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
+ }
164705
164741
  if (result2.errors.length > 0) {
164706
164742
  for (const error3 of result2.errors) {
164707
- debug(`Module error in ${stack} tests: ${error3.message}
164708
- ${error3.stack ?? "(no stack captured)"}`);
164743
+ debug(`Module error in ${stack} tests: ${formatModuleError(error3)}`);
164709
164744
  }
164710
164745
  throw result2.errors.map((error3) => new ExpectedError(error3.type && error3.type !== "Error" ? `${error3.type}: ${error3.message}` : error3.message));
164711
164746
  }
@@ -167237,8 +167272,10 @@ var createBundleWorker = (props) => {
167237
167272
  }
167238
167273
  }
167239
167274
  }
167240
- process[stream2].write((route ? `[${route}] ` : "") + text2 + `
167275
+ if (!props.quiet?.()) {
167276
+ process[stream2].write((route ? `[${route}] ` : "") + text2 + `
167241
167277
  `);
167278
+ }
167242
167279
  const clean = stripAnsi2(text2);
167243
167280
  if (clean.trim() !== "") {
167244
167281
  props.onOutput?.(clean, stream2, route);
@@ -167415,6 +167452,13 @@ var dashboardHtml = `<!doctype html>
167415
167452
  cursor: pointer;
167416
167453
  }
167417
167454
  nav h1 span { color: var(--muted); font-weight: normal; }
167455
+ nav h3 {
167456
+ font-size: 10px;
167457
+ text-transform: uppercase;
167458
+ letter-spacing: 1px;
167459
+ color: var(--muted);
167460
+ margin: 14px 8px 4px;
167461
+ }
167418
167462
  nav button {
167419
167463
  display: flex;
167420
167464
  align-items: center;
@@ -167636,6 +167680,16 @@ const GROUPS = [
167636
167680
  ['route', 'Routes'],
167637
167681
  ]
167638
167682
 
167683
+ // The nav renders the features in scannable sections - a section only
167684
+ // shows when the app declares at least one of its resources.
167685
+ const SECTIONS = [
167686
+ ['Web', ['site', 'rpc', 'rest', 'route', 'image', 'icon']],
167687
+ ['Compute', ['function', 'cron', 'task', 'instance']],
167688
+ ['Messaging', ['queue', 'topic', 'subscriber', 'pubsub']],
167689
+ ['Storage', ['table', 'cache', 'search', 'store']],
167690
+ ['App', ['config', 'auth', 'email', 'worker']],
167691
+ ]
167692
+
167639
167693
  const svg = inner =>
167640
167694
  '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' + inner + '</svg>'
167641
167695
 
@@ -168640,19 +168694,37 @@ const render = () => {
168640
168694
  nav.innerHTML = '<h1>awsless <span>dev</span></h1>'
168641
168695
  nav.querySelector('h1').onclick = showHome
168642
168696
 
168643
- for (const [kind, title] of GROUPS) {
168697
+ const navButton = kind => {
168644
168698
  const count = state.resources.filter(r => r.kind === kind).length
168645
- if (count === 0) continue
168699
+ if (count === 0) return null
168646
168700
 
168647
168701
  // The email outbox is a single feed, so a count of registered
168648
168702
  // resources would only confuse.
168649
168703
  const button = $('button', { className: view.kind === kind ? 'active' : '' }, [
168650
168704
  icon(kind),
168651
- title,
168705
+ groupTitle(kind),
168652
168706
  kind === 'email' || kind === 'worker' ? '' : $('span', { className: 'count' }, String(count)),
168653
168707
  ])
168654
168708
  button.onclick = () => selectKind(kind)
168655
- nav.append(button)
168709
+ return button
168710
+ }
168711
+
168712
+ const sectioned = new Set(SECTIONS.flatMap(([, kinds]) => kinds))
168713
+
168714
+ for (const [label, kinds] of SECTIONS) {
168715
+ const buttons = kinds.map(navButton).filter(Boolean)
168716
+ if (buttons.length === 0) continue
168717
+
168718
+ nav.append($('h3', {}, label), ...buttons)
168719
+ }
168720
+
168721
+ // A kind without a section still shows, so new features never
168722
+ // silently vanish from the nav.
168723
+ for (const [kind] of GROUPS) {
168724
+ if (sectioned.has(kind)) continue
168725
+
168726
+ const button = navButton(kind)
168727
+ if (button) nav.append(button)
168656
168728
  }
168657
168729
 
168658
168730
  if (state.seeds) {
@@ -169311,6 +169383,7 @@ var startDev = async (props) => {
169311
169383
  const log = props.onLog ?? (() => {});
169312
169384
  const phase = props.phase ?? (async ({ start }, fn) => (log(start), fn(() => {})));
169313
169385
  const { appConfig, stackConfigs } = props;
169386
+ useDevBuildDir();
169314
169387
  const { builders, appId, ready: ready2 } = createApp({
169315
169388
  appConfig,
169316
169389
  stackConfigs,
@@ -169400,10 +169473,12 @@ var startDev = async (props) => {
169400
169473
  const emitWorkerLine = (line, error3 = false, route) => {
169401
169474
  dev.events.emit("worker", { date: Date.now(), line, error: error3, route });
169402
169475
  };
169476
+ let seeding = false;
169403
169477
  const worker = createBundleWorker({
169404
169478
  buildDir,
169405
169479
  env: env4,
169406
169480
  functionName: bundleName,
169481
+ quiet: () => seeding,
169407
169482
  onOutput: (line, stream2, route) => emitWorkerLine(line, stream2 === "stderr", route)
169408
169483
  });
169409
169484
  dev.resources.push({
@@ -169480,10 +169555,18 @@ var startDev = async (props) => {
169480
169555
  });
169481
169556
  const seeder = createSeedRunner({ seed: appConfig.seed, env: env4 });
169482
169557
  const resetData = createDataReset({ pool: props.pool, stackConfigs });
169558
+ const runSeed = async () => {
169559
+ seeding = true;
169560
+ try {
169561
+ await seeder.run();
169562
+ } finally {
169563
+ seeding = false;
169564
+ }
169565
+ };
169483
169566
  if (firstBoot && seeder.enabled && !dirty) {
169484
169567
  try {
169485
169568
  await phase({ start: "Seeding the local data...", done: "Seeded the local data" }, async () => {
169486
- await seeder.run();
169569
+ await runSeed();
169487
169570
  });
169488
169571
  } catch (error3) {
169489
169572
  log(`Seeding failed: ${error3 instanceof Error ? error3.message : String(error3)}`);
@@ -169507,7 +169590,7 @@ var startDev = async (props) => {
169507
169590
  const dashboard = createDashboardServer({
169508
169591
  runSeeds: seeder.enabled ? async () => {
169509
169592
  await resetData();
169510
- await seeder.run();
169593
+ await runSeed();
169511
169594
  } : undefined,
169512
169595
  app: appConfig.name,
169513
169596
  region: appConfig.region,
@@ -9948,19 +9948,25 @@ import { stringify } from "@awsless/json";
9948
9948
  import { lambda } from "@awsless/lambda";
9949
9949
  import { maxLength, minLength, object, optional, pipe, string, unknown } from "@awsless/validate";
9950
9950
  import { getRouteEnv } from "awsless";
9951
- var createClient = () => {
9952
- return new import_ioredis.Redis({
9953
- host: getRouteEnv("REDIS_HOST"),
9954
- port: Number(getRouteEnv("REDIS_PORT") ?? 6379),
9955
- tls: getRouteEnv("REDIS_TLS") === "disabled" ? undefined : {},
9956
- lazyConnect: true,
9957
- keepAlive: 0,
9958
- noDelay: true,
9959
- enableReadyCheck: false,
9960
- maxRetriesPerRequest: 3,
9961
- connectTimeout: 5000,
9962
- commandTimeout: 5000
9963
- });
9951
+ var client;
9952
+ var getClient = () => {
9953
+ if (!client || client.status === "end") {
9954
+ client = new import_ioredis.Redis({
9955
+ host: getRouteEnv("REDIS_HOST"),
9956
+ port: Number(getRouteEnv("REDIS_PORT") ?? 6379),
9957
+ tls: getRouteEnv("REDIS_TLS") === "disabled" ? undefined : {},
9958
+ lazyConnect: true,
9959
+ keepAlive: 0,
9960
+ noDelay: true,
9961
+ enableReadyCheck: false,
9962
+ maxRetriesPerRequest: 10,
9963
+ retryStrategy: (times) => times > 20 ? null : Math.min(times * 250, 2000),
9964
+ connectTimeout: 1e4,
9965
+ commandTimeout: 1e4
9966
+ });
9967
+ client.on("error", () => {});
9968
+ }
9969
+ return client;
9964
9970
  };
9965
9971
  var handle_default = lambda({
9966
9972
  schema: object({
@@ -9968,14 +9974,8 @@ var handle_default = lambda({
9968
9974
  event: pipe(string(), minLength(1), maxLength(64)),
9969
9975
  payload: optional(unknown())
9970
9976
  }),
9971
- async handle({ topic, event, payload }, ctx) {
9972
- const redis = createClient();
9973
- ctx.onFinally(() => {
9974
- return redis.quit().catch(() => {
9975
- redis.disconnect();
9976
- });
9977
- });
9978
- await redis.publish(getRouteEnv("CHANNEL"), stringify({
9977
+ async handle({ topic, event, payload }) {
9978
+ await getClient().publish(getRouteEnv("CHANNEL"), stringify({
9979
9979
  topic,
9980
9980
  event,
9981
9981
  payload: typeof payload === "undefined" ? undefined : stringify(payload)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awsless/cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -48,9 +48,9 @@
48
48
  "@awsless/json": "^0.0.11",
49
49
  "@awsless/lambda": "^0.0.48",
50
50
  "@awsless/s3": "^0.0.22",
51
- "@awsless/weak-cache": "^0.0.1",
52
51
  "@awsless/validate": "^0.2.0",
53
- "awsless": "^0.1.3"
52
+ "@awsless/weak-cache": "^0.0.1",
53
+ "awsless": "^0.1.4"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@aws-sdk/client-cloudformation": "^3.369.0",
@@ -108,25 +108,25 @@
108
108
  "zip-a-folder": "^3.1.6",
109
109
  "zod": "^3.24.2",
110
110
  "zod-to-json-schema": "^3.24.3",
111
- "@awsless/big-float": "^0.1.7",
112
- "@awsless/cloudwatch": "^0.0.1",
113
111
  "@awsless/clui": "^0.0.9",
112
+ "@awsless/cloudwatch": "^0.0.1",
114
113
  "@awsless/duration": "^0.0.4",
115
- "@awsless/dynamodb": "^0.3.23",
114
+ "@awsless/big-float": "^0.1.7",
116
115
  "@awsless/iot": "^0.0.5",
117
- "@awsless/lambda": "^0.0.48",
116
+ "@awsless/dynamodb": "^0.3.23",
117
+ "@awsless/json": "^0.0.11",
118
118
  "@awsless/open-search": "^0.0.28",
119
119
  "@awsless/redis": "^0.1.13",
120
- "@awsless/size": "^0.0.2",
120
+ "@awsless/s3": "^0.0.22",
121
121
  "@awsless/scheduler": "^0.0.5",
122
- "@awsless/sns": "^0.0.11",
122
+ "@awsless/size": "^0.0.2",
123
+ "@awsless/lambda": "^0.0.48",
123
124
  "@awsless/sqs": "^0.0.24",
124
- "@awsless/json": "^0.0.11",
125
- "@awsless/s3": "^0.0.22",
126
- "@awsless/validate": "^0.2.0",
127
- "@awsless/weak-cache": "^0.0.1",
128
125
  "@awsless/ssm": "^0.0.8",
129
- "awsless": "^0.1.3"
126
+ "@awsless/sns": "^0.0.11",
127
+ "@awsless/weak-cache": "^0.0.1",
128
+ "@awsless/validate": "^0.2.0",
129
+ "awsless": "^0.1.4"
130
130
  },
131
131
  "scripts": {
132
132
  "test": "bun cli/build-handlers.ts && pnpm vitest",