@effortless-aws/cli 0.1.1 → 0.2.1

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 +502 -290
  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,92 @@ 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 extractExportPaths = (value) => {
2664
+ if (typeof value === "string") return [value];
2665
+ if (typeof value === "object" && value !== null) {
2666
+ return Object.values(value).flatMap(extractExportPaths);
2667
+ }
2668
+ return [];
2669
+ };
2670
+ var isRawTypeScript = (p) => /\.(?:ts|tsx|mts|cts)$/.test(p) && !p.endsWith(".d.ts") && !p.endsWith(".d.mts") && !p.endsWith(".d.cts");
2671
+ var checkDependencyWarnings = (projectDir) => Effect18.gen(function* () {
2672
+ const pkgPath = path.join(projectDir, "package.json");
2673
+ const content = yield* Effect18.tryPromise({
2674
+ try: () => fs.readFile(pkgPath, "utf-8"),
2675
+ catch: () => Effect18.succeed(null)
2676
+ });
2677
+ if (!content) return [];
2678
+ const pkg = JSON.parse(content);
2679
+ const deps = Object.keys(pkg.dependencies ?? {});
2680
+ const devDeps = Object.keys(pkg.devDependencies ?? {});
2681
+ const warnings = [];
2682
+ const devInProd = deps.filter(
2683
+ (d) => DEV_ONLY_PACKAGES.has(d) || DEV_ONLY_PREFIXES.some((p) => d.startsWith(p))
2684
+ );
2685
+ if (devInProd.length > 0) {
2686
+ warnings.push(
2687
+ `These packages are in "dependencies" but look like dev tools (they will bloat the Lambda layer): ${devInProd.join(", ")}. Consider moving them to "devDependencies".`
2688
+ );
2689
+ }
2690
+ if (deps.length === 0 && devDeps.length > 0) {
2691
+ warnings.push(
2692
+ `"dependencies" is empty but "devDependencies" has ${devDeps.length} package(s). Runtime packages must be in "dependencies" to be included in the Lambda layer.`
2693
+ );
2694
+ }
2695
+ for (const dep of deps) {
2696
+ const depPath = getPackageRealPath(projectDir, dep);
2697
+ if (!depPath) continue;
2698
+ const depPkgPath = path.join(depPath, "package.json");
2699
+ if (!fsSync.existsSync(depPkgPath)) continue;
2700
+ try {
2701
+ const depPkg = JSON.parse(fsSync.readFileSync(depPkgPath, "utf-8"));
2702
+ const entryPoints = [];
2703
+ if (typeof depPkg.main === "string") entryPoints.push(depPkg.main);
2704
+ if (typeof depPkg.module === "string") entryPoints.push(depPkg.module);
2705
+ entryPoints.push(...extractExportPaths(depPkg.exports));
2706
+ const tsEntries = [...new Set(entryPoints.filter(isRawTypeScript))];
2707
+ if (tsEntries.length > 0) {
2708
+ warnings.push(
2709
+ `Package "${dep}" has TypeScript entry points (${tsEntries.join(", ")}) that will fail at runtime in node_modules (ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING). Move it to "devDependencies" so esbuild can bundle and transpile it.`
2710
+ );
2711
+ }
2712
+ } catch {
2713
+ }
2714
+ }
2715
+ return warnings;
2716
+ });
2631
2717
  var getPackageRealPath = (projectDir, pkgName) => {
2632
2718
  const pkgPath = path.join(projectDir, "node_modules", pkgName);
2633
2719
  if (!fsSync.existsSync(pkgPath)) return null;
@@ -2816,6 +2902,12 @@ var getExistingLayerByHash = (layerName, expectedHash) => Effect18.gen(function*
2816
2902
  };
2817
2903
  });
2818
2904
  var ensureLayer = (config) => Effect18.gen(function* () {
2905
+ const depWarnings = yield* checkDependencyWarnings(config.projectDir).pipe(
2906
+ Effect18.catchAll(() => Effect18.succeed([]))
2907
+ );
2908
+ for (const w of depWarnings) {
2909
+ yield* Effect18.logWarning(`[layer] ${w}`);
2910
+ }
2819
2911
  const dependencies = yield* readProductionDependencies(config.projectDir).pipe(
2820
2912
  Effect18.catchAll(() => Effect18.succeed([]))
2821
2913
  );
@@ -3194,6 +3286,16 @@ var CACHING_OPTIMIZED_POLICY_ID = "658327ea-f89d-4fab-a63d-7e88639e58f6";
3194
3286
  var CACHING_DISABLED_POLICY_ID = "4135ea2d-6df8-44a3-9df3-4b5a84be39ad";
3195
3287
  var ALL_VIEWER_EXCEPT_HOST_HEADER_POLICY_ID = "b689b0a8-53d0-40ab-baf2-68738e2966ac";
3196
3288
  var SECURITY_HEADERS_POLICY_ID = "67f7725c-6f97-4210-82d7-5512b31e9d03";
3289
+ var expandRoutePatterns = (patterns) => {
3290
+ const expanded = /* @__PURE__ */ new Set();
3291
+ for (const p of patterns) {
3292
+ expanded.add(p);
3293
+ if (p.endsWith("/*")) {
3294
+ expanded.add(p.slice(0, -2));
3295
+ }
3296
+ }
3297
+ return [...expanded];
3298
+ };
3197
3299
  var ensureOAC = (input) => Effect21.gen(function* () {
3198
3300
  const { name, originType = "s3" } = input;
3199
3301
  const result = yield* cloudfront_exports.make("list_origin_access_controls", {});
@@ -3304,7 +3406,8 @@ var ensureDistribution = (input) => Effect21.gen(function* () {
3304
3406
  const comment = makeDistComment(project, stage, handlerName);
3305
3407
  const s3OriginId = `S3-${bucketName}`;
3306
3408
  const s3OriginDomain = `${bucketName}.s3.${bucketRegion}.amazonaws.com`;
3307
- const hasApiRoutes = apiOriginDomain && routePatterns && routePatterns.length > 0;
3409
+ const expandedRoutePatterns = routePatterns ? expandRoutePatterns(routePatterns) : void 0;
3410
+ const hasApiRoutes = apiOriginDomain && expandedRoutePatterns && expandedRoutePatterns.length > 0;
3308
3411
  const apiOriginId = hasApiRoutes ? `API-${project}-${stage}` : void 0;
3309
3412
  const originsItems = [
3310
3413
  {
@@ -3335,8 +3438,8 @@ var ensureDistribution = (input) => Effect21.gen(function* () {
3335
3438
  const API_METHODS = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"];
3336
3439
  const CACHED_METHODS = ["GET", "HEAD"];
3337
3440
  const cacheBehaviors = hasApiRoutes ? {
3338
- Quantity: routePatterns.length,
3339
- Items: routePatterns.map((pattern) => ({
3441
+ Quantity: expandedRoutePatterns.length,
3442
+ Items: expandedRoutePatterns.map((pattern) => ({
3340
3443
  PathPattern: pattern,
3341
3444
  TargetOriginId: apiOriginId,
3342
3445
  ViewerProtocolPolicy: "redirect-to-https",
@@ -3404,7 +3507,7 @@ var ensureDistribution = (input) => Effect21.gen(function* () {
3404
3507
  const currentLambdaEdgeArn = currentConfig.DefaultCacheBehavior?.LambdaFunctionAssociations?.Items?.[0]?.LambdaFunctionARN;
3405
3508
  const originsMatch = (currentConfig.Origins?.Quantity ?? 0) === originsItems.length;
3406
3509
  const currentBehaviorPatterns = (currentConfig.CacheBehaviors?.Items ?? []).map((b) => b.PathPattern).sort();
3407
- const desiredBehaviorPatterns = (routePatterns ?? []).slice().sort();
3510
+ const desiredBehaviorPatterns = (expandedRoutePatterns ?? []).slice().sort();
3408
3511
  const behaviorsMatch = currentBehaviorPatterns.length === desiredBehaviorPatterns.length && desiredBehaviorPatterns.every((p, i) => currentBehaviorPatterns[i] === p);
3409
3512
  const apiOriginMatch = !hasApiRoutes || currentConfig.Origins?.Items?.some((o) => o.DomainName === apiOriginDomain);
3410
3513
  const needsUpdate = currentOrigin?.DomainName !== s3OriginDomain || currentOrigin?.OriginAccessControlId !== oacId || currentConfig.DefaultRootObject !== index || currentConfig.DefaultCacheBehavior?.CachePolicyId !== CACHING_OPTIMIZED_POLICY_ID || currentConfig.DefaultCacheBehavior?.ResponseHeadersPolicyId !== SECURITY_HEADERS_POLICY_ID || (currentConfig.CustomErrorResponses?.Quantity ?? 0) !== customErrorResponses.Quantity || (currentConfig.DefaultCacheBehavior?.FunctionAssociations?.Quantity ?? 0) !== functionAssociations.Quantity || currentConfig.DefaultCacheBehavior?.FunctionAssociations?.Items?.[0]?.FunctionARN !== (urlRewriteFunctionArn ?? void 0) || (currentConfig.DefaultCacheBehavior?.LambdaFunctionAssociations?.Quantity ?? 0) !== lambdaFunctionAssociations.Quantity || currentLambdaEdgeArn !== (lambdaEdgeArn ?? void 0) || !aliasesMatch || !certMatch || !originsMatch || !behaviorsMatch || !apiOriginMatch;
@@ -3524,7 +3627,7 @@ var ensureDistribution = (input) => Effect21.gen(function* () {
3524
3627
  };
3525
3628
  });
3526
3629
  var ensureSsrDistribution = (input) => Effect21.gen(function* () {
3527
- const { project, stage, handlerName, bucketName, bucketRegion, s3OacId, lambdaOriginDomain, lambdaOacId, assetPatterns, tags, aliases, acmCertificateArn } = input;
3630
+ const { project, stage, handlerName, bucketName, bucketRegion, s3OacId, lambdaOriginDomain, lambdaOacId, assetPatterns, tags, aliases, acmCertificateArn, apiOriginDomain, routePatterns } = input;
3528
3631
  const comment = makeDistComment(project, stage, handlerName);
3529
3632
  const lambdaOriginId = `Lambda-${project}-${stage}-${handlerName}`;
3530
3633
  const s3OriginId = `S3-${bucketName}`;
@@ -3537,6 +3640,9 @@ var ensureSsrDistribution = (input) => Effect21.gen(function* () {
3537
3640
  } : void 0;
3538
3641
  const ALL_METHODS = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"];
3539
3642
  const CACHED_METHODS = ["GET", "HEAD"];
3643
+ const expandedRoutePatterns = routePatterns ? expandRoutePatterns(routePatterns) : void 0;
3644
+ const hasApiRoutes = apiOriginDomain && expandedRoutePatterns && expandedRoutePatterns.length > 0;
3645
+ const apiOriginId = hasApiRoutes ? `API-${project}-${stage}` : void 0;
3540
3646
  const originsItems = [
3541
3647
  {
3542
3648
  Id: lambdaOriginId,
@@ -3560,7 +3666,23 @@ var ensureSsrDistribution = (input) => Effect21.gen(function* () {
3560
3666
  OriginAccessControlId: s3OacId,
3561
3667
  S3OriginConfig: { OriginAccessIdentity: "" },
3562
3668
  CustomHeaders: { Quantity: 0, Items: [] }
3563
- }
3669
+ },
3670
+ ...hasApiRoutes ? [{
3671
+ Id: apiOriginId,
3672
+ DomainName: apiOriginDomain,
3673
+ OriginPath: "",
3674
+ ConnectionAttempts: 3,
3675
+ ConnectionTimeout: 10,
3676
+ CustomOriginConfig: {
3677
+ HTTPPort: 80,
3678
+ HTTPSPort: 443,
3679
+ OriginProtocolPolicy: "https-only",
3680
+ OriginSslProtocols: { Quantity: 1, Items: ["TLSv1.2"] },
3681
+ OriginReadTimeout: 30,
3682
+ OriginKeepaliveTimeout: 5
3683
+ },
3684
+ CustomHeaders: { Quantity: 0, Items: [] }
3685
+ }] : []
3564
3686
  ];
3565
3687
  const defaultCacheBehavior = {
3566
3688
  TargetOriginId: lambdaOriginId,
@@ -3579,26 +3701,42 @@ var ensureSsrDistribution = (input) => Effect21.gen(function* () {
3579
3701
  LambdaFunctionAssociations: { Quantity: 0, Items: [] },
3580
3702
  FieldLevelEncryptionId: ""
3581
3703
  };
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: [] };
3704
+ const apiRouteBehaviors = hasApiRoutes ? expandedRoutePatterns.map((pattern) => ({
3705
+ PathPattern: pattern,
3706
+ TargetOriginId: apiOriginId,
3707
+ ViewerProtocolPolicy: "redirect-to-https",
3708
+ AllowedMethods: {
3709
+ Quantity: 7,
3710
+ Items: [...ALL_METHODS],
3711
+ CachedMethods: { Quantity: 2, Items: [...CACHED_METHODS] }
3712
+ },
3713
+ Compress: true,
3714
+ SmoothStreaming: false,
3715
+ CachePolicyId: CACHING_DISABLED_POLICY_ID,
3716
+ OriginRequestPolicyId: ALL_VIEWER_EXCEPT_HOST_HEADER_POLICY_ID,
3717
+ FunctionAssociations: { Quantity: 0, Items: [] },
3718
+ LambdaFunctionAssociations: { Quantity: 0, Items: [] },
3719
+ FieldLevelEncryptionId: ""
3720
+ })) : [];
3721
+ const assetBehaviors = assetPatterns.map((pattern) => ({
3722
+ PathPattern: pattern,
3723
+ TargetOriginId: s3OriginId,
3724
+ ViewerProtocolPolicy: "redirect-to-https",
3725
+ AllowedMethods: {
3726
+ Quantity: 2,
3727
+ Items: [...CACHED_METHODS],
3728
+ CachedMethods: { Quantity: 2, Items: [...CACHED_METHODS] }
3729
+ },
3730
+ Compress: true,
3731
+ SmoothStreaming: false,
3732
+ CachePolicyId: CACHING_OPTIMIZED_POLICY_ID,
3733
+ ResponseHeadersPolicyId: SECURITY_HEADERS_POLICY_ID,
3734
+ FunctionAssociations: { Quantity: 0, Items: [] },
3735
+ LambdaFunctionAssociations: { Quantity: 0, Items: [] },
3736
+ FieldLevelEncryptionId: ""
3737
+ }));
3738
+ const allBehaviors = [...apiRouteBehaviors, ...assetBehaviors];
3739
+ const cacheBehaviors = allBehaviors.length > 0 ? { Quantity: allBehaviors.length, Items: allBehaviors } : { Quantity: 0, Items: [] };
3602
3740
  const existing = yield* findDistributionByTags(project, stage, handlerName);
3603
3741
  if (existing) {
3604
3742
  const configResult = yield* cloudfront_exports.make("get_distribution_config", {
@@ -4375,6 +4513,19 @@ var discoverHandlers = (files) => {
4375
4513
  }
4376
4514
  return { httpHandlers, tableHandlers, appHandlers, staticSiteHandlers, fifoQueueHandlers, bucketHandlers, mailerHandlers, apiHandlers };
4377
4515
  };
4516
+ var flattenHandlers = (discovered) => {
4517
+ const entries = (type, handlers) => handlers.flatMap((h) => h.exports.map((e) => ({ exportName: e.exportName, file: h.file, type })));
4518
+ return [
4519
+ ...entries("http", discovered.httpHandlers),
4520
+ ...entries("table", discovered.tableHandlers),
4521
+ ...entries("app", discovered.appHandlers),
4522
+ ...entries("site", discovered.staticSiteHandlers),
4523
+ ...entries("queue", discovered.fifoQueueHandlers),
4524
+ ...entries("bucket", discovered.bucketHandlers),
4525
+ ...entries("mailer", discovered.mailerHandlers),
4526
+ ...entries("api", discovered.apiHandlers)
4527
+ ];
4528
+ };
4378
4529
 
4379
4530
  // src/deploy/resolve-config.ts
4380
4531
  import { Effect as Effect25 } from "effect";
@@ -4448,10 +4599,10 @@ var ensureLayerAndExternal = (input) => Effect26.gen(function* () {
4448
4599
  project: input.project,
4449
4600
  stage: input.stage,
4450
4601
  region: input.region,
4451
- projectDir: input.projectDir
4602
+ projectDir: input.packageDir
4452
4603
  });
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: [] };
4604
+ const prodDeps = layerResult ? yield* readProductionDependencies(input.packageDir) : [];
4605
+ const { packages: external, warnings: layerWarnings } = prodDeps.length > 0 ? yield* Effect26.sync(() => collectLayerPackages(input.packageDir, prodDeps)) : { packages: [], warnings: [] };
4455
4606
  for (const warning of layerWarnings) {
4456
4607
  yield* Effect26.logWarning(`[layer] ${warning}`);
4457
4608
  }
@@ -4566,7 +4717,7 @@ var deploy = (input) => Effect27.gen(function* () {
4566
4717
  project: input.project,
4567
4718
  stage: tagCtx.stage,
4568
4719
  region: input.region,
4569
- projectDir: input.projectDir
4720
+ packageDir: input.packageDir ?? input.projectDir
4570
4721
  });
4571
4722
  const { functionArn } = yield* deployLambda({
4572
4723
  input,
@@ -4619,7 +4770,7 @@ var deployAll = (input) => Effect27.gen(function* () {
4619
4770
  project: input.project,
4620
4771
  stage: tagCtx.stage,
4621
4772
  region: input.region,
4622
- projectDir: input.projectDir
4773
+ packageDir: input.packageDir ?? input.projectDir
4623
4774
  });
4624
4775
  yield* Effect27.logDebug("Setting up API Gateway...");
4625
4776
  const { apiId } = yield* ensureProjectApi({
@@ -4731,7 +4882,7 @@ var deployTable = (input) => Effect28.gen(function* () {
4731
4882
  project: input.project,
4732
4883
  stage: resolveStage(input.stage),
4733
4884
  region: input.region,
4734
- projectDir: input.projectDir
4885
+ packageDir: input.packageDir ?? input.projectDir
4735
4886
  });
4736
4887
  const result = yield* deployTableFunction({
4737
4888
  input,
@@ -4760,7 +4911,7 @@ var deployAllTables = (input) => Effect28.gen(function* () {
4760
4911
  project: input.project,
4761
4912
  stage: resolveStage(input.stage),
4762
4913
  region: input.region,
4763
- projectDir: input.projectDir
4914
+ packageDir: input.packageDir ?? input.projectDir
4764
4915
  });
4765
4916
  const results = yield* Effect28.forEach(
4766
4917
  functions,
@@ -4793,12 +4944,31 @@ var deployApp = (input) => Effect29.gen(function* () {
4793
4944
  const stage = resolveStage(input.stage);
4794
4945
  const handlerName = exportName;
4795
4946
  const tagCtx = { project, stage, handler: handlerName };
4947
+ const routePatterns = fn13.routePatterns;
4948
+ if (routePatterns.length > 0 && !input.apiOriginDomain) {
4949
+ return yield* Effect29.fail(
4950
+ new Error(
4951
+ `App "${exportName}" has routes but no API Gateway exists. Ensure defineHttp() or defineApi() handlers are included in the discovery patterns.`
4952
+ )
4953
+ );
4954
+ }
4796
4955
  if (config.build) {
4797
4956
  yield* Effect29.logDebug(`Building app: ${config.build}`);
4957
+ const buildStart = Date.now();
4798
4958
  yield* Effect29.try({
4799
- try: () => execSync(config.build, { cwd: projectDir, stdio: "inherit" }),
4800
- catch: (error) => new Error(`App build failed: ${error}`)
4959
+ try: () => execSync(config.build, {
4960
+ cwd: projectDir,
4961
+ stdio: input.verbose ? "inherit" : "pipe"
4962
+ }),
4963
+ catch: (error) => {
4964
+ if (!input.verbose && error && typeof error === "object" && "stderr" in error) {
4965
+ const stderr = String(error.stderr);
4966
+ if (stderr) process.stderr.write(stderr);
4967
+ }
4968
+ return new Error(`App build failed: ${config.build}`);
4969
+ }
4801
4970
  });
4971
+ yield* Effect29.logDebug(`App built in ${((Date.now() - buildStart) / 1e3).toFixed(1)}s`);
4802
4972
  }
4803
4973
  const serverDir = path5.resolve(projectDir, config.server);
4804
4974
  yield* Effect29.logDebug(`Zipping server directory: ${serverDir}`);
@@ -4864,7 +5034,8 @@ var deployApp = (input) => Effect29.gen(function* () {
4864
5034
  assetPatterns,
4865
5035
  tags: makeTags(tagCtx, "cloudfront-distribution"),
4866
5036
  aliases,
4867
- acmCertificateArn
5037
+ acmCertificateArn,
5038
+ ...input.apiOriginDomain && routePatterns.length > 0 ? { apiOriginDomain: input.apiOriginDomain, routePatterns } : {}
4868
5039
  });
4869
5040
  yield* addCloudFrontPermission(lambdaName, distributionArn);
4870
5041
  yield* putBucketPolicyForOAC(bucketName, distributionArn);
@@ -4978,10 +5149,21 @@ var deployStaticSite = (input) => Effect30.gen(function* () {
4978
5149
  }
4979
5150
  if (config.build) {
4980
5151
  yield* Effect30.logDebug(`Building site: ${config.build}`);
5152
+ const buildStart = Date.now();
4981
5153
  yield* Effect30.try({
4982
- try: () => execSync2(config.build, { cwd: projectDir, stdio: "inherit" }),
4983
- catch: (error) => new Error(`Site build failed: ${error}`)
5154
+ try: () => execSync2(config.build, {
5155
+ cwd: projectDir,
5156
+ stdio: input.verbose ? "inherit" : "pipe"
5157
+ }),
5158
+ catch: (error) => {
5159
+ if (!input.verbose && error && typeof error === "object" && "stderr" in error) {
5160
+ const stderr = String(error.stderr);
5161
+ if (stderr) process.stderr.write(stderr);
5162
+ }
5163
+ return new Error(`Site build failed: ${config.build}`);
5164
+ }
4984
5165
  });
5166
+ yield* Effect30.logDebug(`Site built in ${((Date.now() - buildStart) / 1e3).toFixed(1)}s`);
4985
5167
  }
4986
5168
  const bucketName = `${project}-${stage}-${handlerName}-site`.toLowerCase();
4987
5169
  yield* ensureBucket({
@@ -5325,7 +5507,7 @@ var prepareLayer = (input) => Effect35.gen(function* () {
5325
5507
  project: input.project,
5326
5508
  stage: input.stage,
5327
5509
  region: input.region,
5328
- projectDir: input.projectDir
5510
+ projectDir: input.packageDir
5329
5511
  }).pipe(
5330
5512
  Effect35.provide(
5331
5513
  clients_exports.makeClients({
@@ -5333,8 +5515,8 @@ var prepareLayer = (input) => Effect35.gen(function* () {
5333
5515
  })
5334
5516
  )
5335
5517
  );
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: [] };
5518
+ const prodDeps = layerResult ? yield* readProductionDependencies(input.packageDir) : [];
5519
+ const { packages: external, warnings: layerWarnings } = prodDeps.length > 0 ? yield* Effect35.sync(() => collectLayerPackages(input.packageDir, prodDeps)) : { packages: [], warnings: [] };
5338
5520
  for (const warning of layerWarnings) {
5339
5521
  yield* Effect35.logWarning(`[layer] ${warning}`);
5340
5522
  }
@@ -5398,6 +5580,31 @@ var buildBucketNameMap = (bucketHandlers, project, stage) => {
5398
5580
  }
5399
5581
  return map;
5400
5582
  };
5583
+ var validateDeps = (discovered, tableNameMap, bucketNameMap, mailerDomainMap) => {
5584
+ const errors = [];
5585
+ const allGroups = [
5586
+ ...discovered.httpHandlers,
5587
+ ...discovered.apiHandlers,
5588
+ ...discovered.tableHandlers,
5589
+ ...discovered.fifoQueueHandlers,
5590
+ ...discovered.bucketHandlers,
5591
+ ...discovered.staticSiteHandlers,
5592
+ ...discovered.appHandlers,
5593
+ ...discovered.mailerHandlers
5594
+ ];
5595
+ for (const { exports } of allGroups) {
5596
+ for (const fn13 of exports) {
5597
+ for (const key of fn13.depsKeys) {
5598
+ if (!tableNameMap.has(key) && !bucketNameMap.has(key) && !mailerDomainMap.has(key)) {
5599
+ errors.push(
5600
+ `Handler "${fn13.exportName}" depends on "${key}", but no matching table, bucket, or mailer handler was found. Make sure it is exported.`
5601
+ );
5602
+ }
5603
+ }
5604
+ }
5605
+ }
5606
+ return errors;
5607
+ };
5401
5608
  var resolveDeps = (depsKeys, tableNameMap, bucketNameMap, mailerDomainMap) => {
5402
5609
  if (depsKeys.length === 0) return void 0;
5403
5610
  const depsEnv = {};
@@ -5523,9 +5730,10 @@ var buildTableTasks = (ctx, handlers, results) => {
5523
5730
  }
5524
5731
  return tasks;
5525
5732
  };
5526
- var buildAppTasks = (ctx, handlers, results) => {
5733
+ var buildAppTasks = (ctx, handlers, results, apiId) => {
5527
5734
  const tasks = [];
5528
5735
  const { region } = ctx.input;
5736
+ const apiOriginDomain = apiId ? `${apiId}.execute-api.${region}.amazonaws.com` : void 0;
5529
5737
  for (const { exports } of handlers) {
5530
5738
  for (const fn13 of exports) {
5531
5739
  tasks.push(
@@ -5535,7 +5743,9 @@ var buildAppTasks = (ctx, handlers, results) => {
5535
5743
  project: ctx.input.project,
5536
5744
  stage: ctx.input.stage,
5537
5745
  region,
5538
- fn: fn13
5746
+ fn: fn13,
5747
+ verbose: ctx.input.verbose,
5748
+ ...apiOriginDomain ? { apiOriginDomain } : {}
5539
5749
  }).pipe(Effect35.provide(clients_exports.makeClients({
5540
5750
  lambda: { region },
5541
5751
  iam: { region },
@@ -5566,6 +5776,7 @@ var buildStaticSiteTasks = (ctx, handlers, results, apiId) => {
5566
5776
  stage: ctx.input.stage,
5567
5777
  region,
5568
5778
  fn: fn13,
5779
+ verbose: ctx.input.verbose,
5569
5780
  ...fn13.hasHandler ? { file } : {},
5570
5781
  ...apiOriginDomain ? { apiOriginDomain } : {}
5571
5782
  }).pipe(Effect35.provide(clients_exports.makeClients({
@@ -5744,11 +5955,19 @@ var deployProject = (input) => Effect35.gen(function* () {
5744
5955
  const tableNameMap = buildTableNameMap(tableHandlers, input.project, stage);
5745
5956
  const bucketNameMap = buildBucketNameMap(bucketHandlers, input.project, stage);
5746
5957
  const mailerDomainMap = buildMailerDomainMap(mailerHandlers);
5958
+ const depsErrors = validateDeps(discovered, tableNameMap, bucketNameMap, mailerDomainMap);
5959
+ if (depsErrors.length > 0) {
5960
+ yield* Console2.log("");
5961
+ for (const err of depsErrors) {
5962
+ yield* Console2.log(` ${c.red("\u2717")} ${err}`);
5963
+ }
5964
+ return yield* Effect35.fail(new Error("Unresolved deps \u2014 aborting deploy"));
5965
+ }
5747
5966
  const { layerArn, layerVersion, layerStatus, external } = yield* prepareLayer({
5748
5967
  project: input.project,
5749
5968
  stage,
5750
5969
  region: input.region,
5751
- projectDir: input.projectDir
5970
+ packageDir: input.packageDir ?? input.projectDir
5752
5971
  });
5753
5972
  if (layerArn && layerStatus) {
5754
5973
  const status = layerStatus === "cached" ? c.dim("cached") : c.green("created");
@@ -5759,7 +5978,10 @@ var deployProject = (input) => Effect35.gen(function* () {
5759
5978
  const staticSitesNeedApi = !input.noSites && staticSiteHandlers.some(
5760
5979
  ({ exports }) => exports.some((fn13) => fn13.routePatterns.length > 0)
5761
5980
  );
5762
- if (totalHttpHandlers > 0 || totalApiHandlers > 0 || staticSitesNeedApi) {
5981
+ const appsNeedApi = appHandlers.some(
5982
+ ({ exports }) => exports.some((fn13) => fn13.routePatterns.length > 0)
5983
+ );
5984
+ if (totalHttpHandlers > 0 || totalApiHandlers > 0 || staticSitesNeedApi || appsNeedApi) {
5763
5985
  const tagCtx = {
5764
5986
  project: input.project,
5765
5987
  stage,
@@ -5824,7 +6046,7 @@ var deployProject = (input) => Effect35.gen(function* () {
5824
6046
  const tasks = [
5825
6047
  ...apiId ? buildHttpTasks(ctx, httpHandlers, apiId, httpResults) : [],
5826
6048
  ...buildTableTasks(ctx, tableHandlers, tableResults),
5827
- ...buildAppTasks(ctx, appHandlers, appResults),
6049
+ ...buildAppTasks(ctx, appHandlers, appResults, apiId),
5828
6050
  ...input.noSites ? [] : buildStaticSiteTasks(ctx, staticSiteHandlers, staticSiteResults, apiId),
5829
6051
  ...buildFifoQueueTasks(ctx, fifoQueueHandlers, fifoQueueResults),
5830
6052
  ...buildBucketTasks(ctx, bucketHandlers, bucketResults),
@@ -5876,18 +6098,22 @@ import * as path7 from "path";
5876
6098
  import * as fs4 from "fs";
5877
6099
  import { pathToFileURL } from "url";
5878
6100
  import * as esbuild2 from "esbuild";
5879
- var loadConfig = async () => {
6101
+ import { Effect as Effect36 } from "effect";
6102
+ var loadConfig = Effect36.fn("loadConfig")(function* () {
5880
6103
  const configPath = path7.resolve(process.cwd(), "effortless.config.ts");
5881
6104
  if (!fs4.existsSync(configPath)) {
5882
6105
  return null;
5883
6106
  }
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"]
6107
+ const result = yield* Effect36.tryPromise({
6108
+ try: () => esbuild2.build({
6109
+ entryPoints: [configPath],
6110
+ bundle: true,
6111
+ write: false,
6112
+ format: "esm",
6113
+ platform: "node",
6114
+ external: ["effortless-aws"]
6115
+ }),
6116
+ catch: (error) => new Error(`Failed to compile config: ${error}`)
5891
6117
  });
5892
6118
  const output = result.outputFiles?.[0];
5893
6119
  if (!output) {
@@ -5896,13 +6122,12 @@ var loadConfig = async () => {
5896
6122
  const code = output.text;
5897
6123
  const tempFile = path7.join(process.cwd(), ".effortless-config.mjs");
5898
6124
  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
- };
6125
+ const mod = yield* Effect36.tryPromise({
6126
+ try: () => import(pathToFileURL(tempFile).href),
6127
+ catch: (error) => new Error(`Failed to load config: ${error}`)
6128
+ }).pipe(Effect36.ensuring(Effect36.sync(() => fs4.unlinkSync(tempFile))));
6129
+ return mod.default;
6130
+ });
5906
6131
  var projectOption = Options.text("project").pipe(
5907
6132
  Options.withAlias("p"),
5908
6133
  Options.withDescription("Project name (or 'name' in effortless.config.ts)"),
@@ -5910,12 +6135,12 @@ var projectOption = Options.text("project").pipe(
5910
6135
  );
5911
6136
  var stageOption = Options.text("stage").pipe(
5912
6137
  Options.withAlias("s"),
5913
- Options.withDescription("Stage name"),
6138
+ Options.withDescription("Deployment stage (default: dev)"),
5914
6139
  Options.withDefault("dev")
5915
6140
  );
5916
6141
  var regionOption = Options.text("region").pipe(
5917
6142
  Options.withAlias("r"),
5918
- Options.withDescription("AWS region"),
6143
+ Options.withDescription("AWS region (default: eu-central-1)"),
5919
6144
  Options.withDefault("eu-central-1")
5920
6145
  );
5921
6146
  var verboseOption = Options.boolean("verbose").pipe(
@@ -5948,6 +6173,23 @@ var getPatternsFromConfig = (config) => {
5948
6173
  });
5949
6174
  };
5950
6175
 
6176
+ // src/cli/project-config.ts
6177
+ import * as Context13 from "effect/Context";
6178
+ import * as Layer14 from "effect/Layer";
6179
+ import * as Effect37 from "effect/Effect";
6180
+ import * as path8 from "path";
6181
+ var ProjectConfig = class _ProjectConfig extends Context13.Tag("ProjectConfig")() {
6182
+ static Live = Layer14.effect(
6183
+ _ProjectConfig,
6184
+ Effect37.gen(function* () {
6185
+ const config = yield* loadConfig();
6186
+ const cwd = process.cwd();
6187
+ const projectDir = config?.root ? path8.resolve(cwd, config.root) : cwd;
6188
+ return { config, cwd, projectDir };
6189
+ })
6190
+ );
6191
+ };
6192
+
5951
6193
  // src/cli/commands/deploy.ts
5952
6194
  var deployTargetArg = Args.text({ name: "target" }).pipe(
5953
6195
  Args.withDescription("Handler name or file path to deploy (optional - uses config patterns if not specified)"),
@@ -5959,8 +6201,8 @@ var isFilePath = (target) => {
5959
6201
  var deployCommand = Command.make(
5960
6202
  "deploy",
5961
6203
  { 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);
6204
+ ({ target, project: projectOpt, stage, region, verbose, noSites }) => Effect38.gen(function* () {
6205
+ const { config, cwd, projectDir } = yield* ProjectConfig;
5964
6206
  const project = Option.getOrElse(projectOpt, () => config?.name ?? "");
5965
6207
  const finalStage = config?.stage ?? stage;
5966
6208
  const finalRegion = config?.region ?? region;
@@ -5979,9 +6221,8 @@ var deployCommand = Command.make(
5979
6221
  acm: { region: "us-east-1" }
5980
6222
  });
5981
6223
  const logLevel = verbose ? LogLevel.Debug : LogLevel.Warning;
5982
- const projectDir = process.cwd();
5983
6224
  yield* Option.match(target, {
5984
- onNone: () => Effect36.gen(function* () {
6225
+ onNone: () => Effect38.gen(function* () {
5985
6226
  const patterns = getPatternsFromConfig(config);
5986
6227
  if (!patterns) {
5987
6228
  yield* Console3.error("Error: No target specified and no 'handlers' patterns in config");
@@ -5989,13 +6230,15 @@ var deployCommand = Command.make(
5989
6230
  }
5990
6231
  const results = yield* deployProject({
5991
6232
  projectDir,
6233
+ packageDir: cwd,
5992
6234
  patterns,
5993
6235
  project,
5994
6236
  stage: finalStage,
5995
6237
  region: finalRegion,
5996
- noSites
6238
+ noSites,
6239
+ verbose
5997
6240
  });
5998
- const total = results.httpResults.length + results.tableResults.length + results.appResults.length + results.staticSiteResults.length;
6241
+ const total = results.httpResults.length + results.tableResults.length + results.appResults.length + results.staticSiteResults.length + results.apiResults.length;
5999
6242
  yield* Console3.log(`
6000
6243
  ${c.green(`Deployed ${total} handler(s):`)}`);
6001
6244
  if (results.apiUrl) {
@@ -6014,6 +6257,10 @@ ${c.green(`Deployed ${total} handler(s):`)}`);
6014
6257
  for (const r of results.tableResults) {
6015
6258
  summaryLines.push({ name: r.exportName, line: ` ${c.cyan("[table]")} ${c.bold(r.exportName)}` });
6016
6259
  }
6260
+ for (const r of results.apiResults) {
6261
+ const pathPart = results.apiUrl ? r.url.replace(results.apiUrl, "") : r.url;
6262
+ summaryLines.push({ name: r.exportName, line: ` ${c.cyan("[api]")} ${c.bold(r.exportName)} ${c.dim(pathPart)}` });
6263
+ }
6017
6264
  for (const r of results.staticSiteResults) {
6018
6265
  summaryLines.push({ name: r.exportName, line: ` ${c.cyan("[site]")} ${c.bold(r.exportName)}: ${c.cyan(r.url)}` });
6019
6266
  }
@@ -6022,26 +6269,27 @@ ${c.green(`Deployed ${total} handler(s):`)}`);
6022
6269
  yield* Console3.log(line);
6023
6270
  }
6024
6271
  }),
6025
- onSome: (targetValue) => Effect36.gen(function* () {
6272
+ onSome: (targetValue) => Effect38.gen(function* () {
6026
6273
  if (isFilePath(targetValue)) {
6027
- const fullPath = path8.isAbsolute(targetValue) ? targetValue : path8.resolve(projectDir, targetValue);
6274
+ const fullPath = path9.isAbsolute(targetValue) ? targetValue : path9.resolve(projectDir, targetValue);
6028
6275
  const input = {
6029
6276
  projectDir,
6277
+ packageDir: cwd,
6030
6278
  file: fullPath,
6031
6279
  project,
6032
6280
  stage: finalStage,
6033
6281
  region: finalRegion
6034
6282
  };
6035
6283
  const httpResult = yield* deployAll(input).pipe(
6036
- Effect36.catchIf(
6284
+ Effect38.catchIf(
6037
6285
  (e) => e instanceof Error && e.message.includes("No defineHttp"),
6038
- () => Effect36.succeed(null)
6286
+ () => Effect38.succeed(null)
6039
6287
  )
6040
6288
  );
6041
6289
  const tableResults = yield* deployAllTables(input).pipe(
6042
- Effect36.catchIf(
6290
+ Effect38.catchIf(
6043
6291
  (e) => e instanceof Error && e.message.includes("No defineTable"),
6044
- () => Effect36.succeed([])
6292
+ () => Effect38.succeed([])
6045
6293
  )
6046
6294
  );
6047
6295
  if (!httpResult && tableResults.length === 0) {
@@ -6071,68 +6319,23 @@ Deployed ${tableResults.length} table handler(s):`));
6071
6319
  }
6072
6320
  const files = findHandlerFiles(patterns, projectDir);
6073
6321
  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) {
6322
+ const allHandlers = flattenHandlers(discovered);
6323
+ const found = allHandlers.find((h) => h.exportName === targetValue);
6324
+ if (!found) {
6114
6325
  yield* Console3.error(`Error: Handler "${targetValue}" not found`);
6115
6326
  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
- }
6327
+ for (const h of allHandlers) {
6328
+ yield* Console3.log(` ${c.cyan(`[${h.type}]`.padEnd(9))} ${h.exportName}`);
6130
6329
  }
6131
6330
  return;
6132
6331
  }
6133
- yield* Console3.log(`Found handler ${c.bold(targetValue)} in ${c.dim(path8.relative(projectDir, foundFile))}`);
6332
+ const foundFile = found.file;
6333
+ const foundExport = found.exportName;
6334
+ const handlerType = found.type;
6335
+ yield* Console3.log(`Found handler ${c.bold(targetValue)} in ${c.dim(path9.relative(projectDir, foundFile))}`);
6134
6336
  const input = {
6135
6337
  projectDir,
6338
+ packageDir: cwd,
6136
6339
  file: foundFile,
6137
6340
  project,
6138
6341
  stage: finalStage,
@@ -6151,15 +6354,15 @@ ${c.green("Deployed:")} ${c.cyan(result.url)}`);
6151
6354
  }
6152
6355
  })
6153
6356
  }).pipe(
6154
- Effect36.provide(clientsLayer),
6357
+ Effect38.provide(clientsLayer),
6155
6358
  Logger.withMinimumLogLevel(logLevel)
6156
6359
  );
6157
- })
6158
- ).pipe(Command.withDescription("Deploy handlers (all from config, by file path, or by handler name)"));
6360
+ }).pipe(Effect38.provide(ProjectConfig.Live))
6361
+ ).pipe(Command.withDescription("Deploy handlers to AWS Lambda. Accepts a handler name, file path, or deploys all from config"));
6159
6362
 
6160
6363
  // src/cli/commands/status.ts
6161
6364
  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";
6365
+ import { Effect as Effect39, Console as Console4, Logger as Logger2, LogLevel as LogLevel2, Option as Option2 } from "effect";
6163
6366
  var { lambda, apigatewayv2: apigateway } = clients_exports;
6164
6367
  var INTERNAL_HANDLERS = /* @__PURE__ */ new Set(["api", "platform"]);
6165
6368
  var extractFunctionName = (arn) => {
@@ -6184,7 +6387,7 @@ var formatDate = (date) => {
6184
6387
  if (days < 7) return `${days}d ago`;
6185
6388
  return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
6186
6389
  };
6187
- var getLambdaDetails = (functionName) => Effect37.gen(function* () {
6390
+ var getLambdaDetails = (functionName) => Effect39.gen(function* () {
6188
6391
  const config = yield* lambda.make("get_function_configuration", {
6189
6392
  FunctionName: functionName
6190
6393
  });
@@ -6194,62 +6397,21 @@ var getLambdaDetails = (functionName) => Effect37.gen(function* () {
6194
6397
  timeout: config.Timeout
6195
6398
  };
6196
6399
  }).pipe(
6197
- Effect37.catchAll(() => Effect37.succeed({}))
6400
+ Effect39.catchAll(() => Effect39.succeed({}))
6198
6401
  );
6199
- var getApiUrl = (apiId) => Effect37.gen(function* () {
6402
+ var getApiUrl = (apiId) => Effect39.gen(function* () {
6200
6403
  const api = yield* apigateway.make("get_api", { ApiId: apiId });
6201
6404
  return api.ApiEndpoint;
6202
6405
  }).pipe(
6203
- Effect37.catchAll(() => Effect37.succeed(void 0))
6406
+ Effect39.catchAll(() => Effect39.succeed(void 0))
6204
6407
  );
6205
6408
  var discoverCodeHandlers = (projectDir, patterns) => {
6206
6409
  const files = findHandlerFiles(patterns, projectDir);
6207
6410
  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;
6411
+ return flattenHandlers(discovered).map((h) => ({
6412
+ name: h.exportName,
6413
+ type: h.type
6414
+ }));
6253
6415
  };
6254
6416
  var discoverAwsHandlers = (resources) => {
6255
6417
  const byHandler = groupResourcesByHandler(resources);
@@ -6277,6 +6439,7 @@ var TYPE_LABELS = {
6277
6439
  http: "http",
6278
6440
  table: "table",
6279
6441
  app: "app",
6442
+ api: "api",
6280
6443
  site: "site",
6281
6444
  queue: "queue",
6282
6445
  lambda: "lambda",
@@ -6297,9 +6460,9 @@ var STATUS_COLORS = {
6297
6460
  var formatStatus = (status) => {
6298
6461
  return STATUS_COLORS[status](status.padEnd(10));
6299
6462
  };
6300
- var formatRoute = (method, path10) => {
6301
- if (method && path10) return `${method.padEnd(5)} ${path10}`;
6302
- if (path10) return path10;
6463
+ var formatRoute = (method, path11) => {
6464
+ if (method && path11) return `${method.padEnd(5)} ${path11}`;
6465
+ if (path11) return path11;
6303
6466
  return "";
6304
6467
  };
6305
6468
  var formatEntry = (entry) => {
@@ -6321,8 +6484,8 @@ var formatEntry = (entry) => {
6321
6484
  var statusCommand = Command2.make(
6322
6485
  "status",
6323
6486
  { project: projectOption, stage: stageOption, region: regionOption, verbose: verboseOption },
6324
- ({ project: projectOpt, stage, region, verbose }) => Effect37.gen(function* () {
6325
- const config = yield* Effect37.promise(loadConfig);
6487
+ ({ project: projectOpt, stage, region, verbose }) => Effect39.gen(function* () {
6488
+ const { config, projectDir } = yield* ProjectConfig;
6326
6489
  const project = Option2.getOrElse(projectOpt, () => config?.name ?? "");
6327
6490
  const finalStage = config?.stage ?? stage;
6328
6491
  const finalRegion = config?.region ?? region;
@@ -6336,11 +6499,10 @@ var statusCommand = Command2.make(
6336
6499
  resource_groups_tagging_api: { region: finalRegion }
6337
6500
  });
6338
6501
  const logLevel = verbose ? LogLevel2.Debug : LogLevel2.Info;
6339
- const projectDir = process.cwd();
6340
6502
  const patterns = getPatternsFromConfig(config);
6341
6503
  const codeHandlers = patterns ? discoverCodeHandlers(projectDir, patterns) : [];
6342
6504
  const codeHandlerNames = new Set(codeHandlers.map((h) => h.name));
6343
- yield* Effect37.gen(function* () {
6505
+ yield* Effect39.gen(function* () {
6344
6506
  yield* Console4.log(`
6345
6507
  Status for ${c.bold(project + "/" + finalStage)}:
6346
6508
  `);
@@ -6436,19 +6598,28 @@ API: ${c.cyan(apiUrl)}`);
6436
6598
  if (counts.orphaned > 0) parts.push(c.red(`${counts.orphaned} orphaned`));
6437
6599
  yield* Console4.log(`
6438
6600
  Total: ${parts.join(", ")}`);
6601
+ const depWarnings = yield* checkDependencyWarnings(projectDir).pipe(
6602
+ Effect39.catchAll(() => Effect39.succeed([]))
6603
+ );
6604
+ if (depWarnings.length > 0) {
6605
+ yield* Console4.log("");
6606
+ for (const w of depWarnings) {
6607
+ yield* Console4.log(c.yellow(` \u26A0 ${w}`));
6608
+ }
6609
+ }
6439
6610
  }).pipe(
6440
- Effect37.provide(clientsLayer),
6611
+ Effect39.provide(clientsLayer),
6441
6612
  Logger2.withMinimumLogLevel(logLevel)
6442
6613
  );
6443
- })
6444
- ).pipe(Command2.withDescription("Show status of handlers (code vs deployed)"));
6614
+ }).pipe(Effect39.provide(ProjectConfig.Live))
6615
+ ).pipe(Command2.withDescription("Compare local handlers with deployed AWS resources. Shows new, deployed, and orphaned handlers"));
6445
6616
 
6446
6617
  // src/cli/commands/cleanup.ts
6447
6618
  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";
6619
+ import { Effect as Effect41, Console as Console5, Logger as Logger3, LogLevel as LogLevel3, Option as Option3 } from "effect";
6449
6620
 
6450
6621
  // src/deploy/cleanup.ts
6451
- import { Effect as Effect38 } from "effect";
6622
+ import { Effect as Effect40 } from "effect";
6452
6623
  var extractResourceName = (arn, type) => {
6453
6624
  switch (type) {
6454
6625
  case "lambda": {
@@ -6491,7 +6662,7 @@ var extractLayerInfo = (arn) => {
6491
6662
  version: parseInt(parts[parts.length - 1] ?? "0", 10)
6492
6663
  };
6493
6664
  };
6494
- var deleteResource = (resource) => Effect38.gen(function* () {
6665
+ var deleteResource = (resource) => Effect40.gen(function* () {
6495
6666
  const name = extractResourceName(resource.arn, resource.type);
6496
6667
  switch (resource.type) {
6497
6668
  case "lambda":
@@ -6524,18 +6695,18 @@ var deleteResource = (resource) => Effect38.gen(function* () {
6524
6695
  yield* deleteSesIdentity(name);
6525
6696
  break;
6526
6697
  default:
6527
- yield* Effect38.logWarning(`Unknown resource type: ${resource.type}, skipping ${resource.arn}`);
6698
+ yield* Effect40.logWarning(`Unknown resource type: ${resource.type}, skipping ${resource.arn}`);
6528
6699
  }
6529
6700
  });
6530
- var deleteResources = (resources) => Effect38.gen(function* () {
6701
+ var deleteResources = (resources) => Effect40.gen(function* () {
6531
6702
  const orderedTypes = ["lambda", "api-gateway", "cloudfront-distribution", "sqs", "ses", "dynamodb", "s3-bucket", "lambda-layer", "iam-role"];
6532
6703
  const iamRolesToDelete = /* @__PURE__ */ new Set();
6533
6704
  for (const type of orderedTypes) {
6534
6705
  const resourcesOfType = resources.filter((r) => r.type === type);
6535
6706
  for (const resource of resourcesOfType) {
6536
6707
  yield* deleteResource(resource).pipe(
6537
- Effect38.catchAll(
6538
- (error) => Effect38.logError(`Failed to delete ${resource.type} ${resource.arn}: ${error}`)
6708
+ Effect40.catchAll(
6709
+ (error) => Effect40.logError(`Failed to delete ${resource.type} ${resource.arn}: ${error}`)
6539
6710
  )
6540
6711
  );
6541
6712
  if (resource.type === "lambda") {
@@ -6547,8 +6718,8 @@ var deleteResources = (resources) => Effect38.gen(function* () {
6547
6718
  }
6548
6719
  for (const roleName of iamRolesToDelete) {
6549
6720
  yield* deleteRole(roleName).pipe(
6550
- Effect38.catchAll(
6551
- (error) => Effect38.logError(`Failed to delete IAM role ${roleName}: ${error}`)
6721
+ Effect40.catchAll(
6722
+ (error) => Effect40.logError(`Failed to delete IAM role ${roleName}: ${error}`)
6552
6723
  )
6553
6724
  );
6554
6725
  }
@@ -6569,11 +6740,14 @@ var layerOption = Options2.boolean("layer").pipe(
6569
6740
  var rolesOption = Options2.boolean("roles").pipe(
6570
6741
  Options2.withDescription("Clean up orphaned IAM roles instead of handler resources")
6571
6742
  );
6743
+ var orphanedOption = Options2.boolean("orphaned").pipe(
6744
+ Options2.withDescription("Delete only handlers that exist in AWS but not in code")
6745
+ );
6572
6746
  var cleanupCommand = Command3.make(
6573
6747
  "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);
6748
+ { project: projectOption, stage: stageOption, region: regionOption, handler: handlerOption, layer: layerOption, roles: rolesOption, orphaned: orphanedOption, all: cleanupAllOption, dryRun: dryRunOption, verbose: verboseOption },
6749
+ ({ project: projectOpt, stage, region, handler: handlerOpt, layer: cleanupLayer, roles: cleanupRoles, orphaned: cleanupOrphaned, all: deleteAll, dryRun, verbose }) => Effect41.gen(function* () {
6750
+ const { config, projectDir } = yield* ProjectConfig;
6577
6751
  const project = Option3.getOrElse(projectOpt, () => config?.name ?? "");
6578
6752
  const finalStage = config?.stage ?? stage;
6579
6753
  const finalRegion = config?.region ?? region;
@@ -6584,14 +6758,14 @@ var cleanupCommand = Command3.make(
6584
6758
  const logLevel = verbose ? LogLevel3.Debug : LogLevel3.Info;
6585
6759
  if (cleanupLayer) {
6586
6760
  yield* cleanupLayerVersions({ project, region: finalRegion, deleteAll, dryRun }).pipe(
6587
- Effect39.provide(clients_exports.makeClients({ lambda: { region: finalRegion } })),
6761
+ Effect41.provide(clients_exports.makeClients({ lambda: { region: finalRegion } })),
6588
6762
  Logger3.withMinimumLogLevel(logLevel)
6589
6763
  );
6590
6764
  return;
6591
6765
  }
6592
6766
  if (cleanupRoles) {
6593
6767
  yield* cleanupIamRoles({ project, stage: finalStage, region: finalRegion, deleteAll, dryRun }).pipe(
6594
- Effect39.provide(clients_exports.makeClients({ iam: { region: finalRegion } })),
6768
+ Effect41.provide(clients_exports.makeClients({ iam: { region: finalRegion } })),
6595
6769
  Logger3.withMinimumLogLevel(logLevel)
6596
6770
  );
6597
6771
  return;
@@ -6606,7 +6780,7 @@ var cleanupCommand = Command3.make(
6606
6780
  s3: { region: finalRegion },
6607
6781
  cloudfront: { region: "us-east-1" }
6608
6782
  });
6609
- yield* Effect39.gen(function* () {
6783
+ yield* Effect41.gen(function* () {
6610
6784
  yield* Console5.log(`
6611
6785
  Looking for resources in ${c.bold(project + "/" + finalStage)}...
6612
6786
  `);
@@ -6616,14 +6790,34 @@ Looking for resources in ${c.bold(project + "/" + finalStage)}...
6616
6790
  return;
6617
6791
  }
6618
6792
  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}`);
6793
+ let handlersToDelete;
6794
+ if (cleanupOrphaned) {
6795
+ const patterns = getPatternsFromConfig(config);
6796
+ if (!patterns) {
6797
+ yield* Console5.error("Error: No 'handlers' patterns in config \u2014 cannot determine orphaned handlers");
6798
+ return;
6625
6799
  }
6626
- return;
6800
+ const files = findHandlerFiles(patterns, projectDir);
6801
+ const discovered = discoverHandlers(files);
6802
+ const codeNames = new Set(flattenHandlers(discovered).map((h) => h.exportName));
6803
+ const INTERNAL_HANDLERS2 = /* @__PURE__ */ new Set(["api", "platform"]);
6804
+ handlersToDelete = Array.from(byHandler.entries()).filter(([name]) => !codeNames.has(name) && !INTERNAL_HANDLERS2.has(name));
6805
+ if (handlersToDelete.length === 0) {
6806
+ yield* Console5.log("No orphaned handlers found.");
6807
+ return;
6808
+ }
6809
+ } else if (handlerFilter) {
6810
+ if (!byHandler.has(handlerFilter)) {
6811
+ yield* Console5.error(`Handler "${handlerFilter}" not found.`);
6812
+ yield* Console5.log("\nAvailable handlers:");
6813
+ for (const [h] of byHandler) {
6814
+ yield* Console5.log(` - ${h}`);
6815
+ }
6816
+ return;
6817
+ }
6818
+ handlersToDelete = [[handlerFilter, byHandler.get(handlerFilter) ?? []]];
6819
+ } else {
6820
+ handlersToDelete = Array.from(byHandler.entries());
6627
6821
  }
6628
6822
  const resourcesToDelete = [];
6629
6823
  const derivedRoles = [];
@@ -6654,9 +6848,10 @@ Total: ${totalResources} resource(s) (${derivedRoles.length} derived)`);
6654
6848
  ${c.yellow("[DRY RUN]")} No resources were deleted.`);
6655
6849
  return;
6656
6850
  }
6657
- if (!handlerFilter && !deleteAll) {
6851
+ if (!handlerFilter && !cleanupOrphaned && !deleteAll) {
6658
6852
  yield* Console5.log("\nTo delete these resources, use one of:");
6659
6853
  yield* Console5.log(` ${c.dim("eff cleanup --all")} # Delete all resources`);
6854
+ yield* Console5.log(` ${c.dim("eff cleanup --orphaned")} # Delete orphaned handlers only`);
6660
6855
  yield* Console5.log(` ${c.dim("eff cleanup --handler <name>")} # Delete specific handler`);
6661
6856
  yield* Console5.log(` ${c.dim("eff cleanup --dry-run")} # Preview without deleting`);
6662
6857
  return;
@@ -6665,12 +6860,12 @@ ${c.yellow("[DRY RUN]")} No resources were deleted.`);
6665
6860
  yield* deleteResources(resourcesToDelete);
6666
6861
  yield* Console5.log(c.green("\nDone!"));
6667
6862
  }).pipe(
6668
- Effect39.provide(clientsLayer),
6863
+ Effect41.provide(clientsLayer),
6669
6864
  Logger3.withMinimumLogLevel(logLevel)
6670
6865
  );
6671
- })
6672
- ).pipe(Command3.withDescription("Delete deployed resources"));
6673
- var cleanupLayerVersions = (input) => Effect39.gen(function* () {
6866
+ }).pipe(Effect41.provide(ProjectConfig.Live))
6867
+ ).pipe(Command3.withDescription("Delete deployed resources (Lambda, API Gateway, DynamoDB, IAM roles, layers)"));
6868
+ var cleanupLayerVersions = (input) => Effect41.gen(function* () {
6674
6869
  const layerName = `${input.project}-deps`;
6675
6870
  yield* Console5.log(`
6676
6871
  Searching for layer versions: ${layerName}
@@ -6702,7 +6897,7 @@ ${c.yellow("[DRY RUN]")} No layers were deleted.`);
6702
6897
  yield* Console5.log(c.green(`
6703
6898
  Deleted ${deleted} layer version(s).`));
6704
6899
  });
6705
- var cleanupIamRoles = (input) => Effect39.gen(function* () {
6900
+ var cleanupIamRoles = (input) => Effect41.gen(function* () {
6706
6901
  yield* Console5.log("\nSearching for effortless IAM roles...\n");
6707
6902
  const allRoles = yield* listEffortlessRoles();
6708
6903
  if (allRoles.length === 0) {
@@ -6751,8 +6946,8 @@ ${c.yellow("[DRY RUN]")} No roles were deleted.`);
6751
6946
  yield* Console5.log(c.red("\nDeleting roles..."));
6752
6947
  for (const role of roles) {
6753
6948
  yield* deleteRole(role.name).pipe(
6754
- Effect39.catchAll(
6755
- (error) => Effect39.logError(`Failed to delete ${role.name}: ${error}`)
6949
+ Effect41.catchAll(
6950
+ (error) => Effect41.logError(`Failed to delete ${role.name}: ${error}`)
6756
6951
  )
6757
6952
  );
6758
6953
  }
@@ -6761,7 +6956,7 @@ ${c.yellow("[DRY RUN]")} No roles were deleted.`);
6761
6956
 
6762
6957
  // src/cli/commands/logs.ts
6763
6958
  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";
6959
+ import { Effect as Effect42, Console as Console6, Logger as Logger4, LogLevel as LogLevel4, Option as Option4, Schedule as Schedule4 } from "effect";
6765
6960
  var { cloudwatch_logs } = clients_exports;
6766
6961
  var handlerArg = Args2.text({ name: "handler" }).pipe(
6767
6962
  Args2.withDescription("Handler name to show logs for")
@@ -6832,8 +7027,8 @@ var fetchLogs = (logGroupName, startTime, nextToken) => cloudwatch_logs.make("fi
6832
7027
  var logsCommand = Command4.make(
6833
7028
  "logs",
6834
7029
  { 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);
7030
+ ({ handler: handlerName, project: projectOpt, stage, region, tail, since, verbose }) => Effect42.gen(function* () {
7031
+ const { config, projectDir } = yield* ProjectConfig;
6837
7032
  const project = Option4.getOrElse(projectOpt, () => config?.name ?? "");
6838
7033
  const finalStage = config?.stage ?? stage;
6839
7034
  const finalRegion = config?.region ?? region;
@@ -6841,17 +7036,11 @@ var logsCommand = Command4.make(
6841
7036
  yield* Console6.error("Error: --project is required (or set 'name' in effortless.config.ts)");
6842
7037
  return;
6843
7038
  }
6844
- const projectDir = process.cwd();
6845
7039
  const patterns = getPatternsFromConfig(config);
6846
7040
  if (patterns) {
6847
7041
  const files = findHandlerFiles(patterns, projectDir);
6848
7042
  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
- ];
7043
+ const allHandlerNames = flattenHandlers(discovered).map((h) => h.exportName);
6855
7044
  if (!allHandlerNames.includes(handlerName)) {
6856
7045
  yield* Console6.error(`Handler "${handlerName}" not found in code.`);
6857
7046
  if (allHandlerNames.length > 0) {
@@ -6869,18 +7058,18 @@ var logsCommand = Command4.make(
6869
7058
  cloudwatch_logs: { region: finalRegion }
6870
7059
  });
6871
7060
  const logLevel = verbose ? LogLevel4.Debug : LogLevel4.Info;
6872
- yield* Effect40.gen(function* () {
7061
+ yield* Effect42.gen(function* () {
6873
7062
  const durationMs = parseDuration(since);
6874
7063
  let startTime = Date.now() - durationMs;
6875
7064
  yield* Console6.log(`Logs for ${c.bold(handlerName)} ${c.dim(`(${logGroupName})`)}:
6876
7065
  `);
6877
7066
  let hasLogs = false;
6878
7067
  const result = yield* fetchLogs(logGroupName, startTime).pipe(
6879
- Effect40.catchAll((error) => {
7068
+ Effect42.catchAll((error) => {
6880
7069
  if (error instanceof clients_exports.cloudwatch_logs.CloudWatchLogsError && error.cause.name === "ResourceNotFoundException") {
6881
- return Effect40.succeed({ events: void 0, nextToken: void 0 });
7070
+ return Effect42.succeed({ events: void 0, nextToken: void 0 });
6882
7071
  }
6883
- return Effect40.fail(error);
7072
+ return Effect42.fail(error);
6884
7073
  })
6885
7074
  );
6886
7075
  if (result.events && result.events.length > 0) {
@@ -6904,10 +7093,10 @@ var logsCommand = Command4.make(
6904
7093
  if (!hasLogs) {
6905
7094
  yield* Console6.log("Waiting for logs... (Ctrl+C to stop)\n");
6906
7095
  }
6907
- yield* Effect40.repeat(
6908
- Effect40.gen(function* () {
7096
+ yield* Effect42.repeat(
7097
+ Effect42.gen(function* () {
6909
7098
  const result2 = yield* fetchLogs(logGroupName, startTime).pipe(
6910
- Effect40.catchAll(() => Effect40.succeed({ events: void 0, nextToken: void 0 }))
7099
+ Effect42.catchAll(() => Effect42.succeed({ events: void 0, nextToken: void 0 }))
6911
7100
  );
6912
7101
  if (result2.events && result2.events.length > 0) {
6913
7102
  for (const event of result2.events) {
@@ -6925,16 +7114,16 @@ var logsCommand = Command4.make(
6925
7114
  Schedule4.spaced("2 seconds")
6926
7115
  );
6927
7116
  }).pipe(
6928
- Effect40.provide(clientsLayer),
7117
+ Effect42.provide(clientsLayer),
6929
7118
  Logger4.withMinimumLogLevel(logLevel)
6930
7119
  );
6931
- })
6932
- ).pipe(Command4.withDescription("Show logs for a handler"));
7120
+ }).pipe(Effect42.provide(ProjectConfig.Live))
7121
+ ).pipe(Command4.withDescription("Stream CloudWatch logs for a handler. Supports --tail for live tailing and --since for time range"));
6933
7122
 
6934
7123
  // src/cli/commands/layer.ts
6935
7124
  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";
7125
+ import { Effect as Effect43, Console as Console7 } from "effect";
7126
+ import * as path10 from "path";
6938
7127
  import * as fs5 from "fs";
6939
7128
  var buildOption = Options4.boolean("build").pipe(
6940
7129
  Options4.withDescription("Build layer directory locally (for debugging)")
@@ -6942,22 +7131,28 @@ var buildOption = Options4.boolean("build").pipe(
6942
7131
  var layerCommand = Command5.make(
6943
7132
  "layer",
6944
7133
  { 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();
7134
+ ({ build: build3, output, verbose }) => Effect43.gen(function* () {
7135
+ const { config, cwd } = yield* ProjectConfig;
6948
7136
  if (build3) {
6949
- yield* buildLayer(projectDir, output, verbose);
7137
+ yield* buildLayer(cwd, output, verbose);
6950
7138
  } else {
6951
- yield* showLayerInfo(projectDir, config?.name, verbose);
7139
+ yield* showLayerInfo(cwd, config?.name, verbose);
6952
7140
  }
6953
- })
6954
- ).pipe(Command5.withDescription("Show or build the dependency layer"));
6955
- var showLayerInfo = (projectDir, projectName, verbose) => Effect41.gen(function* () {
7141
+ }).pipe(Effect43.provide(ProjectConfig.Live))
7142
+ ).pipe(Command5.withDescription("Inspect or locally build the shared Lambda dependency layer from package.json"));
7143
+ var showLayerInfo = (projectDir, projectName, verbose) => Effect43.gen(function* () {
6956
7144
  yield* Console7.log(`
6957
7145
  ${c.bold("=== Layer Packages Preview ===")}
6958
7146
  `);
7147
+ const depWarnings = yield* checkDependencyWarnings(projectDir).pipe(
7148
+ Effect43.catchAll(() => Effect43.succeed([]))
7149
+ );
7150
+ for (const w of depWarnings) {
7151
+ yield* Console7.log(c.yellow(` \u26A0 ${w}`));
7152
+ }
7153
+ if (depWarnings.length > 0) yield* Console7.log("");
6959
7154
  const prodDeps = yield* readProductionDependencies(projectDir).pipe(
6960
- Effect41.catchAll(() => Effect41.succeed([]))
7155
+ Effect43.catchAll(() => Effect43.succeed([]))
6961
7156
  );
6962
7157
  if (prodDeps.length === 0) {
6963
7158
  yield* Console7.log("No production dependencies found in package.json");
@@ -6969,7 +7164,7 @@ ${c.bold("=== Layer Packages Preview ===")}
6969
7164
  yield* Console7.log(` ${dep}`);
6970
7165
  }
6971
7166
  const hash = yield* computeLockfileHash(projectDir).pipe(
6972
- Effect41.catchAll(() => Effect41.succeed(null))
7167
+ Effect43.catchAll(() => Effect43.succeed(null))
6973
7168
  );
6974
7169
  if (hash) {
6975
7170
  yield* Console7.log(`
@@ -6977,7 +7172,7 @@ Lockfile hash: ${hash}`);
6977
7172
  } else {
6978
7173
  yield* Console7.log("\nNo lockfile found (package-lock.json, pnpm-lock.yaml, or yarn.lock)");
6979
7174
  }
6980
- const { packages: allPackages, warnings: layerWarnings } = yield* Effect41.sync(() => collectLayerPackages(projectDir, prodDeps));
7175
+ const { packages: allPackages, warnings: layerWarnings } = yield* Effect43.sync(() => collectLayerPackages(projectDir, prodDeps));
6981
7176
  if (layerWarnings.length > 0) {
6982
7177
  yield* Console7.log(c.yellow(`
6983
7178
  Warnings (${layerWarnings.length}):`));
@@ -7006,10 +7201,10 @@ Total packages for layer ${c.dim(`(${allPackages.length})`)}:`);
7006
7201
  Layer name: ${projectName}-deps`);
7007
7202
  }
7008
7203
  });
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");
7204
+ var buildLayer = (projectDir, output, verbose) => Effect43.gen(function* () {
7205
+ const outputDir = path10.isAbsolute(output) ? output : path10.resolve(projectDir, output);
7206
+ const layerDir = path10.join(outputDir, "nodejs", "node_modules");
7207
+ const layerRoot = path10.join(outputDir, "nodejs");
7013
7208
  if (fs5.existsSync(layerRoot)) {
7014
7209
  fs5.rmSync(layerRoot, { recursive: true });
7015
7210
  }
@@ -7017,8 +7212,15 @@ var buildLayer = (projectDir, output, verbose) => Effect41.gen(function* () {
7017
7212
  yield* Console7.log(`
7018
7213
  ${c.bold("=== Building Layer Locally ===")}
7019
7214
  `);
7215
+ const depWarnings = yield* checkDependencyWarnings(projectDir).pipe(
7216
+ Effect43.catchAll(() => Effect43.succeed([]))
7217
+ );
7218
+ for (const w of depWarnings) {
7219
+ yield* Console7.log(c.yellow(` \u26A0 ${w}`));
7220
+ }
7221
+ if (depWarnings.length > 0) yield* Console7.log("");
7020
7222
  const prodDeps = yield* readProductionDependencies(projectDir).pipe(
7021
- Effect41.catchAll(() => Effect41.succeed([]))
7223
+ Effect43.catchAll(() => Effect43.succeed([]))
7022
7224
  );
7023
7225
  if (prodDeps.length === 0) {
7024
7226
  yield* Console7.log("No production dependencies found in package.json");
@@ -7030,11 +7232,11 @@ ${c.bold("=== Building Layer Locally ===")}
7030
7232
  yield* Console7.log(` ${dep}`);
7031
7233
  }
7032
7234
  const hash = yield* computeLockfileHash(projectDir).pipe(
7033
- Effect41.catchAll(() => Effect41.succeed("unknown"))
7235
+ Effect43.catchAll(() => Effect43.succeed("unknown"))
7034
7236
  );
7035
7237
  yield* Console7.log(`
7036
7238
  Lockfile hash: ${hash}`);
7037
- const { packages: allPackages, resolvedPaths, warnings: layerWarnings } = yield* Effect41.sync(() => collectLayerPackages(projectDir, prodDeps));
7239
+ const { packages: allPackages, resolvedPaths, warnings: layerWarnings } = yield* Effect43.sync(() => collectLayerPackages(projectDir, prodDeps));
7038
7240
  if (layerWarnings.length > 0) {
7039
7241
  yield* Console7.log(`
7040
7242
  Warnings (${layerWarnings.length}):`);
@@ -7061,9 +7263,9 @@ Collected ${allPackages.length} packages for layer`);
7061
7263
  }
7062
7264
  continue;
7063
7265
  }
7064
- const destPath = path9.join(layerDir, pkgName);
7266
+ const destPath = path10.join(layerDir, pkgName);
7065
7267
  if (pkgName.startsWith("@")) {
7066
- const scopeDir = path9.join(layerDir, pkgName.split("/")[0] ?? pkgName);
7268
+ const scopeDir = path10.join(layerDir, pkgName.split("/")[0] ?? pkgName);
7067
7269
  if (!fs5.existsSync(scopeDir)) {
7068
7270
  fs5.mkdirSync(scopeDir, { recursive: true });
7069
7271
  }
@@ -7084,20 +7286,20 @@ To inspect: ls ${layerDir}`);
7084
7286
  // src/cli/commands/config.ts
7085
7287
  import { Args as Args3, Command as Command6 } from "@effect/cli";
7086
7288
  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);
7289
+ import { Effect as Effect44, Console as Console8, Logger as Logger5, LogLevel as LogLevel5, Option as Option5 } from "effect";
7290
+ var loadRequiredParams = (projectOpt, stage, region) => Effect44.gen(function* () {
7291
+ const { config, projectDir } = yield* ProjectConfig;
7090
7292
  const project = Option5.getOrElse(projectOpt, () => config?.name ?? "");
7091
7293
  if (!project) {
7092
7294
  yield* Console8.error("Error: --project is required (or set 'name' in effortless.config.ts)");
7093
- return yield* Effect42.fail(new Error("Missing project name"));
7295
+ return yield* Effect44.fail(new Error("Missing project name"));
7094
7296
  }
7095
7297
  const patterns = getPatternsFromConfig(config);
7096
7298
  if (!patterns) {
7097
7299
  yield* Console8.error("Error: No 'handlers' patterns in config");
7098
- return yield* Effect42.fail(new Error("Missing handler patterns"));
7300
+ return yield* Effect44.fail(new Error("Missing handler patterns"));
7099
7301
  }
7100
- const files = findHandlerFiles(patterns, process.cwd());
7302
+ const files = findHandlerFiles(patterns, projectDir);
7101
7303
  const handlers = discoverHandlers(files);
7102
7304
  const finalStage = config?.stage ?? stage;
7103
7305
  const finalRegion = config?.region ?? region;
@@ -7107,7 +7309,7 @@ var loadRequiredParams = (projectOpt, stage, region) => Effect42.gen(function* (
7107
7309
  var listCommand = Command6.make(
7108
7310
  "list",
7109
7311
  { project: projectOption, stage: stageOption, region: regionOption, verbose: verboseOption },
7110
- ({ project: projectOpt, stage, region, verbose }) => Effect42.gen(function* () {
7312
+ ({ project: projectOpt, stage, region, verbose }) => Effect44.gen(function* () {
7111
7313
  const ctx = yield* loadRequiredParams(projectOpt, stage, region);
7112
7314
  const { params } = ctx;
7113
7315
  if (params.length === 0) {
@@ -7115,7 +7317,7 @@ var listCommand = Command6.make(
7115
7317
  return;
7116
7318
  }
7117
7319
  const { existing, missing } = yield* checkMissingParams(params).pipe(
7118
- Effect42.provide(clients_exports.makeClients({ ssm: { region: ctx.region } }))
7320
+ Effect44.provide(clients_exports.makeClients({ ssm: { region: ctx.region } }))
7119
7321
  );
7120
7322
  yield* Console8.log(`
7121
7323
  ${c.bold("Config parameters")} ${c.dim(`(${ctx.project} / ${ctx.stage})`)}
@@ -7138,16 +7340,19 @@ ${c.bold("Config parameters")} ${c.dim(`(${ctx.project} / ${ctx.stage})`)}
7138
7340
  ${c.green("All parameters are set.")}`);
7139
7341
  }
7140
7342
  yield* Console8.log("");
7141
- }).pipe(Logger5.withMinimumLogLevel(LogLevel5.Warning))
7142
- ).pipe(Command6.withDescription("List all config parameters and their status"));
7343
+ }).pipe(
7344
+ Effect44.provide(ProjectConfig.Live),
7345
+ Logger5.withMinimumLogLevel(LogLevel5.Warning)
7346
+ )
7347
+ ).pipe(Command6.withDescription("List all declared config parameters and show which are set vs missing"));
7143
7348
  var setKeyArg = Args3.text({ name: "key" }).pipe(
7144
7349
  Args3.withDescription("SSM parameter key (e.g. stripe/secret-key)")
7145
7350
  );
7146
7351
  var setCommand = Command6.make(
7147
7352
  "set",
7148
7353
  { 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);
7354
+ ({ key, project: projectOpt, stage, region, verbose }) => Effect44.gen(function* () {
7355
+ const { config } = yield* ProjectConfig;
7151
7356
  const project = Option5.getOrElse(projectOpt, () => config?.name ?? "");
7152
7357
  if (!project) {
7153
7358
  yield* Console8.error("Error: --project is required (or set 'name' in effortless.config.ts)");
@@ -7164,15 +7369,18 @@ var setCommand = Command6.make(
7164
7369
  Value: value,
7165
7370
  Type: "SecureString",
7166
7371
  Overwrite: true
7167
- }).pipe(Effect42.provide(clients_exports.makeClients({ ssm: { region: finalRegion } })));
7372
+ }).pipe(Effect44.provide(clients_exports.makeClients({ ssm: { region: finalRegion } })));
7168
7373
  yield* Console8.log(`
7169
7374
  ${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"));
7375
+ }).pipe(
7376
+ Effect44.provide(ProjectConfig.Live),
7377
+ Logger5.withMinimumLogLevel(LogLevel5.Warning)
7378
+ )
7379
+ ).pipe(Command6.withDescription("Set a config parameter value (stored encrypted in AWS)"));
7172
7380
  var configRootCommand = Command6.make(
7173
7381
  "config",
7174
7382
  { project: projectOption, stage: stageOption, region: regionOption, verbose: verboseOption },
7175
- ({ project: projectOpt, stage, region, verbose }) => Effect42.gen(function* () {
7383
+ ({ project: projectOpt, stage, region, verbose }) => Effect44.gen(function* () {
7176
7384
  const ctx = yield* loadRequiredParams(projectOpt, stage, region);
7177
7385
  const { params } = ctx;
7178
7386
  if (params.length === 0) {
@@ -7180,7 +7388,7 @@ var configRootCommand = Command6.make(
7180
7388
  return;
7181
7389
  }
7182
7390
  const { missing } = yield* checkMissingParams(params).pipe(
7183
- Effect42.provide(clients_exports.makeClients({ ssm: { region: ctx.region } }))
7391
+ Effect44.provide(clients_exports.makeClients({ ssm: { region: ctx.region } }))
7184
7392
  );
7185
7393
  if (missing.length === 0) {
7186
7394
  yield* Console8.log(`
@@ -7205,7 +7413,7 @@ ${c.bold("Missing parameters")} ${c.dim(`(${ctx.project} / ${ctx.stage})`)}
7205
7413
  Value: value,
7206
7414
  Type: "SecureString",
7207
7415
  Overwrite: false
7208
- }).pipe(Effect42.provide(clients_exports.makeClients({ ssm: { region: ctx.region } })));
7416
+ }).pipe(Effect44.provide(clients_exports.makeClients({ ssm: { region: ctx.region } })));
7209
7417
  yield* Console8.log(` ${c.green("\u2713")} created`);
7210
7418
  created++;
7211
7419
  }
@@ -7218,9 +7426,12 @@ ${c.bold("Missing parameters")} ${c.dim(`(${ctx.project} / ${ctx.stage})`)}
7218
7426
  No parameters created.
7219
7427
  `);
7220
7428
  }
7221
- }).pipe(Logger5.withMinimumLogLevel(LogLevel5.Warning))
7429
+ }).pipe(
7430
+ Effect44.provide(ProjectConfig.Live),
7431
+ Logger5.withMinimumLogLevel(LogLevel5.Warning)
7432
+ )
7222
7433
  ).pipe(
7223
- Command6.withDescription("Manage SSM config parameters for your handlers"),
7434
+ Command6.withDescription("Manage config values declared via param() in handlers. Run without subcommand to interactively set missing values"),
7224
7435
  Command6.withSubcommands([listCommand, setCommand])
7225
7436
  );
7226
7437
  var configCommand = configRootCommand;
@@ -7237,6 +7448,7 @@ var cli = Command7.run(mainCommand, {
7237
7448
  version
7238
7449
  });
7239
7450
  cli(process.argv).pipe(
7240
- Effect43.provide(NodeContext.layer),
7451
+ Effect45.provide(NodeContext.layer),
7452
+ Effect45.provide(CliConfig.layer({ showBuiltIns: false, showTypes: false })),
7241
7453
  NodeRuntime.runMain
7242
7454
  );