@effortless-aws/cli 0.1.0 → 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.
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";
@@ -1955,19 +1955,47 @@ var ensureFunctionUrl = (functionName) => Effect13.gen(function* () {
1955
1955
  return { functionUrl: result.FunctionUrl };
1956
1956
  });
1957
1957
  var addCloudFrontPermission = (functionName, distributionArn) => Effect13.gen(function* () {
1958
- yield* lambda_exports.make("add_permission", {
1959
- FunctionName: functionName,
1960
- StatementId: "cloudfront-oac",
1961
- Action: "lambda:InvokeFunctionUrl",
1962
- Principal: "cloudfront.amazonaws.com",
1963
- SourceArn: distributionArn,
1964
- FunctionUrlAuthType: "AWS_IAM"
1965
- }).pipe(
1966
- Effect13.catchIf(
1967
- (e) => e._tag === "LambdaError" && e.is("ResourceConflictException"),
1968
- () => Effect13.logDebug(`CloudFront permission already exists for ${functionName}`)
1969
- )
1970
- );
1958
+ const statements = [
1959
+ {
1960
+ statementId: "cloudfront-oac",
1961
+ action: "lambda:InvokeFunctionUrl",
1962
+ authType: "AWS_IAM"
1963
+ },
1964
+ {
1965
+ statementId: "cloudfront-oac-invoke",
1966
+ action: "lambda:InvokeFunction",
1967
+ authType: void 0
1968
+ }
1969
+ ];
1970
+ for (const stmt of statements) {
1971
+ yield* lambda_exports.make("add_permission", {
1972
+ FunctionName: functionName,
1973
+ StatementId: stmt.statementId,
1974
+ Action: stmt.action,
1975
+ Principal: "cloudfront.amazonaws.com",
1976
+ SourceArn: distributionArn,
1977
+ ...stmt.authType ? { FunctionUrlAuthType: stmt.authType } : {}
1978
+ }).pipe(
1979
+ Effect13.catchIf(
1980
+ (e) => e._tag === "LambdaError" && e.is("ResourceConflictException"),
1981
+ () => Effect13.gen(function* () {
1982
+ yield* Effect13.logDebug(`Permission ${stmt.statementId} exists for ${functionName}, replacing...`);
1983
+ yield* lambda_exports.make("remove_permission", {
1984
+ FunctionName: functionName,
1985
+ StatementId: stmt.statementId
1986
+ });
1987
+ yield* lambda_exports.make("add_permission", {
1988
+ FunctionName: functionName,
1989
+ StatementId: stmt.statementId,
1990
+ Action: stmt.action,
1991
+ Principal: "cloudfront.amazonaws.com",
1992
+ SourceArn: distributionArn,
1993
+ ...stmt.authType ? { FunctionUrlAuthType: stmt.authType } : {}
1994
+ });
1995
+ })
1996
+ )
1997
+ );
1998
+ }
1971
1999
  });
1972
2000
  var deleteLambda = (functionName) => Effect13.gen(function* () {
1973
2001
  yield* Effect13.logDebug(`Deleting Lambda function: ${functionName}`);
@@ -2600,6 +2628,64 @@ var readProductionDependencies = (projectDir) => Effect18.gen(function* () {
2600
2628
  const pkg = JSON.parse(content);
2601
2629
  return Object.keys(pkg.dependencies ?? {});
2602
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
+ });
2603
2689
  var getPackageRealPath = (projectDir, pkgName) => {
2604
2690
  const pkgPath = path.join(projectDir, "node_modules", pkgName);
2605
2691
  if (!fsSync.existsSync(pkgPath)) return null;
@@ -2788,6 +2874,12 @@ var getExistingLayerByHash = (layerName, expectedHash) => Effect18.gen(function*
2788
2874
  };
2789
2875
  });
2790
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
+ }
2791
2883
  const dependencies = yield* readProductionDependencies(config.projectDir).pipe(
2792
2884
  Effect18.catchAll(() => Effect18.succeed([]))
2793
2885
  );
@@ -3496,7 +3588,7 @@ var ensureDistribution = (input) => Effect21.gen(function* () {
3496
3588
  };
3497
3589
  });
3498
3590
  var ensureSsrDistribution = (input) => Effect21.gen(function* () {
3499
- 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;
3500
3592
  const comment = makeDistComment(project, stage, handlerName);
3501
3593
  const lambdaOriginId = `Lambda-${project}-${stage}-${handlerName}`;
3502
3594
  const s3OriginId = `S3-${bucketName}`;
@@ -3509,6 +3601,8 @@ var ensureSsrDistribution = (input) => Effect21.gen(function* () {
3509
3601
  } : void 0;
3510
3602
  const ALL_METHODS = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"];
3511
3603
  const CACHED_METHODS = ["GET", "HEAD"];
3604
+ const hasApiRoutes = apiOriginDomain && routePatterns && routePatterns.length > 0;
3605
+ const apiOriginId = hasApiRoutes ? `API-${project}-${stage}` : void 0;
3512
3606
  const originsItems = [
3513
3607
  {
3514
3608
  Id: lambdaOriginId,
@@ -3532,7 +3626,23 @@ var ensureSsrDistribution = (input) => Effect21.gen(function* () {
3532
3626
  OriginAccessControlId: s3OacId,
3533
3627
  S3OriginConfig: { OriginAccessIdentity: "" },
3534
3628
  CustomHeaders: { Quantity: 0, Items: [] }
3535
- }
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
+ }] : []
3536
3646
  ];
3537
3647
  const defaultCacheBehavior = {
3538
3648
  TargetOriginId: lambdaOriginId,
@@ -3543,32 +3653,50 @@ var ensureSsrDistribution = (input) => Effect21.gen(function* () {
3543
3653
  CachedMethods: { Quantity: 2, Items: [...CACHED_METHODS] }
3544
3654
  },
3545
3655
  Compress: true,
3656
+ SmoothStreaming: false,
3546
3657
  CachePolicyId: CACHING_DISABLED_POLICY_ID,
3547
3658
  OriginRequestPolicyId: ALL_VIEWER_EXCEPT_HOST_HEADER_POLICY_ID,
3548
3659
  ResponseHeadersPolicyId: SECURITY_HEADERS_POLICY_ID,
3549
3660
  FunctionAssociations: { Quantity: 0, Items: [] },
3550
- LambdaFunctionAssociations: { Quantity: 0, Items: [] }
3661
+ LambdaFunctionAssociations: { Quantity: 0, Items: [] },
3662
+ FieldLevelEncryptionId: ""
3551
3663
  };
3552
- const cacheBehaviors = assetPatterns.length > 0 ? {
3553
- Quantity: assetPatterns.length,
3554
- Items: assetPatterns.map((pattern) => ({
3555
- PathPattern: pattern,
3556
- TargetOriginId: s3OriginId,
3557
- ViewerProtocolPolicy: "redirect-to-https",
3558
- AllowedMethods: {
3559
- Quantity: 2,
3560
- Items: [...CACHED_METHODS],
3561
- CachedMethods: { Quantity: 2, Items: [...CACHED_METHODS] }
3562
- },
3563
- Compress: true,
3564
- SmoothStreaming: false,
3565
- CachePolicyId: CACHING_OPTIMIZED_POLICY_ID,
3566
- ResponseHeadersPolicyId: SECURITY_HEADERS_POLICY_ID,
3567
- FunctionAssociations: { Quantity: 0, Items: [] },
3568
- LambdaFunctionAssociations: { Quantity: 0, Items: [] },
3569
- FieldLevelEncryptionId: ""
3570
- }))
3571
- } : { 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: [] };
3572
3700
  const existing = yield* findDistributionByTags(project, stage, handlerName);
3573
3701
  if (existing) {
3574
3702
  const configResult = yield* cloudfront_exports.make("get_distribution_config", {
@@ -4345,6 +4473,19 @@ var discoverHandlers = (files) => {
4345
4473
  }
4346
4474
  return { httpHandlers, tableHandlers, appHandlers, staticSiteHandlers, fifoQueueHandlers, bucketHandlers, mailerHandlers, apiHandlers };
4347
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
+ };
4348
4489
 
4349
4490
  // src/deploy/resolve-config.ts
4350
4491
  import { Effect as Effect25 } from "effect";
@@ -4418,10 +4559,10 @@ var ensureLayerAndExternal = (input) => Effect26.gen(function* () {
4418
4559
  project: input.project,
4419
4560
  stage: input.stage,
4420
4561
  region: input.region,
4421
- projectDir: input.projectDir
4562
+ projectDir: input.packageDir
4422
4563
  });
4423
- const prodDeps = layerResult ? yield* readProductionDependencies(input.projectDir) : [];
4424
- 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: [] };
4425
4566
  for (const warning of layerWarnings) {
4426
4567
  yield* Effect26.logWarning(`[layer] ${warning}`);
4427
4568
  }
@@ -4536,7 +4677,7 @@ var deploy = (input) => Effect27.gen(function* () {
4536
4677
  project: input.project,
4537
4678
  stage: tagCtx.stage,
4538
4679
  region: input.region,
4539
- projectDir: input.projectDir
4680
+ packageDir: input.packageDir ?? input.projectDir
4540
4681
  });
4541
4682
  const { functionArn } = yield* deployLambda({
4542
4683
  input,
@@ -4589,7 +4730,7 @@ var deployAll = (input) => Effect27.gen(function* () {
4589
4730
  project: input.project,
4590
4731
  stage: tagCtx.stage,
4591
4732
  region: input.region,
4592
- projectDir: input.projectDir
4733
+ packageDir: input.packageDir ?? input.projectDir
4593
4734
  });
4594
4735
  yield* Effect27.logDebug("Setting up API Gateway...");
4595
4736
  const { apiId } = yield* ensureProjectApi({
@@ -4701,7 +4842,7 @@ var deployTable = (input) => Effect28.gen(function* () {
4701
4842
  project: input.project,
4702
4843
  stage: resolveStage(input.stage),
4703
4844
  region: input.region,
4704
- projectDir: input.projectDir
4845
+ packageDir: input.packageDir ?? input.projectDir
4705
4846
  });
4706
4847
  const result = yield* deployTableFunction({
4707
4848
  input,
@@ -4730,7 +4871,7 @@ var deployAllTables = (input) => Effect28.gen(function* () {
4730
4871
  project: input.project,
4731
4872
  stage: resolveStage(input.stage),
4732
4873
  region: input.region,
4733
- projectDir: input.projectDir
4874
+ packageDir: input.packageDir ?? input.projectDir
4734
4875
  });
4735
4876
  const results = yield* Effect28.forEach(
4736
4877
  functions,
@@ -4763,12 +4904,31 @@ var deployApp = (input) => Effect29.gen(function* () {
4763
4904
  const stage = resolveStage(input.stage);
4764
4905
  const handlerName = exportName;
4765
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
+ }
4766
4915
  if (config.build) {
4767
4916
  yield* Effect29.logDebug(`Building app: ${config.build}`);
4917
+ const buildStart = Date.now();
4768
4918
  yield* Effect29.try({
4769
- try: () => execSync(config.build, { cwd: projectDir, stdio: "inherit" }),
4770
- 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
+ }
4771
4930
  });
4931
+ yield* Effect29.logDebug(`App built in ${((Date.now() - buildStart) / 1e3).toFixed(1)}s`);
4772
4932
  }
4773
4933
  const serverDir = path5.resolve(projectDir, config.server);
4774
4934
  yield* Effect29.logDebug(`Zipping server directory: ${serverDir}`);
@@ -4834,7 +4994,8 @@ var deployApp = (input) => Effect29.gen(function* () {
4834
4994
  assetPatterns,
4835
4995
  tags: makeTags(tagCtx, "cloudfront-distribution"),
4836
4996
  aliases,
4837
- acmCertificateArn
4997
+ acmCertificateArn,
4998
+ ...input.apiOriginDomain && routePatterns.length > 0 ? { apiOriginDomain: input.apiOriginDomain, routePatterns } : {}
4838
4999
  });
4839
5000
  yield* addCloudFrontPermission(lambdaName, distributionArn);
4840
5001
  yield* putBucketPolicyForOAC(bucketName, distributionArn);
@@ -4948,10 +5109,21 @@ var deployStaticSite = (input) => Effect30.gen(function* () {
4948
5109
  }
4949
5110
  if (config.build) {
4950
5111
  yield* Effect30.logDebug(`Building site: ${config.build}`);
5112
+ const buildStart = Date.now();
4951
5113
  yield* Effect30.try({
4952
- try: () => execSync2(config.build, { cwd: projectDir, stdio: "inherit" }),
4953
- 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
+ }
4954
5125
  });
5126
+ yield* Effect30.logDebug(`Site built in ${((Date.now() - buildStart) / 1e3).toFixed(1)}s`);
4955
5127
  }
4956
5128
  const bucketName = `${project}-${stage}-${handlerName}-site`.toLowerCase();
4957
5129
  yield* ensureBucket({
@@ -5295,7 +5467,7 @@ var prepareLayer = (input) => Effect35.gen(function* () {
5295
5467
  project: input.project,
5296
5468
  stage: input.stage,
5297
5469
  region: input.region,
5298
- projectDir: input.projectDir
5470
+ projectDir: input.packageDir
5299
5471
  }).pipe(
5300
5472
  Effect35.provide(
5301
5473
  clients_exports.makeClients({
@@ -5303,8 +5475,8 @@ var prepareLayer = (input) => Effect35.gen(function* () {
5303
5475
  })
5304
5476
  )
5305
5477
  );
5306
- const prodDeps = layerResult ? yield* readProductionDependencies(input.projectDir) : [];
5307
- 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: [] };
5308
5480
  for (const warning of layerWarnings) {
5309
5481
  yield* Effect35.logWarning(`[layer] ${warning}`);
5310
5482
  }
@@ -5493,9 +5665,10 @@ var buildTableTasks = (ctx, handlers, results) => {
5493
5665
  }
5494
5666
  return tasks;
5495
5667
  };
5496
- var buildAppTasks = (ctx, handlers, results) => {
5668
+ var buildAppTasks = (ctx, handlers, results, apiId) => {
5497
5669
  const tasks = [];
5498
5670
  const { region } = ctx.input;
5671
+ const apiOriginDomain = apiId ? `${apiId}.execute-api.${region}.amazonaws.com` : void 0;
5499
5672
  for (const { exports } of handlers) {
5500
5673
  for (const fn13 of exports) {
5501
5674
  tasks.push(
@@ -5505,7 +5678,9 @@ var buildAppTasks = (ctx, handlers, results) => {
5505
5678
  project: ctx.input.project,
5506
5679
  stage: ctx.input.stage,
5507
5680
  region,
5508
- fn: fn13
5681
+ fn: fn13,
5682
+ verbose: ctx.input.verbose,
5683
+ ...apiOriginDomain ? { apiOriginDomain } : {}
5509
5684
  }).pipe(Effect35.provide(clients_exports.makeClients({
5510
5685
  lambda: { region },
5511
5686
  iam: { region },
@@ -5536,6 +5711,7 @@ var buildStaticSiteTasks = (ctx, handlers, results, apiId) => {
5536
5711
  stage: ctx.input.stage,
5537
5712
  region,
5538
5713
  fn: fn13,
5714
+ verbose: ctx.input.verbose,
5539
5715
  ...fn13.hasHandler ? { file } : {},
5540
5716
  ...apiOriginDomain ? { apiOriginDomain } : {}
5541
5717
  }).pipe(Effect35.provide(clients_exports.makeClients({
@@ -5718,7 +5894,7 @@ var deployProject = (input) => Effect35.gen(function* () {
5718
5894
  project: input.project,
5719
5895
  stage,
5720
5896
  region: input.region,
5721
- projectDir: input.projectDir
5897
+ packageDir: input.packageDir ?? input.projectDir
5722
5898
  });
5723
5899
  if (layerArn && layerStatus) {
5724
5900
  const status = layerStatus === "cached" ? c.dim("cached") : c.green("created");
@@ -5729,7 +5905,10 @@ var deployProject = (input) => Effect35.gen(function* () {
5729
5905
  const staticSitesNeedApi = !input.noSites && staticSiteHandlers.some(
5730
5906
  ({ exports }) => exports.some((fn13) => fn13.routePatterns.length > 0)
5731
5907
  );
5732
- 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) {
5733
5912
  const tagCtx = {
5734
5913
  project: input.project,
5735
5914
  stage,
@@ -5794,7 +5973,7 @@ var deployProject = (input) => Effect35.gen(function* () {
5794
5973
  const tasks = [
5795
5974
  ...apiId ? buildHttpTasks(ctx, httpHandlers, apiId, httpResults) : [],
5796
5975
  ...buildTableTasks(ctx, tableHandlers, tableResults),
5797
- ...buildAppTasks(ctx, appHandlers, appResults),
5976
+ ...buildAppTasks(ctx, appHandlers, appResults, apiId),
5798
5977
  ...input.noSites ? [] : buildStaticSiteTasks(ctx, staticSiteHandlers, staticSiteResults, apiId),
5799
5978
  ...buildFifoQueueTasks(ctx, fifoQueueHandlers, fifoQueueResults),
5800
5979
  ...buildBucketTasks(ctx, bucketHandlers, bucketResults),
@@ -5846,18 +6025,22 @@ import * as path7 from "path";
5846
6025
  import * as fs4 from "fs";
5847
6026
  import { pathToFileURL } from "url";
5848
6027
  import * as esbuild2 from "esbuild";
5849
- var loadConfig = async () => {
6028
+ import { Effect as Effect36 } from "effect";
6029
+ var loadConfig = Effect36.fn("loadConfig")(function* () {
5850
6030
  const configPath = path7.resolve(process.cwd(), "effortless.config.ts");
5851
6031
  if (!fs4.existsSync(configPath)) {
5852
6032
  return null;
5853
6033
  }
5854
- const result = await esbuild2.build({
5855
- entryPoints: [configPath],
5856
- bundle: true,
5857
- write: false,
5858
- format: "esm",
5859
- platform: "node",
5860
- 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}`)
5861
6044
  });
5862
6045
  const output = result.outputFiles?.[0];
5863
6046
  if (!output) {
@@ -5866,13 +6049,12 @@ var loadConfig = async () => {
5866
6049
  const code = output.text;
5867
6050
  const tempFile = path7.join(process.cwd(), ".effortless-config.mjs");
5868
6051
  fs4.writeFileSync(tempFile, code);
5869
- try {
5870
- const mod = await import(pathToFileURL(tempFile).href);
5871
- return mod.default;
5872
- } finally {
5873
- fs4.unlinkSync(tempFile);
5874
- }
5875
- };
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
+ });
5876
6058
  var projectOption = Options.text("project").pipe(
5877
6059
  Options.withAlias("p"),
5878
6060
  Options.withDescription("Project name (or 'name' in effortless.config.ts)"),
@@ -5880,12 +6062,12 @@ var projectOption = Options.text("project").pipe(
5880
6062
  );
5881
6063
  var stageOption = Options.text("stage").pipe(
5882
6064
  Options.withAlias("s"),
5883
- Options.withDescription("Stage name"),
6065
+ Options.withDescription("Deployment stage (default: dev)"),
5884
6066
  Options.withDefault("dev")
5885
6067
  );
5886
6068
  var regionOption = Options.text("region").pipe(
5887
6069
  Options.withAlias("r"),
5888
- Options.withDescription("AWS region"),
6070
+ Options.withDescription("AWS region (default: eu-central-1)"),
5889
6071
  Options.withDefault("eu-central-1")
5890
6072
  );
5891
6073
  var verboseOption = Options.boolean("verbose").pipe(
@@ -5918,6 +6100,23 @@ var getPatternsFromConfig = (config) => {
5918
6100
  });
5919
6101
  };
5920
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
+
5921
6120
  // src/cli/commands/deploy.ts
5922
6121
  var deployTargetArg = Args.text({ name: "target" }).pipe(
5923
6122
  Args.withDescription("Handler name or file path to deploy (optional - uses config patterns if not specified)"),
@@ -5929,8 +6128,8 @@ var isFilePath = (target) => {
5929
6128
  var deployCommand = Command.make(
5930
6129
  "deploy",
5931
6130
  { target: deployTargetArg, project: projectOption, stage: stageOption, region: regionOption, verbose: verboseOption, noSites: noSitesOption },
5932
- ({ target, project: projectOpt, stage, region, verbose, noSites }) => Effect36.gen(function* () {
5933
- const config = yield* Effect36.promise(loadConfig);
6131
+ ({ target, project: projectOpt, stage, region, verbose, noSites }) => Effect38.gen(function* () {
6132
+ const { config, cwd, projectDir } = yield* ProjectConfig;
5934
6133
  const project = Option.getOrElse(projectOpt, () => config?.name ?? "");
5935
6134
  const finalStage = config?.stage ?? stage;
5936
6135
  const finalRegion = config?.region ?? region;
@@ -5949,9 +6148,8 @@ var deployCommand = Command.make(
5949
6148
  acm: { region: "us-east-1" }
5950
6149
  });
5951
6150
  const logLevel = verbose ? LogLevel.Debug : LogLevel.Warning;
5952
- const projectDir = process.cwd();
5953
6151
  yield* Option.match(target, {
5954
- onNone: () => Effect36.gen(function* () {
6152
+ onNone: () => Effect38.gen(function* () {
5955
6153
  const patterns = getPatternsFromConfig(config);
5956
6154
  if (!patterns) {
5957
6155
  yield* Console3.error("Error: No target specified and no 'handlers' patterns in config");
@@ -5959,13 +6157,15 @@ var deployCommand = Command.make(
5959
6157
  }
5960
6158
  const results = yield* deployProject({
5961
6159
  projectDir,
6160
+ packageDir: cwd,
5962
6161
  patterns,
5963
6162
  project,
5964
6163
  stage: finalStage,
5965
6164
  region: finalRegion,
5966
- noSites
6165
+ noSites,
6166
+ verbose
5967
6167
  });
5968
- 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;
5969
6169
  yield* Console3.log(`
5970
6170
  ${c.green(`Deployed ${total} handler(s):`)}`);
5971
6171
  if (results.apiUrl) {
@@ -5984,6 +6184,10 @@ ${c.green(`Deployed ${total} handler(s):`)}`);
5984
6184
  for (const r of results.tableResults) {
5985
6185
  summaryLines.push({ name: r.exportName, line: ` ${c.cyan("[table]")} ${c.bold(r.exportName)}` });
5986
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
+ }
5987
6191
  for (const r of results.staticSiteResults) {
5988
6192
  summaryLines.push({ name: r.exportName, line: ` ${c.cyan("[site]")} ${c.bold(r.exportName)}: ${c.cyan(r.url)}` });
5989
6193
  }
@@ -5992,26 +6196,27 @@ ${c.green(`Deployed ${total} handler(s):`)}`);
5992
6196
  yield* Console3.log(line);
5993
6197
  }
5994
6198
  }),
5995
- onSome: (targetValue) => Effect36.gen(function* () {
6199
+ onSome: (targetValue) => Effect38.gen(function* () {
5996
6200
  if (isFilePath(targetValue)) {
5997
- const fullPath = path8.isAbsolute(targetValue) ? targetValue : path8.resolve(projectDir, targetValue);
6201
+ const fullPath = path9.isAbsolute(targetValue) ? targetValue : path9.resolve(projectDir, targetValue);
5998
6202
  const input = {
5999
6203
  projectDir,
6204
+ packageDir: cwd,
6000
6205
  file: fullPath,
6001
6206
  project,
6002
6207
  stage: finalStage,
6003
6208
  region: finalRegion
6004
6209
  };
6005
6210
  const httpResult = yield* deployAll(input).pipe(
6006
- Effect36.catchIf(
6211
+ Effect38.catchIf(
6007
6212
  (e) => e instanceof Error && e.message.includes("No defineHttp"),
6008
- () => Effect36.succeed(null)
6213
+ () => Effect38.succeed(null)
6009
6214
  )
6010
6215
  );
6011
6216
  const tableResults = yield* deployAllTables(input).pipe(
6012
- Effect36.catchIf(
6217
+ Effect38.catchIf(
6013
6218
  (e) => e instanceof Error && e.message.includes("No defineTable"),
6014
- () => Effect36.succeed([])
6219
+ () => Effect38.succeed([])
6015
6220
  )
6016
6221
  );
6017
6222
  if (!httpResult && tableResults.length === 0) {
@@ -6041,68 +6246,23 @@ Deployed ${tableResults.length} table handler(s):`));
6041
6246
  }
6042
6247
  const files = findHandlerFiles(patterns, projectDir);
6043
6248
  const discovered = discoverHandlers(files);
6044
- let foundFile = null;
6045
- let foundExport = null;
6046
- let handlerType = "http";
6047
- for (const { file, exports } of discovered.httpHandlers) {
6048
- for (const { exportName } of exports) {
6049
- if (exportName === targetValue) {
6050
- foundFile = file;
6051
- foundExport = exportName;
6052
- break;
6053
- }
6054
- }
6055
- if (foundFile) break;
6056
- }
6057
- if (!foundFile) {
6058
- for (const { file, exports } of discovered.tableHandlers) {
6059
- for (const { exportName } of exports) {
6060
- if (exportName === targetValue) {
6061
- foundFile = file;
6062
- foundExport = exportName;
6063
- handlerType = "table";
6064
- break;
6065
- }
6066
- }
6067
- if (foundFile) break;
6068
- }
6069
- }
6070
- if (!foundFile) {
6071
- for (const { file, exports } of discovered.appHandlers) {
6072
- for (const { exportName } of exports) {
6073
- if (exportName === targetValue) {
6074
- foundFile = file;
6075
- foundExport = exportName;
6076
- handlerType = "app";
6077
- break;
6078
- }
6079
- }
6080
- if (foundFile) break;
6081
- }
6082
- }
6083
- if (!foundFile || !foundExport) {
6249
+ const allHandlers = flattenHandlers(discovered);
6250
+ const found = allHandlers.find((h) => h.exportName === targetValue);
6251
+ if (!found) {
6084
6252
  yield* Console3.error(`Error: Handler "${targetValue}" not found`);
6085
6253
  yield* Console3.log("\nAvailable handlers:");
6086
- for (const { exports } of discovered.httpHandlers) {
6087
- for (const { exportName } of exports) {
6088
- yield* Console3.log(` ${c.cyan("[http]")} ${exportName}`);
6089
- }
6090
- }
6091
- for (const { exports } of discovered.tableHandlers) {
6092
- for (const { exportName } of exports) {
6093
- yield* Console3.log(` ${c.cyan("[table]")} ${exportName}`);
6094
- }
6095
- }
6096
- for (const { exports } of discovered.appHandlers) {
6097
- for (const { exportName } of exports) {
6098
- yield* Console3.log(` ${c.cyan("[app]")} ${exportName}`);
6099
- }
6254
+ for (const h of allHandlers) {
6255
+ yield* Console3.log(` ${c.cyan(`[${h.type}]`.padEnd(9))} ${h.exportName}`);
6100
6256
  }
6101
6257
  return;
6102
6258
  }
6103
- 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))}`);
6104
6263
  const input = {
6105
6264
  projectDir,
6265
+ packageDir: cwd,
6106
6266
  file: foundFile,
6107
6267
  project,
6108
6268
  stage: finalStage,
@@ -6121,15 +6281,15 @@ ${c.green("Deployed:")} ${c.cyan(result.url)}`);
6121
6281
  }
6122
6282
  })
6123
6283
  }).pipe(
6124
- Effect36.provide(clientsLayer),
6284
+ Effect38.provide(clientsLayer),
6125
6285
  Logger.withMinimumLogLevel(logLevel)
6126
6286
  );
6127
- })
6128
- ).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"));
6129
6289
 
6130
6290
  // src/cli/commands/status.ts
6131
6291
  import { Command as Command2 } from "@effect/cli";
6132
- 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";
6133
6293
  var { lambda, apigatewayv2: apigateway } = clients_exports;
6134
6294
  var INTERNAL_HANDLERS = /* @__PURE__ */ new Set(["api", "platform"]);
6135
6295
  var extractFunctionName = (arn) => {
@@ -6154,7 +6314,7 @@ var formatDate = (date) => {
6154
6314
  if (days < 7) return `${days}d ago`;
6155
6315
  return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
6156
6316
  };
6157
- var getLambdaDetails = (functionName) => Effect37.gen(function* () {
6317
+ var getLambdaDetails = (functionName) => Effect39.gen(function* () {
6158
6318
  const config = yield* lambda.make("get_function_configuration", {
6159
6319
  FunctionName: functionName
6160
6320
  });
@@ -6164,62 +6324,21 @@ var getLambdaDetails = (functionName) => Effect37.gen(function* () {
6164
6324
  timeout: config.Timeout
6165
6325
  };
6166
6326
  }).pipe(
6167
- Effect37.catchAll(() => Effect37.succeed({}))
6327
+ Effect39.catchAll(() => Effect39.succeed({}))
6168
6328
  );
6169
- var getApiUrl = (apiId) => Effect37.gen(function* () {
6329
+ var getApiUrl = (apiId) => Effect39.gen(function* () {
6170
6330
  const api = yield* apigateway.make("get_api", { ApiId: apiId });
6171
6331
  return api.ApiEndpoint;
6172
6332
  }).pipe(
6173
- Effect37.catchAll(() => Effect37.succeed(void 0))
6333
+ Effect39.catchAll(() => Effect39.succeed(void 0))
6174
6334
  );
6175
6335
  var discoverCodeHandlers = (projectDir, patterns) => {
6176
6336
  const files = findHandlerFiles(patterns, projectDir);
6177
6337
  const discovered = discoverHandlers(files);
6178
- const handlers = [];
6179
- for (const { exports } of discovered.httpHandlers) {
6180
- for (const fn13 of exports) {
6181
- handlers.push({
6182
- name: fn13.exportName,
6183
- type: "http",
6184
- method: fn13.config.method,
6185
- path: fn13.config.path
6186
- });
6187
- }
6188
- }
6189
- for (const { exports } of discovered.tableHandlers) {
6190
- for (const fn13 of exports) {
6191
- handlers.push({
6192
- name: fn13.exportName,
6193
- type: "table"
6194
- });
6195
- }
6196
- }
6197
- for (const { exports } of discovered.appHandlers) {
6198
- for (const fn13 of exports) {
6199
- handlers.push({
6200
- name: fn13.exportName,
6201
- type: "app",
6202
- path: fn13.config.path
6203
- });
6204
- }
6205
- }
6206
- for (const { exports } of discovered.staticSiteHandlers) {
6207
- for (const fn13 of exports) {
6208
- handlers.push({
6209
- name: fn13.exportName,
6210
- type: "site"
6211
- });
6212
- }
6213
- }
6214
- for (const { exports } of discovered.fifoQueueHandlers) {
6215
- for (const fn13 of exports) {
6216
- handlers.push({
6217
- name: fn13.exportName,
6218
- type: "queue"
6219
- });
6220
- }
6221
- }
6222
- return handlers;
6338
+ return flattenHandlers(discovered).map((h) => ({
6339
+ name: h.exportName,
6340
+ type: h.type
6341
+ }));
6223
6342
  };
6224
6343
  var discoverAwsHandlers = (resources) => {
6225
6344
  const byHandler = groupResourcesByHandler(resources);
@@ -6247,6 +6366,7 @@ var TYPE_LABELS = {
6247
6366
  http: "http",
6248
6367
  table: "table",
6249
6368
  app: "app",
6369
+ api: "api",
6250
6370
  site: "site",
6251
6371
  queue: "queue",
6252
6372
  lambda: "lambda",
@@ -6267,9 +6387,9 @@ var STATUS_COLORS = {
6267
6387
  var formatStatus = (status) => {
6268
6388
  return STATUS_COLORS[status](status.padEnd(10));
6269
6389
  };
6270
- var formatRoute = (method, path10) => {
6271
- if (method && path10) return `${method.padEnd(5)} ${path10}`;
6272
- if (path10) return path10;
6390
+ var formatRoute = (method, path11) => {
6391
+ if (method && path11) return `${method.padEnd(5)} ${path11}`;
6392
+ if (path11) return path11;
6273
6393
  return "";
6274
6394
  };
6275
6395
  var formatEntry = (entry) => {
@@ -6291,8 +6411,8 @@ var formatEntry = (entry) => {
6291
6411
  var statusCommand = Command2.make(
6292
6412
  "status",
6293
6413
  { project: projectOption, stage: stageOption, region: regionOption, verbose: verboseOption },
6294
- ({ project: projectOpt, stage, region, verbose }) => Effect37.gen(function* () {
6295
- const config = yield* Effect37.promise(loadConfig);
6414
+ ({ project: projectOpt, stage, region, verbose }) => Effect39.gen(function* () {
6415
+ const { config, projectDir } = yield* ProjectConfig;
6296
6416
  const project = Option2.getOrElse(projectOpt, () => config?.name ?? "");
6297
6417
  const finalStage = config?.stage ?? stage;
6298
6418
  const finalRegion = config?.region ?? region;
@@ -6306,11 +6426,10 @@ var statusCommand = Command2.make(
6306
6426
  resource_groups_tagging_api: { region: finalRegion }
6307
6427
  });
6308
6428
  const logLevel = verbose ? LogLevel2.Debug : LogLevel2.Info;
6309
- const projectDir = process.cwd();
6310
6429
  const patterns = getPatternsFromConfig(config);
6311
6430
  const codeHandlers = patterns ? discoverCodeHandlers(projectDir, patterns) : [];
6312
6431
  const codeHandlerNames = new Set(codeHandlers.map((h) => h.name));
6313
- yield* Effect37.gen(function* () {
6432
+ yield* Effect39.gen(function* () {
6314
6433
  yield* Console4.log(`
6315
6434
  Status for ${c.bold(project + "/" + finalStage)}:
6316
6435
  `);
@@ -6407,18 +6526,18 @@ API: ${c.cyan(apiUrl)}`);
6407
6526
  yield* Console4.log(`
6408
6527
  Total: ${parts.join(", ")}`);
6409
6528
  }).pipe(
6410
- Effect37.provide(clientsLayer),
6529
+ Effect39.provide(clientsLayer),
6411
6530
  Logger2.withMinimumLogLevel(logLevel)
6412
6531
  );
6413
- })
6414
- ).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"));
6415
6534
 
6416
6535
  // src/cli/commands/cleanup.ts
6417
6536
  import { Command as Command3, Options as Options2 } from "@effect/cli";
6418
- 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";
6419
6538
 
6420
6539
  // src/deploy/cleanup.ts
6421
- import { Effect as Effect38 } from "effect";
6540
+ import { Effect as Effect40 } from "effect";
6422
6541
  var extractResourceName = (arn, type) => {
6423
6542
  switch (type) {
6424
6543
  case "lambda": {
@@ -6461,7 +6580,7 @@ var extractLayerInfo = (arn) => {
6461
6580
  version: parseInt(parts[parts.length - 1] ?? "0", 10)
6462
6581
  };
6463
6582
  };
6464
- var deleteResource = (resource) => Effect38.gen(function* () {
6583
+ var deleteResource = (resource) => Effect40.gen(function* () {
6465
6584
  const name = extractResourceName(resource.arn, resource.type);
6466
6585
  switch (resource.type) {
6467
6586
  case "lambda":
@@ -6494,18 +6613,18 @@ var deleteResource = (resource) => Effect38.gen(function* () {
6494
6613
  yield* deleteSesIdentity(name);
6495
6614
  break;
6496
6615
  default:
6497
- yield* Effect38.logWarning(`Unknown resource type: ${resource.type}, skipping ${resource.arn}`);
6616
+ yield* Effect40.logWarning(`Unknown resource type: ${resource.type}, skipping ${resource.arn}`);
6498
6617
  }
6499
6618
  });
6500
- var deleteResources = (resources) => Effect38.gen(function* () {
6619
+ var deleteResources = (resources) => Effect40.gen(function* () {
6501
6620
  const orderedTypes = ["lambda", "api-gateway", "cloudfront-distribution", "sqs", "ses", "dynamodb", "s3-bucket", "lambda-layer", "iam-role"];
6502
6621
  const iamRolesToDelete = /* @__PURE__ */ new Set();
6503
6622
  for (const type of orderedTypes) {
6504
6623
  const resourcesOfType = resources.filter((r) => r.type === type);
6505
6624
  for (const resource of resourcesOfType) {
6506
6625
  yield* deleteResource(resource).pipe(
6507
- Effect38.catchAll(
6508
- (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}`)
6509
6628
  )
6510
6629
  );
6511
6630
  if (resource.type === "lambda") {
@@ -6517,8 +6636,8 @@ var deleteResources = (resources) => Effect38.gen(function* () {
6517
6636
  }
6518
6637
  for (const roleName of iamRolesToDelete) {
6519
6638
  yield* deleteRole(roleName).pipe(
6520
- Effect38.catchAll(
6521
- (error) => Effect38.logError(`Failed to delete IAM role ${roleName}: ${error}`)
6639
+ Effect40.catchAll(
6640
+ (error) => Effect40.logError(`Failed to delete IAM role ${roleName}: ${error}`)
6522
6641
  )
6523
6642
  );
6524
6643
  }
@@ -6539,11 +6658,14 @@ var layerOption = Options2.boolean("layer").pipe(
6539
6658
  var rolesOption = Options2.boolean("roles").pipe(
6540
6659
  Options2.withDescription("Clean up orphaned IAM roles instead of handler resources")
6541
6660
  );
6661
+ var orphanedOption = Options2.boolean("orphaned").pipe(
6662
+ Options2.withDescription("Delete only handlers that exist in AWS but not in code")
6663
+ );
6542
6664
  var cleanupCommand = Command3.make(
6543
6665
  "cleanup",
6544
- { project: projectOption, stage: stageOption, region: regionOption, handler: handlerOption, layer: layerOption, roles: rolesOption, all: cleanupAllOption, dryRun: dryRunOption, verbose: verboseOption },
6545
- ({ project: projectOpt, stage, region, handler: handlerOpt, layer: cleanupLayer, roles: cleanupRoles, all: deleteAll, dryRun, verbose }) => Effect39.gen(function* () {
6546
- 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;
6547
6669
  const project = Option3.getOrElse(projectOpt, () => config?.name ?? "");
6548
6670
  const finalStage = config?.stage ?? stage;
6549
6671
  const finalRegion = config?.region ?? region;
@@ -6554,14 +6676,14 @@ var cleanupCommand = Command3.make(
6554
6676
  const logLevel = verbose ? LogLevel3.Debug : LogLevel3.Info;
6555
6677
  if (cleanupLayer) {
6556
6678
  yield* cleanupLayerVersions({ project, region: finalRegion, deleteAll, dryRun }).pipe(
6557
- Effect39.provide(clients_exports.makeClients({ lambda: { region: finalRegion } })),
6679
+ Effect41.provide(clients_exports.makeClients({ lambda: { region: finalRegion } })),
6558
6680
  Logger3.withMinimumLogLevel(logLevel)
6559
6681
  );
6560
6682
  return;
6561
6683
  }
6562
6684
  if (cleanupRoles) {
6563
6685
  yield* cleanupIamRoles({ project, stage: finalStage, region: finalRegion, deleteAll, dryRun }).pipe(
6564
- Effect39.provide(clients_exports.makeClients({ iam: { region: finalRegion } })),
6686
+ Effect41.provide(clients_exports.makeClients({ iam: { region: finalRegion } })),
6565
6687
  Logger3.withMinimumLogLevel(logLevel)
6566
6688
  );
6567
6689
  return;
@@ -6576,7 +6698,7 @@ var cleanupCommand = Command3.make(
6576
6698
  s3: { region: finalRegion },
6577
6699
  cloudfront: { region: "us-east-1" }
6578
6700
  });
6579
- yield* Effect39.gen(function* () {
6701
+ yield* Effect41.gen(function* () {
6580
6702
  yield* Console5.log(`
6581
6703
  Looking for resources in ${c.bold(project + "/" + finalStage)}...
6582
6704
  `);
@@ -6586,14 +6708,34 @@ Looking for resources in ${c.bold(project + "/" + finalStage)}...
6586
6708
  return;
6587
6709
  }
6588
6710
  const byHandler = groupResourcesByHandler(resources);
6589
- const handlersToDelete = handlerFilter ? [[handlerFilter, byHandler.get(handlerFilter) ?? []]] : Array.from(byHandler.entries());
6590
- if (handlerFilter && !byHandler.has(handlerFilter)) {
6591
- yield* Console5.error(`Handler "${handlerFilter}" not found.`);
6592
- yield* Console5.log("\nAvailable handlers:");
6593
- for (const [h] of byHandler) {
6594
- 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;
6595
6717
  }
6596
- 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());
6597
6739
  }
6598
6740
  const resourcesToDelete = [];
6599
6741
  const derivedRoles = [];
@@ -6624,9 +6766,10 @@ Total: ${totalResources} resource(s) (${derivedRoles.length} derived)`);
6624
6766
  ${c.yellow("[DRY RUN]")} No resources were deleted.`);
6625
6767
  return;
6626
6768
  }
6627
- if (!handlerFilter && !deleteAll) {
6769
+ if (!handlerFilter && !cleanupOrphaned && !deleteAll) {
6628
6770
  yield* Console5.log("\nTo delete these resources, use one of:");
6629
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`);
6630
6773
  yield* Console5.log(` ${c.dim("eff cleanup --handler <name>")} # Delete specific handler`);
6631
6774
  yield* Console5.log(` ${c.dim("eff cleanup --dry-run")} # Preview without deleting`);
6632
6775
  return;
@@ -6635,12 +6778,12 @@ ${c.yellow("[DRY RUN]")} No resources were deleted.`);
6635
6778
  yield* deleteResources(resourcesToDelete);
6636
6779
  yield* Console5.log(c.green("\nDone!"));
6637
6780
  }).pipe(
6638
- Effect39.provide(clientsLayer),
6781
+ Effect41.provide(clientsLayer),
6639
6782
  Logger3.withMinimumLogLevel(logLevel)
6640
6783
  );
6641
- })
6642
- ).pipe(Command3.withDescription("Delete deployed resources"));
6643
- 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* () {
6644
6787
  const layerName = `${input.project}-deps`;
6645
6788
  yield* Console5.log(`
6646
6789
  Searching for layer versions: ${layerName}
@@ -6672,7 +6815,7 @@ ${c.yellow("[DRY RUN]")} No layers were deleted.`);
6672
6815
  yield* Console5.log(c.green(`
6673
6816
  Deleted ${deleted} layer version(s).`));
6674
6817
  });
6675
- var cleanupIamRoles = (input) => Effect39.gen(function* () {
6818
+ var cleanupIamRoles = (input) => Effect41.gen(function* () {
6676
6819
  yield* Console5.log("\nSearching for effortless IAM roles...\n");
6677
6820
  const allRoles = yield* listEffortlessRoles();
6678
6821
  if (allRoles.length === 0) {
@@ -6721,8 +6864,8 @@ ${c.yellow("[DRY RUN]")} No roles were deleted.`);
6721
6864
  yield* Console5.log(c.red("\nDeleting roles..."));
6722
6865
  for (const role of roles) {
6723
6866
  yield* deleteRole(role.name).pipe(
6724
- Effect39.catchAll(
6725
- (error) => Effect39.logError(`Failed to delete ${role.name}: ${error}`)
6867
+ Effect41.catchAll(
6868
+ (error) => Effect41.logError(`Failed to delete ${role.name}: ${error}`)
6726
6869
  )
6727
6870
  );
6728
6871
  }
@@ -6731,7 +6874,7 @@ ${c.yellow("[DRY RUN]")} No roles were deleted.`);
6731
6874
 
6732
6875
  // src/cli/commands/logs.ts
6733
6876
  import { Args as Args2, Command as Command4, Options as Options3 } from "@effect/cli";
6734
- 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";
6735
6878
  var { cloudwatch_logs } = clients_exports;
6736
6879
  var handlerArg = Args2.text({ name: "handler" }).pipe(
6737
6880
  Args2.withDescription("Handler name to show logs for")
@@ -6802,8 +6945,8 @@ var fetchLogs = (logGroupName, startTime, nextToken) => cloudwatch_logs.make("fi
6802
6945
  var logsCommand = Command4.make(
6803
6946
  "logs",
6804
6947
  { handler: handlerArg, project: projectOption, stage: stageOption, region: regionOption, tail: tailOption, since: sinceOption, verbose: verboseOption },
6805
- ({ handler: handlerName, project: projectOpt, stage, region, tail, since, verbose }) => Effect40.gen(function* () {
6806
- 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;
6807
6950
  const project = Option4.getOrElse(projectOpt, () => config?.name ?? "");
6808
6951
  const finalStage = config?.stage ?? stage;
6809
6952
  const finalRegion = config?.region ?? region;
@@ -6811,17 +6954,11 @@ var logsCommand = Command4.make(
6811
6954
  yield* Console6.error("Error: --project is required (or set 'name' in effortless.config.ts)");
6812
6955
  return;
6813
6956
  }
6814
- const projectDir = process.cwd();
6815
6957
  const patterns = getPatternsFromConfig(config);
6816
6958
  if (patterns) {
6817
6959
  const files = findHandlerFiles(patterns, projectDir);
6818
6960
  const discovered = discoverHandlers(files);
6819
- const allHandlerNames = [
6820
- ...discovered.httpHandlers.flatMap((h) => h.exports.map((e) => e.exportName)),
6821
- ...discovered.tableHandlers.flatMap((h) => h.exports.map((e) => e.exportName)),
6822
- ...discovered.appHandlers.flatMap((h) => h.exports.map((e) => e.exportName)),
6823
- ...discovered.fifoQueueHandlers.flatMap((h) => h.exports.map((e) => e.exportName))
6824
- ];
6961
+ const allHandlerNames = flattenHandlers(discovered).map((h) => h.exportName);
6825
6962
  if (!allHandlerNames.includes(handlerName)) {
6826
6963
  yield* Console6.error(`Handler "${handlerName}" not found in code.`);
6827
6964
  if (allHandlerNames.length > 0) {
@@ -6839,18 +6976,18 @@ var logsCommand = Command4.make(
6839
6976
  cloudwatch_logs: { region: finalRegion }
6840
6977
  });
6841
6978
  const logLevel = verbose ? LogLevel4.Debug : LogLevel4.Info;
6842
- yield* Effect40.gen(function* () {
6979
+ yield* Effect42.gen(function* () {
6843
6980
  const durationMs = parseDuration(since);
6844
6981
  let startTime = Date.now() - durationMs;
6845
6982
  yield* Console6.log(`Logs for ${c.bold(handlerName)} ${c.dim(`(${logGroupName})`)}:
6846
6983
  `);
6847
6984
  let hasLogs = false;
6848
6985
  const result = yield* fetchLogs(logGroupName, startTime).pipe(
6849
- Effect40.catchAll((error) => {
6986
+ Effect42.catchAll((error) => {
6850
6987
  if (error instanceof clients_exports.cloudwatch_logs.CloudWatchLogsError && error.cause.name === "ResourceNotFoundException") {
6851
- return Effect40.succeed({ events: void 0, nextToken: void 0 });
6988
+ return Effect42.succeed({ events: void 0, nextToken: void 0 });
6852
6989
  }
6853
- return Effect40.fail(error);
6990
+ return Effect42.fail(error);
6854
6991
  })
6855
6992
  );
6856
6993
  if (result.events && result.events.length > 0) {
@@ -6874,10 +7011,10 @@ var logsCommand = Command4.make(
6874
7011
  if (!hasLogs) {
6875
7012
  yield* Console6.log("Waiting for logs... (Ctrl+C to stop)\n");
6876
7013
  }
6877
- yield* Effect40.repeat(
6878
- Effect40.gen(function* () {
7014
+ yield* Effect42.repeat(
7015
+ Effect42.gen(function* () {
6879
7016
  const result2 = yield* fetchLogs(logGroupName, startTime).pipe(
6880
- Effect40.catchAll(() => Effect40.succeed({ events: void 0, nextToken: void 0 }))
7017
+ Effect42.catchAll(() => Effect42.succeed({ events: void 0, nextToken: void 0 }))
6881
7018
  );
6882
7019
  if (result2.events && result2.events.length > 0) {
6883
7020
  for (const event of result2.events) {
@@ -6895,16 +7032,16 @@ var logsCommand = Command4.make(
6895
7032
  Schedule4.spaced("2 seconds")
6896
7033
  );
6897
7034
  }).pipe(
6898
- Effect40.provide(clientsLayer),
7035
+ Effect42.provide(clientsLayer),
6899
7036
  Logger4.withMinimumLogLevel(logLevel)
6900
7037
  );
6901
- })
6902
- ).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"));
6903
7040
 
6904
7041
  // src/cli/commands/layer.ts
6905
7042
  import { Command as Command5, Options as Options4 } from "@effect/cli";
6906
- import { Effect as Effect41, Console as Console7 } from "effect";
6907
- import * as path9 from "path";
7043
+ import { Effect as Effect43, Console as Console7 } from "effect";
7044
+ import * as path10 from "path";
6908
7045
  import * as fs5 from "fs";
6909
7046
  var buildOption = Options4.boolean("build").pipe(
6910
7047
  Options4.withDescription("Build layer directory locally (for debugging)")
@@ -6912,22 +7049,28 @@ var buildOption = Options4.boolean("build").pipe(
6912
7049
  var layerCommand = Command5.make(
6913
7050
  "layer",
6914
7051
  { build: buildOption, output: outputOption, verbose: verboseOption },
6915
- ({ build: build3, output, verbose }) => Effect41.gen(function* () {
6916
- const config = yield* Effect41.promise(loadConfig);
6917
- const projectDir = process.cwd();
7052
+ ({ build: build3, output, verbose }) => Effect43.gen(function* () {
7053
+ const { config, cwd } = yield* ProjectConfig;
6918
7054
  if (build3) {
6919
- yield* buildLayer(projectDir, output, verbose);
7055
+ yield* buildLayer(cwd, output, verbose);
6920
7056
  } else {
6921
- yield* showLayerInfo(projectDir, config?.name, verbose);
7057
+ yield* showLayerInfo(cwd, config?.name, verbose);
6922
7058
  }
6923
- })
6924
- ).pipe(Command5.withDescription("Show or build the dependency layer"));
6925
- 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* () {
6926
7062
  yield* Console7.log(`
6927
7063
  ${c.bold("=== Layer Packages Preview ===")}
6928
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("");
6929
7072
  const prodDeps = yield* readProductionDependencies(projectDir).pipe(
6930
- Effect41.catchAll(() => Effect41.succeed([]))
7073
+ Effect43.catchAll(() => Effect43.succeed([]))
6931
7074
  );
6932
7075
  if (prodDeps.length === 0) {
6933
7076
  yield* Console7.log("No production dependencies found in package.json");
@@ -6939,7 +7082,7 @@ ${c.bold("=== Layer Packages Preview ===")}
6939
7082
  yield* Console7.log(` ${dep}`);
6940
7083
  }
6941
7084
  const hash = yield* computeLockfileHash(projectDir).pipe(
6942
- Effect41.catchAll(() => Effect41.succeed(null))
7085
+ Effect43.catchAll(() => Effect43.succeed(null))
6943
7086
  );
6944
7087
  if (hash) {
6945
7088
  yield* Console7.log(`
@@ -6947,7 +7090,7 @@ Lockfile hash: ${hash}`);
6947
7090
  } else {
6948
7091
  yield* Console7.log("\nNo lockfile found (package-lock.json, pnpm-lock.yaml, or yarn.lock)");
6949
7092
  }
6950
- const { packages: allPackages, warnings: layerWarnings } = yield* Effect41.sync(() => collectLayerPackages(projectDir, prodDeps));
7093
+ const { packages: allPackages, warnings: layerWarnings } = yield* Effect43.sync(() => collectLayerPackages(projectDir, prodDeps));
6951
7094
  if (layerWarnings.length > 0) {
6952
7095
  yield* Console7.log(c.yellow(`
6953
7096
  Warnings (${layerWarnings.length}):`));
@@ -6976,10 +7119,10 @@ Total packages for layer ${c.dim(`(${allPackages.length})`)}:`);
6976
7119
  Layer name: ${projectName}-deps`);
6977
7120
  }
6978
7121
  });
6979
- var buildLayer = (projectDir, output, verbose) => Effect41.gen(function* () {
6980
- const outputDir = path9.isAbsolute(output) ? output : path9.resolve(projectDir, output);
6981
- const layerDir = path9.join(outputDir, "nodejs", "node_modules");
6982
- 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");
6983
7126
  if (fs5.existsSync(layerRoot)) {
6984
7127
  fs5.rmSync(layerRoot, { recursive: true });
6985
7128
  }
@@ -6987,8 +7130,15 @@ var buildLayer = (projectDir, output, verbose) => Effect41.gen(function* () {
6987
7130
  yield* Console7.log(`
6988
7131
  ${c.bold("=== Building Layer Locally ===")}
6989
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("");
6990
7140
  const prodDeps = yield* readProductionDependencies(projectDir).pipe(
6991
- Effect41.catchAll(() => Effect41.succeed([]))
7141
+ Effect43.catchAll(() => Effect43.succeed([]))
6992
7142
  );
6993
7143
  if (prodDeps.length === 0) {
6994
7144
  yield* Console7.log("No production dependencies found in package.json");
@@ -7000,11 +7150,11 @@ ${c.bold("=== Building Layer Locally ===")}
7000
7150
  yield* Console7.log(` ${dep}`);
7001
7151
  }
7002
7152
  const hash = yield* computeLockfileHash(projectDir).pipe(
7003
- Effect41.catchAll(() => Effect41.succeed("unknown"))
7153
+ Effect43.catchAll(() => Effect43.succeed("unknown"))
7004
7154
  );
7005
7155
  yield* Console7.log(`
7006
7156
  Lockfile hash: ${hash}`);
7007
- 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));
7008
7158
  if (layerWarnings.length > 0) {
7009
7159
  yield* Console7.log(`
7010
7160
  Warnings (${layerWarnings.length}):`);
@@ -7031,9 +7181,9 @@ Collected ${allPackages.length} packages for layer`);
7031
7181
  }
7032
7182
  continue;
7033
7183
  }
7034
- const destPath = path9.join(layerDir, pkgName);
7184
+ const destPath = path10.join(layerDir, pkgName);
7035
7185
  if (pkgName.startsWith("@")) {
7036
- const scopeDir = path9.join(layerDir, pkgName.split("/")[0] ?? pkgName);
7186
+ const scopeDir = path10.join(layerDir, pkgName.split("/")[0] ?? pkgName);
7037
7187
  if (!fs5.existsSync(scopeDir)) {
7038
7188
  fs5.mkdirSync(scopeDir, { recursive: true });
7039
7189
  }
@@ -7054,20 +7204,20 @@ To inspect: ls ${layerDir}`);
7054
7204
  // src/cli/commands/config.ts
7055
7205
  import { Args as Args3, Command as Command6 } from "@effect/cli";
7056
7206
  import { Prompt } from "@effect/cli";
7057
- import { Effect as Effect42, Console as Console8, Logger as Logger5, LogLevel as LogLevel5, Option as Option5 } from "effect";
7058
- var loadRequiredParams = (projectOpt, stage, region) => Effect42.gen(function* () {
7059
- 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;
7060
7210
  const project = Option5.getOrElse(projectOpt, () => config?.name ?? "");
7061
7211
  if (!project) {
7062
7212
  yield* Console8.error("Error: --project is required (or set 'name' in effortless.config.ts)");
7063
- return yield* Effect42.fail(new Error("Missing project name"));
7213
+ return yield* Effect44.fail(new Error("Missing project name"));
7064
7214
  }
7065
7215
  const patterns = getPatternsFromConfig(config);
7066
7216
  if (!patterns) {
7067
7217
  yield* Console8.error("Error: No 'handlers' patterns in config");
7068
- return yield* Effect42.fail(new Error("Missing handler patterns"));
7218
+ return yield* Effect44.fail(new Error("Missing handler patterns"));
7069
7219
  }
7070
- const files = findHandlerFiles(patterns, process.cwd());
7220
+ const files = findHandlerFiles(patterns, projectDir);
7071
7221
  const handlers = discoverHandlers(files);
7072
7222
  const finalStage = config?.stage ?? stage;
7073
7223
  const finalRegion = config?.region ?? region;
@@ -7077,7 +7227,7 @@ var loadRequiredParams = (projectOpt, stage, region) => Effect42.gen(function* (
7077
7227
  var listCommand = Command6.make(
7078
7228
  "list",
7079
7229
  { project: projectOption, stage: stageOption, region: regionOption, verbose: verboseOption },
7080
- ({ project: projectOpt, stage, region, verbose }) => Effect42.gen(function* () {
7230
+ ({ project: projectOpt, stage, region, verbose }) => Effect44.gen(function* () {
7081
7231
  const ctx = yield* loadRequiredParams(projectOpt, stage, region);
7082
7232
  const { params } = ctx;
7083
7233
  if (params.length === 0) {
@@ -7085,7 +7235,7 @@ var listCommand = Command6.make(
7085
7235
  return;
7086
7236
  }
7087
7237
  const { existing, missing } = yield* checkMissingParams(params).pipe(
7088
- Effect42.provide(clients_exports.makeClients({ ssm: { region: ctx.region } }))
7238
+ Effect44.provide(clients_exports.makeClients({ ssm: { region: ctx.region } }))
7089
7239
  );
7090
7240
  yield* Console8.log(`
7091
7241
  ${c.bold("Config parameters")} ${c.dim(`(${ctx.project} / ${ctx.stage})`)}
@@ -7108,16 +7258,19 @@ ${c.bold("Config parameters")} ${c.dim(`(${ctx.project} / ${ctx.stage})`)}
7108
7258
  ${c.green("All parameters are set.")}`);
7109
7259
  }
7110
7260
  yield* Console8.log("");
7111
- }).pipe(Logger5.withMinimumLogLevel(LogLevel5.Warning))
7112
- ).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"));
7113
7266
  var setKeyArg = Args3.text({ name: "key" }).pipe(
7114
7267
  Args3.withDescription("SSM parameter key (e.g. stripe/secret-key)")
7115
7268
  );
7116
7269
  var setCommand = Command6.make(
7117
7270
  "set",
7118
7271
  { key: setKeyArg, project: projectOption, stage: stageOption, region: regionOption, verbose: verboseOption },
7119
- ({ key, project: projectOpt, stage, region, verbose }) => Effect42.gen(function* () {
7120
- const config = yield* Effect42.promise(loadConfig);
7272
+ ({ key, project: projectOpt, stage, region, verbose }) => Effect44.gen(function* () {
7273
+ const { config } = yield* ProjectConfig;
7121
7274
  const project = Option5.getOrElse(projectOpt, () => config?.name ?? "");
7122
7275
  if (!project) {
7123
7276
  yield* Console8.error("Error: --project is required (or set 'name' in effortless.config.ts)");
@@ -7134,15 +7287,18 @@ var setCommand = Command6.make(
7134
7287
  Value: value,
7135
7288
  Type: "SecureString",
7136
7289
  Overwrite: true
7137
- }).pipe(Effect42.provide(clients_exports.makeClients({ ssm: { region: finalRegion } })));
7290
+ }).pipe(Effect44.provide(clients_exports.makeClients({ ssm: { region: finalRegion } })));
7138
7291
  yield* Console8.log(`
7139
7292
  ${c.green("\u2713")} ${c.cyan(ssmPath)} ${c.dim("(SecureString)")}`);
7140
- }).pipe(Logger5.withMinimumLogLevel(LogLevel5.Warning))
7141
- ).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)"));
7142
7298
  var configRootCommand = Command6.make(
7143
7299
  "config",
7144
7300
  { project: projectOption, stage: stageOption, region: regionOption, verbose: verboseOption },
7145
- ({ project: projectOpt, stage, region, verbose }) => Effect42.gen(function* () {
7301
+ ({ project: projectOpt, stage, region, verbose }) => Effect44.gen(function* () {
7146
7302
  const ctx = yield* loadRequiredParams(projectOpt, stage, region);
7147
7303
  const { params } = ctx;
7148
7304
  if (params.length === 0) {
@@ -7150,7 +7306,7 @@ var configRootCommand = Command6.make(
7150
7306
  return;
7151
7307
  }
7152
7308
  const { missing } = yield* checkMissingParams(params).pipe(
7153
- Effect42.provide(clients_exports.makeClients({ ssm: { region: ctx.region } }))
7309
+ Effect44.provide(clients_exports.makeClients({ ssm: { region: ctx.region } }))
7154
7310
  );
7155
7311
  if (missing.length === 0) {
7156
7312
  yield* Console8.log(`
@@ -7175,7 +7331,7 @@ ${c.bold("Missing parameters")} ${c.dim(`(${ctx.project} / ${ctx.stage})`)}
7175
7331
  Value: value,
7176
7332
  Type: "SecureString",
7177
7333
  Overwrite: false
7178
- }).pipe(Effect42.provide(clients_exports.makeClients({ ssm: { region: ctx.region } })));
7334
+ }).pipe(Effect44.provide(clients_exports.makeClients({ ssm: { region: ctx.region } })));
7179
7335
  yield* Console8.log(` ${c.green("\u2713")} created`);
7180
7336
  created++;
7181
7337
  }
@@ -7188,9 +7344,12 @@ ${c.bold("Missing parameters")} ${c.dim(`(${ctx.project} / ${ctx.stage})`)}
7188
7344
  No parameters created.
7189
7345
  `);
7190
7346
  }
7191
- }).pipe(Logger5.withMinimumLogLevel(LogLevel5.Warning))
7347
+ }).pipe(
7348
+ Effect44.provide(ProjectConfig.Live),
7349
+ Logger5.withMinimumLogLevel(LogLevel5.Warning)
7350
+ )
7192
7351
  ).pipe(
7193
- 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"),
7194
7353
  Command6.withSubcommands([listCommand, setCommand])
7195
7354
  );
7196
7355
  var configCommand = configRootCommand;
@@ -7207,6 +7366,7 @@ var cli = Command7.run(mainCommand, {
7207
7366
  version
7208
7367
  });
7209
7368
  cli(process.argv).pipe(
7210
- Effect43.provide(NodeContext.layer),
7369
+ Effect45.provide(NodeContext.layer),
7370
+ Effect45.provide(CliConfig.layer({ showBuiltIns: false, showTypes: false })),
7211
7371
  NodeRuntime.runMain
7212
7372
  );