@effortless-aws/cli 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli/index.js +416 -286
  2. package/package.json +2 -2
package/dist/cli/index.js CHANGED
@@ -6,15 +6,15 @@ var __export = (target, all) => {
6
6
  };
7
7
 
8
8
  // src/cli/index.ts
9
- import { Command as Command7 } from "@effect/cli";
9
+ import { CliConfig, Command as Command7 } from "@effect/cli";
10
10
  import { NodeContext, NodeRuntime } from "@effect/platform-node";
11
- import { Effect as Effect43 } from "effect";
11
+ import { Effect as Effect45 } from "effect";
12
12
  import { createRequire as createRequire2 } from "module";
13
13
 
14
14
  // src/cli/commands/deploy.ts
15
15
  import { Args, Command } from "@effect/cli";
16
- import { Effect as Effect36, Console as Console3, Logger, LogLevel, Option } from "effect";
17
- import * as path8 from "path";
16
+ import { Effect as Effect38, Console as Console3, Logger, LogLevel, Option } from "effect";
17
+ import * as path9 from "path";
18
18
 
19
19
  // src/deploy/deploy.ts
20
20
  import { Effect as Effect35, Console as Console2 } from "effect";
@@ -2628,6 +2628,64 @@ var readProductionDependencies = (projectDir) => Effect18.gen(function* () {
2628
2628
  const pkg = JSON.parse(content);
2629
2629
  return Object.keys(pkg.dependencies ?? {});
2630
2630
  });
2631
+ var DEV_ONLY_PACKAGES = /* @__PURE__ */ new Set([
2632
+ "typescript",
2633
+ "ts-node",
2634
+ "tsx",
2635
+ "vitest",
2636
+ "jest",
2637
+ "mocha",
2638
+ "eslint",
2639
+ "prettier",
2640
+ "tsup",
2641
+ "esbuild",
2642
+ "webpack",
2643
+ "rollup",
2644
+ "vite",
2645
+ "turbo",
2646
+ "husky",
2647
+ "lint-staged",
2648
+ "commitlint",
2649
+ "nodemon",
2650
+ "ts-jest",
2651
+ "concurrently",
2652
+ "rimraf"
2653
+ ]);
2654
+ var DEV_ONLY_PREFIXES = [
2655
+ "@types/",
2656
+ "@typescript-eslint/",
2657
+ "@eslint/",
2658
+ "eslint-plugin-",
2659
+ "eslint-config-",
2660
+ "@vitest/",
2661
+ "@jest/"
2662
+ ];
2663
+ var checkDependencyWarnings = (projectDir) => Effect18.gen(function* () {
2664
+ const pkgPath = path.join(projectDir, "package.json");
2665
+ const content = yield* Effect18.tryPromise({
2666
+ try: () => fs.readFile(pkgPath, "utf-8"),
2667
+ catch: () => Effect18.succeed(null)
2668
+ });
2669
+ if (!content) return [];
2670
+ const pkg = JSON.parse(content);
2671
+ const deps = Object.keys(pkg.dependencies ?? {});
2672
+ const devDeps = Object.keys(pkg.devDependencies ?? {});
2673
+ const warnings = [];
2674
+ const devInProd = deps.filter(
2675
+ (d) => DEV_ONLY_PACKAGES.has(d) || DEV_ONLY_PREFIXES.some((p) => d.startsWith(p))
2676
+ );
2677
+ if (devInProd.length > 0) {
2678
+ warnings.push(
2679
+ `These packages are in "dependencies" but look like dev tools (they will bloat the Lambda layer): ${devInProd.join(", ")}. Consider moving them to "devDependencies".`
2680
+ );
2681
+ }
2682
+ if (deps.length === 0 && devDeps.length > 0) {
2683
+ warnings.push(
2684
+ `"dependencies" is empty but "devDependencies" has ${devDeps.length} package(s). Runtime packages must be in "dependencies" to be included in the Lambda layer.`
2685
+ );
2686
+ }
2687
+ return warnings;
2688
+ });
2631
2689
  var getPackageRealPath = (projectDir, pkgName) => {
2632
2690
  const pkgPath = path.join(projectDir, "node_modules", pkgName);
2633
2691
  if (!fsSync.existsSync(pkgPath)) return null;
@@ -2816,6 +2874,12 @@ var getExistingLayerByHash = (layerName, expectedHash) => Effect18.gen(function*
2816
2874
  };
2817
2875
  });
2818
2876
  var ensureLayer = (config) => Effect18.gen(function* () {
2877
+ const depWarnings = yield* checkDependencyWarnings(config.projectDir).pipe(
2878
+ Effect18.catchAll(() => Effect18.succeed([]))
2879
+ );
2880
+ for (const w of depWarnings) {
2881
+ yield* Effect18.logWarning(`[layer] ${w}`);
2882
+ }
2819
2883
  const dependencies = yield* readProductionDependencies(config.projectDir).pipe(
2820
2884
  Effect18.catchAll(() => Effect18.succeed([]))
2821
2885
  );
@@ -3524,7 +3588,7 @@ var ensureDistribution = (input) => Effect21.gen(function* () {
3524
3588
  };
3525
3589
  });
3526
3590
  var ensureSsrDistribution = (input) => Effect21.gen(function* () {
3527
- const { project, stage, handlerName, bucketName, bucketRegion, s3OacId, lambdaOriginDomain, lambdaOacId, assetPatterns, tags, aliases, acmCertificateArn } = input;
3591
+ const { project, stage, handlerName, bucketName, bucketRegion, s3OacId, lambdaOriginDomain, lambdaOacId, assetPatterns, tags, aliases, acmCertificateArn, apiOriginDomain, routePatterns } = input;
3528
3592
  const comment = makeDistComment(project, stage, handlerName);
3529
3593
  const lambdaOriginId = `Lambda-${project}-${stage}-${handlerName}`;
3530
3594
  const s3OriginId = `S3-${bucketName}`;
@@ -3537,6 +3601,8 @@ var ensureSsrDistribution = (input) => Effect21.gen(function* () {
3537
3601
  } : void 0;
3538
3602
  const ALL_METHODS = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"];
3539
3603
  const CACHED_METHODS = ["GET", "HEAD"];
3604
+ const hasApiRoutes = apiOriginDomain && routePatterns && routePatterns.length > 0;
3605
+ const apiOriginId = hasApiRoutes ? `API-${project}-${stage}` : void 0;
3540
3606
  const originsItems = [
3541
3607
  {
3542
3608
  Id: lambdaOriginId,
@@ -3560,7 +3626,23 @@ var ensureSsrDistribution = (input) => Effect21.gen(function* () {
3560
3626
  OriginAccessControlId: s3OacId,
3561
3627
  S3OriginConfig: { OriginAccessIdentity: "" },
3562
3628
  CustomHeaders: { Quantity: 0, Items: [] }
3563
- }
3629
+ },
3630
+ ...hasApiRoutes ? [{
3631
+ Id: apiOriginId,
3632
+ DomainName: apiOriginDomain,
3633
+ OriginPath: "",
3634
+ ConnectionAttempts: 3,
3635
+ ConnectionTimeout: 10,
3636
+ CustomOriginConfig: {
3637
+ HTTPPort: 80,
3638
+ HTTPSPort: 443,
3639
+ OriginProtocolPolicy: "https-only",
3640
+ OriginSslProtocols: { Quantity: 1, Items: ["TLSv1.2"] },
3641
+ OriginReadTimeout: 30,
3642
+ OriginKeepaliveTimeout: 5
3643
+ },
3644
+ CustomHeaders: { Quantity: 0, Items: [] }
3645
+ }] : []
3564
3646
  ];
3565
3647
  const defaultCacheBehavior = {
3566
3648
  TargetOriginId: lambdaOriginId,
@@ -3579,26 +3661,42 @@ var ensureSsrDistribution = (input) => Effect21.gen(function* () {
3579
3661
  LambdaFunctionAssociations: { Quantity: 0, Items: [] },
3580
3662
  FieldLevelEncryptionId: ""
3581
3663
  };
3582
- const cacheBehaviors = assetPatterns.length > 0 ? {
3583
- Quantity: assetPatterns.length,
3584
- Items: assetPatterns.map((pattern) => ({
3585
- PathPattern: pattern,
3586
- TargetOriginId: s3OriginId,
3587
- ViewerProtocolPolicy: "redirect-to-https",
3588
- AllowedMethods: {
3589
- Quantity: 2,
3590
- Items: [...CACHED_METHODS],
3591
- CachedMethods: { Quantity: 2, Items: [...CACHED_METHODS] }
3592
- },
3593
- Compress: true,
3594
- SmoothStreaming: false,
3595
- CachePolicyId: CACHING_OPTIMIZED_POLICY_ID,
3596
- ResponseHeadersPolicyId: SECURITY_HEADERS_POLICY_ID,
3597
- FunctionAssociations: { Quantity: 0, Items: [] },
3598
- LambdaFunctionAssociations: { Quantity: 0, Items: [] },
3599
- FieldLevelEncryptionId: ""
3600
- }))
3601
- } : { Quantity: 0, Items: [] };
3664
+ const apiRouteBehaviors = hasApiRoutes ? routePatterns.map((pattern) => ({
3665
+ PathPattern: pattern,
3666
+ TargetOriginId: apiOriginId,
3667
+ ViewerProtocolPolicy: "redirect-to-https",
3668
+ AllowedMethods: {
3669
+ Quantity: 7,
3670
+ Items: [...ALL_METHODS],
3671
+ CachedMethods: { Quantity: 2, Items: [...CACHED_METHODS] }
3672
+ },
3673
+ Compress: true,
3674
+ SmoothStreaming: false,
3675
+ CachePolicyId: CACHING_DISABLED_POLICY_ID,
3676
+ OriginRequestPolicyId: ALL_VIEWER_EXCEPT_HOST_HEADER_POLICY_ID,
3677
+ FunctionAssociations: { Quantity: 0, Items: [] },
3678
+ LambdaFunctionAssociations: { Quantity: 0, Items: [] },
3679
+ FieldLevelEncryptionId: ""
3680
+ })) : [];
3681
+ const assetBehaviors = assetPatterns.map((pattern) => ({
3682
+ PathPattern: pattern,
3683
+ TargetOriginId: s3OriginId,
3684
+ ViewerProtocolPolicy: "redirect-to-https",
3685
+ AllowedMethods: {
3686
+ Quantity: 2,
3687
+ Items: [...CACHED_METHODS],
3688
+ CachedMethods: { Quantity: 2, Items: [...CACHED_METHODS] }
3689
+ },
3690
+ Compress: true,
3691
+ SmoothStreaming: false,
3692
+ CachePolicyId: CACHING_OPTIMIZED_POLICY_ID,
3693
+ ResponseHeadersPolicyId: SECURITY_HEADERS_POLICY_ID,
3694
+ FunctionAssociations: { Quantity: 0, Items: [] },
3695
+ LambdaFunctionAssociations: { Quantity: 0, Items: [] },
3696
+ FieldLevelEncryptionId: ""
3697
+ }));
3698
+ const allBehaviors = [...apiRouteBehaviors, ...assetBehaviors];
3699
+ const cacheBehaviors = allBehaviors.length > 0 ? { Quantity: allBehaviors.length, Items: allBehaviors } : { Quantity: 0, Items: [] };
3602
3700
  const existing = yield* findDistributionByTags(project, stage, handlerName);
3603
3701
  if (existing) {
3604
3702
  const configResult = yield* cloudfront_exports.make("get_distribution_config", {
@@ -4375,6 +4473,19 @@ var discoverHandlers = (files) => {
4375
4473
  }
4376
4474
  return { httpHandlers, tableHandlers, appHandlers, staticSiteHandlers, fifoQueueHandlers, bucketHandlers, mailerHandlers, apiHandlers };
4377
4475
  };
4476
+ var flattenHandlers = (discovered) => {
4477
+ const entries = (type, handlers) => handlers.flatMap((h) => h.exports.map((e) => ({ exportName: e.exportName, file: h.file, type })));
4478
+ return [
4479
+ ...entries("http", discovered.httpHandlers),
4480
+ ...entries("table", discovered.tableHandlers),
4481
+ ...entries("app", discovered.appHandlers),
4482
+ ...entries("site", discovered.staticSiteHandlers),
4483
+ ...entries("queue", discovered.fifoQueueHandlers),
4484
+ ...entries("bucket", discovered.bucketHandlers),
4485
+ ...entries("mailer", discovered.mailerHandlers),
4486
+ ...entries("api", discovered.apiHandlers)
4487
+ ];
4488
+ };
4378
4489
 
4379
4490
  // src/deploy/resolve-config.ts
4380
4491
  import { Effect as Effect25 } from "effect";
@@ -4448,10 +4559,10 @@ var ensureLayerAndExternal = (input) => Effect26.gen(function* () {
4448
4559
  project: input.project,
4449
4560
  stage: input.stage,
4450
4561
  region: input.region,
4451
- projectDir: input.projectDir
4562
+ projectDir: input.packageDir
4452
4563
  });
4453
- const prodDeps = layerResult ? yield* readProductionDependencies(input.projectDir) : [];
4454
- const { packages: external, warnings: layerWarnings } = prodDeps.length > 0 ? yield* Effect26.sync(() => collectLayerPackages(input.projectDir, prodDeps)) : { packages: [], warnings: [] };
4564
+ const prodDeps = layerResult ? yield* readProductionDependencies(input.packageDir) : [];
4565
+ const { packages: external, warnings: layerWarnings } = prodDeps.length > 0 ? yield* Effect26.sync(() => collectLayerPackages(input.packageDir, prodDeps)) : { packages: [], warnings: [] };
4455
4566
  for (const warning of layerWarnings) {
4456
4567
  yield* Effect26.logWarning(`[layer] ${warning}`);
4457
4568
  }
@@ -4566,7 +4677,7 @@ var deploy = (input) => Effect27.gen(function* () {
4566
4677
  project: input.project,
4567
4678
  stage: tagCtx.stage,
4568
4679
  region: input.region,
4569
- projectDir: input.projectDir
4680
+ packageDir: input.packageDir ?? input.projectDir
4570
4681
  });
4571
4682
  const { functionArn } = yield* deployLambda({
4572
4683
  input,
@@ -4619,7 +4730,7 @@ var deployAll = (input) => Effect27.gen(function* () {
4619
4730
  project: input.project,
4620
4731
  stage: tagCtx.stage,
4621
4732
  region: input.region,
4622
- projectDir: input.projectDir
4733
+ packageDir: input.packageDir ?? input.projectDir
4623
4734
  });
4624
4735
  yield* Effect27.logDebug("Setting up API Gateway...");
4625
4736
  const { apiId } = yield* ensureProjectApi({
@@ -4731,7 +4842,7 @@ var deployTable = (input) => Effect28.gen(function* () {
4731
4842
  project: input.project,
4732
4843
  stage: resolveStage(input.stage),
4733
4844
  region: input.region,
4734
- projectDir: input.projectDir
4845
+ packageDir: input.packageDir ?? input.projectDir
4735
4846
  });
4736
4847
  const result = yield* deployTableFunction({
4737
4848
  input,
@@ -4760,7 +4871,7 @@ var deployAllTables = (input) => Effect28.gen(function* () {
4760
4871
  project: input.project,
4761
4872
  stage: resolveStage(input.stage),
4762
4873
  region: input.region,
4763
- projectDir: input.projectDir
4874
+ packageDir: input.packageDir ?? input.projectDir
4764
4875
  });
4765
4876
  const results = yield* Effect28.forEach(
4766
4877
  functions,
@@ -4793,12 +4904,31 @@ var deployApp = (input) => Effect29.gen(function* () {
4793
4904
  const stage = resolveStage(input.stage);
4794
4905
  const handlerName = exportName;
4795
4906
  const tagCtx = { project, stage, handler: handlerName };
4907
+ const routePatterns = fn13.routePatterns;
4908
+ if (routePatterns.length > 0 && !input.apiOriginDomain) {
4909
+ return yield* Effect29.fail(
4910
+ new Error(
4911
+ `App "${exportName}" has routes but no API Gateway exists. Ensure defineHttp() or defineApi() handlers are included in the discovery patterns.`
4912
+ )
4913
+ );
4914
+ }
4796
4915
  if (config.build) {
4797
4916
  yield* Effect29.logDebug(`Building app: ${config.build}`);
4917
+ const buildStart = Date.now();
4798
4918
  yield* Effect29.try({
4799
- try: () => execSync(config.build, { cwd: projectDir, stdio: "inherit" }),
4800
- catch: (error) => new Error(`App build failed: ${error}`)
4919
+ try: () => execSync(config.build, {
4920
+ cwd: projectDir,
4921
+ stdio: input.verbose ? "inherit" : "pipe"
4922
+ }),
4923
+ catch: (error) => {
4924
+ if (!input.verbose && error && typeof error === "object" && "stderr" in error) {
4925
+ const stderr = String(error.stderr);
4926
+ if (stderr) process.stderr.write(stderr);
4927
+ }
4928
+ return new Error(`App build failed: ${config.build}`);
4929
+ }
4801
4930
  });
4931
+ yield* Effect29.logDebug(`App built in ${((Date.now() - buildStart) / 1e3).toFixed(1)}s`);
4802
4932
  }
4803
4933
  const serverDir = path5.resolve(projectDir, config.server);
4804
4934
  yield* Effect29.logDebug(`Zipping server directory: ${serverDir}`);
@@ -4864,7 +4994,8 @@ var deployApp = (input) => Effect29.gen(function* () {
4864
4994
  assetPatterns,
4865
4995
  tags: makeTags(tagCtx, "cloudfront-distribution"),
4866
4996
  aliases,
4867
- acmCertificateArn
4997
+ acmCertificateArn,
4998
+ ...input.apiOriginDomain && routePatterns.length > 0 ? { apiOriginDomain: input.apiOriginDomain, routePatterns } : {}
4868
4999
  });
4869
5000
  yield* addCloudFrontPermission(lambdaName, distributionArn);
4870
5001
  yield* putBucketPolicyForOAC(bucketName, distributionArn);
@@ -4978,10 +5109,21 @@ var deployStaticSite = (input) => Effect30.gen(function* () {
4978
5109
  }
4979
5110
  if (config.build) {
4980
5111
  yield* Effect30.logDebug(`Building site: ${config.build}`);
5112
+ const buildStart = Date.now();
4981
5113
  yield* Effect30.try({
4982
- try: () => execSync2(config.build, { cwd: projectDir, stdio: "inherit" }),
4983
- catch: (error) => new Error(`Site build failed: ${error}`)
5114
+ try: () => execSync2(config.build, {
5115
+ cwd: projectDir,
5116
+ stdio: input.verbose ? "inherit" : "pipe"
5117
+ }),
5118
+ catch: (error) => {
5119
+ if (!input.verbose && error && typeof error === "object" && "stderr" in error) {
5120
+ const stderr = String(error.stderr);
5121
+ if (stderr) process.stderr.write(stderr);
5122
+ }
5123
+ return new Error(`Site build failed: ${config.build}`);
5124
+ }
4984
5125
  });
5126
+ yield* Effect30.logDebug(`Site built in ${((Date.now() - buildStart) / 1e3).toFixed(1)}s`);
4985
5127
  }
4986
5128
  const bucketName = `${project}-${stage}-${handlerName}-site`.toLowerCase();
4987
5129
  yield* ensureBucket({
@@ -5325,7 +5467,7 @@ var prepareLayer = (input) => Effect35.gen(function* () {
5325
5467
  project: input.project,
5326
5468
  stage: input.stage,
5327
5469
  region: input.region,
5328
- projectDir: input.projectDir
5470
+ projectDir: input.packageDir
5329
5471
  }).pipe(
5330
5472
  Effect35.provide(
5331
5473
  clients_exports.makeClients({
@@ -5333,8 +5475,8 @@ var prepareLayer = (input) => Effect35.gen(function* () {
5333
5475
  })
5334
5476
  )
5335
5477
  );
5336
- const prodDeps = layerResult ? yield* readProductionDependencies(input.projectDir) : [];
5337
- const { packages: external, warnings: layerWarnings } = prodDeps.length > 0 ? yield* Effect35.sync(() => collectLayerPackages(input.projectDir, prodDeps)) : { packages: [], warnings: [] };
5478
+ const prodDeps = layerResult ? yield* readProductionDependencies(input.packageDir) : [];
5479
+ const { packages: external, warnings: layerWarnings } = prodDeps.length > 0 ? yield* Effect35.sync(() => collectLayerPackages(input.packageDir, prodDeps)) : { packages: [], warnings: [] };
5338
5480
  for (const warning of layerWarnings) {
5339
5481
  yield* Effect35.logWarning(`[layer] ${warning}`);
5340
5482
  }
@@ -5523,9 +5665,10 @@ var buildTableTasks = (ctx, handlers, results) => {
5523
5665
  }
5524
5666
  return tasks;
5525
5667
  };
5526
- var buildAppTasks = (ctx, handlers, results) => {
5668
+ var buildAppTasks = (ctx, handlers, results, apiId) => {
5527
5669
  const tasks = [];
5528
5670
  const { region } = ctx.input;
5671
+ const apiOriginDomain = apiId ? `${apiId}.execute-api.${region}.amazonaws.com` : void 0;
5529
5672
  for (const { exports } of handlers) {
5530
5673
  for (const fn13 of exports) {
5531
5674
  tasks.push(
@@ -5535,7 +5678,9 @@ var buildAppTasks = (ctx, handlers, results) => {
5535
5678
  project: ctx.input.project,
5536
5679
  stage: ctx.input.stage,
5537
5680
  region,
5538
- fn: fn13
5681
+ fn: fn13,
5682
+ verbose: ctx.input.verbose,
5683
+ ...apiOriginDomain ? { apiOriginDomain } : {}
5539
5684
  }).pipe(Effect35.provide(clients_exports.makeClients({
5540
5685
  lambda: { region },
5541
5686
  iam: { region },
@@ -5566,6 +5711,7 @@ var buildStaticSiteTasks = (ctx, handlers, results, apiId) => {
5566
5711
  stage: ctx.input.stage,
5567
5712
  region,
5568
5713
  fn: fn13,
5714
+ verbose: ctx.input.verbose,
5569
5715
  ...fn13.hasHandler ? { file } : {},
5570
5716
  ...apiOriginDomain ? { apiOriginDomain } : {}
5571
5717
  }).pipe(Effect35.provide(clients_exports.makeClients({
@@ -5748,7 +5894,7 @@ var deployProject = (input) => Effect35.gen(function* () {
5748
5894
  project: input.project,
5749
5895
  stage,
5750
5896
  region: input.region,
5751
- projectDir: input.projectDir
5897
+ packageDir: input.packageDir ?? input.projectDir
5752
5898
  });
5753
5899
  if (layerArn && layerStatus) {
5754
5900
  const status = layerStatus === "cached" ? c.dim("cached") : c.green("created");
@@ -5759,7 +5905,10 @@ var deployProject = (input) => Effect35.gen(function* () {
5759
5905
  const staticSitesNeedApi = !input.noSites && staticSiteHandlers.some(
5760
5906
  ({ exports }) => exports.some((fn13) => fn13.routePatterns.length > 0)
5761
5907
  );
5762
- if (totalHttpHandlers > 0 || totalApiHandlers > 0 || staticSitesNeedApi) {
5908
+ const appsNeedApi = appHandlers.some(
5909
+ ({ exports }) => exports.some((fn13) => fn13.routePatterns.length > 0)
5910
+ );
5911
+ if (totalHttpHandlers > 0 || totalApiHandlers > 0 || staticSitesNeedApi || appsNeedApi) {
5763
5912
  const tagCtx = {
5764
5913
  project: input.project,
5765
5914
  stage,
@@ -5824,7 +5973,7 @@ var deployProject = (input) => Effect35.gen(function* () {
5824
5973
  const tasks = [
5825
5974
  ...apiId ? buildHttpTasks(ctx, httpHandlers, apiId, httpResults) : [],
5826
5975
  ...buildTableTasks(ctx, tableHandlers, tableResults),
5827
- ...buildAppTasks(ctx, appHandlers, appResults),
5976
+ ...buildAppTasks(ctx, appHandlers, appResults, apiId),
5828
5977
  ...input.noSites ? [] : buildStaticSiteTasks(ctx, staticSiteHandlers, staticSiteResults, apiId),
5829
5978
  ...buildFifoQueueTasks(ctx, fifoQueueHandlers, fifoQueueResults),
5830
5979
  ...buildBucketTasks(ctx, bucketHandlers, bucketResults),
@@ -5876,18 +6025,22 @@ import * as path7 from "path";
5876
6025
  import * as fs4 from "fs";
5877
6026
  import { pathToFileURL } from "url";
5878
6027
  import * as esbuild2 from "esbuild";
5879
- var loadConfig = async () => {
6028
+ import { Effect as Effect36 } from "effect";
6029
+ var loadConfig = Effect36.fn("loadConfig")(function* () {
5880
6030
  const configPath = path7.resolve(process.cwd(), "effortless.config.ts");
5881
6031
  if (!fs4.existsSync(configPath)) {
5882
6032
  return null;
5883
6033
  }
5884
- const result = await esbuild2.build({
5885
- entryPoints: [configPath],
5886
- bundle: true,
5887
- write: false,
5888
- format: "esm",
5889
- platform: "node",
5890
- external: ["effortless-aws"]
6034
+ const result = yield* Effect36.tryPromise({
6035
+ try: () => esbuild2.build({
6036
+ entryPoints: [configPath],
6037
+ bundle: true,
6038
+ write: false,
6039
+ format: "esm",
6040
+ platform: "node",
6041
+ external: ["effortless-aws"]
6042
+ }),
6043
+ catch: (error) => new Error(`Failed to compile config: ${error}`)
5891
6044
  });
5892
6045
  const output = result.outputFiles?.[0];
5893
6046
  if (!output) {
@@ -5896,13 +6049,12 @@ var loadConfig = async () => {
5896
6049
  const code = output.text;
5897
6050
  const tempFile = path7.join(process.cwd(), ".effortless-config.mjs");
5898
6051
  fs4.writeFileSync(tempFile, code);
5899
- try {
5900
- const mod = await import(pathToFileURL(tempFile).href);
5901
- return mod.default;
5902
- } finally {
5903
- fs4.unlinkSync(tempFile);
5904
- }
5905
- };
6052
+ const mod = yield* Effect36.tryPromise({
6053
+ try: () => import(pathToFileURL(tempFile).href),
6054
+ catch: (error) => new Error(`Failed to load config: ${error}`)
6055
+ }).pipe(Effect36.ensuring(Effect36.sync(() => fs4.unlinkSync(tempFile))));
6056
+ return mod.default;
6057
+ });
5906
6058
  var projectOption = Options.text("project").pipe(
5907
6059
  Options.withAlias("p"),
5908
6060
  Options.withDescription("Project name (or 'name' in effortless.config.ts)"),
@@ -5910,12 +6062,12 @@ var projectOption = Options.text("project").pipe(
5910
6062
  );
5911
6063
  var stageOption = Options.text("stage").pipe(
5912
6064
  Options.withAlias("s"),
5913
- Options.withDescription("Stage name"),
6065
+ Options.withDescription("Deployment stage (default: dev)"),
5914
6066
  Options.withDefault("dev")
5915
6067
  );
5916
6068
  var regionOption = Options.text("region").pipe(
5917
6069
  Options.withAlias("r"),
5918
- Options.withDescription("AWS region"),
6070
+ Options.withDescription("AWS region (default: eu-central-1)"),
5919
6071
  Options.withDefault("eu-central-1")
5920
6072
  );
5921
6073
  var verboseOption = Options.boolean("verbose").pipe(
@@ -5948,6 +6100,23 @@ var getPatternsFromConfig = (config) => {
5948
6100
  });
5949
6101
  };
5950
6102
 
6103
+ // src/cli/project-config.ts
6104
+ import * as Context13 from "effect/Context";
6105
+ import * as Layer14 from "effect/Layer";
6106
+ import * as Effect37 from "effect/Effect";
6107
+ import * as path8 from "path";
6108
+ var ProjectConfig = class _ProjectConfig extends Context13.Tag("ProjectConfig")() {
6109
+ static Live = Layer14.effect(
6110
+ _ProjectConfig,
6111
+ Effect37.gen(function* () {
6112
+ const config = yield* loadConfig();
6113
+ const cwd = process.cwd();
6114
+ const projectDir = config?.root ? path8.resolve(cwd, config.root) : cwd;
6115
+ return { config, cwd, projectDir };
6116
+ })
6117
+ );
6118
+ };
6119
+
5951
6120
  // src/cli/commands/deploy.ts
5952
6121
  var deployTargetArg = Args.text({ name: "target" }).pipe(
5953
6122
  Args.withDescription("Handler name or file path to deploy (optional - uses config patterns if not specified)"),
@@ -5959,8 +6128,8 @@ var isFilePath = (target) => {
5959
6128
  var deployCommand = Command.make(
5960
6129
  "deploy",
5961
6130
  { target: deployTargetArg, project: projectOption, stage: stageOption, region: regionOption, verbose: verboseOption, noSites: noSitesOption },
5962
- ({ target, project: projectOpt, stage, region, verbose, noSites }) => Effect36.gen(function* () {
5963
- const config = yield* Effect36.promise(loadConfig);
6131
+ ({ target, project: projectOpt, stage, region, verbose, noSites }) => Effect38.gen(function* () {
6132
+ const { config, cwd, projectDir } = yield* ProjectConfig;
5964
6133
  const project = Option.getOrElse(projectOpt, () => config?.name ?? "");
5965
6134
  const finalStage = config?.stage ?? stage;
5966
6135
  const finalRegion = config?.region ?? region;
@@ -5979,9 +6148,8 @@ var deployCommand = Command.make(
5979
6148
  acm: { region: "us-east-1" }
5980
6149
  });
5981
6150
  const logLevel = verbose ? LogLevel.Debug : LogLevel.Warning;
5982
- const projectDir = process.cwd();
5983
6151
  yield* Option.match(target, {
5984
- onNone: () => Effect36.gen(function* () {
6152
+ onNone: () => Effect38.gen(function* () {
5985
6153
  const patterns = getPatternsFromConfig(config);
5986
6154
  if (!patterns) {
5987
6155
  yield* Console3.error("Error: No target specified and no 'handlers' patterns in config");
@@ -5989,13 +6157,15 @@ var deployCommand = Command.make(
5989
6157
  }
5990
6158
  const results = yield* deployProject({
5991
6159
  projectDir,
6160
+ packageDir: cwd,
5992
6161
  patterns,
5993
6162
  project,
5994
6163
  stage: finalStage,
5995
6164
  region: finalRegion,
5996
- noSites
6165
+ noSites,
6166
+ verbose
5997
6167
  });
5998
- const total = results.httpResults.length + results.tableResults.length + results.appResults.length + results.staticSiteResults.length;
6168
+ const total = results.httpResults.length + results.tableResults.length + results.appResults.length + results.staticSiteResults.length + results.apiResults.length;
5999
6169
  yield* Console3.log(`
6000
6170
  ${c.green(`Deployed ${total} handler(s):`)}`);
6001
6171
  if (results.apiUrl) {
@@ -6014,6 +6184,10 @@ ${c.green(`Deployed ${total} handler(s):`)}`);
6014
6184
  for (const r of results.tableResults) {
6015
6185
  summaryLines.push({ name: r.exportName, line: ` ${c.cyan("[table]")} ${c.bold(r.exportName)}` });
6016
6186
  }
6187
+ for (const r of results.apiResults) {
6188
+ const pathPart = results.apiUrl ? r.url.replace(results.apiUrl, "") : r.url;
6189
+ summaryLines.push({ name: r.exportName, line: ` ${c.cyan("[api]")} ${c.bold(r.exportName)} ${c.dim(pathPart)}` });
6190
+ }
6017
6191
  for (const r of results.staticSiteResults) {
6018
6192
  summaryLines.push({ name: r.exportName, line: ` ${c.cyan("[site]")} ${c.bold(r.exportName)}: ${c.cyan(r.url)}` });
6019
6193
  }
@@ -6022,26 +6196,27 @@ ${c.green(`Deployed ${total} handler(s):`)}`);
6022
6196
  yield* Console3.log(line);
6023
6197
  }
6024
6198
  }),
6025
- onSome: (targetValue) => Effect36.gen(function* () {
6199
+ onSome: (targetValue) => Effect38.gen(function* () {
6026
6200
  if (isFilePath(targetValue)) {
6027
- const fullPath = path8.isAbsolute(targetValue) ? targetValue : path8.resolve(projectDir, targetValue);
6201
+ const fullPath = path9.isAbsolute(targetValue) ? targetValue : path9.resolve(projectDir, targetValue);
6028
6202
  const input = {
6029
6203
  projectDir,
6204
+ packageDir: cwd,
6030
6205
  file: fullPath,
6031
6206
  project,
6032
6207
  stage: finalStage,
6033
6208
  region: finalRegion
6034
6209
  };
6035
6210
  const httpResult = yield* deployAll(input).pipe(
6036
- Effect36.catchIf(
6211
+ Effect38.catchIf(
6037
6212
  (e) => e instanceof Error && e.message.includes("No defineHttp"),
6038
- () => Effect36.succeed(null)
6213
+ () => Effect38.succeed(null)
6039
6214
  )
6040
6215
  );
6041
6216
  const tableResults = yield* deployAllTables(input).pipe(
6042
- Effect36.catchIf(
6217
+ Effect38.catchIf(
6043
6218
  (e) => e instanceof Error && e.message.includes("No defineTable"),
6044
- () => Effect36.succeed([])
6219
+ () => Effect38.succeed([])
6045
6220
  )
6046
6221
  );
6047
6222
  if (!httpResult && tableResults.length === 0) {
@@ -6071,68 +6246,23 @@ Deployed ${tableResults.length} table handler(s):`));
6071
6246
  }
6072
6247
  const files = findHandlerFiles(patterns, projectDir);
6073
6248
  const discovered = discoverHandlers(files);
6074
- let foundFile = null;
6075
- let foundExport = null;
6076
- let handlerType = "http";
6077
- for (const { file, exports } of discovered.httpHandlers) {
6078
- for (const { exportName } of exports) {
6079
- if (exportName === targetValue) {
6080
- foundFile = file;
6081
- foundExport = exportName;
6082
- break;
6083
- }
6084
- }
6085
- if (foundFile) break;
6086
- }
6087
- if (!foundFile) {
6088
- for (const { file, exports } of discovered.tableHandlers) {
6089
- for (const { exportName } of exports) {
6090
- if (exportName === targetValue) {
6091
- foundFile = file;
6092
- foundExport = exportName;
6093
- handlerType = "table";
6094
- break;
6095
- }
6096
- }
6097
- if (foundFile) break;
6098
- }
6099
- }
6100
- if (!foundFile) {
6101
- for (const { file, exports } of discovered.appHandlers) {
6102
- for (const { exportName } of exports) {
6103
- if (exportName === targetValue) {
6104
- foundFile = file;
6105
- foundExport = exportName;
6106
- handlerType = "app";
6107
- break;
6108
- }
6109
- }
6110
- if (foundFile) break;
6111
- }
6112
- }
6113
- if (!foundFile || !foundExport) {
6249
+ const allHandlers = flattenHandlers(discovered);
6250
+ const found = allHandlers.find((h) => h.exportName === targetValue);
6251
+ if (!found) {
6114
6252
  yield* Console3.error(`Error: Handler "${targetValue}" not found`);
6115
6253
  yield* Console3.log("\nAvailable handlers:");
6116
- for (const { exports } of discovered.httpHandlers) {
6117
- for (const { exportName } of exports) {
6118
- yield* Console3.log(` ${c.cyan("[http]")} ${exportName}`);
6119
- }
6120
- }
6121
- for (const { exports } of discovered.tableHandlers) {
6122
- for (const { exportName } of exports) {
6123
- yield* Console3.log(` ${c.cyan("[table]")} ${exportName}`);
6124
- }
6125
- }
6126
- for (const { exports } of discovered.appHandlers) {
6127
- for (const { exportName } of exports) {
6128
- yield* Console3.log(` ${c.cyan("[app]")} ${exportName}`);
6129
- }
6254
+ for (const h of allHandlers) {
6255
+ yield* Console3.log(` ${c.cyan(`[${h.type}]`.padEnd(9))} ${h.exportName}`);
6130
6256
  }
6131
6257
  return;
6132
6258
  }
6133
- yield* Console3.log(`Found handler ${c.bold(targetValue)} in ${c.dim(path8.relative(projectDir, foundFile))}`);
6259
+ const foundFile = found.file;
6260
+ const foundExport = found.exportName;
6261
+ const handlerType = found.type;
6262
+ yield* Console3.log(`Found handler ${c.bold(targetValue)} in ${c.dim(path9.relative(projectDir, foundFile))}`);
6134
6263
  const input = {
6135
6264
  projectDir,
6265
+ packageDir: cwd,
6136
6266
  file: foundFile,
6137
6267
  project,
6138
6268
  stage: finalStage,
@@ -6151,15 +6281,15 @@ ${c.green("Deployed:")} ${c.cyan(result.url)}`);
6151
6281
  }
6152
6282
  })
6153
6283
  }).pipe(
6154
- Effect36.provide(clientsLayer),
6284
+ Effect38.provide(clientsLayer),
6155
6285
  Logger.withMinimumLogLevel(logLevel)
6156
6286
  );
6157
- })
6158
- ).pipe(Command.withDescription("Deploy handlers (all from config, by file path, or by handler name)"));
6287
+ }).pipe(Effect38.provide(ProjectConfig.Live))
6288
+ ).pipe(Command.withDescription("Deploy handlers to AWS Lambda. Accepts a handler name, file path, or deploys all from config"));
6159
6289
 
6160
6290
  // src/cli/commands/status.ts
6161
6291
  import { Command as Command2 } from "@effect/cli";
6162
- import { Effect as Effect37, Console as Console4, Logger as Logger2, LogLevel as LogLevel2, Option as Option2 } from "effect";
6292
+ import { Effect as Effect39, Console as Console4, Logger as Logger2, LogLevel as LogLevel2, Option as Option2 } from "effect";
6163
6293
  var { lambda, apigatewayv2: apigateway } = clients_exports;
6164
6294
  var INTERNAL_HANDLERS = /* @__PURE__ */ new Set(["api", "platform"]);
6165
6295
  var extractFunctionName = (arn) => {
@@ -6184,7 +6314,7 @@ var formatDate = (date) => {
6184
6314
  if (days < 7) return `${days}d ago`;
6185
6315
  return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
6186
6316
  };
6187
- var getLambdaDetails = (functionName) => Effect37.gen(function* () {
6317
+ var getLambdaDetails = (functionName) => Effect39.gen(function* () {
6188
6318
  const config = yield* lambda.make("get_function_configuration", {
6189
6319
  FunctionName: functionName
6190
6320
  });
@@ -6194,62 +6324,21 @@ var getLambdaDetails = (functionName) => Effect37.gen(function* () {
6194
6324
  timeout: config.Timeout
6195
6325
  };
6196
6326
  }).pipe(
6197
- Effect37.catchAll(() => Effect37.succeed({}))
6327
+ Effect39.catchAll(() => Effect39.succeed({}))
6198
6328
  );
6199
- var getApiUrl = (apiId) => Effect37.gen(function* () {
6329
+ var getApiUrl = (apiId) => Effect39.gen(function* () {
6200
6330
  const api = yield* apigateway.make("get_api", { ApiId: apiId });
6201
6331
  return api.ApiEndpoint;
6202
6332
  }).pipe(
6203
- Effect37.catchAll(() => Effect37.succeed(void 0))
6333
+ Effect39.catchAll(() => Effect39.succeed(void 0))
6204
6334
  );
6205
6335
  var discoverCodeHandlers = (projectDir, patterns) => {
6206
6336
  const files = findHandlerFiles(patterns, projectDir);
6207
6337
  const discovered = discoverHandlers(files);
6208
- const handlers = [];
6209
- for (const { exports } of discovered.httpHandlers) {
6210
- for (const fn13 of exports) {
6211
- handlers.push({
6212
- name: fn13.exportName,
6213
- type: "http",
6214
- method: fn13.config.method,
6215
- path: fn13.config.path
6216
- });
6217
- }
6218
- }
6219
- for (const { exports } of discovered.tableHandlers) {
6220
- for (const fn13 of exports) {
6221
- handlers.push({
6222
- name: fn13.exportName,
6223
- type: "table"
6224
- });
6225
- }
6226
- }
6227
- for (const { exports } of discovered.appHandlers) {
6228
- for (const fn13 of exports) {
6229
- handlers.push({
6230
- name: fn13.exportName,
6231
- type: "app",
6232
- path: fn13.config.path
6233
- });
6234
- }
6235
- }
6236
- for (const { exports } of discovered.staticSiteHandlers) {
6237
- for (const fn13 of exports) {
6238
- handlers.push({
6239
- name: fn13.exportName,
6240
- type: "site"
6241
- });
6242
- }
6243
- }
6244
- for (const { exports } of discovered.fifoQueueHandlers) {
6245
- for (const fn13 of exports) {
6246
- handlers.push({
6247
- name: fn13.exportName,
6248
- type: "queue"
6249
- });
6250
- }
6251
- }
6252
- return handlers;
6338
+ return flattenHandlers(discovered).map((h) => ({
6339
+ name: h.exportName,
6340
+ type: h.type
6341
+ }));
6253
6342
  };
6254
6343
  var discoverAwsHandlers = (resources) => {
6255
6344
  const byHandler = groupResourcesByHandler(resources);
@@ -6277,6 +6366,7 @@ var TYPE_LABELS = {
6277
6366
  http: "http",
6278
6367
  table: "table",
6279
6368
  app: "app",
6369
+ api: "api",
6280
6370
  site: "site",
6281
6371
  queue: "queue",
6282
6372
  lambda: "lambda",
@@ -6297,9 +6387,9 @@ var STATUS_COLORS = {
6297
6387
  var formatStatus = (status) => {
6298
6388
  return STATUS_COLORS[status](status.padEnd(10));
6299
6389
  };
6300
- var formatRoute = (method, path10) => {
6301
- if (method && path10) return `${method.padEnd(5)} ${path10}`;
6302
- if (path10) return path10;
6390
+ var formatRoute = (method, path11) => {
6391
+ if (method && path11) return `${method.padEnd(5)} ${path11}`;
6392
+ if (path11) return path11;
6303
6393
  return "";
6304
6394
  };
6305
6395
  var formatEntry = (entry) => {
@@ -6321,8 +6411,8 @@ var formatEntry = (entry) => {
6321
6411
  var statusCommand = Command2.make(
6322
6412
  "status",
6323
6413
  { project: projectOption, stage: stageOption, region: regionOption, verbose: verboseOption },
6324
- ({ project: projectOpt, stage, region, verbose }) => Effect37.gen(function* () {
6325
- const config = yield* Effect37.promise(loadConfig);
6414
+ ({ project: projectOpt, stage, region, verbose }) => Effect39.gen(function* () {
6415
+ const { config, projectDir } = yield* ProjectConfig;
6326
6416
  const project = Option2.getOrElse(projectOpt, () => config?.name ?? "");
6327
6417
  const finalStage = config?.stage ?? stage;
6328
6418
  const finalRegion = config?.region ?? region;
@@ -6336,11 +6426,10 @@ var statusCommand = Command2.make(
6336
6426
  resource_groups_tagging_api: { region: finalRegion }
6337
6427
  });
6338
6428
  const logLevel = verbose ? LogLevel2.Debug : LogLevel2.Info;
6339
- const projectDir = process.cwd();
6340
6429
  const patterns = getPatternsFromConfig(config);
6341
6430
  const codeHandlers = patterns ? discoverCodeHandlers(projectDir, patterns) : [];
6342
6431
  const codeHandlerNames = new Set(codeHandlers.map((h) => h.name));
6343
- yield* Effect37.gen(function* () {
6432
+ yield* Effect39.gen(function* () {
6344
6433
  yield* Console4.log(`
6345
6434
  Status for ${c.bold(project + "/" + finalStage)}:
6346
6435
  `);
@@ -6437,18 +6526,18 @@ API: ${c.cyan(apiUrl)}`);
6437
6526
  yield* Console4.log(`
6438
6527
  Total: ${parts.join(", ")}`);
6439
6528
  }).pipe(
6440
- Effect37.provide(clientsLayer),
6529
+ Effect39.provide(clientsLayer),
6441
6530
  Logger2.withMinimumLogLevel(logLevel)
6442
6531
  );
6443
- })
6444
- ).pipe(Command2.withDescription("Show status of handlers (code vs deployed)"));
6532
+ }).pipe(Effect39.provide(ProjectConfig.Live))
6533
+ ).pipe(Command2.withDescription("Compare local handlers with deployed AWS resources. Shows new, deployed, and orphaned handlers"));
6445
6534
 
6446
6535
  // src/cli/commands/cleanup.ts
6447
6536
  import { Command as Command3, Options as Options2 } from "@effect/cli";
6448
- import { Effect as Effect39, Console as Console5, Logger as Logger3, LogLevel as LogLevel3, Option as Option3 } from "effect";
6537
+ import { Effect as Effect41, Console as Console5, Logger as Logger3, LogLevel as LogLevel3, Option as Option3 } from "effect";
6449
6538
 
6450
6539
  // src/deploy/cleanup.ts
6451
- import { Effect as Effect38 } from "effect";
6540
+ import { Effect as Effect40 } from "effect";
6452
6541
  var extractResourceName = (arn, type) => {
6453
6542
  switch (type) {
6454
6543
  case "lambda": {
@@ -6491,7 +6580,7 @@ var extractLayerInfo = (arn) => {
6491
6580
  version: parseInt(parts[parts.length - 1] ?? "0", 10)
6492
6581
  };
6493
6582
  };
6494
- var deleteResource = (resource) => Effect38.gen(function* () {
6583
+ var deleteResource = (resource) => Effect40.gen(function* () {
6495
6584
  const name = extractResourceName(resource.arn, resource.type);
6496
6585
  switch (resource.type) {
6497
6586
  case "lambda":
@@ -6524,18 +6613,18 @@ var deleteResource = (resource) => Effect38.gen(function* () {
6524
6613
  yield* deleteSesIdentity(name);
6525
6614
  break;
6526
6615
  default:
6527
- yield* Effect38.logWarning(`Unknown resource type: ${resource.type}, skipping ${resource.arn}`);
6616
+ yield* Effect40.logWarning(`Unknown resource type: ${resource.type}, skipping ${resource.arn}`);
6528
6617
  }
6529
6618
  });
6530
- var deleteResources = (resources) => Effect38.gen(function* () {
6619
+ var deleteResources = (resources) => Effect40.gen(function* () {
6531
6620
  const orderedTypes = ["lambda", "api-gateway", "cloudfront-distribution", "sqs", "ses", "dynamodb", "s3-bucket", "lambda-layer", "iam-role"];
6532
6621
  const iamRolesToDelete = /* @__PURE__ */ new Set();
6533
6622
  for (const type of orderedTypes) {
6534
6623
  const resourcesOfType = resources.filter((r) => r.type === type);
6535
6624
  for (const resource of resourcesOfType) {
6536
6625
  yield* deleteResource(resource).pipe(
6537
- Effect38.catchAll(
6538
- (error) => Effect38.logError(`Failed to delete ${resource.type} ${resource.arn}: ${error}`)
6626
+ Effect40.catchAll(
6627
+ (error) => Effect40.logError(`Failed to delete ${resource.type} ${resource.arn}: ${error}`)
6539
6628
  )
6540
6629
  );
6541
6630
  if (resource.type === "lambda") {
@@ -6547,8 +6636,8 @@ var deleteResources = (resources) => Effect38.gen(function* () {
6547
6636
  }
6548
6637
  for (const roleName of iamRolesToDelete) {
6549
6638
  yield* deleteRole(roleName).pipe(
6550
- Effect38.catchAll(
6551
- (error) => Effect38.logError(`Failed to delete IAM role ${roleName}: ${error}`)
6639
+ Effect40.catchAll(
6640
+ (error) => Effect40.logError(`Failed to delete IAM role ${roleName}: ${error}`)
6552
6641
  )
6553
6642
  );
6554
6643
  }
@@ -6569,11 +6658,14 @@ var layerOption = Options2.boolean("layer").pipe(
6569
6658
  var rolesOption = Options2.boolean("roles").pipe(
6570
6659
  Options2.withDescription("Clean up orphaned IAM roles instead of handler resources")
6571
6660
  );
6661
+ var orphanedOption = Options2.boolean("orphaned").pipe(
6662
+ Options2.withDescription("Delete only handlers that exist in AWS but not in code")
6663
+ );
6572
6664
  var cleanupCommand = Command3.make(
6573
6665
  "cleanup",
6574
- { project: projectOption, stage: stageOption, region: regionOption, handler: handlerOption, layer: layerOption, roles: rolesOption, all: cleanupAllOption, dryRun: dryRunOption, verbose: verboseOption },
6575
- ({ project: projectOpt, stage, region, handler: handlerOpt, layer: cleanupLayer, roles: cleanupRoles, all: deleteAll, dryRun, verbose }) => Effect39.gen(function* () {
6576
- const config = yield* Effect39.promise(loadConfig);
6666
+ { project: projectOption, stage: stageOption, region: regionOption, handler: handlerOption, layer: layerOption, roles: rolesOption, orphaned: orphanedOption, all: cleanupAllOption, dryRun: dryRunOption, verbose: verboseOption },
6667
+ ({ project: projectOpt, stage, region, handler: handlerOpt, layer: cleanupLayer, roles: cleanupRoles, orphaned: cleanupOrphaned, all: deleteAll, dryRun, verbose }) => Effect41.gen(function* () {
6668
+ const { config, projectDir } = yield* ProjectConfig;
6577
6669
  const project = Option3.getOrElse(projectOpt, () => config?.name ?? "");
6578
6670
  const finalStage = config?.stage ?? stage;
6579
6671
  const finalRegion = config?.region ?? region;
@@ -6584,14 +6676,14 @@ var cleanupCommand = Command3.make(
6584
6676
  const logLevel = verbose ? LogLevel3.Debug : LogLevel3.Info;
6585
6677
  if (cleanupLayer) {
6586
6678
  yield* cleanupLayerVersions({ project, region: finalRegion, deleteAll, dryRun }).pipe(
6587
- Effect39.provide(clients_exports.makeClients({ lambda: { region: finalRegion } })),
6679
+ Effect41.provide(clients_exports.makeClients({ lambda: { region: finalRegion } })),
6588
6680
  Logger3.withMinimumLogLevel(logLevel)
6589
6681
  );
6590
6682
  return;
6591
6683
  }
6592
6684
  if (cleanupRoles) {
6593
6685
  yield* cleanupIamRoles({ project, stage: finalStage, region: finalRegion, deleteAll, dryRun }).pipe(
6594
- Effect39.provide(clients_exports.makeClients({ iam: { region: finalRegion } })),
6686
+ Effect41.provide(clients_exports.makeClients({ iam: { region: finalRegion } })),
6595
6687
  Logger3.withMinimumLogLevel(logLevel)
6596
6688
  );
6597
6689
  return;
@@ -6606,7 +6698,7 @@ var cleanupCommand = Command3.make(
6606
6698
  s3: { region: finalRegion },
6607
6699
  cloudfront: { region: "us-east-1" }
6608
6700
  });
6609
- yield* Effect39.gen(function* () {
6701
+ yield* Effect41.gen(function* () {
6610
6702
  yield* Console5.log(`
6611
6703
  Looking for resources in ${c.bold(project + "/" + finalStage)}...
6612
6704
  `);
@@ -6616,14 +6708,34 @@ Looking for resources in ${c.bold(project + "/" + finalStage)}...
6616
6708
  return;
6617
6709
  }
6618
6710
  const byHandler = groupResourcesByHandler(resources);
6619
- const handlersToDelete = handlerFilter ? [[handlerFilter, byHandler.get(handlerFilter) ?? []]] : Array.from(byHandler.entries());
6620
- if (handlerFilter && !byHandler.has(handlerFilter)) {
6621
- yield* Console5.error(`Handler "${handlerFilter}" not found.`);
6622
- yield* Console5.log("\nAvailable handlers:");
6623
- for (const [h] of byHandler) {
6624
- yield* Console5.log(` - ${h}`);
6711
+ let handlersToDelete;
6712
+ if (cleanupOrphaned) {
6713
+ const patterns = getPatternsFromConfig(config);
6714
+ if (!patterns) {
6715
+ yield* Console5.error("Error: No 'handlers' patterns in config \u2014 cannot determine orphaned handlers");
6716
+ return;
6625
6717
  }
6626
- return;
6718
+ const files = findHandlerFiles(patterns, projectDir);
6719
+ const discovered = discoverHandlers(files);
6720
+ const codeNames = new Set(flattenHandlers(discovered).map((h) => h.exportName));
6721
+ const INTERNAL_HANDLERS2 = /* @__PURE__ */ new Set(["api", "platform"]);
6722
+ handlersToDelete = Array.from(byHandler.entries()).filter(([name]) => !codeNames.has(name) && !INTERNAL_HANDLERS2.has(name));
6723
+ if (handlersToDelete.length === 0) {
6724
+ yield* Console5.log("No orphaned handlers found.");
6725
+ return;
6726
+ }
6727
+ } else if (handlerFilter) {
6728
+ if (!byHandler.has(handlerFilter)) {
6729
+ yield* Console5.error(`Handler "${handlerFilter}" not found.`);
6730
+ yield* Console5.log("\nAvailable handlers:");
6731
+ for (const [h] of byHandler) {
6732
+ yield* Console5.log(` - ${h}`);
6733
+ }
6734
+ return;
6735
+ }
6736
+ handlersToDelete = [[handlerFilter, byHandler.get(handlerFilter) ?? []]];
6737
+ } else {
6738
+ handlersToDelete = Array.from(byHandler.entries());
6627
6739
  }
6628
6740
  const resourcesToDelete = [];
6629
6741
  const derivedRoles = [];
@@ -6654,9 +6766,10 @@ Total: ${totalResources} resource(s) (${derivedRoles.length} derived)`);
6654
6766
  ${c.yellow("[DRY RUN]")} No resources were deleted.`);
6655
6767
  return;
6656
6768
  }
6657
- if (!handlerFilter && !deleteAll) {
6769
+ if (!handlerFilter && !cleanupOrphaned && !deleteAll) {
6658
6770
  yield* Console5.log("\nTo delete these resources, use one of:");
6659
6771
  yield* Console5.log(` ${c.dim("eff cleanup --all")} # Delete all resources`);
6772
+ yield* Console5.log(` ${c.dim("eff cleanup --orphaned")} # Delete orphaned handlers only`);
6660
6773
  yield* Console5.log(` ${c.dim("eff cleanup --handler <name>")} # Delete specific handler`);
6661
6774
  yield* Console5.log(` ${c.dim("eff cleanup --dry-run")} # Preview without deleting`);
6662
6775
  return;
@@ -6665,12 +6778,12 @@ ${c.yellow("[DRY RUN]")} No resources were deleted.`);
6665
6778
  yield* deleteResources(resourcesToDelete);
6666
6779
  yield* Console5.log(c.green("\nDone!"));
6667
6780
  }).pipe(
6668
- Effect39.provide(clientsLayer),
6781
+ Effect41.provide(clientsLayer),
6669
6782
  Logger3.withMinimumLogLevel(logLevel)
6670
6783
  );
6671
- })
6672
- ).pipe(Command3.withDescription("Delete deployed resources"));
6673
- var cleanupLayerVersions = (input) => Effect39.gen(function* () {
6784
+ }).pipe(Effect41.provide(ProjectConfig.Live))
6785
+ ).pipe(Command3.withDescription("Delete deployed resources (Lambda, API Gateway, DynamoDB, IAM roles, layers)"));
6786
+ var cleanupLayerVersions = (input) => Effect41.gen(function* () {
6674
6787
  const layerName = `${input.project}-deps`;
6675
6788
  yield* Console5.log(`
6676
6789
  Searching for layer versions: ${layerName}
@@ -6702,7 +6815,7 @@ ${c.yellow("[DRY RUN]")} No layers were deleted.`);
6702
6815
  yield* Console5.log(c.green(`
6703
6816
  Deleted ${deleted} layer version(s).`));
6704
6817
  });
6705
- var cleanupIamRoles = (input) => Effect39.gen(function* () {
6818
+ var cleanupIamRoles = (input) => Effect41.gen(function* () {
6706
6819
  yield* Console5.log("\nSearching for effortless IAM roles...\n");
6707
6820
  const allRoles = yield* listEffortlessRoles();
6708
6821
  if (allRoles.length === 0) {
@@ -6751,8 +6864,8 @@ ${c.yellow("[DRY RUN]")} No roles were deleted.`);
6751
6864
  yield* Console5.log(c.red("\nDeleting roles..."));
6752
6865
  for (const role of roles) {
6753
6866
  yield* deleteRole(role.name).pipe(
6754
- Effect39.catchAll(
6755
- (error) => Effect39.logError(`Failed to delete ${role.name}: ${error}`)
6867
+ Effect41.catchAll(
6868
+ (error) => Effect41.logError(`Failed to delete ${role.name}: ${error}`)
6756
6869
  )
6757
6870
  );
6758
6871
  }
@@ -6761,7 +6874,7 @@ ${c.yellow("[DRY RUN]")} No roles were deleted.`);
6761
6874
 
6762
6875
  // src/cli/commands/logs.ts
6763
6876
  import { Args as Args2, Command as Command4, Options as Options3 } from "@effect/cli";
6764
- import { Effect as Effect40, Console as Console6, Logger as Logger4, LogLevel as LogLevel4, Option as Option4, Schedule as Schedule4 } from "effect";
6877
+ import { Effect as Effect42, Console as Console6, Logger as Logger4, LogLevel as LogLevel4, Option as Option4, Schedule as Schedule4 } from "effect";
6765
6878
  var { cloudwatch_logs } = clients_exports;
6766
6879
  var handlerArg = Args2.text({ name: "handler" }).pipe(
6767
6880
  Args2.withDescription("Handler name to show logs for")
@@ -6832,8 +6945,8 @@ var fetchLogs = (logGroupName, startTime, nextToken) => cloudwatch_logs.make("fi
6832
6945
  var logsCommand = Command4.make(
6833
6946
  "logs",
6834
6947
  { handler: handlerArg, project: projectOption, stage: stageOption, region: regionOption, tail: tailOption, since: sinceOption, verbose: verboseOption },
6835
- ({ handler: handlerName, project: projectOpt, stage, region, tail, since, verbose }) => Effect40.gen(function* () {
6836
- const config = yield* Effect40.promise(loadConfig);
6948
+ ({ handler: handlerName, project: projectOpt, stage, region, tail, since, verbose }) => Effect42.gen(function* () {
6949
+ const { config, projectDir } = yield* ProjectConfig;
6837
6950
  const project = Option4.getOrElse(projectOpt, () => config?.name ?? "");
6838
6951
  const finalStage = config?.stage ?? stage;
6839
6952
  const finalRegion = config?.region ?? region;
@@ -6841,17 +6954,11 @@ var logsCommand = Command4.make(
6841
6954
  yield* Console6.error("Error: --project is required (or set 'name' in effortless.config.ts)");
6842
6955
  return;
6843
6956
  }
6844
- const projectDir = process.cwd();
6845
6957
  const patterns = getPatternsFromConfig(config);
6846
6958
  if (patterns) {
6847
6959
  const files = findHandlerFiles(patterns, projectDir);
6848
6960
  const discovered = discoverHandlers(files);
6849
- const allHandlerNames = [
6850
- ...discovered.httpHandlers.flatMap((h) => h.exports.map((e) => e.exportName)),
6851
- ...discovered.tableHandlers.flatMap((h) => h.exports.map((e) => e.exportName)),
6852
- ...discovered.appHandlers.flatMap((h) => h.exports.map((e) => e.exportName)),
6853
- ...discovered.fifoQueueHandlers.flatMap((h) => h.exports.map((e) => e.exportName))
6854
- ];
6961
+ const allHandlerNames = flattenHandlers(discovered).map((h) => h.exportName);
6855
6962
  if (!allHandlerNames.includes(handlerName)) {
6856
6963
  yield* Console6.error(`Handler "${handlerName}" not found in code.`);
6857
6964
  if (allHandlerNames.length > 0) {
@@ -6869,18 +6976,18 @@ var logsCommand = Command4.make(
6869
6976
  cloudwatch_logs: { region: finalRegion }
6870
6977
  });
6871
6978
  const logLevel = verbose ? LogLevel4.Debug : LogLevel4.Info;
6872
- yield* Effect40.gen(function* () {
6979
+ yield* Effect42.gen(function* () {
6873
6980
  const durationMs = parseDuration(since);
6874
6981
  let startTime = Date.now() - durationMs;
6875
6982
  yield* Console6.log(`Logs for ${c.bold(handlerName)} ${c.dim(`(${logGroupName})`)}:
6876
6983
  `);
6877
6984
  let hasLogs = false;
6878
6985
  const result = yield* fetchLogs(logGroupName, startTime).pipe(
6879
- Effect40.catchAll((error) => {
6986
+ Effect42.catchAll((error) => {
6880
6987
  if (error instanceof clients_exports.cloudwatch_logs.CloudWatchLogsError && error.cause.name === "ResourceNotFoundException") {
6881
- return Effect40.succeed({ events: void 0, nextToken: void 0 });
6988
+ return Effect42.succeed({ events: void 0, nextToken: void 0 });
6882
6989
  }
6883
- return Effect40.fail(error);
6990
+ return Effect42.fail(error);
6884
6991
  })
6885
6992
  );
6886
6993
  if (result.events && result.events.length > 0) {
@@ -6904,10 +7011,10 @@ var logsCommand = Command4.make(
6904
7011
  if (!hasLogs) {
6905
7012
  yield* Console6.log("Waiting for logs... (Ctrl+C to stop)\n");
6906
7013
  }
6907
- yield* Effect40.repeat(
6908
- Effect40.gen(function* () {
7014
+ yield* Effect42.repeat(
7015
+ Effect42.gen(function* () {
6909
7016
  const result2 = yield* fetchLogs(logGroupName, startTime).pipe(
6910
- Effect40.catchAll(() => Effect40.succeed({ events: void 0, nextToken: void 0 }))
7017
+ Effect42.catchAll(() => Effect42.succeed({ events: void 0, nextToken: void 0 }))
6911
7018
  );
6912
7019
  if (result2.events && result2.events.length > 0) {
6913
7020
  for (const event of result2.events) {
@@ -6925,16 +7032,16 @@ var logsCommand = Command4.make(
6925
7032
  Schedule4.spaced("2 seconds")
6926
7033
  );
6927
7034
  }).pipe(
6928
- Effect40.provide(clientsLayer),
7035
+ Effect42.provide(clientsLayer),
6929
7036
  Logger4.withMinimumLogLevel(logLevel)
6930
7037
  );
6931
- })
6932
- ).pipe(Command4.withDescription("Show logs for a handler"));
7038
+ }).pipe(Effect42.provide(ProjectConfig.Live))
7039
+ ).pipe(Command4.withDescription("Stream CloudWatch logs for a handler. Supports --tail for live tailing and --since for time range"));
6933
7040
 
6934
7041
  // src/cli/commands/layer.ts
6935
7042
  import { Command as Command5, Options as Options4 } from "@effect/cli";
6936
- import { Effect as Effect41, Console as Console7 } from "effect";
6937
- import * as path9 from "path";
7043
+ import { Effect as Effect43, Console as Console7 } from "effect";
7044
+ import * as path10 from "path";
6938
7045
  import * as fs5 from "fs";
6939
7046
  var buildOption = Options4.boolean("build").pipe(
6940
7047
  Options4.withDescription("Build layer directory locally (for debugging)")
@@ -6942,22 +7049,28 @@ var buildOption = Options4.boolean("build").pipe(
6942
7049
  var layerCommand = Command5.make(
6943
7050
  "layer",
6944
7051
  { build: buildOption, output: outputOption, verbose: verboseOption },
6945
- ({ build: build3, output, verbose }) => Effect41.gen(function* () {
6946
- const config = yield* Effect41.promise(loadConfig);
6947
- const projectDir = process.cwd();
7052
+ ({ build: build3, output, verbose }) => Effect43.gen(function* () {
7053
+ const { config, cwd } = yield* ProjectConfig;
6948
7054
  if (build3) {
6949
- yield* buildLayer(projectDir, output, verbose);
7055
+ yield* buildLayer(cwd, output, verbose);
6950
7056
  } else {
6951
- yield* showLayerInfo(projectDir, config?.name, verbose);
7057
+ yield* showLayerInfo(cwd, config?.name, verbose);
6952
7058
  }
6953
- })
6954
- ).pipe(Command5.withDescription("Show or build the dependency layer"));
6955
- var showLayerInfo = (projectDir, projectName, verbose) => Effect41.gen(function* () {
7059
+ }).pipe(Effect43.provide(ProjectConfig.Live))
7060
+ ).pipe(Command5.withDescription("Inspect or locally build the shared Lambda dependency layer from package.json"));
7061
+ var showLayerInfo = (projectDir, projectName, verbose) => Effect43.gen(function* () {
6956
7062
  yield* Console7.log(`
6957
7063
  ${c.bold("=== Layer Packages Preview ===")}
6958
7064
  `);
7065
+ const depWarnings = yield* checkDependencyWarnings(projectDir).pipe(
7066
+ Effect43.catchAll(() => Effect43.succeed([]))
7067
+ );
7068
+ for (const w of depWarnings) {
7069
+ yield* Console7.log(c.yellow(` \u26A0 ${w}`));
7070
+ }
7071
+ if (depWarnings.length > 0) yield* Console7.log("");
6959
7072
  const prodDeps = yield* readProductionDependencies(projectDir).pipe(
6960
- Effect41.catchAll(() => Effect41.succeed([]))
7073
+ Effect43.catchAll(() => Effect43.succeed([]))
6961
7074
  );
6962
7075
  if (prodDeps.length === 0) {
6963
7076
  yield* Console7.log("No production dependencies found in package.json");
@@ -6969,7 +7082,7 @@ ${c.bold("=== Layer Packages Preview ===")}
6969
7082
  yield* Console7.log(` ${dep}`);
6970
7083
  }
6971
7084
  const hash = yield* computeLockfileHash(projectDir).pipe(
6972
- Effect41.catchAll(() => Effect41.succeed(null))
7085
+ Effect43.catchAll(() => Effect43.succeed(null))
6973
7086
  );
6974
7087
  if (hash) {
6975
7088
  yield* Console7.log(`
@@ -6977,7 +7090,7 @@ Lockfile hash: ${hash}`);
6977
7090
  } else {
6978
7091
  yield* Console7.log("\nNo lockfile found (package-lock.json, pnpm-lock.yaml, or yarn.lock)");
6979
7092
  }
6980
- const { packages: allPackages, warnings: layerWarnings } = yield* Effect41.sync(() => collectLayerPackages(projectDir, prodDeps));
7093
+ const { packages: allPackages, warnings: layerWarnings } = yield* Effect43.sync(() => collectLayerPackages(projectDir, prodDeps));
6981
7094
  if (layerWarnings.length > 0) {
6982
7095
  yield* Console7.log(c.yellow(`
6983
7096
  Warnings (${layerWarnings.length}):`));
@@ -7006,10 +7119,10 @@ Total packages for layer ${c.dim(`(${allPackages.length})`)}:`);
7006
7119
  Layer name: ${projectName}-deps`);
7007
7120
  }
7008
7121
  });
7009
- var buildLayer = (projectDir, output, verbose) => Effect41.gen(function* () {
7010
- const outputDir = path9.isAbsolute(output) ? output : path9.resolve(projectDir, output);
7011
- const layerDir = path9.join(outputDir, "nodejs", "node_modules");
7012
- const layerRoot = path9.join(outputDir, "nodejs");
7122
+ var buildLayer = (projectDir, output, verbose) => Effect43.gen(function* () {
7123
+ const outputDir = path10.isAbsolute(output) ? output : path10.resolve(projectDir, output);
7124
+ const layerDir = path10.join(outputDir, "nodejs", "node_modules");
7125
+ const layerRoot = path10.join(outputDir, "nodejs");
7013
7126
  if (fs5.existsSync(layerRoot)) {
7014
7127
  fs5.rmSync(layerRoot, { recursive: true });
7015
7128
  }
@@ -7017,8 +7130,15 @@ var buildLayer = (projectDir, output, verbose) => Effect41.gen(function* () {
7017
7130
  yield* Console7.log(`
7018
7131
  ${c.bold("=== Building Layer Locally ===")}
7019
7132
  `);
7133
+ const depWarnings = yield* checkDependencyWarnings(projectDir).pipe(
7134
+ Effect43.catchAll(() => Effect43.succeed([]))
7135
+ );
7136
+ for (const w of depWarnings) {
7137
+ yield* Console7.log(c.yellow(` \u26A0 ${w}`));
7138
+ }
7139
+ if (depWarnings.length > 0) yield* Console7.log("");
7020
7140
  const prodDeps = yield* readProductionDependencies(projectDir).pipe(
7021
- Effect41.catchAll(() => Effect41.succeed([]))
7141
+ Effect43.catchAll(() => Effect43.succeed([]))
7022
7142
  );
7023
7143
  if (prodDeps.length === 0) {
7024
7144
  yield* Console7.log("No production dependencies found in package.json");
@@ -7030,11 +7150,11 @@ ${c.bold("=== Building Layer Locally ===")}
7030
7150
  yield* Console7.log(` ${dep}`);
7031
7151
  }
7032
7152
  const hash = yield* computeLockfileHash(projectDir).pipe(
7033
- Effect41.catchAll(() => Effect41.succeed("unknown"))
7153
+ Effect43.catchAll(() => Effect43.succeed("unknown"))
7034
7154
  );
7035
7155
  yield* Console7.log(`
7036
7156
  Lockfile hash: ${hash}`);
7037
- const { packages: allPackages, resolvedPaths, warnings: layerWarnings } = yield* Effect41.sync(() => collectLayerPackages(projectDir, prodDeps));
7157
+ const { packages: allPackages, resolvedPaths, warnings: layerWarnings } = yield* Effect43.sync(() => collectLayerPackages(projectDir, prodDeps));
7038
7158
  if (layerWarnings.length > 0) {
7039
7159
  yield* Console7.log(`
7040
7160
  Warnings (${layerWarnings.length}):`);
@@ -7061,9 +7181,9 @@ Collected ${allPackages.length} packages for layer`);
7061
7181
  }
7062
7182
  continue;
7063
7183
  }
7064
- const destPath = path9.join(layerDir, pkgName);
7184
+ const destPath = path10.join(layerDir, pkgName);
7065
7185
  if (pkgName.startsWith("@")) {
7066
- const scopeDir = path9.join(layerDir, pkgName.split("/")[0] ?? pkgName);
7186
+ const scopeDir = path10.join(layerDir, pkgName.split("/")[0] ?? pkgName);
7067
7187
  if (!fs5.existsSync(scopeDir)) {
7068
7188
  fs5.mkdirSync(scopeDir, { recursive: true });
7069
7189
  }
@@ -7084,20 +7204,20 @@ To inspect: ls ${layerDir}`);
7084
7204
  // src/cli/commands/config.ts
7085
7205
  import { Args as Args3, Command as Command6 } from "@effect/cli";
7086
7206
  import { Prompt } from "@effect/cli";
7087
- import { Effect as Effect42, Console as Console8, Logger as Logger5, LogLevel as LogLevel5, Option as Option5 } from "effect";
7088
- var loadRequiredParams = (projectOpt, stage, region) => Effect42.gen(function* () {
7089
- const config = yield* Effect42.promise(loadConfig);
7207
+ import { Effect as Effect44, Console as Console8, Logger as Logger5, LogLevel as LogLevel5, Option as Option5 } from "effect";
7208
+ var loadRequiredParams = (projectOpt, stage, region) => Effect44.gen(function* () {
7209
+ const { config, projectDir } = yield* ProjectConfig;
7090
7210
  const project = Option5.getOrElse(projectOpt, () => config?.name ?? "");
7091
7211
  if (!project) {
7092
7212
  yield* Console8.error("Error: --project is required (or set 'name' in effortless.config.ts)");
7093
- return yield* Effect42.fail(new Error("Missing project name"));
7213
+ return yield* Effect44.fail(new Error("Missing project name"));
7094
7214
  }
7095
7215
  const patterns = getPatternsFromConfig(config);
7096
7216
  if (!patterns) {
7097
7217
  yield* Console8.error("Error: No 'handlers' patterns in config");
7098
- return yield* Effect42.fail(new Error("Missing handler patterns"));
7218
+ return yield* Effect44.fail(new Error("Missing handler patterns"));
7099
7219
  }
7100
- const files = findHandlerFiles(patterns, process.cwd());
7220
+ const files = findHandlerFiles(patterns, projectDir);
7101
7221
  const handlers = discoverHandlers(files);
7102
7222
  const finalStage = config?.stage ?? stage;
7103
7223
  const finalRegion = config?.region ?? region;
@@ -7107,7 +7227,7 @@ var loadRequiredParams = (projectOpt, stage, region) => Effect42.gen(function* (
7107
7227
  var listCommand = Command6.make(
7108
7228
  "list",
7109
7229
  { project: projectOption, stage: stageOption, region: regionOption, verbose: verboseOption },
7110
- ({ project: projectOpt, stage, region, verbose }) => Effect42.gen(function* () {
7230
+ ({ project: projectOpt, stage, region, verbose }) => Effect44.gen(function* () {
7111
7231
  const ctx = yield* loadRequiredParams(projectOpt, stage, region);
7112
7232
  const { params } = ctx;
7113
7233
  if (params.length === 0) {
@@ -7115,7 +7235,7 @@ var listCommand = Command6.make(
7115
7235
  return;
7116
7236
  }
7117
7237
  const { existing, missing } = yield* checkMissingParams(params).pipe(
7118
- Effect42.provide(clients_exports.makeClients({ ssm: { region: ctx.region } }))
7238
+ Effect44.provide(clients_exports.makeClients({ ssm: { region: ctx.region } }))
7119
7239
  );
7120
7240
  yield* Console8.log(`
7121
7241
  ${c.bold("Config parameters")} ${c.dim(`(${ctx.project} / ${ctx.stage})`)}
@@ -7138,16 +7258,19 @@ ${c.bold("Config parameters")} ${c.dim(`(${ctx.project} / ${ctx.stage})`)}
7138
7258
  ${c.green("All parameters are set.")}`);
7139
7259
  }
7140
7260
  yield* Console8.log("");
7141
- }).pipe(Logger5.withMinimumLogLevel(LogLevel5.Warning))
7142
- ).pipe(Command6.withDescription("List all config parameters and their status"));
7261
+ }).pipe(
7262
+ Effect44.provide(ProjectConfig.Live),
7263
+ Logger5.withMinimumLogLevel(LogLevel5.Warning)
7264
+ )
7265
+ ).pipe(Command6.withDescription("List all declared config parameters and show which are set vs missing"));
7143
7266
  var setKeyArg = Args3.text({ name: "key" }).pipe(
7144
7267
  Args3.withDescription("SSM parameter key (e.g. stripe/secret-key)")
7145
7268
  );
7146
7269
  var setCommand = Command6.make(
7147
7270
  "set",
7148
7271
  { key: setKeyArg, project: projectOption, stage: stageOption, region: regionOption, verbose: verboseOption },
7149
- ({ key, project: projectOpt, stage, region, verbose }) => Effect42.gen(function* () {
7150
- const config = yield* Effect42.promise(loadConfig);
7272
+ ({ key, project: projectOpt, stage, region, verbose }) => Effect44.gen(function* () {
7273
+ const { config } = yield* ProjectConfig;
7151
7274
  const project = Option5.getOrElse(projectOpt, () => config?.name ?? "");
7152
7275
  if (!project) {
7153
7276
  yield* Console8.error("Error: --project is required (or set 'name' in effortless.config.ts)");
@@ -7164,15 +7287,18 @@ var setCommand = Command6.make(
7164
7287
  Value: value,
7165
7288
  Type: "SecureString",
7166
7289
  Overwrite: true
7167
- }).pipe(Effect42.provide(clients_exports.makeClients({ ssm: { region: finalRegion } })));
7290
+ }).pipe(Effect44.provide(clients_exports.makeClients({ ssm: { region: finalRegion } })));
7168
7291
  yield* Console8.log(`
7169
7292
  ${c.green("\u2713")} ${c.cyan(ssmPath)} ${c.dim("(SecureString)")}`);
7170
- }).pipe(Logger5.withMinimumLogLevel(LogLevel5.Warning))
7171
- ).pipe(Command6.withDescription("Set a specific config parameter value"));
7293
+ }).pipe(
7294
+ Effect44.provide(ProjectConfig.Live),
7295
+ Logger5.withMinimumLogLevel(LogLevel5.Warning)
7296
+ )
7297
+ ).pipe(Command6.withDescription("Set a config parameter value (stored encrypted in AWS)"));
7172
7298
  var configRootCommand = Command6.make(
7173
7299
  "config",
7174
7300
  { project: projectOption, stage: stageOption, region: regionOption, verbose: verboseOption },
7175
- ({ project: projectOpt, stage, region, verbose }) => Effect42.gen(function* () {
7301
+ ({ project: projectOpt, stage, region, verbose }) => Effect44.gen(function* () {
7176
7302
  const ctx = yield* loadRequiredParams(projectOpt, stage, region);
7177
7303
  const { params } = ctx;
7178
7304
  if (params.length === 0) {
@@ -7180,7 +7306,7 @@ var configRootCommand = Command6.make(
7180
7306
  return;
7181
7307
  }
7182
7308
  const { missing } = yield* checkMissingParams(params).pipe(
7183
- Effect42.provide(clients_exports.makeClients({ ssm: { region: ctx.region } }))
7309
+ Effect44.provide(clients_exports.makeClients({ ssm: { region: ctx.region } }))
7184
7310
  );
7185
7311
  if (missing.length === 0) {
7186
7312
  yield* Console8.log(`
@@ -7205,7 +7331,7 @@ ${c.bold("Missing parameters")} ${c.dim(`(${ctx.project} / ${ctx.stage})`)}
7205
7331
  Value: value,
7206
7332
  Type: "SecureString",
7207
7333
  Overwrite: false
7208
- }).pipe(Effect42.provide(clients_exports.makeClients({ ssm: { region: ctx.region } })));
7334
+ }).pipe(Effect44.provide(clients_exports.makeClients({ ssm: { region: ctx.region } })));
7209
7335
  yield* Console8.log(` ${c.green("\u2713")} created`);
7210
7336
  created++;
7211
7337
  }
@@ -7218,9 +7344,12 @@ ${c.bold("Missing parameters")} ${c.dim(`(${ctx.project} / ${ctx.stage})`)}
7218
7344
  No parameters created.
7219
7345
  `);
7220
7346
  }
7221
- }).pipe(Logger5.withMinimumLogLevel(LogLevel5.Warning))
7347
+ }).pipe(
7348
+ Effect44.provide(ProjectConfig.Live),
7349
+ Logger5.withMinimumLogLevel(LogLevel5.Warning)
7350
+ )
7222
7351
  ).pipe(
7223
- Command6.withDescription("Manage SSM config parameters for your handlers"),
7352
+ Command6.withDescription("Manage config values declared via param() in handlers. Run without subcommand to interactively set missing values"),
7224
7353
  Command6.withSubcommands([listCommand, setCommand])
7225
7354
  );
7226
7355
  var configCommand = configRootCommand;
@@ -7237,6 +7366,7 @@ var cli = Command7.run(mainCommand, {
7237
7366
  version
7238
7367
  });
7239
7368
  cli(process.argv).pipe(
7240
- Effect43.provide(NodeContext.layer),
7369
+ Effect45.provide(NodeContext.layer),
7370
+ Effect45.provide(CliConfig.layer({ showBuiltIns: false, showTypes: false })),
7241
7371
  NodeRuntime.runMain
7242
7372
  );