@effortless-aws/cli 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli/index.js +932 -219
  2. package/package.json +2 -2
package/dist/cli/index.js CHANGED
@@ -6,9 +6,9 @@ var __export = (target, all) => {
6
6
  };
7
7
 
8
8
  // src/cli/index.ts
9
- import { CliConfig, Command as Command7 } from "@effect/cli";
9
+ import { CliConfig, Command as Command8 } from "@effect/cli";
10
10
  import { NodeContext, NodeRuntime } from "@effect/platform-node";
11
- import { Effect as Effect45 } from "effect";
11
+ import { Effect as Effect46 } from "effect";
12
12
  import { createRequire as createRequire2 } from "module";
13
13
 
14
14
  // src/cli/commands/deploy.ts
@@ -1933,6 +1933,18 @@ var publishVersion = (functionName) => Effect13.gen(function* () {
1933
1933
  version: result.Version
1934
1934
  };
1935
1935
  });
1936
+ var ensureEdgePermission = (functionName) => lambda_exports.make("add_permission", {
1937
+ FunctionName: functionName,
1938
+ StatementId: "replicator",
1939
+ Action: "lambda:GetFunction",
1940
+ Principal: "replicator.lambda.amazonaws.com"
1941
+ }).pipe(
1942
+ Effect13.catchIf(
1943
+ (e) => e._tag === "LambdaError" && e.is("ResourceConflictException"),
1944
+ () => Effect13.logDebug(`Replicator permission already exists for ${functionName}`)
1945
+ ),
1946
+ Effect13.asVoid
1947
+ );
1936
1948
  var DEFAULT_CORS = {
1937
1949
  AllowOrigins: ["*"],
1938
1950
  AllowMethods: ["*"],
@@ -2459,15 +2471,24 @@ var getPackageVersion = (pkgPath) => {
2459
2471
  return null;
2460
2472
  }
2461
2473
  };
2462
- var computeLockfileHash = (projectDir) => Effect18.gen(function* () {
2474
+ var computeLockfileHash = (projectDir, extraNodeModules) => Effect18.gen(function* () {
2463
2475
  const prodDeps = yield* readProductionDependencies(projectDir);
2464
2476
  if (prodDeps.length === 0) {
2465
2477
  return yield* Effect18.fail(new Error("No production dependencies"));
2466
2478
  }
2467
- const { packages: allPackages, resolvedPaths } = collectTransitiveDeps(projectDir, prodDeps);
2479
+ const { packages: allPackages, resolvedPaths } = collectTransitiveDeps(
2480
+ projectDir,
2481
+ prodDeps,
2482
+ void 0,
2483
+ void 0,
2484
+ void 0,
2485
+ void 0,
2486
+ void 0,
2487
+ extraNodeModules
2488
+ );
2468
2489
  const packageVersions = [];
2469
2490
  for (const pkgName of Array.from(allPackages).filter((p) => !isAwsRuntime(p)).sort()) {
2470
- const pkgPath = resolvedPaths.get(pkgName) ?? findInPnpmStore(projectDir, pkgName) ?? getPackageRealPath(projectDir, pkgName);
2491
+ const pkgPath = resolvedPaths.get(pkgName) ?? findInPnpmStore(projectDir, pkgName, extraNodeModules) ?? getPackageRealPath(projectDir, pkgName);
2471
2492
  if (pkgPath) {
2472
2493
  const version2 = getPackageVersion(pkgPath);
2473
2494
  if (version2) {
@@ -2522,10 +2543,13 @@ var DEV_ONLY_PREFIXES = [
2522
2543
  "@vitest/",
2523
2544
  "@jest/"
2524
2545
  ];
2525
- var extractExportPaths = (value) => {
2546
+ var RUNTIME_CONDITIONS = /* @__PURE__ */ new Set(["import", "require", "default", "node"]);
2547
+ var isSubpath = (key) => key.startsWith(".");
2548
+ var extractExportPaths = (value, key) => {
2549
+ if (key !== void 0 && !isSubpath(key) && !RUNTIME_CONDITIONS.has(key)) return [];
2526
2550
  if (typeof value === "string") return [value];
2527
2551
  if (typeof value === "object" && value !== null) {
2528
- return Object.values(value).flatMap(extractExportPaths);
2552
+ return Object.entries(value).flatMap(([k, v]) => extractExportPaths(v, k));
2529
2553
  }
2530
2554
  return [];
2531
2555
  };
@@ -2568,7 +2592,7 @@ var checkDependencyWarnings = (projectDir) => Effect18.gen(function* () {
2568
2592
  const tsEntries = [...new Set(entryPoints.filter(isRawTypeScript))];
2569
2593
  if (tsEntries.length > 0) {
2570
2594
  warnings.push(
2571
- `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.`
2595
+ `Package "${dep}" has TypeScript entry points (${tsEntries.join(", ")}) \u2014 it will be bundled by esbuild instead of included in the layer.`
2572
2596
  );
2573
2597
  }
2574
2598
  } catch {
@@ -2601,8 +2625,7 @@ var getPackageDeps = (pkgPath) => {
2601
2625
  return EMPTY_DEPS;
2602
2626
  }
2603
2627
  };
2604
- var findInPnpmStore = (projectDir, pkgName) => {
2605
- const pnpmDir = path.join(projectDir, "node_modules", ".pnpm");
2628
+ var searchPnpmDir = (pnpmDir, pkgName) => {
2606
2629
  if (!fsSync.existsSync(pnpmDir)) return null;
2607
2630
  const pnpmPkgName = pkgName.replace("/", "+");
2608
2631
  try {
@@ -2622,7 +2645,18 @@ var findInPnpmStore = (projectDir, pkgName) => {
2622
2645
  }
2623
2646
  return null;
2624
2647
  };
2625
- var collectTransitiveDeps = (projectDir, rootDeps, searchPath = path.join(projectDir, "node_modules"), visited = /* @__PURE__ */ new Set(), resolvedPaths = /* @__PURE__ */ new Map(), warnings = [], optionalNames = /* @__PURE__ */ new Set()) => {
2648
+ var findInPnpmStore = (projectDir, pkgName, extraNodeModules) => {
2649
+ const result = searchPnpmDir(path.join(projectDir, "node_modules", ".pnpm"), pkgName);
2650
+ if (result) return result;
2651
+ if (extraNodeModules) {
2652
+ for (const dir of extraNodeModules) {
2653
+ const found = searchPnpmDir(path.join(dir, ".pnpm"), pkgName);
2654
+ if (found) return found;
2655
+ }
2656
+ }
2657
+ return null;
2658
+ };
2659
+ var collectTransitiveDeps = (projectDir, rootDeps, searchPath = path.join(projectDir, "node_modules"), visited = /* @__PURE__ */ new Set(), resolvedPaths = /* @__PURE__ */ new Map(), warnings = [], optionalNames = /* @__PURE__ */ new Set(), extraNodeModules) => {
2626
2660
  const rootNodeModules = path.join(projectDir, "node_modules");
2627
2661
  for (const dep of rootDeps) {
2628
2662
  if (visited.has(dep)) continue;
@@ -2645,8 +2679,20 @@ var collectTransitiveDeps = (projectDir, rootDeps, searchPath = path.join(projec
2645
2679
  }
2646
2680
  }
2647
2681
  }
2682
+ if (!realPath && extraNodeModules) {
2683
+ for (const dir of extraNodeModules) {
2684
+ pkgPath = path.join(dir, dep);
2685
+ if (fsSync.existsSync(pkgPath)) {
2686
+ try {
2687
+ realPath = fsSync.realpathSync(pkgPath);
2688
+ break;
2689
+ } catch {
2690
+ }
2691
+ }
2692
+ }
2693
+ }
2648
2694
  if (!realPath) {
2649
- realPath = findInPnpmStore(projectDir, dep);
2695
+ realPath = findInPnpmStore(projectDir, dep, extraNodeModules);
2650
2696
  }
2651
2697
  if (!realPath) {
2652
2698
  if (!optionalNames.has(dep)) {
@@ -2662,15 +2708,38 @@ var collectTransitiveDeps = (projectDir, rootDeps, searchPath = path.join(projec
2662
2708
  const pkgNodeModules = isScoped ? path.dirname(path.dirname(realPath)) : path.dirname(realPath);
2663
2709
  const nextOptional = new Set(optionalNames);
2664
2710
  for (const name of pkgDeps.optional) nextOptional.add(name);
2665
- collectTransitiveDeps(projectDir, pkgDeps.all, pkgNodeModules, visited, resolvedPaths, warnings, nextOptional);
2711
+ collectTransitiveDeps(projectDir, pkgDeps.all, pkgNodeModules, visited, resolvedPaths, warnings, nextOptional, extraNodeModules);
2666
2712
  }
2667
2713
  }
2668
2714
  return { packages: visited, resolvedPaths, warnings };
2669
2715
  };
2670
2716
  var isAwsRuntime = (pkg) => pkg.startsWith("@aws-sdk/") || pkg.startsWith("@smithy/") || pkg.startsWith("@aws-crypto/") || pkg.startsWith("@aws/");
2671
- var collectLayerPackages = (projectDir, dependencies) => {
2717
+ var hasTypeScriptEntryPoints = (pkgPath) => {
2718
+ const pkgJsonPath = path.join(pkgPath, "package.json");
2719
+ if (!fsSync.existsSync(pkgJsonPath)) return false;
2720
+ try {
2721
+ const pkgJson = JSON.parse(fsSync.readFileSync(pkgJsonPath, "utf-8"));
2722
+ const entryPoints = [];
2723
+ if (typeof pkgJson.main === "string") entryPoints.push(pkgJson.main);
2724
+ if (typeof pkgJson.module === "string") entryPoints.push(pkgJson.module);
2725
+ entryPoints.push(...extractExportPaths(pkgJson.exports));
2726
+ return entryPoints.some(isRawTypeScript);
2727
+ } catch {
2728
+ return false;
2729
+ }
2730
+ };
2731
+ var collectLayerPackages = (projectDir, dependencies, extraNodeModules) => {
2672
2732
  if (dependencies.length === 0) return { packages: [], resolvedPaths: /* @__PURE__ */ new Map(), warnings: [] };
2673
- const { packages, resolvedPaths, warnings } = collectTransitiveDeps(projectDir, dependencies);
2733
+ const { packages, resolvedPaths, warnings } = collectTransitiveDeps(
2734
+ projectDir,
2735
+ dependencies,
2736
+ void 0,
2737
+ void 0,
2738
+ void 0,
2739
+ void 0,
2740
+ void 0,
2741
+ extraNodeModules
2742
+ );
2674
2743
  let changed = true;
2675
2744
  while (changed) {
2676
2745
  changed = false;
@@ -2679,7 +2748,7 @@ var collectLayerPackages = (projectDir, dependencies) => {
2679
2748
  const pkgPaths = /* @__PURE__ */ new Set();
2680
2749
  const resolved = resolvedPaths.get(pkg);
2681
2750
  if (resolved) pkgPaths.add(resolved);
2682
- const found = findPackagePath(projectDir, pkg);
2751
+ const found = findPackagePath(projectDir, pkg, extraNodeModules);
2683
2752
  if (found) pkgPaths.add(found);
2684
2753
  if (pkgPaths.size === 0) continue;
2685
2754
  for (const pkgPath of pkgPaths) {
@@ -2687,7 +2756,7 @@ var collectLayerPackages = (projectDir, dependencies) => {
2687
2756
  const optionalSet = new Set(pkgDeps.optional);
2688
2757
  for (const dep of pkgDeps.all) {
2689
2758
  if (!packages.has(dep) && !isAwsRuntime(dep)) {
2690
- let depPath = findPackagePath(projectDir, dep);
2759
+ let depPath = findPackagePath(projectDir, dep, extraNodeModules);
2691
2760
  if (!depPath) {
2692
2761
  const isScoped = pkg.startsWith("@");
2693
2762
  const parentNodeModules = isScoped ? path.dirname(path.dirname(pkgPath)) : path.dirname(pkgPath);
@@ -2708,13 +2777,33 @@ var collectLayerPackages = (projectDir, dependencies) => {
2708
2777
  }
2709
2778
  }
2710
2779
  }
2711
- const filtered = Array.from(packages).filter((pkg) => !isAwsRuntime(pkg));
2780
+ const filtered = Array.from(packages).filter((pkg) => {
2781
+ if (isAwsRuntime(pkg)) return false;
2782
+ const pkgPath = resolvedPaths.get(pkg) ?? findPackagePath(projectDir, pkg, extraNodeModules);
2783
+ if (!pkgPath) return false;
2784
+ if (hasTypeScriptEntryPoints(pkgPath)) {
2785
+ warnings.push(`Package "${pkg}" has TypeScript entry points \u2014 it will be bundled by esbuild instead of included in the layer`);
2786
+ return false;
2787
+ }
2788
+ return true;
2789
+ });
2712
2790
  return { packages: filtered, resolvedPaths, warnings };
2713
2791
  };
2714
- var findPackagePath = (projectDir, pkgName) => {
2792
+ var findPackagePath = (projectDir, pkgName, extraNodeModules) => {
2715
2793
  const rootPath = getPackageRealPath(projectDir, pkgName);
2716
2794
  if (rootPath) return rootPath;
2717
- return findInPnpmStore(projectDir, pkgName);
2795
+ if (extraNodeModules) {
2796
+ for (const dir of extraNodeModules) {
2797
+ const pkgPath = path.join(dir, pkgName);
2798
+ if (fsSync.existsSync(pkgPath)) {
2799
+ try {
2800
+ return fsSync.realpathSync(pkgPath);
2801
+ } catch {
2802
+ }
2803
+ }
2804
+ }
2805
+ }
2806
+ return findInPnpmStore(projectDir, pkgName, extraNodeModules);
2718
2807
  };
2719
2808
  var createLayerZip = (projectDir, packages, resolvedPaths) => Effect18.async((resume) => {
2720
2809
  const chunks = [];
@@ -2778,7 +2867,7 @@ var ensureLayer = (config) => Effect18.gen(function* () {
2778
2867
  yield* Effect18.logDebug("No production dependencies, skipping layer creation");
2779
2868
  return null;
2780
2869
  }
2781
- const hash = yield* computeLockfileHash(config.projectDir).pipe(
2870
+ const hash = yield* computeLockfileHash(config.projectDir, config.extraNodeModules).pipe(
2782
2871
  Effect18.catchAll((e) => {
2783
2872
  const message = e instanceof Error ? e.message : String(e);
2784
2873
  return Effect18.logWarning(`Cannot compute lockfile hash: ${message}, skipping layer`).pipe(
@@ -2795,7 +2884,7 @@ var ensureLayer = (config) => Effect18.gen(function* () {
2795
2884
  yield* Effect18.logDebug(`Layer ${layerName} with hash ${hash} already exists (version ${existing.version})`);
2796
2885
  return existing;
2797
2886
  }
2798
- const { packages: allPackages, resolvedPaths, warnings: layerWarnings } = yield* Effect18.sync(() => collectLayerPackages(config.projectDir, dependencies));
2887
+ const { packages: allPackages, resolvedPaths, warnings: layerWarnings } = yield* Effect18.sync(() => collectLayerPackages(config.projectDir, dependencies, config.extraNodeModules));
2799
2888
  for (const warning of layerWarnings) {
2800
2889
  yield* Effect18.logWarning(`[layer] ${warning}`);
2801
2890
  }
@@ -2805,11 +2894,46 @@ var ensureLayer = (config) => Effect18.gen(function* () {
2805
2894
  if (skippedPackages.length > 0) {
2806
2895
  yield* Effect18.logWarning(`Skipped ${skippedPackages.length} packages (not found): ${skippedPackages.slice(0, 10).join(", ")}${skippedPackages.length > 10 ? "..." : ""}`);
2807
2896
  }
2808
- yield* Effect18.logDebug(`Layer zip size: ${(layerZip.length / 1024 / 1024).toFixed(2)} MB (${includedPackages.length} packages)`);
2897
+ const zipSizeMB = layerZip.length / 1024 / 1024;
2898
+ yield* Effect18.logDebug(`Layer zip size: ${zipSizeMB.toFixed(2)} MB (${includedPackages.length} packages)`);
2899
+ const MAX_DIRECT_UPLOAD = 50 * 1024 * 1024;
2900
+ let content;
2901
+ if (layerZip.length > MAX_DIRECT_UPLOAD) {
2902
+ const bucketName = `${config.project}-${config.stage}-deploy-artifacts`;
2903
+ const s3Key = `layers/${layerName}-${hash}.zip`;
2904
+ yield* Effect18.logDebug(`Layer zip too large for direct upload (${zipSizeMB.toFixed(1)} MB), uploading to S3: s3://${bucketName}/${s3Key}`);
2905
+ const bucketExists = yield* s3_exports.make("head_bucket", { Bucket: bucketName }).pipe(
2906
+ Effect18.map(() => true),
2907
+ Effect18.catchIf((e) => e._tag === "S3Error", () => Effect18.succeed(false))
2908
+ );
2909
+ if (!bucketExists) {
2910
+ yield* s3_exports.make("create_bucket", {
2911
+ Bucket: bucketName,
2912
+ ...config.region !== "us-east-1" ? { CreateBucketConfiguration: { LocationConstraint: config.region } } : {}
2913
+ });
2914
+ yield* s3_exports.make("put_public_access_block", {
2915
+ Bucket: bucketName,
2916
+ PublicAccessBlockConfiguration: {
2917
+ BlockPublicAcls: true,
2918
+ IgnorePublicAcls: true,
2919
+ BlockPublicPolicy: true,
2920
+ RestrictPublicBuckets: true
2921
+ }
2922
+ });
2923
+ }
2924
+ yield* s3_exports.make("put_object", {
2925
+ Bucket: bucketName,
2926
+ Key: s3Key,
2927
+ Body: layerZip
2928
+ });
2929
+ content = { S3Bucket: bucketName, S3Key: s3Key };
2930
+ } else {
2931
+ content = { ZipFile: layerZip };
2932
+ }
2809
2933
  const result = yield* lambda_exports.make("publish_layer_version", {
2810
2934
  LayerName: layerName,
2811
2935
  Description: `effortless deps layer hash:${hash}`,
2812
- Content: { ZipFile: layerZip },
2936
+ Content: content,
2813
2937
  CompatibleRuntimes: [Runtime2.nodejs24x],
2814
2938
  CompatibleArchitectures: [Architecture2.arm64]
2815
2939
  });
@@ -4002,6 +4126,7 @@ import { Effect as Effect24 } from "effect";
4002
4126
  import * as esbuild from "esbuild";
4003
4127
  import * as fsSync2 from "fs";
4004
4128
  import * as path3 from "path";
4129
+ import { builtinModules } from "module";
4005
4130
  import { createRequire } from "module";
4006
4131
  import archiver2 from "archiver";
4007
4132
  import { globSync } from "glob";
@@ -4012,7 +4137,36 @@ var parseSource = (source) => {
4012
4137
  const project = new Project({ useInMemoryFileSystem: true });
4013
4138
  return project.createSourceFile("input.ts", source);
4014
4139
  };
4015
- var RUNTIME_PROPS = ["onRecord", "onBatchComplete", "onBatch", "onMessage", "onObjectCreated", "onObjectRemoved", "setup", "schema", "onError", "deps", "config", "static", "middleware", "routes", "get", "post"];
4140
+ var bareName = (expr) => {
4141
+ const dot = expr.lastIndexOf(".");
4142
+ return dot === -1 ? expr : expr.slice(dot + 1);
4143
+ };
4144
+ var getProp = (obj, name) => obj.getProperties().find((p) => p.getKind() === SyntaxKind.PropertyAssignment && p.getName() === name)?.asKindOrThrow(SyntaxKind.PropertyAssignment).getInitializer();
4145
+ var findDefineCalls = (sourceFile, defineFn) => {
4146
+ const results = [];
4147
+ const tryAdd = (callExpr, exportName) => {
4148
+ if (bareName(callExpr.getExpression().getText()) !== defineFn) return;
4149
+ const firstArg = callExpr.getArguments()[0];
4150
+ if (firstArg?.getKind() === SyntaxKind.ObjectLiteralExpression) {
4151
+ results.push({ exportName, args: firstArg });
4152
+ }
4153
+ };
4154
+ const def = sourceFile.getExportAssignment((e) => !e.isExportEquals());
4155
+ if (def?.getExpression().getKind() === SyntaxKind.CallExpression) {
4156
+ tryAdd(def.getExpression().asKindOrThrow(SyntaxKind.CallExpression), "default");
4157
+ }
4158
+ for (const stmt of sourceFile.getVariableStatements()) {
4159
+ if (!stmt.isExported()) continue;
4160
+ for (const decl of stmt.getDeclarations()) {
4161
+ const init = decl.getInitializer();
4162
+ if (init?.getKind() === SyntaxKind.CallExpression) {
4163
+ tryAdd(init.asKindOrThrow(SyntaxKind.CallExpression), decl.getName());
4164
+ }
4165
+ }
4166
+ }
4167
+ return results;
4168
+ };
4169
+ var RUNTIME_PROPS = ["onRecord", "onBatchComplete", "onBatch", "onMessage", "onObjectCreated", "onObjectRemoved", "setup", "schema", "onError", "deps", "config", "static", "middleware", "auth", "routes", "get", "post"];
4016
4170
  var evalConfig = (configText, exportName) => {
4017
4171
  try {
4018
4172
  return new Function(`return ${configText}`)();
@@ -4027,35 +4181,18 @@ Handler config must use only literal values (no variables, imports, or expressio
4027
4181
  var buildConfigWithoutRuntime = (obj) => {
4028
4182
  const props = obj.getProperties().filter((p) => {
4029
4183
  if (p.getKind() === SyntaxKind.PropertyAssignment) {
4030
- const propAssign = p;
4031
- return !RUNTIME_PROPS.includes(propAssign.getName());
4184
+ return !RUNTIME_PROPS.includes(p.getName());
4185
+ }
4186
+ if (p.getKind() === SyntaxKind.ShorthandPropertyAssignment) {
4187
+ return !RUNTIME_PROPS.includes(p.asKindOrThrow(SyntaxKind.ShorthandPropertyAssignment).getName());
4032
4188
  }
4033
4189
  return true;
4034
4190
  }).map((p) => p.getText()).join(", ");
4035
4191
  return `{ ${props} }`;
4036
4192
  };
4037
- var extractPropertyFromObject = (obj, propName) => {
4038
- const prop = obj.getProperties().find((p) => {
4039
- if (p.getKind() === SyntaxKind.PropertyAssignment) {
4040
- return p.getName() === propName;
4041
- }
4042
- return false;
4043
- });
4044
- if (prop && prop.getKind() === SyntaxKind.PropertyAssignment) {
4045
- const propAssign = prop;
4046
- return propAssign.getInitializer()?.getText();
4047
- }
4048
- return void 0;
4049
- };
4193
+ var toKebabCase = (str) => str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
4050
4194
  var extractDepsKeys = (obj) => {
4051
- const depsProp = obj.getProperties().find((p) => {
4052
- if (p.getKind() === SyntaxKind.PropertyAssignment) {
4053
- return p.getName() === "deps";
4054
- }
4055
- return false;
4056
- });
4057
- if (!depsProp || depsProp.getKind() !== SyntaxKind.PropertyAssignment) return [];
4058
- let init = depsProp.getInitializer();
4195
+ let init = getProp(obj, "deps");
4059
4196
  if (!init) return [];
4060
4197
  if (init.getKind() === SyntaxKind.ArrowFunction) {
4061
4198
  const body = init.getBody();
@@ -4063,9 +4200,8 @@ var extractDepsKeys = (obj) => {
4063
4200
  init = body.getExpression();
4064
4201
  }
4065
4202
  }
4066
- if (!init || init.getKind() !== SyntaxKind.ObjectLiteralExpression) return [];
4067
- const depsObj = init;
4068
- return depsObj.getProperties().map((p) => {
4203
+ if (init.getKind() !== SyntaxKind.ObjectLiteralExpression) return [];
4204
+ return init.getProperties().map((p) => {
4069
4205
  if (p.getKind() === SyntaxKind.ShorthandPropertyAssignment) {
4070
4206
  return p.asKindOrThrow(SyntaxKind.ShorthandPropertyAssignment).getName();
4071
4207
  }
@@ -4075,15 +4211,17 @@ var extractDepsKeys = (obj) => {
4075
4211
  return "";
4076
4212
  }).filter(Boolean);
4077
4213
  };
4078
- var extractParamEntries = (obj) => {
4079
- const configProp = obj.getProperties().find((p) => {
4080
- if (p.getKind() === SyntaxKind.PropertyAssignment) {
4081
- return p.getName() === "config";
4082
- }
4083
- return false;
4084
- });
4085
- if (!configProp || configProp.getKind() !== SyntaxKind.PropertyAssignment) return [];
4086
- const init = configProp.getInitializer();
4214
+ var parseGenerateSpec = (text) => {
4215
+ if (!text) return void 0;
4216
+ const hexMatch = text.match(/generateHex\((\d+)\)/);
4217
+ if (hexMatch) return { type: "hex", bytes: Number(hexMatch[1]) };
4218
+ const base64Match = text.match(/generateBase64\((\d+)\)/);
4219
+ if (base64Match) return { type: "base64", bytes: Number(base64Match[1]) };
4220
+ if (text.includes("generateUuid")) return { type: "uuid" };
4221
+ return void 0;
4222
+ };
4223
+ var extractSecretEntries = (obj) => {
4224
+ const init = getProp(obj, "config");
4087
4225
  if (!init || init.getKind() !== SyntaxKind.ObjectLiteralExpression) return [];
4088
4226
  const configObj = init;
4089
4227
  const entries = [];
@@ -4094,47 +4232,46 @@ var extractParamEntries = (obj) => {
4094
4232
  const propInit = propAssign.getInitializer();
4095
4233
  if (!propInit) continue;
4096
4234
  if (propInit.getKind() === SyntaxKind.StringLiteral) {
4097
- const ssmKey = propInit.asKindOrThrow(SyntaxKind.StringLiteral).getLiteralValue();
4098
- entries.push({ propName, ssmKey });
4235
+ entries.push({ propName, ssmKey: propInit.asKindOrThrow(SyntaxKind.StringLiteral).getLiteralValue() });
4099
4236
  continue;
4100
4237
  }
4101
4238
  if (propInit.getKind() !== SyntaxKind.CallExpression) continue;
4102
4239
  const callExpr = propInit;
4103
- const callArgs = callExpr.getArguments();
4104
- if (callArgs.length === 0) continue;
4105
- const firstArg = callArgs[0];
4106
- if (firstArg.getKind() === SyntaxKind.StringLiteral) {
4107
- const ssmKey = firstArg.asKindOrThrow(SyntaxKind.StringLiteral).getLiteralValue();
4108
- entries.push({ propName, ssmKey });
4240
+ const fnName = bareName(callExpr.getExpression().getText());
4241
+ if (fnName === "secret") {
4242
+ const callArgs = callExpr.getArguments();
4243
+ if (callArgs.length === 0) {
4244
+ entries.push({ propName, ssmKey: toKebabCase(propName) });
4245
+ continue;
4246
+ }
4247
+ const firstArg = callArgs[0];
4248
+ if (firstArg.getKind() === SyntaxKind.ObjectLiteralExpression) {
4249
+ const optObj = firstArg;
4250
+ const keyText = getProp(optObj, "key")?.getText();
4251
+ const ssmKey = keyText ? keyText.replace(/^["']|["']$/g, "") : toKebabCase(propName);
4252
+ const generate = parseGenerateSpec(getProp(optObj, "generate")?.getText());
4253
+ entries.push({ propName, ssmKey, ...generate ? { generate } : {} });
4254
+ }
4255
+ continue;
4256
+ }
4257
+ if (fnName === "param") {
4258
+ const firstArg = callExpr.getArguments()[0];
4259
+ if (firstArg?.getKind() === SyntaxKind.StringLiteral) {
4260
+ entries.push({ propName, ssmKey: firstArg.asKindOrThrow(SyntaxKind.StringLiteral).getLiteralValue() });
4261
+ }
4109
4262
  }
4110
4263
  }
4111
4264
  return entries;
4112
4265
  };
4113
4266
  var extractStaticGlobs = (obj) => {
4114
- const staticProp = obj.getProperties().find((p) => {
4115
- if (p.getKind() === SyntaxKind.PropertyAssignment) {
4116
- return p.getName() === "static";
4117
- }
4118
- return false;
4119
- });
4120
- if (!staticProp || staticProp.getKind() !== SyntaxKind.PropertyAssignment) return [];
4121
- const init = staticProp.getInitializer();
4267
+ const init = getProp(obj, "static");
4122
4268
  if (!init || init.getKind() !== SyntaxKind.ArrayLiteralExpression) return [];
4123
- const arrayLiteral = init;
4124
- return arrayLiteral.getElements().filter((e) => e.getKind() === SyntaxKind.StringLiteral).map((e) => e.asKindOrThrow(SyntaxKind.StringLiteral).getLiteralValue());
4269
+ return init.asKindOrThrow(SyntaxKind.ArrayLiteralExpression).getElements().filter((e) => e.getKind() === SyntaxKind.StringLiteral).map((e) => e.asKindOrThrow(SyntaxKind.StringLiteral).getLiteralValue());
4125
4270
  };
4126
4271
  var extractRoutePatterns = (obj) => {
4127
- const routesProp = obj.getProperties().find((p) => {
4128
- if (p.getKind() === SyntaxKind.PropertyAssignment) {
4129
- return p.getName() === "routes";
4130
- }
4131
- return false;
4132
- });
4133
- if (!routesProp || routesProp.getKind() !== SyntaxKind.PropertyAssignment) return [];
4134
- const init = routesProp.getInitializer();
4272
+ const init = getProp(obj, "routes");
4135
4273
  if (!init || init.getKind() !== SyntaxKind.ObjectLiteralExpression) return [];
4136
- const routesObj = init;
4137
- return routesObj.getProperties().map((p) => {
4274
+ return init.getProperties().map((p) => {
4138
4275
  if (p.getKind() !== SyntaxKind.PropertyAssignment) return "";
4139
4276
  const nameNode = p.getNameNode();
4140
4277
  if (nameNode.getKind() === SyntaxKind.StringLiteral) {
@@ -4143,6 +4280,30 @@ var extractRoutePatterns = (obj) => {
4143
4280
  return nameNode.getText();
4144
4281
  }).filter(Boolean);
4145
4282
  };
4283
+ var extractAuthConfigFromCall = (callExpr) => {
4284
+ if (bareName(callExpr.getExpression().getText()) !== "defineAuth") return void 0;
4285
+ const firstArg = callExpr.getArguments()[0];
4286
+ if (!firstArg || firstArg.getKind() !== SyntaxKind.ObjectLiteralExpression) return void 0;
4287
+ try {
4288
+ return new Function(`return ${firstArg.getText()}`)();
4289
+ } catch {
4290
+ return void 0;
4291
+ }
4292
+ };
4293
+ var extractAuthConfig = (obj, sourceFile) => {
4294
+ const init = getProp(obj, "auth");
4295
+ if (!init) return void 0;
4296
+ if (init.getKind() === SyntaxKind.CallExpression) {
4297
+ return extractAuthConfigFromCall(init);
4298
+ }
4299
+ if (init.getKind() === SyntaxKind.Identifier) {
4300
+ const varInit = sourceFile.getVariableDeclaration(init.getText())?.getInitializer();
4301
+ if (varInit?.getKind() === SyntaxKind.CallExpression) {
4302
+ return extractAuthConfigFromCall(varInit);
4303
+ }
4304
+ }
4305
+ return void 0;
4306
+ };
4146
4307
  var handlerRegistry = {
4147
4308
  table: {
4148
4309
  defineFn: "defineTable",
@@ -4190,53 +4351,56 @@ var handlerRegistry = {
4190
4351
  var extractHandlerConfigs = (source, type) => {
4191
4352
  const { defineFn, handlerProps } = handlerRegistry[type];
4192
4353
  const sourceFile = parseSource(source);
4193
- const results = [];
4194
- const exportDefault = sourceFile.getExportAssignment((e) => !e.isExportEquals());
4195
- if (exportDefault) {
4196
- const expr = exportDefault.getExpression();
4197
- if (expr.getKind() === SyntaxKind.CallExpression) {
4198
- const callExpr = expr.asKindOrThrow(SyntaxKind.CallExpression);
4199
- if (callExpr.getExpression().getText() === defineFn) {
4200
- const args = callExpr.getArguments();
4201
- const firstArg = args[0];
4202
- if (firstArg && firstArg.getKind() === SyntaxKind.ObjectLiteralExpression) {
4203
- const objLiteral = firstArg;
4204
- const configText = buildConfigWithoutRuntime(objLiteral);
4205
- const config = evalConfig(configText, "default");
4206
- const hasHandler = handlerProps.some((p) => extractPropertyFromObject(objLiteral, p) !== void 0);
4207
- const depsKeys = extractDepsKeys(objLiteral);
4208
- const paramEntries = extractParamEntries(objLiteral);
4209
- const staticGlobs = extractStaticGlobs(objLiteral);
4210
- const routePatterns = extractRoutePatterns(objLiteral);
4211
- results.push({ exportName: "default", config, hasHandler, depsKeys, paramEntries, staticGlobs, routePatterns });
4212
- }
4213
- }
4354
+ return findDefineCalls(sourceFile, defineFn).map(({ exportName, args }) => {
4355
+ const config = evalConfig(buildConfigWithoutRuntime(args), exportName);
4356
+ const hasHandler = handlerProps.some((p) => getProp(args, p) !== void 0);
4357
+ const authCfg = extractAuthConfig(args, sourceFile);
4358
+ return {
4359
+ exportName,
4360
+ config,
4361
+ hasHandler,
4362
+ depsKeys: extractDepsKeys(args),
4363
+ secretEntries: extractSecretEntries(args),
4364
+ staticGlobs: extractStaticGlobs(args),
4365
+ routePatterns: extractRoutePatterns(args),
4366
+ ...authCfg ? { authConfig: authCfg } : {}
4367
+ };
4368
+ });
4369
+ };
4370
+ var generateMiddlewareEntryPoint = (source, runtimeDir2) => {
4371
+ const sourceFile = parseSource(source);
4372
+ const calls = findDefineCalls(sourceFile, handlerRegistry.staticSite.defineFn);
4373
+ let middlewareFnText;
4374
+ let exportName;
4375
+ for (const call of calls) {
4376
+ const mw = getProp(call.args, "middleware")?.getText();
4377
+ if (mw) {
4378
+ middlewareFnText = mw;
4379
+ exportName = call.exportName;
4380
+ break;
4214
4381
  }
4215
4382
  }
4216
- sourceFile.getVariableStatements().forEach((stmt) => {
4217
- if (!stmt.isExported()) return;
4218
- stmt.getDeclarations().forEach((decl) => {
4219
- const initializer = decl.getInitializer();
4220
- if (!initializer || initializer.getKind() !== SyntaxKind.CallExpression) return;
4221
- const callExpr = initializer.asKindOrThrow(SyntaxKind.CallExpression);
4222
- if (callExpr.getExpression().getText() !== defineFn) return;
4223
- const args = callExpr.getArguments();
4224
- const firstArg = args[0];
4225
- if (firstArg && firstArg.getKind() === SyntaxKind.ObjectLiteralExpression) {
4226
- const objLiteral = firstArg;
4227
- const configText = buildConfigWithoutRuntime(objLiteral);
4228
- const exportName = decl.getName();
4229
- const config = evalConfig(configText, exportName);
4230
- const hasHandler = handlerProps.some((p) => extractPropertyFromObject(objLiteral, p) !== void 0);
4231
- const depsKeys = extractDepsKeys(objLiteral);
4232
- const paramEntries = extractParamEntries(objLiteral);
4233
- const staticGlobs = extractStaticGlobs(objLiteral);
4234
- const routePatterns = extractRoutePatterns(objLiteral);
4235
- results.push({ exportName, config, hasHandler, depsKeys, paramEntries, staticGlobs, routePatterns });
4236
- }
4237
- });
4238
- });
4239
- return results;
4383
+ if (!middlewareFnText || !exportName) {
4384
+ throw new Error("Could not extract middleware function from source");
4385
+ }
4386
+ const imports = sourceFile.getImportDeclarations().filter((d) => {
4387
+ const defaultImport = d.getDefaultImport()?.getText();
4388
+ if (defaultImport && middlewareFnText.includes(defaultImport)) return true;
4389
+ for (const spec of d.getNamedImports()) {
4390
+ const alias = spec.getAliasNode()?.getText() ?? spec.getName();
4391
+ if (middlewareFnText.includes(alias)) return true;
4392
+ }
4393
+ const ns = d.getNamespaceImport()?.getText();
4394
+ if (ns && middlewareFnText.includes(ns)) return true;
4395
+ return false;
4396
+ }).map((d) => d.getText()).join("\n");
4397
+ const wrapperPath = runtimeDir2 ? handlerRegistry.staticSite.wrapperPath.replace("~/runtime", runtimeDir2) : handlerRegistry.staticSite.wrapperPath;
4398
+ const entryPoint = `${imports}
4399
+ import { wrapMiddlewareFn } from "${wrapperPath}";
4400
+ const __middleware = ${middlewareFnText};
4401
+ export const handler = wrapMiddlewareFn(__middleware);
4402
+ `;
4403
+ return { entryPoint, exportName };
4240
4404
  };
4241
4405
  var generateEntryPoint = (sourcePath, exportName, type, runtimeDir2) => {
4242
4406
  const { wrapperFn, wrapperPath } = handlerRegistry[type];
@@ -4250,6 +4414,7 @@ export const handler = ${wrapperFn}(${importName});
4250
4414
  };
4251
4415
 
4252
4416
  // src/build/bundle.ts
4417
+ var AUTH_COOKIE_NAME = "__eff_session";
4253
4418
  var extractTableConfigs = (source) => extractHandlerConfigs(source, "table");
4254
4419
  var extractAppConfigs = (source) => extractHandlerConfigs(source, "app");
4255
4420
  var extractStaticSiteConfigs = (source) => extractHandlerConfigs(source, "staticSite");
@@ -4266,7 +4431,8 @@ var bundle = (input) => Effect24.gen(function* () {
4266
4431
  const sourcePath = path3.isAbsolute(input.file) ? input.file : `./${input.file}`;
4267
4432
  const entryPoint = generateEntryPoint(sourcePath, exportName, type, runtimeDir);
4268
4433
  const awsExternals = ["@aws-sdk/*", "@smithy/*"];
4269
- const allExternals = [.../* @__PURE__ */ new Set([...awsExternals, ...externals])];
4434
+ const nodeExternals = builtinModules.flatMap((m) => [m, `node:${m}`]);
4435
+ const allExternals = [.../* @__PURE__ */ new Set([...awsExternals, ...nodeExternals, ...externals])];
4270
4436
  const result = yield* Effect24.tryPromise({
4271
4437
  try: () => esbuild.build({
4272
4438
  stdin: {
@@ -4281,7 +4447,8 @@ var bundle = (input) => Effect24.gen(function* () {
4281
4447
  minify: false,
4282
4448
  sourcemap: false,
4283
4449
  format: input.format ?? "esm",
4284
- external: allExternals
4450
+ external: allExternals,
4451
+ metafile: true
4285
4452
  }),
4286
4453
  catch: (error) => new Error(`esbuild failed: ${error}`)
4287
4454
  });
@@ -4289,6 +4456,160 @@ var bundle = (input) => Effect24.gen(function* () {
4289
4456
  if (!output) {
4290
4457
  throw new Error("esbuild produced no output");
4291
4458
  }
4459
+ const bundleResult = { code: output.text };
4460
+ if (result.metafile) {
4461
+ bundleResult.topModules = analyzeMetafile(result.metafile);
4462
+ }
4463
+ return bundleResult;
4464
+ });
4465
+ var analyzeMetafile = (metafile) => {
4466
+ const packageSizes = /* @__PURE__ */ new Map();
4467
+ for (const [filePath, info] of Object.entries(metafile.inputs)) {
4468
+ const nodeModIdx = filePath.lastIndexOf("node_modules/");
4469
+ let key;
4470
+ if (nodeModIdx !== -1) {
4471
+ const afterNm = filePath.slice(nodeModIdx + "node_modules/".length);
4472
+ if (afterNm.startsWith("@")) {
4473
+ const parts = afterNm.split("/");
4474
+ key = `${parts[0]}/${parts[1]}`;
4475
+ } else {
4476
+ key = afterNm.split("/")[0];
4477
+ }
4478
+ } else {
4479
+ key = "<project>";
4480
+ }
4481
+ packageSizes.set(key, (packageSizes.get(key) ?? 0) + info.bytes);
4482
+ }
4483
+ return Array.from(packageSizes.entries()).map(([p, bytes]) => ({ path: p, bytes })).sort((a, b) => b.bytes - a.bytes);
4484
+ };
4485
+ var bundleMiddleware = (input) => Effect24.gen(function* () {
4486
+ const absFile = path3.isAbsolute(input.file) ? input.file : path3.resolve(input.projectDir, input.file);
4487
+ const source = fsSync2.readFileSync(absFile, "utf-8");
4488
+ const sourceDir = path3.dirname(absFile);
4489
+ const { entryPoint } = generateMiddlewareEntryPoint(source, runtimeDir);
4490
+ const awsExternals = ["@aws-sdk/*", "@smithy/*"];
4491
+ const result = yield* Effect24.tryPromise({
4492
+ try: () => esbuild.build({
4493
+ stdin: {
4494
+ contents: entryPoint,
4495
+ loader: "ts",
4496
+ resolveDir: sourceDir
4497
+ },
4498
+ bundle: true,
4499
+ platform: "node",
4500
+ target: "node22",
4501
+ write: false,
4502
+ minify: false,
4503
+ sourcemap: false,
4504
+ format: "esm",
4505
+ external: awsExternals
4506
+ }),
4507
+ catch: (error) => new Error(`esbuild failed (middleware): ${error}`)
4508
+ });
4509
+ const output = result.outputFiles?.[0];
4510
+ if (!output) {
4511
+ throw new Error("esbuild produced no output for middleware");
4512
+ }
4513
+ return output.text;
4514
+ });
4515
+ var bundleAuthMiddleware = (input) => Effect24.gen(function* () {
4516
+ const { authConfig, secret } = input;
4517
+ const loginPath = authConfig.loginPath;
4518
+ const publicPatterns = authConfig.public ?? [];
4519
+ const entryPoint = `
4520
+ import { createHmac } from "crypto";
4521
+
4522
+ const SECRET = ${JSON.stringify(secret)};
4523
+ const LOGIN_PATH = ${JSON.stringify(loginPath)};
4524
+ const PUBLIC = ${JSON.stringify(publicPatterns)};
4525
+ const COOKIE = ${JSON.stringify(AUTH_COOKIE_NAME)};
4526
+
4527
+ const isPublic = (uri) => {
4528
+ for (const p of PUBLIC) {
4529
+ if (p.endsWith("/*")) {
4530
+ if (uri.startsWith(p.slice(0, -1))) return true;
4531
+ } else if (p.endsWith("*")) {
4532
+ if (uri.startsWith(p.slice(0, -1))) return true;
4533
+ } else {
4534
+ if (uri === p) return true;
4535
+ }
4536
+ }
4537
+ return false;
4538
+ };
4539
+
4540
+ const verify = (cookie) => {
4541
+ if (!cookie) return false;
4542
+ const dot = cookie.indexOf(".");
4543
+ if (dot === -1) return false;
4544
+ const payload = cookie.slice(0, dot);
4545
+ const sig = cookie.slice(dot + 1);
4546
+ const expected = createHmac("sha256", SECRET).update(payload).digest("base64url");
4547
+ if (sig !== expected) return false;
4548
+ try {
4549
+ const data = JSON.parse(Buffer.from(payload, "base64url").toString("utf-8"));
4550
+ return data.exp > Math.floor(Date.now() / 1000);
4551
+ } catch { return false; }
4552
+ };
4553
+
4554
+ const parseCookies = (headers) => {
4555
+ const cookies = {};
4556
+ const cookieHeaders = headers.cookie;
4557
+ if (!cookieHeaders) return cookies;
4558
+ for (const { value } of cookieHeaders) {
4559
+ for (const pair of value.split(";")) {
4560
+ const eq = pair.indexOf("=");
4561
+ if (eq === -1) continue;
4562
+ const name = pair.slice(0, eq).trim();
4563
+ const val = pair.slice(eq + 1).trim();
4564
+ if (name) cookies[name] = val;
4565
+ }
4566
+ }
4567
+ return cookies;
4568
+ };
4569
+
4570
+ const rewrite = (uri) => {
4571
+ if (uri.endsWith("/")) return uri + "index.html";
4572
+ if (!uri.includes(".")) return uri + "/index.html";
4573
+ return uri;
4574
+ };
4575
+
4576
+ export const handler = async (event) => {
4577
+ const req = event.Records[0].cf.request;
4578
+
4579
+ if (isPublic(req.uri)) {
4580
+ req.uri = rewrite(req.uri);
4581
+ return req;
4582
+ }
4583
+
4584
+ const cookies = parseCookies(req.headers);
4585
+ if (verify(cookies[COOKIE])) {
4586
+ req.uri = rewrite(req.uri);
4587
+ return req;
4588
+ }
4589
+
4590
+ return {
4591
+ status: "302",
4592
+ statusDescription: "Found",
4593
+ headers: { location: [{ key: "Location", value: LOGIN_PATH }] },
4594
+ };
4595
+ };
4596
+ `;
4597
+ const result = yield* Effect24.tryPromise({
4598
+ try: () => esbuild.build({
4599
+ stdin: { contents: entryPoint, loader: "js", resolveDir: process.cwd() },
4600
+ bundle: true,
4601
+ platform: "node",
4602
+ target: "node22",
4603
+ write: false,
4604
+ minify: true,
4605
+ sourcemap: false,
4606
+ format: "esm",
4607
+ external: ["crypto"]
4608
+ }),
4609
+ catch: (error) => new Error(`esbuild failed (auth middleware): ${error}`)
4610
+ });
4611
+ const output = result.outputFiles?.[0];
4612
+ if (!output) throw new Error("esbuild produced no output for auth middleware");
4292
4613
  return output.text;
4293
4614
  });
4294
4615
  var FIXED_DATE2 = /* @__PURE__ */ new Date(0);
@@ -4390,21 +4711,25 @@ var flattenHandlers = (discovered) => {
4390
4711
  ];
4391
4712
  };
4392
4713
 
4714
+ // src/deploy/deploy.ts
4715
+ import * as crypto5 from "crypto";
4716
+
4393
4717
  // src/deploy/resolve-config.ts
4394
4718
  import { Effect as Effect25 } from "effect";
4395
- var collectRequiredParams = (handlers, project, stage) => {
4719
+ var collectRequiredSecrets = (handlers, project, stage) => {
4396
4720
  const seen = /* @__PURE__ */ new Map();
4397
4721
  const collect = (handlerGroups) => {
4398
4722
  for (const { exports } of handlerGroups) {
4399
4723
  for (const fn13 of exports) {
4400
- for (const { propName, ssmKey } of fn13.paramEntries) {
4724
+ for (const { propName, ssmKey, generate } of fn13.secretEntries) {
4401
4725
  const ssmPath = `/${project}/${stage}/${ssmKey}`;
4402
4726
  if (!seen.has(ssmPath)) {
4403
4727
  seen.set(ssmPath, {
4404
4728
  ssmPath,
4405
4729
  propName,
4406
4730
  ssmKey,
4407
- handlerName: fn13.exportName
4731
+ handlerName: fn13.exportName,
4732
+ ...generate ? { generate } : {}
4408
4733
  });
4409
4734
  }
4410
4735
  }
@@ -4417,11 +4742,34 @@ var collectRequiredParams = (handlers, project, stage) => {
4417
4742
  collect(handlers.apiHandlers);
4418
4743
  return Array.from(seen.values());
4419
4744
  };
4420
- var checkMissingParams = (params) => Effect25.gen(function* () {
4421
- if (params.length === 0) return { existing: [], missing: [] };
4745
+ var AUTH_SSM_KEY = "_auth-secret";
4746
+ var collectAuthSecret = (handlers, project, stage) => {
4747
+ const hasAuth = (groups) => groups.some((g) => g.exports.some((fn13) => fn13.authConfig));
4748
+ if (hasAuth(handlers.apiHandlers) || hasAuth(handlers.staticSiteHandlers)) {
4749
+ return {
4750
+ ssmPath: `/${project}/${stage}/${AUTH_SSM_KEY}`,
4751
+ propName: "_authSecret",
4752
+ ssmKey: AUTH_SSM_KEY,
4753
+ handlerName: "_auth",
4754
+ generate: { type: "hex", bytes: 32 }
4755
+ };
4756
+ }
4757
+ return void 0;
4758
+ };
4759
+ var fetchAuthSecretValue = (ssmPath) => Effect25.gen(function* () {
4760
+ const result = yield* ssm_exports.make("get_parameters", {
4761
+ Names: [ssmPath],
4762
+ WithDecryption: true
4763
+ });
4764
+ const value = result.Parameters?.[0]?.Value;
4765
+ if (!value) throw new Error(`Auth secret not found at ${ssmPath}`);
4766
+ return value;
4767
+ });
4768
+ var checkMissingSecrets = (secrets) => Effect25.gen(function* () {
4769
+ if (secrets.length === 0) return { existing: [], missing: [] };
4422
4770
  const existingNames = /* @__PURE__ */ new Set();
4423
- for (let i = 0; i < params.length; i += 10) {
4424
- const batch = params.slice(i, i + 10);
4771
+ for (let i = 0; i < secrets.length; i += 10) {
4772
+ const batch = secrets.slice(i, i + 10);
4425
4773
  const result = yield* ssm_exports.make("get_parameters", {
4426
4774
  Names: batch.map((p) => p.ssmPath),
4427
4775
  WithDecryption: false
@@ -4432,7 +4780,7 @@ var checkMissingParams = (params) => Effect25.gen(function* () {
4432
4780
  }
4433
4781
  const existing = [];
4434
4782
  const missing = [];
4435
- for (const p of params) {
4783
+ for (const p of secrets) {
4436
4784
  if (existingNames.has(p.ssmPath)) {
4437
4785
  existing.push(p);
4438
4786
  } else {
@@ -4450,6 +4798,12 @@ import { toSeconds } from "effortless-aws";
4450
4798
  import { Effect as Effect26 } from "effect";
4451
4799
  import * as fs3 from "fs/promises";
4452
4800
  import * as path4 from "path";
4801
+ var formatBytes = (bytes) => {
4802
+ if (bytes < 1024) return `${bytes}B`;
4803
+ const kb = bytes / 1024;
4804
+ if (kb < 1024) return `${kb.toFixed(1)}KB`;
4805
+ return `${(kb / 1024).toFixed(2)}MB`;
4806
+ };
4453
4807
  var readSource = (input) => Effect26.gen(function* () {
4454
4808
  if ("code" in input && typeof input.code === "string") {
4455
4809
  return input.code;
@@ -4462,10 +4816,11 @@ var ensureLayerAndExternal = (input) => Effect26.gen(function* () {
4462
4816
  project: input.project,
4463
4817
  stage: input.stage,
4464
4818
  region: input.region,
4465
- projectDir: input.packageDir
4819
+ projectDir: input.packageDir,
4820
+ extraNodeModules: input.extraNodeModules
4466
4821
  });
4467
4822
  const prodDeps = layerResult ? yield* readProductionDependencies(input.packageDir) : [];
4468
- const { packages: external, warnings: layerWarnings } = prodDeps.length > 0 ? yield* Effect26.sync(() => collectLayerPackages(input.packageDir, prodDeps)) : { packages: [], warnings: [] };
4823
+ const { packages: external, warnings: layerWarnings } = prodDeps.length > 0 ? yield* Effect26.sync(() => collectLayerPackages(input.packageDir, prodDeps, input.extraNodeModules)) : { packages: [], warnings: [] };
4469
4824
  for (const warning of layerWarnings) {
4470
4825
  yield* Effect26.logWarning(`[layer] ${warning}`);
4471
4826
  }
@@ -4510,14 +4865,21 @@ var deployCoreLambda = ({
4510
4865
  mergedPermissions.length > 0 ? mergedPermissions : void 0,
4511
4866
  makeTags(tagCtx, "iam-role")
4512
4867
  );
4513
- const bundled = yield* bundle({
4868
+ const bundleResult = yield* bundle({
4514
4869
  ...input,
4515
4870
  exportName,
4516
4871
  ...bundleType ? { type: bundleType } : {},
4517
4872
  ...external && external.length > 0 ? { external } : {}
4518
4873
  });
4519
4874
  const staticFiles = staticGlobs && staticGlobs.length > 0 ? resolveStaticFiles(staticGlobs, input.projectDir) : void 0;
4520
- const code = yield* zip({ content: bundled, staticFiles });
4875
+ const bundleSize = Buffer.byteLength(bundleResult.code, "utf-8");
4876
+ if (bundleResult.topModules && bundleSize > 500 * 1024) {
4877
+ const top = bundleResult.topModules.slice(0, 10);
4878
+ const lines = top.map((m) => ` ${formatBytes(m.bytes).padStart(8)} ${m.path}`).join("\n");
4879
+ yield* Effect26.logWarning(`Bundle "${handlerName}" is ${formatBytes(bundleSize)} \u2014 top modules:
4880
+ ${lines}`);
4881
+ }
4882
+ const code = yield* zip({ content: bundleResult.code, staticFiles });
4521
4883
  const environment = {
4522
4884
  EFF_PROJECT: input.project,
4523
4885
  EFF_STAGE: tagCtx.stage,
@@ -4537,7 +4899,7 @@ var deployCoreLambda = ({
4537
4899
  ...layerArn ? { layers: [layerArn] } : {},
4538
4900
  environment
4539
4901
  });
4540
- return { functionArn, status, tagCtx };
4902
+ return { functionArn, status, tagCtx, bundleSize };
4541
4903
  });
4542
4904
 
4543
4905
  // src/deploy/deploy-table.ts
@@ -4559,7 +4921,7 @@ var deployTableFunction = ({ input, fn: fn13, layerArn, external, depsEnv, depsP
4559
4921
  tags: makeTags(tagCtx, "dynamodb")
4560
4922
  });
4561
4923
  const selfEnv = { EFF_DEP_SELF: `table:${tableName}`, ...depsEnv };
4562
- const { functionArn, status } = yield* deployCoreLambda({
4924
+ const { functionArn, status, bundleSize } = yield* deployCoreLambda({
4563
4925
  input,
4564
4926
  exportName,
4565
4927
  handlerName,
@@ -4587,6 +4949,7 @@ var deployTableFunction = ({ input, fn: fn13, layerArn, external, depsEnv, depsP
4587
4949
  exportName,
4588
4950
  functionArn,
4589
4951
  status,
4952
+ bundleSize,
4590
4953
  tableArn,
4591
4954
  streamArn
4592
4955
  };
@@ -4603,7 +4966,8 @@ var deployTable = (input) => Effect27.gen(function* () {
4603
4966
  project: input.project,
4604
4967
  stage: resolveStage(input.stage),
4605
4968
  region: input.region,
4606
- packageDir: input.packageDir ?? input.projectDir
4969
+ packageDir: input.packageDir ?? input.projectDir,
4970
+ extraNodeModules: input.extraNodeModules
4607
4971
  });
4608
4972
  const result = yield* deployTableFunction({
4609
4973
  input,
@@ -4632,7 +4996,8 @@ var deployAllTables = (input) => Effect27.gen(function* () {
4632
4996
  project: input.project,
4633
4997
  stage: resolveStage(input.stage),
4634
4998
  region: input.region,
4635
- packageDir: input.packageDir ?? input.projectDir
4999
+ packageDir: input.packageDir ?? input.projectDir,
5000
+ extraNodeModules: input.extraNodeModules
4636
5001
  });
4637
5002
  const results = yield* Effect27.forEach(
4638
5003
  functions,
@@ -4661,7 +5026,7 @@ import { toSeconds as toSeconds2 } from "effortless-aws";
4661
5026
  var deployApiFunction = ({ input, fn: fn13, layerArn, external, depsEnv, depsPermissions, staticGlobs }) => Effect28.gen(function* () {
4662
5027
  const { exportName, config } = fn13;
4663
5028
  const handlerName = exportName;
4664
- const { functionArn, status } = yield* deployCoreLambda({
5029
+ const { functionArn, status, bundleSize } = yield* deployCoreLambda({
4665
5030
  input,
4666
5031
  exportName,
4667
5032
  handlerName,
@@ -4675,7 +5040,7 @@ var deployApiFunction = ({ input, fn: fn13, layerArn, external, depsEnv, depsPer
4675
5040
  ...depsPermissions ? { depsPermissions } : {},
4676
5041
  ...staticGlobs && staticGlobs.length > 0 ? { staticGlobs } : {}
4677
5042
  });
4678
- return { exportName, functionArn, status, config, handlerName };
5043
+ return { exportName, functionArn, status, bundleSize, config, handlerName };
4679
5044
  });
4680
5045
  var deploy = (input) => Effect28.gen(function* () {
4681
5046
  const source = yield* readSource(input);
@@ -4696,7 +5061,8 @@ var deploy = (input) => Effect28.gen(function* () {
4696
5061
  project: input.project,
4697
5062
  stage: tagCtx.stage,
4698
5063
  region: input.region,
4699
- packageDir: input.packageDir ?? input.projectDir
5064
+ packageDir: input.packageDir ?? input.projectDir,
5065
+ extraNodeModules: input.extraNodeModules
4700
5066
  });
4701
5067
  const { functionArn } = yield* deployApiFunction({
4702
5068
  input,
@@ -5048,7 +5414,7 @@ var submitToGoogleIndexing = (input) => Effect30.gen(function* () {
5048
5414
 
5049
5415
  // src/deploy/deploy-static-site.ts
5050
5416
  var deployMiddlewareLambda = (input) => Effect31.gen(function* () {
5051
- const { projectDir, project, stage, handlerName, file, exportName, tagCtx } = input;
5417
+ const { projectDir, project, stage, handlerName, file, tagCtx } = input;
5052
5418
  const middlewareName = `${handlerName}-middleware`;
5053
5419
  yield* Effect31.logDebug(`Deploying middleware Lambda@Edge: ${middlewareName}`);
5054
5420
  const roleArn = yield* ensureEdgeRole(
@@ -5057,12 +5423,13 @@ var deployMiddlewareLambda = (input) => Effect31.gen(function* () {
5057
5423
  middlewareName,
5058
5424
  makeTags(tagCtx, "iam-role")
5059
5425
  );
5060
- const bundled = yield* bundle({
5061
- projectDir,
5062
- file,
5063
- exportName,
5064
- type: "staticSite"
5065
- });
5426
+ const bundled = yield* bundleMiddleware({ projectDir, file });
5427
+ const bundleSizeKB = Math.round(bundled.length / 1024);
5428
+ if (bundleSizeKB > 50) {
5429
+ yield* Effect31.logWarning(
5430
+ `[middleware] Bundle size is ${bundleSizeKB}KB (expected <50KB). Middleware may be pulling in unrelated dependencies via barrel imports. Use direct file imports instead (e.g. import { Auth } from "./core/auth" instead of "./core").`
5431
+ );
5432
+ }
5066
5433
  const code = yield* zip({ content: bundled });
5067
5434
  const { functionArn } = yield* ensureLambda({
5068
5435
  project,
@@ -5078,14 +5445,52 @@ var deployMiddlewareLambda = (input) => Effect31.gen(function* () {
5078
5445
  }).pipe(
5079
5446
  Effect31.provide(clients_exports.makeClients({ lambda: { region: "us-east-1" } }))
5080
5447
  );
5081
- const { versionArn } = yield* publishVersion(
5082
- `${project}-${stage}-${middlewareName}`
5083
- ).pipe(
5448
+ const edgeFunctionName = `${project}-${stage}-${middlewareName}`;
5449
+ yield* ensureEdgePermission(edgeFunctionName).pipe(
5450
+ Effect31.provide(clients_exports.makeClients({ lambda: { region: "us-east-1" } }))
5451
+ );
5452
+ const { versionArn } = yield* publishVersion(edgeFunctionName).pipe(
5084
5453
  Effect31.provide(clients_exports.makeClients({ lambda: { region: "us-east-1" } }))
5085
5454
  );
5086
5455
  yield* Effect31.logDebug(`Middleware deployed: ${versionArn}`);
5087
5456
  return { versionArn };
5088
5457
  });
5458
+ var deployAuthMiddlewareLambda = (input) => Effect31.gen(function* () {
5459
+ const { project, stage, handlerName, tagCtx, authConfig, authSecret } = input;
5460
+ const middlewareName = `${handlerName}-middleware`;
5461
+ yield* Effect31.logDebug(`Deploying auth middleware Lambda@Edge: ${middlewareName}`);
5462
+ const roleArn = yield* ensureEdgeRole(
5463
+ project,
5464
+ stage,
5465
+ middlewareName,
5466
+ makeTags(tagCtx, "iam-role")
5467
+ );
5468
+ const bundled = yield* bundleAuthMiddleware({ authConfig, secret: authSecret });
5469
+ const code = yield* zip({ content: bundled });
5470
+ const edgeFunctionName = `${project}-${stage}-${middlewareName}`;
5471
+ yield* ensureLambda({
5472
+ project,
5473
+ stage,
5474
+ name: middlewareName,
5475
+ region: "us-east-1",
5476
+ roleArn,
5477
+ code,
5478
+ memory: 128,
5479
+ timeout: 5,
5480
+ architecture: Architecture3.x86_64,
5481
+ tags: makeTags(tagCtx, "lambda")
5482
+ }).pipe(
5483
+ Effect31.provide(clients_exports.makeClients({ lambda: { region: "us-east-1" } }))
5484
+ );
5485
+ yield* ensureEdgePermission(edgeFunctionName).pipe(
5486
+ Effect31.provide(clients_exports.makeClients({ lambda: { region: "us-east-1" } }))
5487
+ );
5488
+ const { versionArn } = yield* publishVersion(edgeFunctionName).pipe(
5489
+ Effect31.provide(clients_exports.makeClients({ lambda: { region: "us-east-1" } }))
5490
+ );
5491
+ yield* Effect31.logDebug(`Auth middleware deployed: ${versionArn}`);
5492
+ return { versionArn };
5493
+ });
5089
5494
  var ERROR_PAGE_KEY = "_effortless/404.html";
5090
5495
  var generateErrorPageHtml = () => `<!DOCTYPE html>
5091
5496
  <html lang="en">
@@ -5190,14 +5595,26 @@ var deployStaticSite = (input) => Effect31.gen(function* () {
5190
5595
  const isSpa = config.spa ?? false;
5191
5596
  let urlRewriteFunctionArn;
5192
5597
  let lambdaEdgeArn;
5193
- if (hasMiddleware && input.file) {
5598
+ const hasAuth = !!(input.authSecret && input.authConfig);
5599
+ if (hasAuth) {
5600
+ const authResult = yield* deployAuthMiddlewareLambda({
5601
+ project,
5602
+ stage,
5603
+ handlerName,
5604
+ tagCtx,
5605
+ authConfig: input.authConfig,
5606
+ authSecret: input.authSecret
5607
+ }).pipe(
5608
+ Effect31.provide(clients_exports.makeClients({ iam: { region: "us-east-1" } }))
5609
+ );
5610
+ lambdaEdgeArn = authResult.versionArn;
5611
+ } else if (hasMiddleware && input.file) {
5194
5612
  const result = yield* deployMiddlewareLambda({
5195
5613
  projectDir,
5196
5614
  project,
5197
5615
  stage,
5198
5616
  handlerName,
5199
5617
  file: input.file,
5200
- exportName,
5201
5618
  tagCtx
5202
5619
  }).pipe(
5203
5620
  Effect31.provide(clients_exports.makeClients({ iam: { region: "us-east-1" } }))
@@ -5321,7 +5738,7 @@ var deployFifoQueueFunction = ({ input, fn: fn13, layerArn, external, depsEnv, d
5321
5738
  EFF_QUEUE_ARN: queueArn,
5322
5739
  ...depsEnv
5323
5740
  };
5324
- const { functionArn, status } = yield* deployCoreLambda({
5741
+ const { functionArn, status, bundleSize } = yield* deployCoreLambda({
5325
5742
  input,
5326
5743
  exportName,
5327
5744
  handlerName,
@@ -5348,6 +5765,7 @@ var deployFifoQueueFunction = ({ input, fn: fn13, layerArn, external, depsEnv, d
5348
5765
  exportName,
5349
5766
  functionArn,
5350
5767
  status,
5768
+ bundleSize,
5351
5769
  queueUrl,
5352
5770
  queueArn
5353
5771
  };
@@ -5382,7 +5800,7 @@ var deployBucketFunction = ({ input, fn: fn13, layerArn, external, depsEnv, deps
5382
5800
  };
5383
5801
  }
5384
5802
  const selfEnv = { EFF_DEP_SELF: `bucket:${bucketName}`, ...depsEnv };
5385
- const { functionArn, status } = yield* deployCoreLambda({
5803
+ const { functionArn, status, bundleSize } = yield* deployCoreLambda({
5386
5804
  input,
5387
5805
  exportName,
5388
5806
  handlerName,
@@ -5410,6 +5828,7 @@ var deployBucketFunction = ({ input, fn: fn13, layerArn, external, depsEnv, deps
5410
5828
  exportName,
5411
5829
  functionArn,
5412
5830
  status,
5831
+ bundleSize,
5413
5832
  bucketName,
5414
5833
  bucketArn
5415
5834
  };
@@ -5490,10 +5909,17 @@ var createLiveProgress = (manifest) => {
5490
5909
  const sec = ((Date.now() - startTime) / 1e3).toFixed(1);
5491
5910
  return c.dim(`${sec}s`);
5492
5911
  };
5493
- return (name, type, status) => Effect35.sync(() => {
5912
+ const formatSize = (bytes) => {
5913
+ if (bytes < 1024) return `${bytes}B`;
5914
+ const kb = bytes / 1024;
5915
+ if (kb < 1024) return `${kb.toFixed(0)}KB`;
5916
+ return `${(kb / 1024).toFixed(1)}MB`;
5917
+ };
5918
+ return (name, type, status, bundleSize) => Effect35.sync(() => {
5494
5919
  const key = `${name}:${type}`;
5495
5920
  results.set(key, status);
5496
- const line = ` ${name} ${c.dim(`(${type})`)} ${statusLabel(status)} ${formatDuration()}`;
5921
+ const sizeInfo = bundleSize ? ` ${c.dim(formatSize(bundleSize))}` : "";
5922
+ const line = ` ${name} ${c.dim(`(${type})`)} ${statusLabel(status)}${sizeInfo} ${formatDuration()}`;
5497
5923
  if (isTTY) {
5498
5924
  const idx = lineIndex.get(key) ?? 0;
5499
5925
  const up = manifest.length - idx;
@@ -5502,7 +5928,7 @@ var createLiveProgress = (manifest) => {
5502
5928
  clearInterval(timer);
5503
5929
  }
5504
5930
  } else {
5505
- process.stdout.write(` ${c.dim(`[${results.size}/${manifest.length}]`)} ${name} ${c.dim(`(${type})`)} ${statusLabel(status)} ${formatDuration()}
5931
+ process.stdout.write(` ${c.dim(`[${results.size}/${manifest.length}]`)} ${name} ${c.dim(`(${type})`)} ${statusLabel(status)}${sizeInfo} ${formatDuration()}
5506
5932
  `);
5507
5933
  }
5508
5934
  });
@@ -5513,7 +5939,8 @@ var prepareLayer = (input) => Effect35.gen(function* () {
5513
5939
  project: input.project,
5514
5940
  stage: input.stage,
5515
5941
  region: input.region,
5516
- projectDir: input.packageDir
5942
+ projectDir: input.packageDir,
5943
+ extraNodeModules: input.extraNodeModules
5517
5944
  }).pipe(
5518
5945
  Effect35.provide(
5519
5946
  clients_exports.makeClients({
@@ -5522,7 +5949,7 @@ var prepareLayer = (input) => Effect35.gen(function* () {
5522
5949
  )
5523
5950
  );
5524
5951
  const prodDeps = layerResult ? yield* readProductionDependencies(input.packageDir) : [];
5525
- const { packages: external, warnings: layerWarnings } = prodDeps.length > 0 ? yield* Effect35.sync(() => collectLayerPackages(input.packageDir, prodDeps)) : { packages: [], warnings: [] };
5952
+ const { packages: external, warnings: layerWarnings } = prodDeps.length > 0 ? yield* Effect35.sync(() => collectLayerPackages(input.packageDir, prodDeps, input.extraNodeModules)) : { packages: [], warnings: [] };
5526
5953
  for (const warning of layerWarnings) {
5527
5954
  yield* Effect35.logWarning(`[layer] ${warning}`);
5528
5955
  }
@@ -5668,10 +6095,20 @@ var SSM_PERMISSIONS = [
5668
6095
  "ssm:GetParameter",
5669
6096
  "ssm:GetParameters"
5670
6097
  ];
5671
- var resolveParams = (paramEntries, project, stage) => {
5672
- if (paramEntries.length === 0) return void 0;
6098
+ var executeGenerateSpec = (spec) => {
6099
+ switch (spec.type) {
6100
+ case "hex":
6101
+ return crypto5.randomBytes(spec.bytes).toString("hex");
6102
+ case "base64":
6103
+ return crypto5.randomBytes(spec.bytes).toString("base64url");
6104
+ case "uuid":
6105
+ return crypto5.randomUUID();
6106
+ }
6107
+ };
6108
+ var resolveSecrets = (secretEntries, project, stage) => {
6109
+ if (secretEntries.length === 0) return void 0;
5673
6110
  const paramsEnv = {};
5674
- for (const { propName, ssmKey } of paramEntries) {
6111
+ for (const { propName, ssmKey } of secretEntries) {
5675
6112
  paramsEnv[`EFF_PARAM_${propName}`] = `/${project}/${stage}/${ssmKey}`;
5676
6113
  }
5677
6114
  return { paramsEnv, paramsPermissions: SSM_PERMISSIONS };
@@ -5690,10 +6127,10 @@ var makeDeployInput = (ctx, file) => ({
5690
6127
  region: ctx.input.region,
5691
6128
  ...ctx.input.stage ? { stage: ctx.input.stage } : {}
5692
6129
  });
5693
- var resolveHandlerEnv = (depsKeys, paramEntries, ctx) => {
6130
+ var resolveHandlerEnv = (depsKeys, secretEntries, ctx) => {
5694
6131
  const resolved = mergeResolved(
5695
6132
  resolveDeps(depsKeys, ctx.tableNameMap, ctx.bucketNameMap, ctx.mailerDomainMap, ctx.queueNameMap),
5696
- resolveParams(paramEntries, ctx.input.project, ctx.stage)
6133
+ resolveSecrets(secretEntries, ctx.input.project, ctx.stage)
5697
6134
  );
5698
6135
  return {
5699
6136
  depsEnv: resolved?.depsEnv ?? {},
@@ -5707,7 +6144,7 @@ var buildTableTasks = (ctx, handlers, results) => {
5707
6144
  for (const fn13 of exports) {
5708
6145
  tasks.push(
5709
6146
  Effect35.gen(function* () {
5710
- const env = resolveHandlerEnv(fn13.depsKeys, fn13.paramEntries, ctx);
6147
+ const env = resolveHandlerEnv(fn13.depsKeys, fn13.secretEntries, ctx);
5711
6148
  const result = yield* deployTableFunction({
5712
6149
  input: makeDeployInput(ctx, file),
5713
6150
  fn: fn13,
@@ -5718,7 +6155,7 @@ var buildTableTasks = (ctx, handlers, results) => {
5718
6155
  ...fn13.staticGlobs.length > 0 ? { staticGlobs: fn13.staticGlobs } : {}
5719
6156
  }).pipe(Effect35.provide(clients_exports.makeClients({ lambda: { region }, iam: { region }, dynamodb: { region } })));
5720
6157
  results.push(result);
5721
- yield* ctx.logComplete(fn13.exportName, "table", result.status);
6158
+ yield* ctx.logComplete(fn13.exportName, "table", result.status, result.bundleSize);
5722
6159
  })
5723
6160
  );
5724
6161
  }
@@ -5763,6 +6200,12 @@ var buildStaticSiteTasks = (ctx, handlers, results, apiOriginDomain) => {
5763
6200
  for (const fn13 of exports) {
5764
6201
  tasks.push(
5765
6202
  Effect35.gen(function* () {
6203
+ let authSecretValue;
6204
+ if (fn13.authConfig && ctx.authSecretPath) {
6205
+ authSecretValue = yield* fetchAuthSecretValue(ctx.authSecretPath).pipe(
6206
+ Effect35.provide(clients_exports.makeClients({ ssm: { region: ctx.input.region } }))
6207
+ );
6208
+ }
5766
6209
  const result = yield* deployStaticSite({
5767
6210
  projectDir: ctx.input.projectDir,
5768
6211
  project: ctx.input.project,
@@ -5771,7 +6214,8 @@ var buildStaticSiteTasks = (ctx, handlers, results, apiOriginDomain) => {
5771
6214
  fn: fn13,
5772
6215
  verbose: ctx.input.verbose,
5773
6216
  ...fn13.hasHandler ? { file } : {},
5774
- ...apiOriginDomain ? { apiOriginDomain } : {}
6217
+ ...apiOriginDomain ? { apiOriginDomain } : {},
6218
+ ...authSecretValue && fn13.authConfig ? { authSecret: authSecretValue, authConfig: fn13.authConfig } : {}
5775
6219
  }).pipe(Effect35.provide(clients_exports.makeClients({
5776
6220
  s3: { region },
5777
6221
  cloudfront: { region: "us-east-1" },
@@ -5793,7 +6237,7 @@ var buildFifoQueueTasks = (ctx, handlers, results) => {
5793
6237
  for (const fn13 of exports) {
5794
6238
  tasks.push(
5795
6239
  Effect35.gen(function* () {
5796
- const env = resolveHandlerEnv(fn13.depsKeys, fn13.paramEntries, ctx);
6240
+ const env = resolveHandlerEnv(fn13.depsKeys, fn13.secretEntries, ctx);
5797
6241
  const result = yield* deployFifoQueueFunction({
5798
6242
  input: makeDeployInput(ctx, file),
5799
6243
  fn: fn13,
@@ -5804,7 +6248,7 @@ var buildFifoQueueTasks = (ctx, handlers, results) => {
5804
6248
  ...fn13.staticGlobs.length > 0 ? { staticGlobs: fn13.staticGlobs } : {}
5805
6249
  }).pipe(Effect35.provide(clients_exports.makeClients({ lambda: { region }, iam: { region }, sqs: { region } })));
5806
6250
  results.push(result);
5807
- yield* ctx.logComplete(fn13.exportName, "queue", result.status);
6251
+ yield* ctx.logComplete(fn13.exportName, "queue", result.status, result.bundleSize);
5808
6252
  })
5809
6253
  );
5810
6254
  }
@@ -5818,7 +6262,7 @@ var buildBucketTasks = (ctx, handlers, results) => {
5818
6262
  for (const fn13 of exports) {
5819
6263
  tasks.push(
5820
6264
  Effect35.gen(function* () {
5821
- const env = resolveHandlerEnv(fn13.depsKeys, fn13.paramEntries, ctx);
6265
+ const env = resolveHandlerEnv(fn13.depsKeys, fn13.secretEntries, ctx);
5822
6266
  const result = yield* deployBucketFunction({
5823
6267
  input: makeDeployInput(ctx, file),
5824
6268
  fn: fn13,
@@ -5830,7 +6274,7 @@ var buildBucketTasks = (ctx, handlers, results) => {
5830
6274
  }).pipe(Effect35.provide(clients_exports.makeClients({ lambda: { region }, iam: { region }, s3: { region } })));
5831
6275
  results.push(result);
5832
6276
  const status = result.status === "resource-only" ? "created" : result.status;
5833
- yield* ctx.logComplete(fn13.exportName, "bucket", status);
6277
+ yield* ctx.logComplete(fn13.exportName, "bucket", status, result.bundleSize);
5834
6278
  })
5835
6279
  );
5836
6280
  }
@@ -5865,8 +6309,12 @@ var buildApiTasks = (ctx, handlers, results) => {
5865
6309
  for (const fn13 of exports) {
5866
6310
  tasks.push(
5867
6311
  Effect35.gen(function* () {
5868
- const env = resolveHandlerEnv(fn13.depsKeys, fn13.paramEntries, ctx);
5869
- const { exportName, functionArn, status, handlerName } = yield* deployApiFunction({
6312
+ const env = resolveHandlerEnv(fn13.depsKeys, fn13.secretEntries, ctx);
6313
+ if (fn13.authConfig && ctx.authSecretPath) {
6314
+ env.depsEnv["EFF_AUTH_SECRET"] = ctx.authSecretPath;
6315
+ env.depsPermissions = [...env.depsPermissions, "ssm:GetParameter", "ssm:GetParameters"];
6316
+ }
6317
+ const { exportName, functionArn, status, bundleSize, handlerName } = yield* deployApiFunction({
5870
6318
  input: makeDeployInput(ctx, file),
5871
6319
  fn: fn13,
5872
6320
  ...ctx.layerArn ? { layerArn: ctx.layerArn } : {},
@@ -5883,7 +6331,7 @@ var buildApiTasks = (ctx, handlers, results) => {
5883
6331
  Effect35.provide(clients_exports.makeClients({ lambda: { region } }))
5884
6332
  );
5885
6333
  results.push({ exportName, url: functionUrl, functionArn });
5886
- yield* ctx.logComplete(exportName, "api", status);
6334
+ yield* ctx.logComplete(exportName, "api", status, bundleSize);
5887
6335
  })
5888
6336
  );
5889
6337
  }
@@ -5920,16 +6368,33 @@ var deployProject = (input) => Effect35.gen(function* () {
5920
6368
  yield* Console2.log(`
5921
6369
  ${c.dim("Handlers:")} ${parts.join(", ")}`);
5922
6370
  const discovered = { tableHandlers, appHandlers, staticSiteHandlers, fifoQueueHandlers, bucketHandlers, mailerHandlers, apiHandlers };
5923
- const requiredParams = collectRequiredParams(discovered, input.project, stage);
5924
- if (requiredParams.length > 0) {
5925
- const { missing } = yield* checkMissingParams(requiredParams).pipe(
6371
+ const requiredSecrets = collectRequiredSecrets(discovered, input.project, stage);
6372
+ const authSecret = collectAuthSecret(discovered, input.project, stage);
6373
+ if (authSecret) requiredSecrets.push(authSecret);
6374
+ if (requiredSecrets.length > 0) {
6375
+ const { missing } = yield* checkMissingSecrets(requiredSecrets).pipe(
5926
6376
  Effect35.provide(clients_exports.makeClients({ ssm: { region: input.region } }))
5927
6377
  );
5928
- if (missing.length > 0) {
6378
+ const withGenerators = missing.filter((m) => m.generate);
6379
+ const manualOnly = missing.filter((m) => !m.generate);
6380
+ if (withGenerators.length > 0) {
6381
+ for (const entry of withGenerators) {
6382
+ const value = executeGenerateSpec(entry.generate);
6383
+ yield* ssm_exports.make("put_parameter", {
6384
+ Name: entry.ssmPath,
6385
+ Value: value,
6386
+ Type: "SecureString"
6387
+ }).pipe(Effect35.provide(clients_exports.makeClients({ ssm: { region: input.region } })));
6388
+ yield* Effect35.logDebug(`Auto-created SSM parameter: ${entry.ssmPath}`);
6389
+ }
6390
+ yield* Console2.log(`
6391
+ ${c.green("\u2713")} Auto-created ${withGenerators.length} secret(s)`);
6392
+ }
6393
+ if (manualOnly.length > 0) {
5929
6394
  yield* Console2.log(`
5930
- ${c.yellow("\u26A0")} Missing ${missing.length} SSM parameter(s):
6395
+ ${c.yellow("\u26A0")} Missing ${manualOnly.length} SSM parameter(s):
5931
6396
  `);
5932
- for (const p of missing) {
6397
+ for (const p of manualOnly) {
5933
6398
  yield* Console2.log(` ${c.dim(p.handlerName)} \u2192 ${c.yellow(p.ssmPath)}`);
5934
6399
  }
5935
6400
  yield* Console2.log(`
@@ -5953,7 +6418,8 @@ var deployProject = (input) => Effect35.gen(function* () {
5953
6418
  project: input.project,
5954
6419
  stage,
5955
6420
  region: input.region,
5956
- packageDir: input.packageDir ?? input.projectDir
6421
+ packageDir: input.packageDir ?? input.projectDir,
6422
+ extraNodeModules: input.extraNodeModules
5957
6423
  }) : { layerArn: void 0, layerVersion: void 0, layerStatus: void 0, external: [] };
5958
6424
  if (layerArn && layerStatus) {
5959
6425
  const status = layerStatus === "cached" ? c.dim("cached") : c.green("created");
@@ -5988,7 +6454,8 @@ var deployProject = (input) => Effect35.gen(function* () {
5988
6454
  bucketNameMap,
5989
6455
  mailerDomainMap,
5990
6456
  queueNameMap,
5991
- logComplete
6457
+ logComplete,
6458
+ ...authSecret ? { authSecretPath: authSecret.ssmPath } : {}
5992
6459
  };
5993
6460
  const tableResults = [];
5994
6461
  const appResults = [];
@@ -6191,7 +6658,8 @@ var deployCommand = Command.make(
6191
6658
  stage: finalStage,
6192
6659
  region: finalRegion,
6193
6660
  noSites,
6194
- verbose
6661
+ verbose,
6662
+ extraNodeModules: projectDir !== cwd ? [path10.join(projectDir, "node_modules")] : void 0
6195
6663
  });
6196
6664
  const total = results.tableResults.length + results.appResults.length + results.staticSiteResults.length + results.apiResults.length;
6197
6665
  yield* Console3.log(`
@@ -6285,7 +6753,8 @@ Deployed ${tableResults.length} table handler(s):`));
6285
6753
  project,
6286
6754
  stage: finalStage,
6287
6755
  region: finalRegion,
6288
- exportName: foundExport
6756
+ exportName: foundExport,
6757
+ extraNodeModules: projectDir !== cwd ? [path10.join(projectDir, "node_modules")] : void 0
6289
6758
  };
6290
6759
  if (handlerType === "table") {
6291
6760
  const result = yield* deployTable(input);
@@ -6390,9 +6859,9 @@ var STATUS_COLORS = {
6390
6859
  var formatStatus = (status) => {
6391
6860
  return STATUS_COLORS[status](status.padEnd(10));
6392
6861
  };
6393
- var formatRoute = (method, path12) => {
6394
- if (method && path12) return `${method.padEnd(5)} ${path12}`;
6395
- if (path12) return path12;
6862
+ var formatRoute = (method, path13) => {
6863
+ if (method && path13) return `${method.padEnd(5)} ${path13}`;
6864
+ if (path13) return path13;
6396
6865
  return "";
6397
6866
  };
6398
6867
  var formatEntry = (entry) => {
@@ -7038,15 +7507,16 @@ var layerCommand = Command5.make(
7038
7507
  "layer",
7039
7508
  { build: buildOption, output: outputOption, verbose: verboseOption },
7040
7509
  ({ build: build3, output, verbose }) => Effect43.gen(function* () {
7041
- const { config, cwd } = yield* ProjectConfig;
7510
+ const { config, cwd, projectDir } = yield* ProjectConfig;
7511
+ const extraNodeModules = projectDir !== cwd ? [path11.join(projectDir, "node_modules")] : void 0;
7042
7512
  if (build3) {
7043
- yield* buildLayer(cwd, output, verbose);
7513
+ yield* buildLayer(cwd, output, verbose, extraNodeModules);
7044
7514
  } else {
7045
- yield* showLayerInfo(cwd, config?.name, verbose);
7515
+ yield* showLayerInfo(cwd, config?.name, verbose, extraNodeModules);
7046
7516
  }
7047
7517
  }).pipe(Effect43.provide(ProjectConfig.Live))
7048
7518
  ).pipe(Command5.withDescription("Inspect or locally build the shared Lambda dependency layer from package.json"));
7049
- var showLayerInfo = (projectDir, projectName, verbose) => Effect43.gen(function* () {
7519
+ var showLayerInfo = (projectDir, projectName, verbose, extraNodeModules) => Effect43.gen(function* () {
7050
7520
  yield* Console7.log(`
7051
7521
  ${c.bold("=== Layer Packages Preview ===")}
7052
7522
  `);
@@ -7069,7 +7539,7 @@ ${c.bold("=== Layer Packages Preview ===")}
7069
7539
  for (const dep of prodDeps) {
7070
7540
  yield* Console7.log(` ${dep}`);
7071
7541
  }
7072
- const hash = yield* computeLockfileHash(projectDir).pipe(
7542
+ const hash = yield* computeLockfileHash(projectDir, extraNodeModules).pipe(
7073
7543
  Effect43.catchAll(() => Effect43.succeed(null))
7074
7544
  );
7075
7545
  if (hash) {
@@ -7078,7 +7548,7 @@ Lockfile hash: ${hash}`);
7078
7548
  } else {
7079
7549
  yield* Console7.log("\nNo lockfile found (package-lock.json, pnpm-lock.yaml, or yarn.lock)");
7080
7550
  }
7081
- const { packages: allPackages, warnings: layerWarnings } = yield* Effect43.sync(() => collectLayerPackages(projectDir, prodDeps));
7551
+ const { packages: allPackages, warnings: layerWarnings } = yield* Effect43.sync(() => collectLayerPackages(projectDir, prodDeps, extraNodeModules));
7082
7552
  if (layerWarnings.length > 0) {
7083
7553
  yield* Console7.log(c.yellow(`
7084
7554
  Warnings (${layerWarnings.length}):`));
@@ -7107,7 +7577,7 @@ Total packages for layer ${c.dim(`(${allPackages.length})`)}:`);
7107
7577
  Layer name: ${projectName}-deps`);
7108
7578
  }
7109
7579
  });
7110
- var buildLayer = (projectDir, output, verbose) => Effect43.gen(function* () {
7580
+ var buildLayer = (projectDir, output, verbose, extraNodeModules) => Effect43.gen(function* () {
7111
7581
  const outputDir = path11.isAbsolute(output) ? output : path11.resolve(projectDir, output);
7112
7582
  const layerDir = path11.join(outputDir, "nodejs", "node_modules");
7113
7583
  const layerRoot = path11.join(outputDir, "nodejs");
@@ -7137,12 +7607,12 @@ ${c.bold("=== Building Layer Locally ===")}
7137
7607
  for (const dep of prodDeps) {
7138
7608
  yield* Console7.log(` ${dep}`);
7139
7609
  }
7140
- const hash = yield* computeLockfileHash(projectDir).pipe(
7610
+ const hash = yield* computeLockfileHash(projectDir, extraNodeModules).pipe(
7141
7611
  Effect43.catchAll(() => Effect43.succeed("unknown"))
7142
7612
  );
7143
7613
  yield* Console7.log(`
7144
7614
  Lockfile hash: ${hash}`);
7145
- const { packages: allPackages, resolvedPaths, warnings: layerWarnings } = yield* Effect43.sync(() => collectLayerPackages(projectDir, prodDeps));
7615
+ const { packages: allPackages, resolvedPaths, warnings: layerWarnings } = yield* Effect43.sync(() => collectLayerPackages(projectDir, prodDeps, extraNodeModules));
7146
7616
  if (layerWarnings.length > 0) {
7147
7617
  yield* Console7.log(`
7148
7618
  Warnings (${layerWarnings.length}):`);
@@ -7209,7 +7679,7 @@ var loadRequiredParams = (projectOpt, stage, region) => Effect44.gen(function* (
7209
7679
  const handlers = discoverHandlers(files);
7210
7680
  const finalStage = config?.stage ?? stage;
7211
7681
  const finalRegion = config?.region ?? region;
7212
- const params = collectRequiredParams(handlers, project, finalStage);
7682
+ const params = collectRequiredSecrets(handlers, project, finalStage);
7213
7683
  return { params, project, stage: finalStage, region: finalRegion };
7214
7684
  });
7215
7685
  var listCommand = Command6.make(
@@ -7222,7 +7692,7 @@ var listCommand = Command6.make(
7222
7692
  yield* Console8.log("No config parameters declared in handlers.");
7223
7693
  return;
7224
7694
  }
7225
- const { existing, missing } = yield* checkMissingParams(params).pipe(
7695
+ const { existing, missing } = yield* checkMissingSecrets(params).pipe(
7226
7696
  Effect44.provide(clients_exports.makeClients({ ssm: { region: ctx.region } }))
7227
7697
  );
7228
7698
  yield* Console8.log(`
@@ -7293,7 +7763,7 @@ var configRootCommand = Command6.make(
7293
7763
  yield* Console8.log("No config parameters declared in handlers.");
7294
7764
  return;
7295
7765
  }
7296
- const { missing } = yield* checkMissingParams(params).pipe(
7766
+ const { missing } = yield* checkMissingSecrets(params).pipe(
7297
7767
  Effect44.provide(clients_exports.makeClients({ ssm: { region: ctx.region } }))
7298
7768
  );
7299
7769
  if (missing.length === 0) {
@@ -7342,19 +7812,262 @@ ${c.bold("Missing parameters")} ${c.dim(`(${ctx.project} / ${ctx.stage})`)}
7342
7812
  );
7343
7813
  var configCommand = configRootCommand;
7344
7814
 
7815
+ // src/cli/commands/build.ts
7816
+ import { Args as Args4, Command as Command7, Options as Options5 } from "@effect/cli";
7817
+ import { Effect as Effect45, Console as Console9, Option as Option6 } from "effect";
7818
+ import * as path12 from "path";
7819
+ import * as fs8 from "fs";
7820
+ var buildFileArg = Args4.file({ name: "file", exists: "yes" }).pipe(
7821
+ Args4.withDescription("Handler file to build"),
7822
+ Args4.optional
7823
+ );
7824
+ var buildAllOption = Options5.boolean("all").pipe(
7825
+ Options5.withDescription("Build all exports from file")
7826
+ );
7827
+ var buildTableOption = Options5.boolean("table").pipe(
7828
+ Options5.withDescription("Build as table handler (defineTable)")
7829
+ );
7830
+ var buildCommand = Command7.make(
7831
+ "build",
7832
+ { file: buildFileArg, all: buildAllOption, table: buildTableOption, output: outputOption, verbose: verboseOption },
7833
+ ({ file, all, table, output, verbose }) => Effect45.gen(function* () {
7834
+ const { config, projectDir, cwd } = yield* ProjectConfig;
7835
+ const outputDir = path12.isAbsolute(output) ? output : path12.resolve(projectDir, output);
7836
+ const extraNodeModules = projectDir !== cwd ? [path12.join(projectDir, "node_modules")] : void 0;
7837
+ if (!fs8.existsSync(outputDir)) {
7838
+ fs8.mkdirSync(outputDir, { recursive: true });
7839
+ }
7840
+ const prodDeps = yield* readProductionDependencies(projectDir).pipe(
7841
+ Effect45.catchAll(() => Effect45.succeed([]))
7842
+ );
7843
+ const { packages: external, warnings: layerWarnings } = prodDeps.length > 0 ? yield* Effect45.sync(() => collectLayerPackages(projectDir, prodDeps, extraNodeModules)) : { packages: [], warnings: [] };
7844
+ for (const warning of layerWarnings) {
7845
+ yield* Effect45.logWarning(`[layer] ${warning}`);
7846
+ }
7847
+ if (external.length > 0) {
7848
+ yield* Console9.log(`Using ${external.length} external packages (from layer)
7849
+ `);
7850
+ }
7851
+ yield* Option6.match(file, {
7852
+ onNone: () => Effect45.gen(function* () {
7853
+ const patterns = getPatternsFromConfig(config);
7854
+ if (!patterns) {
7855
+ yield* Console9.error("Error: No file specified and no 'handlers' patterns in config");
7856
+ return;
7857
+ }
7858
+ const files = findHandlerFiles(patterns, projectDir);
7859
+ const discovered = discoverHandlers(files);
7860
+ let builtCount = 0;
7861
+ for (const { file: handlerFile, exports } of discovered.apiHandlers) {
7862
+ const relativePath = path12.relative(projectDir, handlerFile);
7863
+ const baseName = path12.basename(handlerFile, path12.extname(handlerFile));
7864
+ for (const { exportName } of exports) {
7865
+ const outputName = exportName === "default" ? baseName : `${baseName}-${exportName}`;
7866
+ const outputPath = path12.join(outputDir, `${outputName}.mjs`);
7867
+ yield* Console9.log(`Building ${c.cyan("[api]")} ${c.bold(relativePath)}:${exportName}...`);
7868
+ const result = yield* bundle({
7869
+ projectDir,
7870
+ file: handlerFile,
7871
+ exportName,
7872
+ type: "api",
7873
+ ...external.length > 0 ? { external } : {}
7874
+ });
7875
+ fs8.writeFileSync(outputPath, result.code);
7876
+ const size = (Buffer.byteLength(result.code) / 1024).toFixed(1);
7877
+ yield* Console9.log(` -> ${outputPath} ${c.dim(`(${size} KB)`)}`);
7878
+ if (result.topModules) {
7879
+ for (const m of result.topModules.slice(0, 5)) {
7880
+ yield* Console9.log(` ${c.dim(`${(m.bytes / 1024).toFixed(1)} KB`)} ${c.dim(m.path)}`);
7881
+ }
7882
+ }
7883
+ builtCount++;
7884
+ }
7885
+ }
7886
+ for (const { file: handlerFile, exports } of discovered.tableHandlers) {
7887
+ const relativePath = path12.relative(projectDir, handlerFile);
7888
+ const baseName = path12.basename(handlerFile, path12.extname(handlerFile));
7889
+ for (const { exportName } of exports) {
7890
+ const outputName = exportName === "default" ? baseName : `${baseName}-${exportName}`;
7891
+ const outputPath = path12.join(outputDir, `${outputName}.mjs`);
7892
+ yield* Console9.log(`Building ${c.cyan("[table]")} ${c.bold(relativePath)}:${exportName}...`);
7893
+ const result = yield* bundle({
7894
+ projectDir,
7895
+ file: handlerFile,
7896
+ exportName,
7897
+ type: "table",
7898
+ ...external.length > 0 ? { external } : {}
7899
+ });
7900
+ fs8.writeFileSync(outputPath, result.code);
7901
+ const size = (Buffer.byteLength(result.code) / 1024).toFixed(1);
7902
+ yield* Console9.log(` -> ${outputPath} ${c.dim(`(${size} KB)`)}`);
7903
+ if (result.topModules) {
7904
+ for (const m of result.topModules.slice(0, 5)) {
7905
+ yield* Console9.log(` ${c.dim(`${(m.bytes / 1024).toFixed(1)} KB`)} ${c.dim(m.path)}`);
7906
+ }
7907
+ }
7908
+ builtCount++;
7909
+ }
7910
+ }
7911
+ yield* Console9.log(c.green(`
7912
+ Built ${builtCount} handler(s) to ${outputDir}`));
7913
+ }),
7914
+ onSome: (filePath) => Effect45.gen(function* () {
7915
+ const fullPath = path12.isAbsolute(filePath) ? filePath : path12.resolve(projectDir, filePath);
7916
+ const source = fs8.readFileSync(fullPath, "utf-8");
7917
+ const baseName = path12.basename(fullPath, path12.extname(fullPath));
7918
+ if (table) {
7919
+ const configs = extractTableConfigs(source);
7920
+ if (configs.length === 0) {
7921
+ yield* Console9.error("No defineTable handlers found in file");
7922
+ return;
7923
+ }
7924
+ const toBundle = all ? configs : [configs[0]];
7925
+ for (const { exportName } of toBundle) {
7926
+ const outputName = exportName === "default" ? baseName : `${baseName}-${exportName}`;
7927
+ const outputPath = path12.join(outputDir, `${outputName}.mjs`);
7928
+ yield* Console9.log(`Building ${c.cyan("[table]")} ${c.bold(exportName)}...`);
7929
+ const result = yield* bundle({
7930
+ projectDir,
7931
+ file: fullPath,
7932
+ exportName,
7933
+ type: "table",
7934
+ ...external.length > 0 ? { external } : {}
7935
+ });
7936
+ fs8.writeFileSync(outputPath, result.code);
7937
+ const size = (Buffer.byteLength(result.code) / 1024).toFixed(1);
7938
+ yield* Console9.log(` -> ${outputPath} ${c.dim(`(${size} KB)`)}`);
7939
+ if (result.topModules) {
7940
+ for (const m of result.topModules.slice(0, 5)) {
7941
+ yield* Console9.log(` ${c.dim(`${(m.bytes / 1024).toFixed(1)} KB`)} ${c.dim(m.path)}`);
7942
+ }
7943
+ }
7944
+ }
7945
+ } else {
7946
+ const configs = extractApiConfigs(source);
7947
+ if (configs.length === 0) {
7948
+ yield* Console9.error("No defineApi handlers found in file");
7949
+ return;
7950
+ }
7951
+ const toBundle = all ? configs : [configs[0]];
7952
+ for (const { exportName } of toBundle) {
7953
+ const outputName = exportName === "default" ? baseName : `${baseName}-${exportName}`;
7954
+ const outputPath = path12.join(outputDir, `${outputName}.mjs`);
7955
+ yield* Console9.log(`Building ${c.cyan("[api]")} ${c.bold(exportName)}...`);
7956
+ const result = yield* bundle({
7957
+ projectDir,
7958
+ file: fullPath,
7959
+ exportName,
7960
+ ...external.length > 0 ? { external } : {}
7961
+ });
7962
+ fs8.writeFileSync(outputPath, result.code);
7963
+ const size = (Buffer.byteLength(result.code) / 1024).toFixed(1);
7964
+ yield* Console9.log(` -> ${outputPath} ${c.dim(`(${size} KB)`)}`);
7965
+ if (result.topModules) {
7966
+ for (const m of result.topModules.slice(0, 5)) {
7967
+ yield* Console9.log(` ${c.dim(`${(m.bytes / 1024).toFixed(1)} KB`)} ${c.dim(m.path)}`);
7968
+ }
7969
+ }
7970
+ }
7971
+ }
7972
+ yield* Console9.log(`
7973
+ Output directory: ${outputDir}`);
7974
+ })
7975
+ });
7976
+ }).pipe(Effect45.provide(ProjectConfig.Live))
7977
+ ).pipe(Command7.withDescription("Build handler bundles locally (for debugging)"));
7978
+
7979
+ // src/cli/update-check.ts
7980
+ import { homedir as homedir2 } from "os";
7981
+ import { join as join11 } from "path";
7982
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
7983
+ var PACKAGE_NAME = "@effortless-aws/cli";
7984
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
7985
+ var CACHE_DIR = join11(homedir2(), ".effortless-aws");
7986
+ var CACHE_FILE = join11(CACHE_DIR, "update-check.json");
7987
+ function readCache() {
7988
+ try {
7989
+ return JSON.parse(readFileSync6(CACHE_FILE, "utf-8"));
7990
+ } catch {
7991
+ return void 0;
7992
+ }
7993
+ }
7994
+ function writeCache(data) {
7995
+ try {
7996
+ mkdirSync3(CACHE_DIR, { recursive: true });
7997
+ writeFileSync3(CACHE_FILE, JSON.stringify(data));
7998
+ } catch {
7999
+ }
8000
+ }
8001
+ async function fetchLatestVersion() {
8002
+ try {
8003
+ const controller = new AbortController();
8004
+ const timeout = setTimeout(() => controller.abort(), 3e3);
8005
+ const res = await fetch(
8006
+ `https://registry.npmjs.org/${PACKAGE_NAME}/latest`,
8007
+ { signal: controller.signal }
8008
+ );
8009
+ clearTimeout(timeout);
8010
+ if (!res.ok) return void 0;
8011
+ const data = await res.json();
8012
+ return data.version;
8013
+ } catch {
8014
+ return void 0;
8015
+ }
8016
+ }
8017
+ function compareVersions(current, latest) {
8018
+ const parse = (v) => v.split(".").map(Number);
8019
+ const [cMajor, cMinor, cPatch] = parse(current);
8020
+ const [lMajor, lMinor, lPatch] = parse(latest);
8021
+ if (lMajor !== cMajor) return lMajor > cMajor;
8022
+ if (lMinor !== cMinor) return lMinor > cMinor;
8023
+ return lPatch > cPatch;
8024
+ }
8025
+ async function checkForUpdate(currentVersion) {
8026
+ const cache = readCache();
8027
+ const now = Date.now();
8028
+ let latestVersion;
8029
+ if (cache && now - cache.lastCheck < CHECK_INTERVAL_MS) {
8030
+ latestVersion = cache.latestVersion;
8031
+ } else {
8032
+ latestVersion = await fetchLatestVersion();
8033
+ if (latestVersion) {
8034
+ writeCache({ lastCheck: now, latestVersion });
8035
+ }
8036
+ }
8037
+ if (latestVersion && compareVersions(currentVersion, latestVersion)) {
8038
+ const border = "\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510";
8039
+ const bottom = "\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518";
8040
+ const pad = (line, width) => {
8041
+ const visible = line.replace(/\x1b\[[0-9;]*m/g, "");
8042
+ return line + " ".repeat(Math.max(0, width - visible.length));
8043
+ };
8044
+ const W = 39;
8045
+ const line1 = `Update available! ${c.dim(currentVersion)} ${c.dim("\u2192")} ${c.green(latestVersion)}`;
8046
+ const line2 = `Run ${c.cyan(`pnpm i -g ${PACKAGE_NAME}`)} to update`;
8047
+ console.log();
8048
+ console.log(` ${border}`);
8049
+ console.log(` \u2502 ${pad(line1, W)} \u2502`);
8050
+ console.log(` \u2502 ${pad(line2, W)} \u2502`);
8051
+ console.log(` ${bottom}`);
8052
+ console.log();
8053
+ }
8054
+ }
8055
+
7345
8056
  // src/cli/index.ts
7346
8057
  var require2 = createRequire2(import.meta.url);
7347
8058
  var { version } = require2("../../package.json");
7348
- var mainCommand = Command7.make("eff").pipe(
7349
- Command7.withSubcommands([deployCommand, statusCommand, logsCommand, cleanupCommand, layerCommand, configCommand]),
7350
- Command7.withDescription("Code-first AWS Lambda framework")
8059
+ var mainCommand = Command8.make("eff").pipe(
8060
+ Command8.withSubcommands([deployCommand, statusCommand, logsCommand, cleanupCommand, layerCommand, configCommand, buildCommand]),
8061
+ Command8.withDescription("Code-first AWS Lambda framework")
7351
8062
  );
7352
- var cli = Command7.run(mainCommand, {
8063
+ var cli = Command8.run(mainCommand, {
7353
8064
  name: "effortless",
7354
8065
  version
7355
8066
  });
8067
+ var updateCheck = checkForUpdate(version);
7356
8068
  cli(process.argv).pipe(
7357
- Effect45.provide(NodeContext.layer),
7358
- Effect45.provide(CliConfig.layer({ showBuiltIns: false, showTypes: false })),
8069
+ Effect46.provide(NodeContext.layer),
8070
+ Effect46.provide(CliConfig.layer({ showBuiltIns: false, showTypes: false })),
8071
+ Effect46.tap(() => Effect46.promise(() => updateCheck)),
7359
8072
  NodeRuntime.runMain
7360
8073
  );