@effortless-aws/cli 0.2.3 → 0.4.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 +473 -586
  2. package/package.json +2 -2
package/dist/cli/index.js CHANGED
@@ -14,7 +14,7 @@ import { createRequire as createRequire2 } from "module";
14
14
  // src/cli/commands/deploy.ts
15
15
  import { Args, Command } from "@effect/cli";
16
16
  import { Effect as Effect38, Console as Console3, Logger, LogLevel, Option } from "effect";
17
- import * as path9 from "path";
17
+ import * as path10 from "path";
18
18
 
19
19
  // src/deploy/deploy.ts
20
20
  import { Effect as Effect35, Console as Console2 } from "effect";
@@ -1933,6 +1933,11 @@ var publishVersion = (functionName) => Effect13.gen(function* () {
1933
1933
  version: result.Version
1934
1934
  };
1935
1935
  });
1936
+ var DEFAULT_CORS = {
1937
+ AllowOrigins: ["*"],
1938
+ AllowMethods: ["*"],
1939
+ AllowHeaders: ["*"]
1940
+ };
1936
1941
  var ensureFunctionUrl = (functionName) => Effect13.gen(function* () {
1937
1942
  const existing = yield* lambda_exports.make("get_function_url_config", {
1938
1943
  FunctionName: functionName
@@ -1945,7 +1950,8 @@ var ensureFunctionUrl = (functionName) => Effect13.gen(function* () {
1945
1950
  if (existing) {
1946
1951
  yield* lambda_exports.make("update_function_url_config", {
1947
1952
  FunctionName: functionName,
1948
- AuthType: "NONE"
1953
+ AuthType: "NONE",
1954
+ Cors: DEFAULT_CORS
1949
1955
  });
1950
1956
  return { functionUrl: existing.FunctionUrl };
1951
1957
  }
@@ -1953,7 +1959,8 @@ var ensureFunctionUrl = (functionName) => Effect13.gen(function* () {
1953
1959
  const result = yield* lambda_exports.make("create_function_url_config", {
1954
1960
  FunctionName: functionName,
1955
1961
  AuthType: "NONE",
1956
- InvokeMode: "BUFFERED"
1962
+ InvokeMode: "BUFFERED",
1963
+ Cors: DEFAULT_CORS
1957
1964
  });
1958
1965
  return { functionUrl: result.FunctionUrl };
1959
1966
  });
@@ -2419,133 +2426,6 @@ var deleteTable = (tableName) => Effect16.gen(function* () {
2419
2426
 
2420
2427
  // src/aws/apigateway.ts
2421
2428
  import { Effect as Effect17 } from "effect";
2422
- var ensureProjectApi = (config) => Effect17.gen(function* () {
2423
- const apiName = `${config.projectName}-${config.stage}`;
2424
- const existingApis = yield* apigatewayv2_exports.make("get_apis", {});
2425
- const existingApi = existingApis.Items?.find((api) => api.Name === apiName);
2426
- let apiId;
2427
- if (existingApi) {
2428
- yield* Effect17.logDebug(`Using existing API Gateway: ${apiName}`);
2429
- apiId = existingApi.ApiId;
2430
- if (config.tags) {
2431
- const apiArn = `arn:aws:apigateway:${config.region}::/apis/${apiId}`;
2432
- yield* apigatewayv2_exports.make("tag_resource", {
2433
- ResourceArn: apiArn,
2434
- Tags: config.tags
2435
- });
2436
- }
2437
- } else {
2438
- yield* Effect17.logDebug(`Creating API Gateway: ${apiName}`);
2439
- const createResult = yield* apigatewayv2_exports.make("create_api", {
2440
- Name: apiName,
2441
- ProtocolType: "HTTP",
2442
- CorsConfiguration: {
2443
- AllowOrigins: ["*"],
2444
- AllowMethods: ["*"],
2445
- AllowHeaders: ["*"]
2446
- },
2447
- Tags: config.tags
2448
- });
2449
- apiId = createResult.ApiId;
2450
- yield* apigatewayv2_exports.make("create_stage", {
2451
- ApiId: apiId,
2452
- StageName: "$default",
2453
- AutoDeploy: true
2454
- });
2455
- }
2456
- return { apiId };
2457
- });
2458
- var addRouteToApi = (config) => Effect17.gen(function* () {
2459
- const integrationUri = `arn:aws:apigateway:${config.region}:lambda:path/2015-03-31/functions/${config.functionArn}/invocations`;
2460
- const existingIntegrations = yield* apigatewayv2_exports.make("get_integrations", { ApiId: config.apiId });
2461
- let integrationId = existingIntegrations.Items?.find(
2462
- (i) => i.IntegrationUri === integrationUri
2463
- )?.IntegrationId;
2464
- if (!integrationId) {
2465
- yield* Effect17.logDebug("Creating integration");
2466
- const integrationResult = yield* apigatewayv2_exports.make("create_integration", {
2467
- ApiId: config.apiId,
2468
- IntegrationType: "AWS_PROXY",
2469
- IntegrationUri: integrationUri,
2470
- IntegrationMethod: "POST",
2471
- PayloadFormatVersion: "2.0"
2472
- });
2473
- integrationId = integrationResult.IntegrationId;
2474
- }
2475
- const routeKey = `${config.method} ${config.path}`;
2476
- const existingRoutes = yield* apigatewayv2_exports.make("get_routes", { ApiId: config.apiId });
2477
- const existingRoute = existingRoutes.Items?.find((r) => r.RouteKey === routeKey);
2478
- const target = `integrations/${integrationId}`;
2479
- if (!existingRoute) {
2480
- yield* Effect17.logDebug(`Creating route: ${routeKey}`);
2481
- yield* apigatewayv2_exports.make("create_route", {
2482
- ApiId: config.apiId,
2483
- RouteKey: routeKey,
2484
- Target: target
2485
- });
2486
- } else if (existingRoute.Target !== target) {
2487
- yield* Effect17.logDebug(`Updating route target: ${routeKey}`);
2488
- yield* apigatewayv2_exports.make("update_route", {
2489
- ApiId: config.apiId,
2490
- RouteId: existingRoute.RouteId,
2491
- RouteKey: routeKey,
2492
- Target: target
2493
- });
2494
- } else {
2495
- yield* Effect17.logDebug(`Route already exists: ${routeKey}`);
2496
- }
2497
- yield* addLambdaPermission(config.functionArn, config.apiId, config.region);
2498
- const apiUrl = `https://${config.apiId}.execute-api.${config.region}.amazonaws.com${config.path}`;
2499
- return { apiUrl };
2500
- });
2501
- var addLambdaPermission = (functionArn, apiId, region) => Effect17.gen(function* () {
2502
- const statementId = `apigateway-${apiId}`;
2503
- const accountId = functionArn.split(":")[4];
2504
- const sourceArn = `arn:aws:execute-api:${region}:${accountId}:${apiId}/*/*`;
2505
- yield* lambda_exports.make("add_permission", {
2506
- FunctionName: functionArn,
2507
- StatementId: statementId,
2508
- Action: "lambda:InvokeFunction",
2509
- Principal: "apigateway.amazonaws.com",
2510
- SourceArn: sourceArn
2511
- }).pipe(
2512
- Effect17.catchIf(
2513
- (e) => e._tag === "LambdaError" && e.is("ResourceConflictException"),
2514
- () => {
2515
- return Effect17.logDebug("Permission already exists");
2516
- }
2517
- )
2518
- );
2519
- });
2520
- var removeStaleRoutes = (apiId, activeRouteKeys) => Effect17.gen(function* () {
2521
- const existingRoutes = yield* apigatewayv2_exports.make("get_routes", { ApiId: apiId });
2522
- for (const route of existingRoutes.Items ?? []) {
2523
- if (route.RouteKey && !activeRouteKeys.has(route.RouteKey)) {
2524
- yield* Effect17.logDebug(`Removing stale route: ${route.RouteKey}`);
2525
- yield* apigatewayv2_exports.make("delete_route", {
2526
- ApiId: apiId,
2527
- RouteId: route.RouteId
2528
- });
2529
- const integrationId = route.Target?.replace("integrations/", "");
2530
- if (integrationId) {
2531
- const remainingRoutes = yield* apigatewayv2_exports.make("get_routes", { ApiId: apiId });
2532
- const stillUsed = remainingRoutes.Items?.some((r) => r.Target === route.Target);
2533
- if (!stillUsed) {
2534
- yield* Effect17.logDebug(`Removing orphaned integration: ${integrationId}`);
2535
- yield* apigatewayv2_exports.make("delete_integration", {
2536
- ApiId: apiId,
2537
- IntegrationId: integrationId
2538
- }).pipe(
2539
- Effect17.catchIf(
2540
- (e) => e._tag === "ApiGatewayV2Error" && e.is("NotFoundException"),
2541
- () => Effect17.logDebug(`Integration ${integrationId} already removed`)
2542
- )
2543
- );
2544
- }
2545
- }
2546
- }
2547
- }
2548
- });
2549
2429
  var deleteApi = (apiId) => Effect17.gen(function* () {
2550
2430
  yield* Effect17.logDebug(`Deleting API Gateway: ${apiId}`);
2551
2431
  yield* apigatewayv2_exports.make("delete_api", {
@@ -3080,6 +2960,7 @@ var syncFiles = (input) => Effect19.gen(function* () {
3080
2960
  walkDir(sourceDir, "");
3081
2961
  let uploaded = 0;
3082
2962
  let unchanged = 0;
2963
+ const uploadedKeys = [];
3083
2964
  for (const [key, filePath] of localFiles) {
3084
2965
  const content = fs2.readFileSync(filePath);
3085
2966
  const md5 = crypto3.createHash("md5").update(content).digest("hex");
@@ -3100,8 +2981,9 @@ var syncFiles = (input) => Effect19.gen(function* () {
3100
2981
  CacheControl: cacheControl
3101
2982
  });
3102
2983
  uploaded++;
2984
+ uploadedKeys.push(key);
3103
2985
  }
3104
- const keysToDelete = [...existingObjects.keys()].filter((k) => !localFiles.has(k));
2986
+ const keysToDelete = [...existingObjects.keys()].filter((k) => !localFiles.has(k) && !k.startsWith("_effortless/"));
3105
2987
  let deleted = 0;
3106
2988
  if (keysToDelete.length > 0) {
3107
2989
  for (let i = 0; i < keysToDelete.length; i += 1e3) {
@@ -3117,7 +2999,7 @@ var syncFiles = (input) => Effect19.gen(function* () {
3117
2999
  }
3118
3000
  }
3119
3001
  yield* Effect19.logDebug(`S3 sync: ${uploaded} uploaded, ${deleted} deleted, ${unchanged} unchanged`);
3120
- return { uploaded, deleted, unchanged };
3002
+ return { uploaded, deleted, unchanged, uploadedKeys };
3121
3003
  });
3122
3004
  var putObject = (input) => s3_exports.make("put_object", {
3123
3005
  Bucket: input.bucketName,
@@ -4125,7 +4007,7 @@ var parseSource = (source) => {
4125
4007
  const project = new Project({ useInMemoryFileSystem: true });
4126
4008
  return project.createSourceFile("input.ts", source);
4127
4009
  };
4128
- var RUNTIME_PROPS = ["onRequest", "onRecord", "onBatchComplete", "onBatch", "onMessage", "onObjectCreated", "onObjectRemoved", "setup", "schema", "onError", "deps", "config", "static", "middleware", "routes", "get", "post"];
4010
+ var RUNTIME_PROPS = ["onRecord", "onBatchComplete", "onBatch", "onMessage", "onObjectCreated", "onObjectRemoved", "setup", "schema", "onError", "deps", "config", "static", "middleware", "routes", "get", "post"];
4129
4011
  var evalConfig = (configText, exportName) => {
4130
4012
  try {
4131
4013
  return new Function(`return ${configText}`)();
@@ -4250,12 +4132,6 @@ var extractRoutePatterns = (obj) => {
4250
4132
  }).filter(Boolean);
4251
4133
  };
4252
4134
  var handlerRegistry = {
4253
- http: {
4254
- defineFn: "defineHttp",
4255
- handlerProps: ["onRequest"],
4256
- wrapperFn: "wrapHttp",
4257
- wrapperPath: "~/runtime/wrap-http"
4258
- },
4259
4135
  table: {
4260
4136
  defineFn: "defineTable",
4261
4137
  handlerProps: ["onRecord", "onBatch"],
@@ -4362,7 +4238,6 @@ export const handler = ${wrapperFn}(${importName});
4362
4238
  };
4363
4239
 
4364
4240
  // src/build/bundle.ts
4365
- var extractConfigs = (source) => extractHandlerConfigs(source, "http");
4366
4241
  var extractTableConfigs = (source) => extractHandlerConfigs(source, "table");
4367
4242
  var extractAppConfigs = (source) => extractHandlerConfigs(source, "app");
4368
4243
  var extractStaticSiteConfigs = (source) => extractHandlerConfigs(source, "staticSite");
@@ -4374,7 +4249,7 @@ var _require = createRequire(import.meta.url);
4374
4249
  var runtimeDir = path3.join(path3.dirname(_require.resolve("effortless-aws/package.json")), "dist/runtime");
4375
4250
  var bundle = (input) => Effect24.gen(function* () {
4376
4251
  const exportName = input.exportName ?? "default";
4377
- const type = input.type ?? "http";
4252
+ const type = input.type ?? "api";
4378
4253
  const externals = input.external ?? [];
4379
4254
  const sourcePath = path3.isAbsolute(input.file) ? input.file : `./${input.file}`;
4380
4255
  const entryPoint = generateEntryPoint(sourcePath, exportName, type, runtimeDir);
@@ -4463,7 +4338,6 @@ var findHandlerFiles = (patterns, cwd) => {
4463
4338
  return Array.from(files);
4464
4339
  };
4465
4340
  var discoverHandlers = (files) => {
4466
- const httpHandlers = [];
4467
4341
  const tableHandlers = [];
4468
4342
  const appHandlers = [];
4469
4343
  const staticSiteHandlers = [];
@@ -4474,7 +4348,6 @@ var discoverHandlers = (files) => {
4474
4348
  for (const file of files) {
4475
4349
  if (!fsSync2.statSync(file).isFile()) continue;
4476
4350
  const source = fsSync2.readFileSync(file, "utf-8");
4477
- const http = extractConfigs(source);
4478
4351
  const table = extractTableConfigs(source);
4479
4352
  const app = extractAppConfigs(source);
4480
4353
  const staticSite = extractStaticSiteConfigs(source);
@@ -4482,7 +4355,6 @@ var discoverHandlers = (files) => {
4482
4355
  const bucket = extractBucketConfigs(source);
4483
4356
  const mailer = extractMailerConfigs(source);
4484
4357
  const api = extractApiConfigs(source);
4485
- if (http.length > 0) httpHandlers.push({ file, exports: http });
4486
4358
  if (table.length > 0) tableHandlers.push({ file, exports: table });
4487
4359
  if (app.length > 0) appHandlers.push({ file, exports: app });
4488
4360
  if (staticSite.length > 0) staticSiteHandlers.push({ file, exports: staticSite });
@@ -4491,12 +4363,11 @@ var discoverHandlers = (files) => {
4491
4363
  if (mailer.length > 0) mailerHandlers.push({ file, exports: mailer });
4492
4364
  if (api.length > 0) apiHandlers.push({ file, exports: api });
4493
4365
  }
4494
- return { httpHandlers, tableHandlers, appHandlers, staticSiteHandlers, fifoQueueHandlers, bucketHandlers, mailerHandlers, apiHandlers };
4366
+ return { tableHandlers, appHandlers, staticSiteHandlers, fifoQueueHandlers, bucketHandlers, mailerHandlers, apiHandlers };
4495
4367
  };
4496
4368
  var flattenHandlers = (discovered) => {
4497
4369
  const entries = (type, handlers) => handlers.flatMap((h) => h.exports.map((e) => ({ exportName: e.exportName, file: h.file, type })));
4498
4370
  return [
4499
- ...entries("http", discovered.httpHandlers),
4500
4371
  ...entries("table", discovered.tableHandlers),
4501
4372
  ...entries("app", discovered.appHandlers),
4502
4373
  ...entries("site", discovered.staticSiteHandlers),
@@ -4528,7 +4399,6 @@ var collectRequiredParams = (handlers, project, stage) => {
4528
4399
  }
4529
4400
  }
4530
4401
  };
4531
- collect(handlers.httpHandlers);
4532
4402
  collect(handlers.tableHandlers);
4533
4403
  collect(handlers.fifoQueueHandlers);
4534
4404
  collect(handlers.bucketHandlers);
@@ -4560,7 +4430,7 @@ var checkMissingParams = (params) => Effect25.gen(function* () {
4560
4430
  return { existing, missing };
4561
4431
  });
4562
4432
 
4563
- // src/deploy/deploy-http.ts
4433
+ // src/deploy/deploy-table.ts
4564
4434
  import { Effect as Effect27 } from "effect";
4565
4435
 
4566
4436
  // src/deploy/shared.ts
@@ -4657,151 +4527,9 @@ var deployCoreLambda = ({
4657
4527
  return { functionArn, status, tagCtx };
4658
4528
  });
4659
4529
 
4660
- // src/deploy/deploy-http.ts
4661
- var deployLambda = ({ input, fn: fn13, layerArn, external, depsEnv, depsPermissions, staticGlobs }) => Effect27.gen(function* () {
4662
- const { exportName, config } = fn13;
4663
- const handlerName = exportName;
4664
- const { functionArn, status } = yield* deployCoreLambda({
4665
- input,
4666
- exportName,
4667
- handlerName,
4668
- bundleType: "http",
4669
- ...config.permissions ? { permissions: config.permissions } : {},
4670
- ...config.memory ? { memory: config.memory } : {},
4671
- ...config.timeout ? { timeout: config.timeout } : {},
4672
- ...layerArn ? { layerArn } : {},
4673
- ...external ? { external } : {},
4674
- ...depsEnv ? { depsEnv } : {},
4675
- ...depsPermissions ? { depsPermissions } : {},
4676
- ...staticGlobs && staticGlobs.length > 0 ? { staticGlobs } : {}
4677
- });
4678
- return { exportName, functionArn, status, config, handlerName };
4679
- });
4680
- var deploy = (input) => Effect27.gen(function* () {
4681
- const source = yield* readSource(input);
4682
- const configs = extractConfigs(source);
4683
- if (configs.length === 0) {
4684
- return yield* Effect27.fail(new Error("Could not extract defineHttp config from source"));
4685
- }
4686
- const targetExport = input.exportName ?? "default";
4687
- const fn13 = configs.find((c2) => c2.exportName === targetExport) ?? configs[0];
4688
- const config = fn13.config;
4689
- const handlerName = fn13.exportName;
4690
- const tagCtx = {
4691
- project: input.project,
4692
- stage: resolveStage(input.stage),
4693
- handler: handlerName
4694
- };
4695
- yield* Effect27.logDebug(`Deploying ${handlerName} to ${input.region} (${tagCtx.project}/${tagCtx.stage})`);
4696
- const { layerArn, external } = yield* ensureLayerAndExternal({
4697
- project: input.project,
4698
- stage: tagCtx.stage,
4699
- region: input.region,
4700
- packageDir: input.packageDir ?? input.projectDir
4701
- });
4702
- const { functionArn } = yield* deployLambda({
4703
- input,
4704
- fn: fn13,
4705
- ...layerArn ? { layerArn } : {},
4706
- ...external.length > 0 ? { external } : {}
4707
- });
4708
- yield* Effect27.logDebug("Setting up API Gateway...");
4709
- const { apiId } = yield* ensureProjectApi({
4710
- projectName: input.project,
4711
- stage: tagCtx.stage,
4712
- region: input.region,
4713
- tags: makeTags(tagCtx, "api-gateway")
4714
- });
4715
- const { apiUrl } = yield* addRouteToApi({
4716
- apiId,
4717
- region: input.region,
4718
- functionArn,
4719
- method: config.method,
4720
- path: config.path
4721
- });
4722
- yield* Effect27.logDebug(`Deployment complete! URL: ${apiUrl}`);
4723
- return {
4724
- exportName: fn13.exportName,
4725
- url: apiUrl,
4726
- functionArn
4727
- };
4728
- }).pipe(
4729
- Effect27.provide(
4730
- clients_exports.makeClients({
4731
- lambda: { region: input.region },
4732
- iam: { region: input.region },
4733
- apigatewayv2: { region: input.region }
4734
- })
4735
- )
4736
- );
4737
- var deployAll = (input) => Effect27.gen(function* () {
4738
- const source = yield* readSource(input);
4739
- const functions = extractConfigs(source);
4740
- if (functions.length === 0) {
4741
- return yield* Effect27.fail(new Error("No defineHttp exports found in source"));
4742
- }
4743
- yield* Effect27.logDebug(`Found ${functions.length} HTTP handler(s) to deploy`);
4744
- const tagCtx = {
4745
- project: input.project,
4746
- stage: resolveStage(input.stage),
4747
- handler: "api"
4748
- };
4749
- const { layerArn, external } = yield* ensureLayerAndExternal({
4750
- project: input.project,
4751
- stage: tagCtx.stage,
4752
- region: input.region,
4753
- packageDir: input.packageDir ?? input.projectDir
4754
- });
4755
- yield* Effect27.logDebug("Setting up API Gateway...");
4756
- const { apiId } = yield* ensureProjectApi({
4757
- projectName: input.project,
4758
- stage: tagCtx.stage,
4759
- region: input.region,
4760
- tags: makeTags(tagCtx, "api-gateway")
4761
- });
4762
- const apiUrl = `https://${apiId}.execute-api.${input.region}.amazonaws.com`;
4763
- const results = [];
4764
- const activeRouteKeys = /* @__PURE__ */ new Set();
4765
- for (const fn13 of functions) {
4766
- const { exportName, functionArn, config, handlerName: fnName } = yield* deployLambda({
4767
- input,
4768
- fn: fn13,
4769
- ...layerArn ? { layerArn } : {},
4770
- ...external.length > 0 ? { external } : {}
4771
- });
4772
- const routeKey = `${config.method} ${config.path}`;
4773
- activeRouteKeys.add(routeKey);
4774
- const { apiUrl: handlerUrl } = yield* addRouteToApi({
4775
- apiId,
4776
- region: input.region,
4777
- functionArn,
4778
- method: config.method,
4779
- path: config.path
4780
- });
4781
- results.push({ exportName, url: handlerUrl, functionArn });
4782
- yield* Effect27.logDebug(` ${config.method} ${config.path} \u2192 ${fn13.exportName}`);
4783
- }
4784
- yield* removeStaleRoutes(apiId, activeRouteKeys);
4785
- yield* Effect27.logDebug(`Deployment complete! API: ${apiUrl}`);
4786
- return {
4787
- apiId,
4788
- apiUrl,
4789
- handlers: results
4790
- };
4791
- }).pipe(
4792
- Effect27.provide(
4793
- clients_exports.makeClients({
4794
- lambda: { region: input.region },
4795
- iam: { region: input.region },
4796
- apigatewayv2: { region: input.region }
4797
- })
4798
- )
4799
- );
4800
-
4801
4530
  // src/deploy/deploy-table.ts
4802
- import { Effect as Effect28 } from "effect";
4803
4531
  var TABLE_DEFAULT_PERMISSIONS = ["dynamodb:*", "logs:*"];
4804
- var deployTableFunction = ({ input, fn: fn13, layerArn, external, depsEnv, depsPermissions, staticGlobs }) => Effect28.gen(function* () {
4532
+ var deployTableFunction = ({ input, fn: fn13, layerArn, external, depsEnv, depsPermissions, staticGlobs }) => Effect27.gen(function* () {
4805
4533
  const { exportName, config } = fn13;
4806
4534
  const handlerName = exportName;
4807
4535
  const tagCtx = {
@@ -4809,7 +4537,7 @@ var deployTableFunction = ({ input, fn: fn13, layerArn, external, depsEnv, depsP
4809
4537
  stage: resolveStage(input.stage),
4810
4538
  handler: handlerName
4811
4539
  };
4812
- yield* Effect28.logDebug("Creating DynamoDB table...");
4540
+ yield* Effect27.logDebug("Creating DynamoDB table...");
4813
4541
  const tableName = `${input.project}-${tagCtx.stage}-${handlerName}`;
4814
4542
  const { tableArn, streamArn } = yield* ensureTable({
4815
4543
  name: tableName,
@@ -4833,7 +4561,7 @@ var deployTableFunction = ({ input, fn: fn13, layerArn, external, depsEnv, depsP
4833
4561
  ...depsPermissions ? { depsPermissions } : {},
4834
4562
  ...staticGlobs && staticGlobs.length > 0 ? { staticGlobs } : {}
4835
4563
  });
4836
- yield* Effect28.logDebug("Setting up event source mapping...");
4564
+ yield* Effect27.logDebug("Setting up event source mapping...");
4837
4565
  yield* ensureEventSourceMapping({
4838
4566
  functionArn,
4839
4567
  streamArn,
@@ -4841,7 +4569,7 @@ var deployTableFunction = ({ input, fn: fn13, layerArn, external, depsEnv, depsP
4841
4569
  batchWindow: config.batchWindow ?? 2,
4842
4570
  startingPosition: config.startingPosition ?? "LATEST"
4843
4571
  });
4844
- yield* Effect28.logDebug(`Table deployment complete! Table: ${tableArn}`);
4572
+ yield* Effect27.logDebug(`Table deployment complete! Table: ${tableArn}`);
4845
4573
  return {
4846
4574
  exportName,
4847
4575
  functionArn,
@@ -4850,11 +4578,11 @@ var deployTableFunction = ({ input, fn: fn13, layerArn, external, depsEnv, depsP
4850
4578
  streamArn
4851
4579
  };
4852
4580
  });
4853
- var deployTable = (input) => Effect28.gen(function* () {
4581
+ var deployTable = (input) => Effect27.gen(function* () {
4854
4582
  const source = yield* readSource(input);
4855
4583
  const configs = extractTableConfigs(source);
4856
4584
  if (configs.length === 0) {
4857
- return yield* Effect28.fail(new Error("No defineTable exports found in source"));
4585
+ return yield* Effect27.fail(new Error("No defineTable exports found in source"));
4858
4586
  }
4859
4587
  const targetExport = input.exportName ?? "default";
4860
4588
  const fn13 = configs.find((c2) => c2.exportName === targetExport) ?? configs[0];
@@ -4872,7 +4600,7 @@ var deployTable = (input) => Effect28.gen(function* () {
4872
4600
  });
4873
4601
  return result;
4874
4602
  }).pipe(
4875
- Effect28.provide(
4603
+ Effect27.provide(
4876
4604
  clients_exports.makeClients({
4877
4605
  lambda: { region: input.region },
4878
4606
  iam: { region: input.region },
@@ -4880,20 +4608,20 @@ var deployTable = (input) => Effect28.gen(function* () {
4880
4608
  })
4881
4609
  )
4882
4610
  );
4883
- var deployAllTables = (input) => Effect28.gen(function* () {
4611
+ var deployAllTables = (input) => Effect27.gen(function* () {
4884
4612
  const source = yield* readSource(input);
4885
4613
  const functions = extractTableConfigs(source);
4886
4614
  if (functions.length === 0) {
4887
- return yield* Effect28.fail(new Error("No defineTable exports found in source"));
4615
+ return yield* Effect27.fail(new Error("No defineTable exports found in source"));
4888
4616
  }
4889
- yield* Effect28.logDebug(`Found ${functions.length} table handler(s) to deploy`);
4617
+ yield* Effect27.logDebug(`Found ${functions.length} table handler(s) to deploy`);
4890
4618
  const { layerArn, external } = yield* ensureLayerAndExternal({
4891
4619
  project: input.project,
4892
4620
  stage: resolveStage(input.stage),
4893
4621
  region: input.region,
4894
4622
  packageDir: input.packageDir ?? input.projectDir
4895
4623
  });
4896
- const results = yield* Effect28.forEach(
4624
+ const results = yield* Effect27.forEach(
4897
4625
  functions,
4898
4626
  (fn13) => deployTableFunction({
4899
4627
  input,
@@ -4905,7 +4633,7 @@ var deployAllTables = (input) => Effect28.gen(function* () {
4905
4633
  );
4906
4634
  return results;
4907
4635
  }).pipe(
4908
- Effect28.provide(
4636
+ Effect27.provide(
4909
4637
  clients_exports.makeClients({
4910
4638
  lambda: { region: input.region },
4911
4639
  iam: { region: input.region },
@@ -4914,6 +4642,72 @@ var deployAllTables = (input) => Effect28.gen(function* () {
4914
4642
  )
4915
4643
  );
4916
4644
 
4645
+ // src/deploy/deploy-api.ts
4646
+ import { Effect as Effect28 } from "effect";
4647
+ var deployApiFunction = ({ input, fn: fn13, layerArn, external, depsEnv, depsPermissions, staticGlobs }) => Effect28.gen(function* () {
4648
+ const { exportName, config } = fn13;
4649
+ const handlerName = exportName;
4650
+ const { functionArn, status } = yield* deployCoreLambda({
4651
+ input,
4652
+ exportName,
4653
+ handlerName,
4654
+ bundleType: "api",
4655
+ ...config.permissions ? { permissions: config.permissions } : {},
4656
+ ...config.memory ? { memory: config.memory } : {},
4657
+ ...config.timeout ? { timeout: config.timeout } : {},
4658
+ ...layerArn ? { layerArn } : {},
4659
+ ...external ? { external } : {},
4660
+ ...depsEnv ? { depsEnv } : {},
4661
+ ...depsPermissions ? { depsPermissions } : {},
4662
+ ...staticGlobs && staticGlobs.length > 0 ? { staticGlobs } : {}
4663
+ });
4664
+ return { exportName, functionArn, status, config, handlerName };
4665
+ });
4666
+ var deploy = (input) => Effect28.gen(function* () {
4667
+ const source = yield* readSource(input);
4668
+ const configs = extractApiConfigs(source);
4669
+ if (configs.length === 0) {
4670
+ return yield* Effect28.fail(new Error("Could not extract defineApi config from source"));
4671
+ }
4672
+ const targetExport = input.exportName ?? "default";
4673
+ const fn13 = configs.find((c2) => c2.exportName === targetExport) ?? configs[0];
4674
+ const handlerName = fn13.exportName;
4675
+ const tagCtx = {
4676
+ project: input.project,
4677
+ stage: resolveStage(input.stage),
4678
+ handler: handlerName
4679
+ };
4680
+ yield* Effect28.logDebug(`Deploying API handler ${handlerName} to ${input.region}`);
4681
+ const { layerArn, external } = yield* ensureLayerAndExternal({
4682
+ project: input.project,
4683
+ stage: tagCtx.stage,
4684
+ region: input.region,
4685
+ packageDir: input.packageDir ?? input.projectDir
4686
+ });
4687
+ const { functionArn } = yield* deployApiFunction({
4688
+ input,
4689
+ fn: fn13,
4690
+ ...layerArn ? { layerArn } : {},
4691
+ ...external.length > 0 ? { external } : {}
4692
+ });
4693
+ const lambdaName = `${input.project}-${tagCtx.stage}-${handlerName}`;
4694
+ const { functionUrl } = yield* ensureFunctionUrl(lambdaName);
4695
+ yield* addFunctionUrlPublicAccess(lambdaName);
4696
+ yield* Effect28.logDebug(`Deployment complete! URL: ${functionUrl}`);
4697
+ return {
4698
+ exportName: fn13.exportName,
4699
+ url: functionUrl,
4700
+ functionArn
4701
+ };
4702
+ }).pipe(
4703
+ Effect28.provide(
4704
+ clients_exports.makeClients({
4705
+ lambda: { region: input.region },
4706
+ iam: { region: input.region }
4707
+ })
4708
+ )
4709
+ );
4710
+
4917
4711
  // src/deploy/deploy-app.ts
4918
4712
  import { Effect as Effect29 } from "effect";
4919
4713
  import { execSync } from "child_process";
@@ -4928,7 +4722,7 @@ var deployApp = (input) => Effect29.gen(function* () {
4928
4722
  if (routePatterns.length > 0 && !input.apiOriginDomain) {
4929
4723
  return yield* Effect29.fail(
4930
4724
  new Error(
4931
- `App "${exportName}" has routes but no API Gateway exists. Ensure defineHttp() or defineApi() handlers are included in the discovery patterns.`
4725
+ `App "${exportName}" has routes but no API handler was deployed. Ensure defineApi() handlers are included in the discovery patterns.`
4932
4726
  )
4933
4727
  );
4934
4728
  }
@@ -5031,14 +4825,217 @@ var deployApp = (input) => Effect29.gen(function* () {
5031
4825
  });
5032
4826
 
5033
4827
  // src/deploy/deploy-static-site.ts
5034
- import { Effect as Effect30 } from "effect";
4828
+ import { Effect as Effect31 } from "effect";
5035
4829
  import { Architecture as Architecture3 } from "@aws-sdk/client-lambda";
5036
4830
  import { execSync as execSync2 } from "child_process";
4831
+ import * as fs5 from "fs";
4832
+ import * as path7 from "path";
4833
+
4834
+ // src/deploy/seo.ts
4835
+ import { Effect as Effect30 } from "effect";
4836
+ import * as fs4 from "fs";
5037
4837
  import * as path6 from "path";
5038
- var deployMiddlewareLambda = (input) => Effect30.gen(function* () {
4838
+ import * as crypto4 from "crypto";
4839
+ import * as os from "os";
4840
+ var collectHtmlPaths = (sourceDir) => {
4841
+ const paths = [];
4842
+ const walk = (dir, prefix) => {
4843
+ for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
4844
+ const key = prefix ? `${prefix}/${entry.name}` : entry.name;
4845
+ if (entry.isDirectory()) {
4846
+ walk(path6.join(dir, entry.name), key);
4847
+ } else if (entry.name.endsWith(".html") || entry.name.endsWith(".htm")) {
4848
+ if (entry.name === "404.html" || entry.name === "500.html") continue;
4849
+ let urlPath = "/" + key;
4850
+ if (urlPath.endsWith("/index.html")) {
4851
+ urlPath = urlPath.slice(0, -"index.html".length);
4852
+ } else if (urlPath.endsWith("/index.htm")) {
4853
+ urlPath = urlPath.slice(0, -"index.htm".length);
4854
+ }
4855
+ paths.push(urlPath);
4856
+ }
4857
+ }
4858
+ };
4859
+ walk(sourceDir, "");
4860
+ return paths.sort();
4861
+ };
4862
+ var generateSitemap = (siteUrl, sourceDir) => {
4863
+ const baseUrl = siteUrl.replace(/\/$/, "");
4864
+ const paths = collectHtmlPaths(sourceDir);
4865
+ const urls = paths.map((urlPath) => ` <url>
4866
+ <loc>${baseUrl}${urlPath}</loc>
4867
+ </url>`).join("\n");
4868
+ return `<?xml version="1.0" encoding="UTF-8"?>
4869
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
4870
+ ${urls}
4871
+ </urlset>
4872
+ `;
4873
+ };
4874
+ var generateRobots = (siteUrl, sitemapName = "sitemap.xml") => {
4875
+ const baseUrl = siteUrl.replace(/\/$/, "");
4876
+ return `User-agent: *
4877
+ Allow: /
4878
+
4879
+ Sitemap: ${baseUrl}/${sitemapName}
4880
+ `;
4881
+ };
4882
+ var createJwt = (serviceAccount) => {
4883
+ const now = Math.floor(Date.now() / 1e3);
4884
+ const header = Buffer.from(JSON.stringify({ alg: "RS256", typ: "JWT" })).toString("base64url");
4885
+ const payload = Buffer.from(
4886
+ JSON.stringify({
4887
+ iss: serviceAccount.client_email,
4888
+ scope: "https://www.googleapis.com/auth/indexing",
4889
+ aud: "https://oauth2.googleapis.com/token",
4890
+ iat: now,
4891
+ exp: now + 3600
4892
+ })
4893
+ ).toString("base64url");
4894
+ const signInput = `${header}.${payload}`;
4895
+ const signature = crypto4.createSign("RSA-SHA256").update(signInput).sign(serviceAccount.private_key, "base64url");
4896
+ return `${signInput}.${signature}`;
4897
+ };
4898
+ var getAccessToken = async (serviceAccount) => {
4899
+ const jwt = createJwt(serviceAccount);
4900
+ const response = await fetch("https://oauth2.googleapis.com/token", {
4901
+ method: "POST",
4902
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
4903
+ body: `grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=${jwt}`
4904
+ });
4905
+ if (!response.ok) {
4906
+ const text = await response.text();
4907
+ throw new Error(`Failed to get Google access token: ${response.status} ${text}`);
4908
+ }
4909
+ const data = await response.json();
4910
+ return data.access_token;
4911
+ };
4912
+ var publishUrl = async (accessToken, url) => {
4913
+ const response = await fetch("https://indexing.googleapis.com/v3/urlNotifications:publish", {
4914
+ method: "POST",
4915
+ headers: {
4916
+ "Content-Type": "application/json",
4917
+ Authorization: `Bearer ${accessToken}`
4918
+ },
4919
+ body: JSON.stringify({ url, type: "URL_UPDATED" })
4920
+ });
4921
+ if (!response.ok) {
4922
+ const text = await response.text();
4923
+ return { url, ok: false, error: `${response.status} ${text}` };
4924
+ }
4925
+ return { url, ok: true };
4926
+ };
4927
+ var collectHtmlKeys = (sourceDir) => {
4928
+ const keys = [];
4929
+ const walk = (dir, prefix) => {
4930
+ for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
4931
+ const fullPath = path6.join(dir, entry.name);
4932
+ const key = prefix ? `${prefix}/${entry.name}` : entry.name;
4933
+ if (entry.isDirectory()) {
4934
+ walk(fullPath, key);
4935
+ } else if (entry.name.endsWith(".html") || entry.name.endsWith(".htm")) {
4936
+ if (entry.name === "404.html" || entry.name === "500.html") continue;
4937
+ keys.push(key);
4938
+ }
4939
+ }
4940
+ };
4941
+ walk(sourceDir, "");
4942
+ return keys;
4943
+ };
4944
+ var keysToUrls = (siteUrl, keys) => {
4945
+ const baseUrl = siteUrl.replace(/\/$/, "");
4946
+ return keys.map((key) => {
4947
+ let urlPath = "/" + key;
4948
+ if (urlPath.endsWith("/index.html")) {
4949
+ urlPath = urlPath.slice(0, -"index.html".length);
4950
+ } else if (urlPath.endsWith("/index.htm")) {
4951
+ urlPath = urlPath.slice(0, -"index.htm".length);
4952
+ }
4953
+ return `${baseUrl}${urlPath}`;
4954
+ });
4955
+ };
4956
+ var INDEXED_URLS_KEY = "_effortless/indexed-urls.json";
4957
+ var loadIndexedUrls = (bucketName) => Effect30.gen(function* () {
4958
+ const result = yield* s3_exports.make("get_object", {
4959
+ Bucket: bucketName,
4960
+ Key: INDEXED_URLS_KEY
4961
+ }).pipe(Effect30.option);
4962
+ if (result._tag === "None") return /* @__PURE__ */ new Set();
4963
+ const body = yield* Effect30.tryPromise({
4964
+ try: () => result.value.Body?.transformToString("utf-8") ?? Promise.resolve("[]"),
4965
+ catch: () => new Error("Failed to read indexed URLs from S3")
4966
+ });
4967
+ const urls = JSON.parse(body);
4968
+ return new Set(urls);
4969
+ });
4970
+ var saveIndexedUrls = (bucketName, urls) => s3_exports.make("put_object", {
4971
+ Bucket: bucketName,
4972
+ Key: INDEXED_URLS_KEY,
4973
+ Body: JSON.stringify([...urls].sort(), null, 2),
4974
+ ContentType: "application/json; charset=utf-8"
4975
+ });
4976
+ var submitToGoogleIndexing = (input) => Effect30.gen(function* () {
4977
+ const { serviceAccountPath, projectDir, bucketName, allPageUrls } = input;
4978
+ const indexedUrls = yield* loadIndexedUrls(bucketName);
4979
+ const urlsToSubmit = allPageUrls.filter((url) => !indexedUrls.has(url));
4980
+ const currentUrlSet = new Set(allPageUrls);
4981
+ for (const url of indexedUrls) {
4982
+ if (!currentUrlSet.has(url)) {
4983
+ indexedUrls.delete(url);
4984
+ }
4985
+ }
4986
+ if (urlsToSubmit.length === 0) {
4987
+ yield* Effect30.logDebug("All pages already indexed, skipping Google Indexing API");
4988
+ return { submitted: 0, failed: 0, skipped: allPageUrls.length };
4989
+ }
4990
+ const expanded = serviceAccountPath.startsWith("~/") ? path6.join(os.homedir(), serviceAccountPath.slice(2)) : serviceAccountPath;
4991
+ const keyPath = path6.resolve(projectDir, expanded);
4992
+ if (!fs4.existsSync(keyPath)) {
4993
+ return yield* Effect30.fail(
4994
+ new Error(`Google service account key not found: ${keyPath}`)
4995
+ );
4996
+ }
4997
+ const serviceAccount = JSON.parse(fs4.readFileSync(keyPath, "utf-8"));
4998
+ if (!serviceAccount.client_email || !serviceAccount.private_key) {
4999
+ return yield* Effect30.fail(
5000
+ new Error(`Invalid service account key: missing client_email or private_key`)
5001
+ );
5002
+ }
5003
+ yield* Effect30.logDebug(`Authenticating with Google as ${serviceAccount.client_email}`);
5004
+ const accessToken = yield* Effect30.tryPromise({
5005
+ try: () => getAccessToken(serviceAccount),
5006
+ catch: (error) => new Error(`Google auth failed: ${error}`)
5007
+ });
5008
+ const maxUrls = Math.min(urlsToSubmit.length, 200);
5009
+ if (urlsToSubmit.length > 200) {
5010
+ yield* Effect30.logDebug(
5011
+ `Google Indexing API daily quota is 200. Submitting first 200 of ${urlsToSubmit.length} URLs.`
5012
+ );
5013
+ }
5014
+ let submitted = 0;
5015
+ let failed = 0;
5016
+ for (const url of urlsToSubmit.slice(0, maxUrls)) {
5017
+ const result = yield* Effect30.tryPromise({
5018
+ try: () => publishUrl(accessToken, url),
5019
+ catch: (error) => new Error(`Failed to submit ${url}: ${error}`)
5020
+ });
5021
+ if (result.ok) {
5022
+ submitted++;
5023
+ indexedUrls.add(url);
5024
+ } else {
5025
+ failed++;
5026
+ yield* Effect30.logDebug(`Failed to index ${result.url}: ${result.error}`);
5027
+ }
5028
+ }
5029
+ yield* saveIndexedUrls(bucketName, indexedUrls);
5030
+ yield* Effect30.logDebug(`Google Indexing: ${submitted} submitted, ${failed} failed, ${allPageUrls.length - urlsToSubmit.length} already indexed`);
5031
+ return { submitted, failed, skipped: allPageUrls.length - urlsToSubmit.length };
5032
+ });
5033
+
5034
+ // src/deploy/deploy-static-site.ts
5035
+ var deployMiddlewareLambda = (input) => Effect31.gen(function* () {
5039
5036
  const { projectDir, project, stage, handlerName, file, exportName, tagCtx } = input;
5040
5037
  const middlewareName = `${handlerName}-middleware`;
5041
- yield* Effect30.logDebug(`Deploying middleware Lambda@Edge: ${middlewareName}`);
5038
+ yield* Effect31.logDebug(`Deploying middleware Lambda@Edge: ${middlewareName}`);
5042
5039
  const roleArn = yield* ensureEdgeRole(
5043
5040
  project,
5044
5041
  stage,
@@ -5064,14 +5061,14 @@ var deployMiddlewareLambda = (input) => Effect30.gen(function* () {
5064
5061
  architecture: Architecture3.x86_64,
5065
5062
  tags: makeTags(tagCtx, "lambda")
5066
5063
  }).pipe(
5067
- Effect30.provide(clients_exports.makeClients({ lambda: { region: "us-east-1" } }))
5064
+ Effect31.provide(clients_exports.makeClients({ lambda: { region: "us-east-1" } }))
5068
5065
  );
5069
5066
  const { versionArn } = yield* publishVersion(
5070
5067
  `${project}-${stage}-${middlewareName}`
5071
5068
  ).pipe(
5072
- Effect30.provide(clients_exports.makeClients({ lambda: { region: "us-east-1" } }))
5069
+ Effect31.provide(clients_exports.makeClients({ lambda: { region: "us-east-1" } }))
5073
5070
  );
5074
- yield* Effect30.logDebug(`Middleware deployed: ${versionArn}`);
5071
+ yield* Effect31.logDebug(`Middleware deployed: ${versionArn}`);
5075
5072
  return { versionArn };
5076
5073
  });
5077
5074
  var ERROR_PAGE_KEY = "_effortless/404.html";
@@ -5109,7 +5106,7 @@ var generateErrorPageHtml = () => `<!DOCTYPE html>
5109
5106
  </div>
5110
5107
  </body>
5111
5108
  </html>`;
5112
- var deployStaticSite = (input) => Effect30.gen(function* () {
5109
+ var deployStaticSite = (input) => Effect31.gen(function* () {
5113
5110
  const { projectDir, project, region, fn: fn13 } = input;
5114
5111
  const { exportName, config } = fn13;
5115
5112
  const stage = resolveStage(input.stage);
@@ -5118,16 +5115,16 @@ var deployStaticSite = (input) => Effect30.gen(function* () {
5118
5115
  const tagCtx = { project, stage, handler: handlerName };
5119
5116
  const routePatterns = fn13.routePatterns;
5120
5117
  if (routePatterns.length > 0 && !input.apiOriginDomain) {
5121
- return yield* Effect30.fail(
5118
+ return yield* Effect31.fail(
5122
5119
  new Error(
5123
- `Static site "${exportName}" has routes but no API Gateway exists. Ensure defineHttp() handlers are included in the discovery patterns.`
5120
+ `Static site "${exportName}" has routes but no API handler was deployed. Ensure defineApi() handlers are included in the discovery patterns.`
5124
5121
  )
5125
5122
  );
5126
5123
  }
5127
5124
  if (config.build) {
5128
- yield* Effect30.logDebug(`Building site: ${config.build}`);
5125
+ yield* Effect31.logDebug(`Building site: ${config.build}`);
5129
5126
  const buildStart = Date.now();
5130
- yield* Effect30.try({
5127
+ yield* Effect31.try({
5131
5128
  try: () => execSync2(config.build, {
5132
5129
  cwd: projectDir,
5133
5130
  stdio: input.verbose ? "inherit" : "pipe"
@@ -5140,7 +5137,7 @@ var deployStaticSite = (input) => Effect30.gen(function* () {
5140
5137
  return new Error(`Site build failed: ${config.build}`);
5141
5138
  }
5142
5139
  });
5143
- yield* Effect30.logDebug(`Site built in ${((Date.now() - buildStart) / 1e3).toFixed(1)}s`);
5140
+ yield* Effect31.logDebug(`Site built in ${((Date.now() - buildStart) / 1e3).toFixed(1)}s`);
5144
5141
  }
5145
5142
  const bucketName = `${project}-${stage}-${handlerName}-site`.toLowerCase();
5146
5143
  yield* ensureBucket({
@@ -5164,10 +5161,10 @@ var deployStaticSite = (input) => Effect30.gen(function* () {
5164
5161
  if (certCoversWww) {
5165
5162
  aliases = [domain, wwwCandidate];
5166
5163
  wwwDomain = wwwCandidate;
5167
- yield* Effect30.logDebug(`ACM certificate covers ${wwwCandidate}, enabling www \u2192 non-www redirect`);
5164
+ yield* Effect31.logDebug(`ACM certificate covers ${wwwCandidate}, enabling www \u2192 non-www redirect`);
5168
5165
  } else {
5169
5166
  aliases = [domain];
5170
- yield* Effect30.logWarning(
5167
+ yield* Effect31.logWarning(
5171
5168
  `ACM certificate does not cover ${wwwCandidate}. For SEO, add ${wwwCandidate} to your ACM certificate in us-east-1 to enable www \u2192 non-www redirect.`
5172
5169
  );
5173
5170
  }
@@ -5188,7 +5185,7 @@ var deployStaticSite = (input) => Effect30.gen(function* () {
5188
5185
  exportName,
5189
5186
  tagCtx
5190
5187
  }).pipe(
5191
- Effect30.provide(clients_exports.makeClients({ iam: { region: "us-east-1" } }))
5188
+ Effect31.provide(clients_exports.makeClients({ iam: { region: "us-east-1" } }))
5192
5189
  );
5193
5190
  lambdaEdgeArn = result.versionArn;
5194
5191
  } else {
@@ -5223,8 +5220,32 @@ var deployStaticSite = (input) => Effect30.gen(function* () {
5223
5220
  ...input.apiOriginDomain && routePatterns.length > 0 ? { apiOriginDomain: input.apiOriginDomain, routePatterns } : {}
5224
5221
  });
5225
5222
  yield* putBucketPolicyForOAC(bucketName, distributionArn);
5226
- const sourceDir = path6.resolve(projectDir, config.dir);
5223
+ const sourceDir = path7.resolve(projectDir, config.dir);
5227
5224
  yield* syncFiles({ bucketName, sourceDir });
5225
+ const seo = config.seo;
5226
+ const siteUrl = domain ? `https://${domain}` : `https://${domainName}`;
5227
+ const seoGenerated = [];
5228
+ if (seo) {
5229
+ const sitemapName = seo.sitemap;
5230
+ if (!fs5.existsSync(path7.join(sourceDir, sitemapName))) {
5231
+ const sitemap = generateSitemap(siteUrl, sourceDir);
5232
+ yield* putObject({
5233
+ bucketName,
5234
+ key: sitemapName,
5235
+ body: sitemap,
5236
+ contentType: "application/xml; charset=utf-8"
5237
+ });
5238
+ seoGenerated.push(sitemapName);
5239
+ }
5240
+ const robots = generateRobots(siteUrl, sitemapName);
5241
+ yield* putObject({
5242
+ bucketName,
5243
+ key: "robots.txt",
5244
+ body: robots,
5245
+ contentType: "text/plain; charset=utf-8"
5246
+ });
5247
+ seoGenerated.push("robots.txt");
5248
+ }
5228
5249
  if (!isSpa && !config.errorPage) {
5229
5250
  yield* putObject({
5230
5251
  bucketName,
@@ -5234,21 +5255,33 @@ var deployStaticSite = (input) => Effect30.gen(function* () {
5234
5255
  });
5235
5256
  }
5236
5257
  yield* invalidateDistribution(distributionId);
5237
- const url = domain ? `https://${domain}` : `https://${domainName}`;
5238
- yield* Effect30.logDebug(`Static site deployed: ${url}`);
5258
+ let indexingResult;
5259
+ if (seo?.googleIndexing) {
5260
+ const allHtmlKeys = collectHtmlKeys(sourceDir);
5261
+ const allPageUrls = keysToUrls(siteUrl, allHtmlKeys);
5262
+ indexingResult = yield* submitToGoogleIndexing({
5263
+ serviceAccountPath: seo.googleIndexing,
5264
+ projectDir,
5265
+ bucketName,
5266
+ allPageUrls
5267
+ });
5268
+ }
5269
+ yield* Effect31.logDebug(`Static site deployed: ${siteUrl}`);
5239
5270
  return {
5240
5271
  exportName,
5241
5272
  handlerName,
5242
- url,
5273
+ url: siteUrl,
5243
5274
  distributionId,
5244
- bucketName
5275
+ bucketName,
5276
+ seoGenerated: seoGenerated.length > 0 ? seoGenerated : void 0,
5277
+ indexingResult
5245
5278
  };
5246
5279
  });
5247
5280
 
5248
5281
  // src/deploy/deploy-fifo-queue.ts
5249
- import { Effect as Effect31 } from "effect";
5282
+ import { Effect as Effect32 } from "effect";
5250
5283
  var FIFO_QUEUE_DEFAULT_PERMISSIONS = ["sqs:*", "logs:*"];
5251
- var deployFifoQueueFunction = ({ input, fn: fn13, layerArn, external, depsEnv, depsPermissions, staticGlobs }) => Effect31.gen(function* () {
5284
+ var deployFifoQueueFunction = ({ input, fn: fn13, layerArn, external, depsEnv, depsPermissions, staticGlobs }) => Effect32.gen(function* () {
5252
5285
  const { exportName, config } = fn13;
5253
5286
  const handlerName = exportName;
5254
5287
  const tagCtx = {
@@ -5256,7 +5289,7 @@ var deployFifoQueueFunction = ({ input, fn: fn13, layerArn, external, depsEnv, d
5256
5289
  stage: resolveStage(input.stage),
5257
5290
  handler: handlerName
5258
5291
  };
5259
- yield* Effect31.logDebug("Creating SQS FIFO queue...");
5292
+ yield* Effect32.logDebug("Creating SQS FIFO queue...");
5260
5293
  const queueName = `${input.project}-${tagCtx.stage}-${handlerName}`;
5261
5294
  const timeout = config.timeout ?? 30;
5262
5295
  const { queueUrl, queueArn } = yield* ensureFifoQueue({
@@ -5286,14 +5319,14 @@ var deployFifoQueueFunction = ({ input, fn: fn13, layerArn, external, depsEnv, d
5286
5319
  ...depsPermissions ? { depsPermissions } : {},
5287
5320
  ...staticGlobs && staticGlobs.length > 0 ? { staticGlobs } : {}
5288
5321
  });
5289
- yield* Effect31.logDebug("Setting up SQS event source mapping...");
5322
+ yield* Effect32.logDebug("Setting up SQS event source mapping...");
5290
5323
  yield* ensureSqsEventSourceMapping({
5291
5324
  functionArn,
5292
5325
  queueArn,
5293
5326
  batchSize: config.batchSize ?? 10,
5294
5327
  batchWindow: config.batchWindow
5295
5328
  });
5296
- yield* Effect31.logDebug(`FIFO queue deployment complete! Queue: ${queueUrl}`);
5329
+ yield* Effect32.logDebug(`FIFO queue deployment complete! Queue: ${queueUrl}`);
5297
5330
  return {
5298
5331
  exportName,
5299
5332
  functionArn,
@@ -5304,9 +5337,9 @@ var deployFifoQueueFunction = ({ input, fn: fn13, layerArn, external, depsEnv, d
5304
5337
  });
5305
5338
 
5306
5339
  // src/deploy/deploy-bucket.ts
5307
- import { Effect as Effect32 } from "effect";
5340
+ import { Effect as Effect33 } from "effect";
5308
5341
  var BUCKET_DEFAULT_PERMISSIONS = ["s3:*", "logs:*"];
5309
- var deployBucketFunction = ({ input, fn: fn13, layerArn, external, depsEnv, depsPermissions, staticGlobs }) => Effect32.gen(function* () {
5342
+ var deployBucketFunction = ({ input, fn: fn13, layerArn, external, depsEnv, depsPermissions, staticGlobs }) => Effect33.gen(function* () {
5310
5343
  const { exportName, config, hasHandler } = fn13;
5311
5344
  const handlerName = exportName;
5312
5345
  const tagCtx = {
@@ -5314,7 +5347,7 @@ var deployBucketFunction = ({ input, fn: fn13, layerArn, external, depsEnv, deps
5314
5347
  stage: resolveStage(input.stage),
5315
5348
  handler: handlerName
5316
5349
  };
5317
- yield* Effect32.logDebug("Creating S3 bucket...");
5350
+ yield* Effect33.logDebug("Creating S3 bucket...");
5318
5351
  const bucketName = `${input.project}-${tagCtx.stage}-${handlerName}`;
5319
5352
  const { bucketArn } = yield* ensureBucket({
5320
5353
  name: bucketName,
@@ -5322,7 +5355,7 @@ var deployBucketFunction = ({ input, fn: fn13, layerArn, external, depsEnv, deps
5322
5355
  tags: makeTags(tagCtx, "s3-bucket")
5323
5356
  });
5324
5357
  if (!hasHandler) {
5325
- yield* Effect32.logDebug(`Bucket deployment complete (resource-only)! Bucket: ${bucketName}`);
5358
+ yield* Effect33.logDebug(`Bucket deployment complete (resource-only)! Bucket: ${bucketName}`);
5326
5359
  return {
5327
5360
  exportName,
5328
5361
  status: "resource-only",
@@ -5354,7 +5387,7 @@ var deployBucketFunction = ({ input, fn: fn13, layerArn, external, depsEnv, deps
5354
5387
  prefix: config.prefix,
5355
5388
  suffix: config.suffix
5356
5389
  });
5357
- yield* Effect32.logDebug(`Bucket deployment complete! Bucket: ${bucketName}`);
5390
+ yield* Effect33.logDebug(`Bucket deployment complete! Bucket: ${bucketName}`);
5358
5391
  return {
5359
5392
  exportName,
5360
5393
  functionArn,
@@ -5365,8 +5398,8 @@ var deployBucketFunction = ({ input, fn: fn13, layerArn, external, depsEnv, deps
5365
5398
  });
5366
5399
 
5367
5400
  // src/deploy/deploy-mailer.ts
5368
- import { Effect as Effect33, Console } from "effect";
5369
- var deployMailer = ({ project, stage, region, fn: fn13 }) => Effect33.gen(function* () {
5401
+ import { Effect as Effect34, Console } from "effect";
5402
+ var deployMailer = ({ project, stage, region, fn: fn13 }) => Effect34.gen(function* () {
5370
5403
  const { exportName, config } = fn13;
5371
5404
  const handlerName = exportName;
5372
5405
  const resolvedStage = resolveStage(stage);
@@ -5375,7 +5408,7 @@ var deployMailer = ({ project, stage, region, fn: fn13 }) => Effect33.gen(functi
5375
5408
  stage: resolvedStage,
5376
5409
  handler: handlerName
5377
5410
  };
5378
- yield* Effect33.logDebug(`Ensuring SES identity for ${config.domain}...`);
5411
+ yield* Effect34.logDebug(`Ensuring SES identity for ${config.domain}...`);
5379
5412
  const { domain, verified, dkimRecords } = yield* ensureSesIdentity({
5380
5413
  domain: config.domain,
5381
5414
  tags: makeTags(tagCtx, "ses")
@@ -5398,28 +5431,6 @@ var deployMailer = ({ project, stage, region, fn: fn13 }) => Effect33.gen(functi
5398
5431
  };
5399
5432
  });
5400
5433
 
5401
- // src/deploy/deploy-api.ts
5402
- import { Effect as Effect34 } from "effect";
5403
- var deployApiFunction = ({ input, fn: fn13, layerArn, external, depsEnv, depsPermissions, staticGlobs }) => Effect34.gen(function* () {
5404
- const { exportName, config } = fn13;
5405
- const handlerName = exportName;
5406
- const { functionArn, status } = yield* deployCoreLambda({
5407
- input,
5408
- exportName,
5409
- handlerName,
5410
- bundleType: "api",
5411
- ...config.permissions ? { permissions: config.permissions } : {},
5412
- ...config.memory ? { memory: config.memory } : {},
5413
- ...config.timeout ? { timeout: config.timeout } : {},
5414
- ...layerArn ? { layerArn } : {},
5415
- ...external ? { external } : {},
5416
- ...depsEnv ? { depsEnv } : {},
5417
- ...depsPermissions ? { depsPermissions } : {},
5418
- ...staticGlobs && staticGlobs.length > 0 ? { staticGlobs } : {}
5419
- });
5420
- return { exportName, functionArn, status, config, handlerName };
5421
- });
5422
-
5423
5434
  // src/deploy/deploy.ts
5424
5435
  var statusLabel = (status) => {
5425
5436
  switch (status) {
@@ -5560,7 +5571,6 @@ var buildBucketNameMap = (bucketHandlers, project, stage) => {
5560
5571
  var validateDeps = (discovered, tableNameMap, bucketNameMap, mailerDomainMap) => {
5561
5572
  const errors = [];
5562
5573
  const allGroups = [
5563
- ...discovered.httpHandlers,
5564
5574
  ...discovered.apiHandlers,
5565
5575
  ...discovered.tableHandlers,
5566
5576
  ...discovered.fifoQueueHandlers,
@@ -5650,38 +5660,6 @@ var resolveHandlerEnv = (depsKeys, paramEntries, ctx) => {
5650
5660
  depsPermissions: resolved?.depsPermissions ?? []
5651
5661
  };
5652
5662
  };
5653
- var buildHttpTasks = (ctx, handlers, apiId, results) => {
5654
- const tasks = [];
5655
- const { region } = ctx.input;
5656
- for (const { file, exports } of handlers) {
5657
- for (const fn13 of exports) {
5658
- tasks.push(
5659
- Effect35.gen(function* () {
5660
- const env = resolveHandlerEnv(fn13.depsKeys, fn13.paramEntries, ctx);
5661
- const { exportName, functionArn, status, config, handlerName } = yield* deployLambda({
5662
- input: makeDeployInput(ctx, file),
5663
- fn: fn13,
5664
- ...ctx.layerArn ? { layerArn: ctx.layerArn } : {},
5665
- ...ctx.external.length > 0 ? { external: ctx.external } : {},
5666
- depsEnv: env.depsEnv,
5667
- depsPermissions: env.depsPermissions,
5668
- ...fn13.staticGlobs.length > 0 ? { staticGlobs: fn13.staticGlobs } : {}
5669
- }).pipe(Effect35.provide(clients_exports.makeClients({ lambda: { region }, iam: { region } })));
5670
- const { apiUrl: handlerUrl } = yield* addRouteToApi({
5671
- apiId,
5672
- region,
5673
- functionArn,
5674
- method: config.method,
5675
- path: config.path
5676
- }).pipe(Effect35.provide(clients_exports.makeClients({ lambda: { region }, apigatewayv2: { region } })));
5677
- results.push({ exportName, url: handlerUrl, functionArn });
5678
- yield* ctx.logComplete(exportName, "http", status);
5679
- })
5680
- );
5681
- }
5682
- }
5683
- return tasks;
5684
- };
5685
5663
  var buildTableTasks = (ctx, handlers, results) => {
5686
5664
  const tasks = [];
5687
5665
  const { region } = ctx.input;
@@ -5707,10 +5685,9 @@ var buildTableTasks = (ctx, handlers, results) => {
5707
5685
  }
5708
5686
  return tasks;
5709
5687
  };
5710
- var buildAppTasks = (ctx, handlers, results, apiId) => {
5688
+ var buildAppTasks = (ctx, handlers, results, apiOriginDomain) => {
5711
5689
  const tasks = [];
5712
5690
  const { region } = ctx.input;
5713
- const apiOriginDomain = apiId ? `${apiId}.execute-api.${region}.amazonaws.com` : void 0;
5714
5691
  for (const { exports } of handlers) {
5715
5692
  for (const fn13 of exports) {
5716
5693
  tasks.push(
@@ -5739,10 +5716,9 @@ var buildAppTasks = (ctx, handlers, results, apiId) => {
5739
5716
  }
5740
5717
  return tasks;
5741
5718
  };
5742
- var buildStaticSiteTasks = (ctx, handlers, results, apiId) => {
5719
+ var buildStaticSiteTasks = (ctx, handlers, results, apiOriginDomain) => {
5743
5720
  const tasks = [];
5744
5721
  const { region } = ctx.input;
5745
- const apiOriginDomain = apiId ? `${apiId}.execute-api.${region}.amazonaws.com` : void 0;
5746
5722
  for (const { file, exports } of handlers) {
5747
5723
  for (const fn13 of exports) {
5748
5724
  tasks.push(
@@ -5842,7 +5818,7 @@ var buildMailerTasks = (ctx, handlers, results) => {
5842
5818
  }
5843
5819
  return tasks;
5844
5820
  };
5845
- var buildApiTasks = (ctx, handlers, apiId, results) => {
5821
+ var buildApiTasks = (ctx, handlers, results) => {
5846
5822
  const tasks = [];
5847
5823
  const { region } = ctx.input;
5848
5824
  for (const { file, exports } of handlers) {
@@ -5850,7 +5826,7 @@ var buildApiTasks = (ctx, handlers, apiId, results) => {
5850
5826
  tasks.push(
5851
5827
  Effect35.gen(function* () {
5852
5828
  const env = resolveHandlerEnv(fn13.depsKeys, fn13.paramEntries, ctx);
5853
- const { exportName, functionArn, status, config } = yield* deployApiFunction({
5829
+ const { exportName, functionArn, status, handlerName } = yield* deployApiFunction({
5854
5830
  input: makeDeployInput(ctx, file),
5855
5831
  fn: fn13,
5856
5832
  ...ctx.layerArn ? { layerArn: ctx.layerArn } : {},
@@ -5859,21 +5835,14 @@ var buildApiTasks = (ctx, handlers, apiId, results) => {
5859
5835
  depsPermissions: env.depsPermissions,
5860
5836
  ...fn13.staticGlobs.length > 0 ? { staticGlobs: fn13.staticGlobs } : {}
5861
5837
  }).pipe(Effect35.provide(clients_exports.makeClients({ lambda: { region }, iam: { region } })));
5862
- yield* addRouteToApi({
5863
- apiId,
5864
- region,
5865
- functionArn,
5866
- method: "ANY",
5867
- path: config.basePath
5868
- }).pipe(Effect35.provide(clients_exports.makeClients({ lambda: { region }, apigatewayv2: { region } })));
5869
- const { apiUrl: handlerUrl } = yield* addRouteToApi({
5870
- apiId,
5871
- region,
5872
- functionArn,
5873
- method: "ANY",
5874
- path: `${config.basePath}/{proxy+}`
5875
- }).pipe(Effect35.provide(clients_exports.makeClients({ lambda: { region }, apigatewayv2: { region } })));
5876
- results.push({ exportName, url: handlerUrl, functionArn });
5838
+ const lambdaName = `${ctx.input.project}-${ctx.stage}-${handlerName}`;
5839
+ const { functionUrl } = yield* ensureFunctionUrl(lambdaName).pipe(
5840
+ Effect35.provide(clients_exports.makeClients({ lambda: { region } }))
5841
+ );
5842
+ yield* addFunctionUrlPublicAccess(lambdaName).pipe(
5843
+ Effect35.provide(clients_exports.makeClients({ lambda: { region } }))
5844
+ );
5845
+ results.push({ exportName, url: functionUrl, functionArn });
5877
5846
  yield* ctx.logComplete(exportName, "api", status);
5878
5847
  })
5879
5848
  );
@@ -5888,8 +5857,7 @@ var deployProject = (input) => Effect35.gen(function* () {
5888
5857
  return yield* Effect35.fail(new Error(`No files match patterns: ${input.patterns.join(", ")}`));
5889
5858
  }
5890
5859
  yield* Effect35.logDebug(`Found ${files.length} file(s) matching patterns`);
5891
- const { httpHandlers, tableHandlers, appHandlers, staticSiteHandlers, fifoQueueHandlers, bucketHandlers, mailerHandlers, apiHandlers } = discoverHandlers(files);
5892
- const totalHttpHandlers = httpHandlers.reduce((acc, h) => acc + h.exports.length, 0);
5860
+ const { tableHandlers, appHandlers, staticSiteHandlers, fifoQueueHandlers, bucketHandlers, mailerHandlers, apiHandlers } = discoverHandlers(files);
5893
5861
  const totalTableHandlers = tableHandlers.reduce((acc, h) => acc + h.exports.length, 0);
5894
5862
  const totalAppHandlers = appHandlers.reduce((acc, h) => acc + h.exports.length, 0);
5895
5863
  const totalStaticSiteHandlers = input.noSites ? 0 : staticSiteHandlers.reduce((acc, h) => acc + h.exports.length, 0);
@@ -5897,12 +5865,11 @@ var deployProject = (input) => Effect35.gen(function* () {
5897
5865
  const totalBucketHandlers = bucketHandlers.reduce((acc, h) => acc + h.exports.length, 0);
5898
5866
  const totalMailerHandlers = mailerHandlers.reduce((acc, h) => acc + h.exports.length, 0);
5899
5867
  const totalApiHandlers = apiHandlers.reduce((acc, h) => acc + h.exports.length, 0);
5900
- const totalAllHandlers = totalHttpHandlers + totalTableHandlers + totalAppHandlers + totalStaticSiteHandlers + totalFifoQueueHandlers + totalBucketHandlers + totalMailerHandlers + totalApiHandlers;
5868
+ const totalAllHandlers = totalTableHandlers + totalAppHandlers + totalStaticSiteHandlers + totalFifoQueueHandlers + totalBucketHandlers + totalMailerHandlers + totalApiHandlers;
5901
5869
  if (totalAllHandlers === 0) {
5902
5870
  return yield* Effect35.fail(new Error("No handlers found in matched files"));
5903
5871
  }
5904
5872
  const parts = [];
5905
- if (totalHttpHandlers > 0) parts.push(`${totalHttpHandlers} http`);
5906
5873
  if (totalTableHandlers > 0) parts.push(`${totalTableHandlers} table`);
5907
5874
  if (totalAppHandlers > 0) parts.push(`${totalAppHandlers} app`);
5908
5875
  if (totalStaticSiteHandlers > 0) parts.push(`${totalStaticSiteHandlers} site`);
@@ -5912,7 +5879,7 @@ var deployProject = (input) => Effect35.gen(function* () {
5912
5879
  if (totalApiHandlers > 0) parts.push(`${totalApiHandlers} api`);
5913
5880
  yield* Console2.log(`
5914
5881
  ${c.dim("Handlers:")} ${parts.join(", ")}`);
5915
- const discovered = { httpHandlers, tableHandlers, appHandlers, staticSiteHandlers, fifoQueueHandlers, bucketHandlers, mailerHandlers, apiHandlers };
5882
+ const discovered = { tableHandlers, appHandlers, staticSiteHandlers, fifoQueueHandlers, bucketHandlers, mailerHandlers, apiHandlers };
5916
5883
  const requiredParams = collectRequiredParams(discovered, input.project, stage);
5917
5884
  if (requiredParams.length > 0) {
5918
5885
  const { missing } = yield* checkMissingParams(requiredParams).pipe(
@@ -5940,50 +5907,19 @@ var deployProject = (input) => Effect35.gen(function* () {
5940
5907
  }
5941
5908
  return yield* Effect35.fail(new Error("Unresolved deps \u2014 aborting deploy"));
5942
5909
  }
5943
- const { layerArn, layerVersion, layerStatus, external } = yield* prepareLayer({
5910
+ const needsLambda = totalTableHandlers + totalAppHandlers + totalFifoQueueHandlers + totalBucketHandlers + totalMailerHandlers + totalApiHandlers > 0;
5911
+ const { layerArn, layerVersion, layerStatus, external } = needsLambda ? yield* prepareLayer({
5944
5912
  project: input.project,
5945
5913
  stage,
5946
5914
  region: input.region,
5947
5915
  packageDir: input.packageDir ?? input.projectDir
5948
- });
5916
+ }) : { layerArn: void 0, layerVersion: void 0, layerStatus: void 0, external: [] };
5949
5917
  if (layerArn && layerStatus) {
5950
5918
  const status = layerStatus === "cached" ? c.dim("cached") : c.green("created");
5951
5919
  yield* Console2.log(` ${c.dim("Layer:")} ${status} ${c.dim(`v${layerVersion}`)} (${external.length} packages)`);
5952
5920
  }
5953
- let apiId;
5954
- let apiUrl;
5955
- const staticSitesNeedApi = !input.noSites && staticSiteHandlers.some(
5956
- ({ exports }) => exports.some((fn13) => fn13.routePatterns.length > 0)
5957
- );
5958
- const appsNeedApi = appHandlers.some(
5959
- ({ exports }) => exports.some((fn13) => fn13.routePatterns.length > 0)
5960
- );
5961
- if (totalHttpHandlers > 0 || totalApiHandlers > 0 || staticSitesNeedApi || appsNeedApi) {
5962
- const tagCtx = {
5963
- project: input.project,
5964
- stage,
5965
- handler: "api"
5966
- };
5967
- const api = yield* ensureProjectApi({
5968
- projectName: input.project,
5969
- stage: tagCtx.stage,
5970
- region: input.region,
5971
- tags: makeTags(tagCtx, "api-gateway")
5972
- }).pipe(
5973
- Effect35.provide(
5974
- clients_exports.makeClients({
5975
- apigatewayv2: { region: input.region }
5976
- })
5977
- )
5978
- );
5979
- apiId = api.apiId;
5980
- apiUrl = `https://${apiId}.execute-api.${input.region}.amazonaws.com`;
5981
- yield* Console2.log(` ${c.dim("API Gateway:")} ${apiId}`);
5982
- }
5983
5921
  yield* Console2.log("");
5984
5922
  const manifest = [];
5985
- for (const { exports } of httpHandlers)
5986
- for (const fn13 of exports) manifest.push({ name: fn13.exportName, type: "http" });
5987
5923
  for (const { exports } of tableHandlers)
5988
5924
  for (const fn13 of exports) manifest.push({ name: fn13.exportName, type: "table" });
5989
5925
  for (const { exports } of appHandlers)
@@ -6012,7 +5948,6 @@ var deployProject = (input) => Effect35.gen(function* () {
6012
5948
  mailerDomainMap,
6013
5949
  logComplete
6014
5950
  };
6015
- const httpResults = [];
6016
5951
  const tableResults = [];
6017
5952
  const appResults = [];
6018
5953
  const staticSiteResults = [];
@@ -6020,17 +5955,43 @@ var deployProject = (input) => Effect35.gen(function* () {
6020
5955
  const bucketResults = [];
6021
5956
  const mailerResults = [];
6022
5957
  const apiResults = [];
6023
- const tasks = [
6024
- ...apiId ? buildHttpTasks(ctx, httpHandlers, apiId, httpResults) : [],
6025
- ...buildTableTasks(ctx, tableHandlers, tableResults),
6026
- ...buildAppTasks(ctx, appHandlers, appResults, apiId),
6027
- ...input.noSites ? [] : buildStaticSiteTasks(ctx, staticSiteHandlers, staticSiteResults, apiId),
6028
- ...buildFifoQueueTasks(ctx, fifoQueueHandlers, fifoQueueResults),
6029
- ...buildBucketTasks(ctx, bucketHandlers, bucketResults),
6030
- ...buildMailerTasks(ctx, mailerHandlers, mailerResults),
6031
- ...apiId ? buildApiTasks(ctx, apiHandlers, apiId, apiResults) : []
6032
- ];
6033
- yield* Effect35.all(tasks, { concurrency: DEPLOY_CONCURRENCY, discard: true });
5958
+ const staticSitesNeedApi = !input.noSites && staticSiteHandlers.some(
5959
+ ({ exports }) => exports.some((fn13) => fn13.routePatterns.length > 0)
5960
+ );
5961
+ const appsNeedApi = appHandlers.some(
5962
+ ({ exports }) => exports.some((fn13) => fn13.routePatterns.length > 0)
5963
+ );
5964
+ const needsTwoPhase = (staticSitesNeedApi || appsNeedApi) && totalApiHandlers > 0;
5965
+ if (needsTwoPhase) {
5966
+ const phase1Tasks = [
5967
+ ...buildApiTasks(ctx, apiHandlers, apiResults),
5968
+ ...buildTableTasks(ctx, tableHandlers, tableResults),
5969
+ ...buildFifoQueueTasks(ctx, fifoQueueHandlers, fifoQueueResults),
5970
+ ...buildBucketTasks(ctx, bucketHandlers, bucketResults),
5971
+ ...buildMailerTasks(ctx, mailerHandlers, mailerResults)
5972
+ ];
5973
+ yield* Effect35.all(phase1Tasks, { concurrency: DEPLOY_CONCURRENCY, discard: true });
5974
+ const firstApiUrl = apiResults[0]?.url;
5975
+ const apiOriginDomain = firstApiUrl ? firstApiUrl.replace("https://", "").replace(/\/$/, "") : void 0;
5976
+ const phase2Tasks = [
5977
+ ...buildAppTasks(ctx, appHandlers, appResults, apiOriginDomain),
5978
+ ...input.noSites ? [] : buildStaticSiteTasks(ctx, staticSiteHandlers, staticSiteResults, apiOriginDomain)
5979
+ ];
5980
+ if (phase2Tasks.length > 0) {
5981
+ yield* Effect35.all(phase2Tasks, { concurrency: DEPLOY_CONCURRENCY, discard: true });
5982
+ }
5983
+ } else {
5984
+ const tasks = [
5985
+ ...buildApiTasks(ctx, apiHandlers, apiResults),
5986
+ ...buildTableTasks(ctx, tableHandlers, tableResults),
5987
+ ...buildAppTasks(ctx, appHandlers, appResults),
5988
+ ...input.noSites ? [] : buildStaticSiteTasks(ctx, staticSiteHandlers, staticSiteResults),
5989
+ ...buildFifoQueueTasks(ctx, fifoQueueHandlers, fifoQueueResults),
5990
+ ...buildBucketTasks(ctx, bucketHandlers, bucketResults),
5991
+ ...buildMailerTasks(ctx, mailerHandlers, mailerResults)
5992
+ ];
5993
+ yield* Effect35.all(tasks, { concurrency: DEPLOY_CONCURRENCY, discard: true });
5994
+ }
6034
5995
  if (!input.noSites && staticSiteResults.length > 0 || appResults.length > 0) {
6035
5996
  yield* cleanupOrphanedFunctions(input.project, stage).pipe(
6036
5997
  Effect35.provide(clients_exports.makeClients({
@@ -6042,43 +6003,19 @@ var deployProject = (input) => Effect35.gen(function* () {
6042
6003
  )
6043
6004
  );
6044
6005
  }
6045
- if (apiId) {
6046
- const activeRouteKeys = /* @__PURE__ */ new Set();
6047
- for (const { exports } of httpHandlers) {
6048
- for (const fn13 of exports) {
6049
- activeRouteKeys.add(`${fn13.config.method} ${fn13.config.path}`);
6050
- }
6051
- }
6052
- for (const { exports } of apiHandlers) {
6053
- for (const fn13 of exports) {
6054
- activeRouteKeys.add(`ANY ${fn13.config.basePath}`);
6055
- activeRouteKeys.add(`ANY ${fn13.config.basePath}/{proxy+}`);
6056
- }
6057
- }
6058
- yield* removeStaleRoutes(apiId, activeRouteKeys).pipe(
6059
- Effect35.provide(
6060
- clients_exports.makeClients({
6061
- apigatewayv2: { region: input.region }
6062
- })
6063
- )
6064
- );
6065
- }
6066
- if (apiUrl) {
6067
- yield* Effect35.logDebug(`Deployment complete! API: ${apiUrl}`);
6068
- }
6069
- return { apiId, apiUrl, httpResults, tableResults, appResults, staticSiteResults, fifoQueueResults, bucketResults, mailerResults, apiResults };
6006
+ return { tableResults, appResults, staticSiteResults, fifoQueueResults, bucketResults, mailerResults, apiResults };
6070
6007
  });
6071
6008
 
6072
6009
  // src/cli/config.ts
6073
6010
  import { Options } from "@effect/cli";
6074
- import * as path7 from "path";
6075
- import * as fs4 from "fs";
6011
+ import * as path8 from "path";
6012
+ import * as fs6 from "fs";
6076
6013
  import { pathToFileURL } from "url";
6077
6014
  import * as esbuild2 from "esbuild";
6078
6015
  import { Effect as Effect36 } from "effect";
6079
6016
  var loadConfig = Effect36.fn("loadConfig")(function* () {
6080
- const configPath = path7.resolve(process.cwd(), "effortless.config.ts");
6081
- if (!fs4.existsSync(configPath)) {
6017
+ const configPath = path8.resolve(process.cwd(), "effortless.config.ts");
6018
+ if (!fs6.existsSync(configPath)) {
6082
6019
  return null;
6083
6020
  }
6084
6021
  const result = yield* Effect36.tryPromise({
@@ -6097,12 +6034,12 @@ var loadConfig = Effect36.fn("loadConfig")(function* () {
6097
6034
  return null;
6098
6035
  }
6099
6036
  const code = output.text;
6100
- const tempFile = path7.join(process.cwd(), ".effortless-config.mjs");
6101
- fs4.writeFileSync(tempFile, code);
6037
+ const tempFile = path8.join(process.cwd(), ".effortless-config.mjs");
6038
+ fs6.writeFileSync(tempFile, code);
6102
6039
  const mod = yield* Effect36.tryPromise({
6103
6040
  try: () => import(pathToFileURL(tempFile).href),
6104
6041
  catch: (error) => new Error(`Failed to load config: ${error}`)
6105
- }).pipe(Effect36.ensuring(Effect36.sync(() => fs4.unlinkSync(tempFile))));
6042
+ }).pipe(Effect36.ensuring(Effect36.sync(() => fs6.unlinkSync(tempFile))));
6106
6043
  return mod.default;
6107
6044
  });
6108
6045
  var projectOption = Options.text("project").pipe(
@@ -6154,14 +6091,14 @@ var getPatternsFromConfig = (config) => {
6154
6091
  import * as Context13 from "effect/Context";
6155
6092
  import * as Layer14 from "effect/Layer";
6156
6093
  import * as Effect37 from "effect/Effect";
6157
- import * as path8 from "path";
6094
+ import * as path9 from "path";
6158
6095
  var ProjectConfig = class _ProjectConfig extends Context13.Tag("ProjectConfig")() {
6159
6096
  static Live = Layer14.effect(
6160
6097
  _ProjectConfig,
6161
6098
  Effect37.gen(function* () {
6162
6099
  const config = yield* loadConfig();
6163
6100
  const cwd = process.cwd();
6164
- const projectDir = config?.root ? path8.resolve(cwd, config.root) : cwd;
6101
+ const projectDir = config?.root ? path9.resolve(cwd, config.root) : cwd;
6165
6102
  return { config, cwd, projectDir };
6166
6103
  })
6167
6104
  );
@@ -6190,7 +6127,6 @@ var deployCommand = Command.make(
6190
6127
  const clientsLayer = clients_exports.makeClients({
6191
6128
  lambda: { region: finalRegion },
6192
6129
  iam: { region: finalRegion },
6193
- apigatewayv2: { region: finalRegion },
6194
6130
  dynamodb: { region: finalRegion },
6195
6131
  resource_groups_tagging_api: { region: finalRegion },
6196
6132
  s3: { region: finalRegion },
@@ -6215,31 +6151,35 @@ var deployCommand = Command.make(
6215
6151
  noSites,
6216
6152
  verbose
6217
6153
  });
6218
- const total = results.httpResults.length + results.tableResults.length + results.appResults.length + results.staticSiteResults.length + results.apiResults.length;
6154
+ const total = results.tableResults.length + results.appResults.length + results.staticSiteResults.length + results.apiResults.length;
6219
6155
  yield* Console3.log(`
6220
6156
  ${c.green(`Deployed ${total} handler(s):`)}`);
6221
- if (results.apiUrl) {
6222
- yield* Console3.log(`
6223
- API: ${c.cyan(results.apiUrl)}`);
6224
- }
6225
6157
  const summaryLines = [];
6226
- for (const r of results.httpResults) {
6227
- const pathPart = results.apiUrl ? r.url.replace(results.apiUrl, "") : r.url;
6228
- summaryLines.push({ name: r.exportName, line: ` ${c.cyan("[http]")} ${c.bold(r.exportName)} ${c.dim(pathPart)}` });
6229
- }
6230
6158
  for (const r of results.appResults) {
6231
- const pathPart = results.apiUrl ? r.url.replace(results.apiUrl, "") : r.url;
6232
- summaryLines.push({ name: r.exportName, line: ` ${c.cyan("[app]")} ${c.bold(r.exportName)} ${c.dim(pathPart)}` });
6159
+ summaryLines.push({ name: r.exportName, line: ` ${c.cyan("[app]")} ${c.bold(r.exportName)} ${c.dim(r.url)}` });
6233
6160
  }
6234
6161
  for (const r of results.tableResults) {
6235
6162
  summaryLines.push({ name: r.exportName, line: ` ${c.cyan("[table]")} ${c.bold(r.exportName)}` });
6236
6163
  }
6237
6164
  for (const r of results.apiResults) {
6238
- const pathPart = results.apiUrl ? r.url.replace(results.apiUrl, "") : r.url;
6239
- summaryLines.push({ name: r.exportName, line: ` ${c.cyan("[api]")} ${c.bold(r.exportName)} ${c.dim(pathPart)}` });
6165
+ summaryLines.push({ name: r.exportName, line: ` ${c.cyan("[api]")} ${c.bold(r.exportName)} ${c.dim(r.url)}` });
6240
6166
  }
6241
6167
  for (const r of results.staticSiteResults) {
6242
- summaryLines.push({ name: r.exportName, line: ` ${c.cyan("[site]")} ${c.bold(r.exportName)}: ${c.cyan(r.url)}` });
6168
+ let line = ` ${c.cyan("[site]")} ${c.bold(r.exportName)}: ${c.cyan(r.url)}`;
6169
+ const extras = [];
6170
+ if (r.seoGenerated) extras.push(`seo: ${r.seoGenerated.join(", ")}`);
6171
+ if (r.indexingResult) {
6172
+ const { submitted, skipped, failed } = r.indexingResult;
6173
+ if (submitted > 0 || failed > 0) {
6174
+ const parts = [`${submitted} submitted`];
6175
+ if (failed > 0) parts.push(c.red(`${failed} failed`));
6176
+ extras.push(`indexing: ${parts.join(", ")}`);
6177
+ } else {
6178
+ extras.push(`indexing: all ${skipped} pages already indexed`);
6179
+ }
6180
+ }
6181
+ if (extras.length > 0) line += ` ${c.dim(extras.join(" | "))}`;
6182
+ summaryLines.push({ name: r.exportName, line });
6243
6183
  }
6244
6184
  summaryLines.sort((a, b) => a.name.localeCompare(b.name));
6245
6185
  for (const { line } of summaryLines) {
@@ -6248,7 +6188,7 @@ ${c.green(`Deployed ${total} handler(s):`)}`);
6248
6188
  }),
6249
6189
  onSome: (targetValue) => Effect38.gen(function* () {
6250
6190
  if (isFilePath(targetValue)) {
6251
- const fullPath = path9.isAbsolute(targetValue) ? targetValue : path9.resolve(projectDir, targetValue);
6191
+ const fullPath = path10.isAbsolute(targetValue) ? targetValue : path10.resolve(projectDir, targetValue);
6252
6192
  const input = {
6253
6193
  projectDir,
6254
6194
  packageDir: cwd,
@@ -6257,30 +6197,16 @@ ${c.green(`Deployed ${total} handler(s):`)}`);
6257
6197
  stage: finalStage,
6258
6198
  region: finalRegion
6259
6199
  };
6260
- const httpResult = yield* deployAll(input).pipe(
6261
- Effect38.catchIf(
6262
- (e) => e instanceof Error && e.message.includes("No defineHttp"),
6263
- () => Effect38.succeed(null)
6264
- )
6265
- );
6266
6200
  const tableResults = yield* deployAllTables(input).pipe(
6267
6201
  Effect38.catchIf(
6268
6202
  (e) => e instanceof Error && e.message.includes("No defineTable"),
6269
6203
  () => Effect38.succeed([])
6270
6204
  )
6271
6205
  );
6272
- if (!httpResult && tableResults.length === 0) {
6206
+ if (tableResults.length === 0) {
6273
6207
  yield* Console3.error("No handlers found in file");
6274
6208
  return;
6275
6209
  }
6276
- if (httpResult) {
6277
- yield* Console3.log(`
6278
- API Gateway: ${c.cyan(httpResult.apiUrl)}`);
6279
- yield* Console3.log(c.green(`Deployed ${httpResult.handlers.length} HTTP handler(s):`));
6280
- for (const r of httpResult.handlers) {
6281
- yield* Console3.log(` ${c.bold(r.exportName)}: ${c.cyan(r.url)}`);
6282
- }
6283
- }
6284
6210
  if (tableResults.length > 0) {
6285
6211
  yield* Console3.log(c.green(`
6286
6212
  Deployed ${tableResults.length} table handler(s):`));
@@ -6309,7 +6235,7 @@ Deployed ${tableResults.length} table handler(s):`));
6309
6235
  const foundFile = found.file;
6310
6236
  const foundExport = found.exportName;
6311
6237
  const handlerType = found.type;
6312
- yield* Console3.log(`Found handler ${c.bold(targetValue)} in ${c.dim(path9.relative(projectDir, foundFile))}`);
6238
+ yield* Console3.log(`Found handler ${c.bold(targetValue)} in ${c.dim(path10.relative(projectDir, foundFile))}`);
6313
6239
  const input = {
6314
6240
  projectDir,
6315
6241
  packageDir: cwd,
@@ -6340,16 +6266,12 @@ ${c.green("Deployed:")} ${c.cyan(result.url)}`);
6340
6266
  // src/cli/commands/status.ts
6341
6267
  import { Command as Command2 } from "@effect/cli";
6342
6268
  import { Effect as Effect39, Console as Console4, Logger as Logger2, LogLevel as LogLevel2, Option as Option2 } from "effect";
6343
- var { lambda, apigatewayv2: apigateway } = clients_exports;
6269
+ var { lambda } = clients_exports;
6344
6270
  var INTERNAL_HANDLERS = /* @__PURE__ */ new Set(["api", "platform"]);
6345
6271
  var extractFunctionName = (arn) => {
6346
6272
  const match = arn.match(/:function:([^:]+)$/);
6347
6273
  return match?.[1];
6348
6274
  };
6349
- var extractApiId = (arn) => {
6350
- const match = arn.match(/\/apis\/([a-z0-9]+)$/);
6351
- return match?.[1];
6352
- };
6353
6275
  var formatDate = (date) => {
6354
6276
  if (!date) return "";
6355
6277
  const d = typeof date === "string" ? new Date(date) : date;
@@ -6376,12 +6298,6 @@ var getLambdaDetails = (functionName) => Effect39.gen(function* () {
6376
6298
  }).pipe(
6377
6299
  Effect39.catchAll(() => Effect39.succeed({}))
6378
6300
  );
6379
- var getApiUrl = (apiId) => Effect39.gen(function* () {
6380
- const api = yield* apigateway.make("get_api", { ApiId: apiId });
6381
- return api.ApiEndpoint;
6382
- }).pipe(
6383
- Effect39.catchAll(() => Effect39.succeed(void 0))
6384
- );
6385
6301
  var discoverCodeHandlers = (projectDir, patterns) => {
6386
6302
  const files = findHandlerFiles(patterns, projectDir);
6387
6303
  const discovered = discoverHandlers(files);
@@ -6398,22 +6314,17 @@ var discoverAwsHandlers = (resources) => {
6398
6314
  const lambdaResource = handlerResources.find(
6399
6315
  (r) => r.Tags?.find((t) => t.Key === "effortless:type" && t.Value === "lambda")
6400
6316
  );
6401
- const apiResource = handlerResources.find(
6402
- (r) => r.Tags?.find((t) => t.Key === "effortless:type" && t.Value === "api-gateway")
6403
- );
6404
6317
  const typeTag = handlerResources[0]?.Tags?.find((t) => t.Key === "effortless:type");
6405
6318
  const type = typeTag?.Value ?? "unknown";
6406
6319
  handlers.push({
6407
6320
  name,
6408
6321
  type,
6409
- lambdaArn: lambdaResource?.ResourceARN ?? void 0,
6410
- apiArn: apiResource?.ResourceARN ?? void 0
6322
+ lambdaArn: lambdaResource?.ResourceARN ?? void 0
6411
6323
  });
6412
6324
  }
6413
6325
  return handlers;
6414
6326
  };
6415
6327
  var TYPE_LABELS = {
6416
- http: "http",
6417
6328
  table: "table",
6418
6329
  app: "app",
6419
6330
  api: "api",
@@ -6437,9 +6348,9 @@ var STATUS_COLORS = {
6437
6348
  var formatStatus = (status) => {
6438
6349
  return STATUS_COLORS[status](status.padEnd(10));
6439
6350
  };
6440
- var formatRoute = (method, path11) => {
6441
- if (method && path11) return `${method.padEnd(5)} ${path11}`;
6442
- if (path11) return path11;
6351
+ var formatRoute = (method, path12) => {
6352
+ if (method && path12) return `${method.padEnd(5)} ${path12}`;
6353
+ if (path12) return path12;
6443
6354
  return "";
6444
6355
  };
6445
6356
  var formatEntry = (entry) => {
@@ -6472,7 +6383,6 @@ var statusCommand = Command2.make(
6472
6383
  }
6473
6384
  const clientsLayer = clients_exports.makeClients({
6474
6385
  lambda: { region: finalRegion },
6475
- apigatewayv2: { region: finalRegion },
6476
6386
  resource_groups_tagging_api: { region: finalRegion }
6477
6387
  });
6478
6388
  const logLevel = verbose ? LogLevel2.Debug : LogLevel2.Info;
@@ -6486,25 +6396,6 @@ Status for ${c.bold(project + "/" + finalStage)}:
6486
6396
  const resources = yield* getAllResourcesByTags(project, finalStage, finalRegion);
6487
6397
  const awsHandlers = discoverAwsHandlers(resources);
6488
6398
  const awsHandlerNames = new Set(awsHandlers.map((h) => h.name));
6489
- let apiUrl;
6490
- for (const handler of awsHandlers) {
6491
- if (handler.apiArn) {
6492
- const apiId = extractApiId(handler.apiArn);
6493
- if (apiId) {
6494
- apiUrl = yield* getApiUrl(apiId);
6495
- break;
6496
- }
6497
- }
6498
- }
6499
- const apiResource = resources.find(
6500
- (r) => r.Tags?.find((t) => t.Key === "effortless:handler" && t.Value === "api") && r.Tags?.find((t) => t.Key === "effortless:type" && t.Value === "api-gateway")
6501
- );
6502
- if (!apiUrl && apiResource?.ResourceARN) {
6503
- const apiId = extractApiId(apiResource.ResourceARN);
6504
- if (apiId) {
6505
- apiUrl = yield* getApiUrl(apiId);
6506
- }
6507
- }
6508
6399
  const entries = [];
6509
6400
  for (const handler of codeHandlers) {
6510
6401
  const inAws = awsHandlers.find((h) => h.name === handler.name);
@@ -6560,10 +6451,6 @@ Status for ${c.bold(project + "/" + finalStage)}:
6560
6451
  for (const entry of entries) {
6561
6452
  yield* Console4.log(formatEntry(entry));
6562
6453
  }
6563
- if (apiUrl) {
6564
- yield* Console4.log(`
6565
- API: ${c.cyan(apiUrl)}`);
6566
- }
6567
6454
  const counts = {
6568
6455
  new: entries.filter((e) => e.status === "new").length,
6569
6456
  deployed: entries.filter((e) => e.status === "deployed").length,
@@ -7100,8 +6987,8 @@ var logsCommand = Command4.make(
7100
6987
  // src/cli/commands/layer.ts
7101
6988
  import { Command as Command5, Options as Options4 } from "@effect/cli";
7102
6989
  import { Effect as Effect43, Console as Console7 } from "effect";
7103
- import * as path10 from "path";
7104
- import * as fs5 from "fs";
6990
+ import * as path11 from "path";
6991
+ import * as fs7 from "fs";
7105
6992
  var buildOption = Options4.boolean("build").pipe(
7106
6993
  Options4.withDescription("Build layer directory locally (for debugging)")
7107
6994
  );
@@ -7179,13 +7066,13 @@ Layer name: ${projectName}-deps`);
7179
7066
  }
7180
7067
  });
7181
7068
  var buildLayer = (projectDir, output, verbose) => Effect43.gen(function* () {
7182
- const outputDir = path10.isAbsolute(output) ? output : path10.resolve(projectDir, output);
7183
- const layerDir = path10.join(outputDir, "nodejs", "node_modules");
7184
- const layerRoot = path10.join(outputDir, "nodejs");
7185
- if (fs5.existsSync(layerRoot)) {
7186
- fs5.rmSync(layerRoot, { recursive: true });
7069
+ const outputDir = path11.isAbsolute(output) ? output : path11.resolve(projectDir, output);
7070
+ const layerDir = path11.join(outputDir, "nodejs", "node_modules");
7071
+ const layerRoot = path11.join(outputDir, "nodejs");
7072
+ if (fs7.existsSync(layerRoot)) {
7073
+ fs7.rmSync(layerRoot, { recursive: true });
7187
7074
  }
7188
- fs5.mkdirSync(layerDir, { recursive: true });
7075
+ fs7.mkdirSync(layerDir, { recursive: true });
7189
7076
  yield* Console7.log(`
7190
7077
  ${c.bold("=== Building Layer Locally ===")}
7191
7078
  `);
@@ -7240,14 +7127,14 @@ Collected ${allPackages.length} packages for layer`);
7240
7127
  }
7241
7128
  continue;
7242
7129
  }
7243
- const destPath = path10.join(layerDir, pkgName);
7130
+ const destPath = path11.join(layerDir, pkgName);
7244
7131
  if (pkgName.startsWith("@")) {
7245
- const scopeDir = path10.join(layerDir, pkgName.split("/")[0] ?? pkgName);
7246
- if (!fs5.existsSync(scopeDir)) {
7247
- fs5.mkdirSync(scopeDir, { recursive: true });
7132
+ const scopeDir = path11.join(layerDir, pkgName.split("/")[0] ?? pkgName);
7133
+ if (!fs7.existsSync(scopeDir)) {
7134
+ fs7.mkdirSync(scopeDir, { recursive: true });
7248
7135
  }
7249
7136
  }
7250
- fs5.cpSync(srcPath, destPath, { recursive: true });
7137
+ fs7.cpSync(srcPath, destPath, { recursive: true });
7251
7138
  copied++;
7252
7139
  }
7253
7140
  yield* Console7.log(c.green(`