@awsless/cli 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -3894,9 +3894,9 @@ var createLambdaFunction = (parentGroup, ctx, ns, id, local) => {
3894
3894
  const policy = new aws4.iam.RolePolicy(group, "policy", {
3895
3895
  role: role.name,
3896
3896
  name: "lambda-policy",
3897
- policy: new Output3(statementDeps, async (resolve) => {
3897
+ policy: new Output3(statementDeps, async (resolve2) => {
3898
3898
  const list3 = await resolveInputs(statements);
3899
- resolve(
3899
+ resolve2(
3900
3900
  JSON.stringify({
3901
3901
  Version: "2012-10-17",
3902
3902
  Statement: list3.map((statement) => ({
@@ -4645,9 +4645,9 @@ var createPrebuildLambdaFunction = (group, ctx, ns, id, props) => {
4645
4645
  const policy = new aws8.iam.RolePolicy(group, "policy", {
4646
4646
  role: role.name,
4647
4647
  name: "lambda-policy",
4648
- policy: new Output4(statementDeps, async (resolve) => {
4648
+ policy: new Output4(statementDeps, async (resolve2) => {
4649
4649
  const list3 = await resolveInputs2(statements);
4650
- resolve(
4650
+ resolve2(
4651
4651
  JSON.stringify({
4652
4652
  Version: "2012-10-17",
4653
4653
  Statement: list3.map((statement) => ({
@@ -7233,11 +7233,14 @@ var iconFeature = defineFeature({
7233
7233
  }
7234
7234
  });
7235
7235
 
7236
- // src/feature/instance/index.ts
7237
- import { Group as Group26 } from "@terraforge/core";
7236
+ // src/feature/job/index.ts
7237
+ import { Group as Group26, Output as Output7, resolveInputs as resolveInputs4, findInputDeps as findInputDeps4 } from "@terraforge/core";
7238
7238
  import { aws as aws27 } from "@terraforge/aws";
7239
+ import { camelCase as camelCase8 } from "change-case";
7240
+ import { relative as relative8 } from "path";
7239
7241
 
7240
- // src/feature/instance/util.ts
7242
+ // src/feature/job/util.ts
7243
+ import { createHash as createHash4 } from "crypto";
7241
7244
  import { toDays as toDays9, toSeconds as toSeconds9 } from "@awsless/duration";
7242
7245
  import { stringify } from "@awsless/json";
7243
7246
  import { toMebibytes as toMebibytes5 } from "@awsless/size";
@@ -7245,46 +7248,79 @@ import { generateFileHash as generateFileHash2 } from "@awsless/ts-file-cache";
7245
7248
  import { aws as aws26 } from "@terraforge/aws";
7246
7249
  import { Group as Group25, Output as Output6, findInputDeps as findInputDeps3, resolveInputs as resolveInputs3 } from "@terraforge/core";
7247
7250
  import { constantCase as constantCase12, pascalCase as pascalCase3 } from "change-case";
7248
- import { createHash as createHash4 } from "crypto";
7249
7251
  import deepmerge4 from "deepmerge";
7250
- import { join as join18 } from "path";
7251
7252
 
7252
- // src/feature/instance/build/executable.ts
7253
+ // src/feature/job/build/executable.ts
7253
7254
  import { createHash as createHash3 } from "crypto";
7254
- import { readFile as readFile4 } from "fs/promises";
7255
- import { join as join17 } from "path";
7256
- var buildExecutable = async (input, outputPath, architecture) => {
7255
+ import { readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
7256
+ import { join as join17, resolve } from "path";
7257
+ var buildJobExecutable = async (input, outputPath, architecture) => {
7257
7258
  const filePath = join17(outputPath, "program");
7259
+ const wrapperPath = join17(outputPath, "wrapper.ts");
7260
+ const handlerPath = resolve(input);
7261
+ await writeFile3(
7262
+ wrapperPath,
7263
+ `import { parse } from '@awsless/json'
7264
+ import { getObject } from '@awsless/s3'
7265
+ import handler from '${handlerPath}'
7266
+
7267
+ let payload = process.env.PAYLOAD ? parse(process.env.PAYLOAD) : undefined
7268
+ if (typeof payload === 'string' && payload.startsWith('s3://')) {
7269
+ const url = new URL(payload)
7270
+ const response = await getObject({ bucket: url.hostname, key: url.pathname.slice(1) })
7271
+ if (!response) throw new Error('Failed to fetch payload from S3: ' + payload)
7272
+ payload = parse(await response.body.transformToString())
7273
+ }
7274
+ await handler(payload)
7275
+ `
7276
+ );
7258
7277
  const target = architecture === "x86_64" ? "bun-linux-x64" : "bun-linux-arm64";
7259
7278
  let result;
7260
7279
  try {
7261
7280
  result = await Bun.build({
7262
- entrypoints: [input],
7281
+ entrypoints: [wrapperPath],
7263
7282
  compile: {
7264
7283
  target,
7265
7284
  outfile: filePath
7266
7285
  },
7267
- target: "bun"
7286
+ target: "bun",
7287
+ loader: {
7288
+ ".md": "text",
7289
+ ".txt": "text",
7290
+ ".html": "text",
7291
+ ".css": "text",
7292
+ ".yaml": "text",
7293
+ ".yml": "text",
7294
+ ".xml": "text",
7295
+ ".csv": "text",
7296
+ ".svg": "text",
7297
+ ".png": "file",
7298
+ ".jpg": "file",
7299
+ ".jpeg": "file",
7300
+ ".gif": "file",
7301
+ ".webp": "file",
7302
+ ".wasm": "file"
7303
+ }
7268
7304
  });
7269
7305
  } catch (error) {
7270
7306
  throw new ExpectedError(
7271
- `Executable build failed: ${error instanceof Error ? error.message : JSON.stringify(error)}`
7307
+ `Job executable build failed: ${error instanceof Error ? error.message : JSON.stringify(error)}`
7272
7308
  );
7273
7309
  }
7274
7310
  if (!result.success) {
7275
- throw new ExpectedError(`Executable build failed:
7311
+ throw new ExpectedError(`Job executable build failed:
7276
7312
  ${result.logs?.map((log35) => log35.message).join("\n")}`);
7277
7313
  }
7278
7314
  const file = await readFile4(filePath);
7279
7315
  return {
7280
- hash: createHash3("sha1").update(file).update("x86_64").digest("hex"),
7316
+ hash: createHash3("sha1").update(file).update(target).digest("hex"),
7281
7317
  file
7282
7318
  };
7283
7319
  };
7284
7320
 
7285
- // src/feature/instance/util.ts
7286
- var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7287
- const group = new Group25(parentGroup, "instance", ns);
7321
+ // src/feature/job/util.ts
7322
+ var createFargateJob = (parentGroup, ctx, ns, id, local) => {
7323
+ const group = new Group25(parentGroup, "job", ns);
7288
7324
  const name = formatLocalResourceName({
7289
7325
  appName: ctx.app.name,
7290
7326
  stackName: ctx.stack.name,
@@ -7292,13 +7328,13 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7292
7328
  resourceName: id
7293
7329
  });
7294
7330
  const shortName = shortId(`${ctx.app.name}:${ctx.stack.name}:${ns}:${id}:${ctx.appId}`);
7295
- const props = deepmerge4(ctx.appConfig.defaults.instance, local);
7296
- const image2 = props.image || (props.architecture === "arm64" ? "public.ecr.aws/aws-cli/aws-cli:arm64" : "public.ecr.aws/aws-cli/aws-cli:amd64");
7297
- ctx.registerBuild("instance", name, async (build3, { workspace }) => {
7298
- const fingerprint = await generateFileHash2(workspace, local.code.file);
7331
+ const props = deepmerge4(ctx.appConfig.defaults.job, local);
7332
+ const image2 = props.image || "public.ecr.aws/amazonlinux/amazonlinux:2023-minimal";
7333
+ ctx.registerBuild("job", name, async (build3, { workspace }) => {
7334
+ const fingerprint = [await generateFileHash2(workspace, local.code.file), props.architecture].join(":");
7299
7335
  return build3(fingerprint, async (write) => {
7300
- const temp = await createTempFolder(`instance--${name}`);
7301
- const executable = await buildExecutable(local.code.file, temp.path, props.architecture);
7336
+ const temp = await createTempFolder(`job--${name}`);
7337
+ const executable = await buildJobExecutable(local.code.file, temp.path, props.architecture);
7302
7338
  await Promise.all([
7303
7339
  //
7304
7340
  write("HASH", executable.hash),
@@ -7311,10 +7347,10 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7311
7347
  });
7312
7348
  });
7313
7349
  const code = new aws26.s3.BucketObject(group, "code", {
7314
- bucket: ctx.shared.get("instance", "bucket-name"),
7350
+ bucket: ctx.shared.get("job", "bucket-name"),
7315
7351
  key: name,
7316
- source: relativePath(getBuildPath("instance", name, "program")),
7317
- sourceHash: $file(getBuildPath("instance", name, "HASH"))
7352
+ source: relativePath(getBuildPath("job", name, "program")),
7353
+ sourceHash: $file(getBuildPath("job", name, "HASH"))
7318
7354
  });
7319
7355
  const executionRole = new aws26.iam.Role(group, "execution-role", {
7320
7356
  name: shortId(`${shortName}:execution-role`),
@@ -7360,8 +7396,8 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7360
7396
  Statement: [
7361
7397
  {
7362
7398
  Effect: pascalCase3("allow"),
7363
- Action: ["s3:getObject", "s3:HeadObject"],
7364
- Resource: `arn:aws:s3:::${bucket}/${key}`
7399
+ Action: ["s3:GetObject", "s3:HeadObject"],
7400
+ Resource: [`arn:aws:s3:::${bucket}/${key}`, `arn:aws:s3:::${bucket}/payloads/*`]
7365
7401
  }
7366
7402
  ]
7367
7403
  });
@@ -7378,9 +7414,9 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7378
7414
  const policy = new aws26.iam.RolePolicy(group, "policy", {
7379
7415
  role: role.name,
7380
7416
  name: "task-policy",
7381
- policy: new Output6(statementDeps, async (resolve) => {
7417
+ policy: new Output6(statementDeps, async (resolve2) => {
7382
7418
  const list3 = await resolveInputs3(statements);
7383
- resolve(
7419
+ resolve2(
7384
7420
  JSON.stringify({
7385
7421
  Version: "2012-10-17",
7386
7422
  Statement: list3.map((statement) => ({
@@ -7405,7 +7441,6 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7405
7441
  if (props.log.retention && props.log.retention.value > 0n) {
7406
7442
  logGroup = new aws26.cloudwatch.LogGroup(group, "log", {
7407
7443
  name: `/aws/ecs/${name}`,
7408
- // name: `/aws/lambda/${name}`,
7409
7444
  retentionInDays: toDays9(props.log.retention)
7410
7445
  });
7411
7446
  if (ctx.shared.has("on-error-log", "subscriber-arn")) {
@@ -7424,6 +7459,63 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7424
7459
  };
7425
7460
  const variables = {};
7426
7461
  const variableDeps = /* @__PURE__ */ new Set();
7462
+ const taskDependsOn = [code];
7463
+ const accessPoint = props.persistentStorage ? (() => {
7464
+ const persistentStorageSecurityGroup = new aws26.security.Group(
7465
+ group,
7466
+ "persistent-storage-security-group",
7467
+ {
7468
+ name: shortId(`${shortName}:storage-sg`),
7469
+ description: name,
7470
+ vpcId: ctx.shared.get("vpc", "id"),
7471
+ revokeRulesOnDelete: true,
7472
+ tags
7473
+ }
7474
+ );
7475
+ new aws26.vpc.SecurityGroupIngressRule(group, "persistent-storage-ingress-rule", {
7476
+ securityGroupId: persistentStorageSecurityGroup.id,
7477
+ referencedSecurityGroupId: ctx.shared.get("job", "security-group-id"),
7478
+ description: "Allow NFS traffic from this job",
7479
+ ipProtocol: "tcp",
7480
+ fromPort: 2049,
7481
+ toPort: 2049,
7482
+ tags
7483
+ });
7484
+ const fileSystem = new aws26.efs.FileSystem(group, "persistent-storage-file-system", {
7485
+ encrypted: true,
7486
+ performanceMode: "generalPurpose",
7487
+ throughputMode: "elastic",
7488
+ tags: {
7489
+ ...tags,
7490
+ Name: `${name}-storage`
7491
+ }
7492
+ });
7493
+ for (const [index, subnetId] of ctx.shared.get("vpc", "public-subnets").entries()) {
7494
+ const mountTarget = new aws26.efs.MountTarget(group, `mount-target-${index + 1}`, {
7495
+ fileSystemId: fileSystem.id,
7496
+ subnetId,
7497
+ securityGroups: [persistentStorageSecurityGroup.id]
7498
+ });
7499
+ taskDependsOn.push(mountTarget);
7500
+ }
7501
+ const accessPoint2 = new aws26.efs.AccessPoint(group, "access-point", {
7502
+ fileSystemId: fileSystem.id,
7503
+ rootDirectory: {
7504
+ path: `/jobs/${name}`,
7505
+ creationInfo: {
7506
+ ownerUid: 0,
7507
+ ownerGid: 0,
7508
+ permissions: "755"
7509
+ }
7510
+ },
7511
+ tags
7512
+ });
7513
+ taskDependsOn.push(fileSystem, accessPoint2);
7514
+ return {
7515
+ accessPoint: accessPoint2,
7516
+ fileSystem
7517
+ };
7518
+ })() : void 0;
7427
7519
  const task2 = new aws26.ecs.TaskDefinition(
7428
7520
  group,
7429
7521
  "task",
@@ -7440,13 +7532,512 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7440
7532
  operatingSystemFamily: "LINUX"
7441
7533
  },
7442
7534
  trackLatest: true,
7443
- containerDefinitions: new Output6(variableDeps, async (resolve) => {
7535
+ ...accessPoint && {
7536
+ volume: [
7537
+ {
7538
+ name: "persistent-storage",
7539
+ efsVolumeConfiguration: {
7540
+ fileSystemId: accessPoint.fileSystem.id,
7541
+ transitEncryption: "ENABLED",
7542
+ authorizationConfig: {
7543
+ accessPointId: accessPoint.accessPoint.id
7544
+ }
7545
+ }
7546
+ }
7547
+ ]
7548
+ },
7549
+ containerDefinitions: new Output6(variableDeps, async (resolve2) => {
7444
7550
  const data = await resolveInputs3(variables);
7445
7551
  const { s3Bucket, s3Key } = await resolveInputs3({
7446
7552
  s3Bucket: code.bucket,
7447
7553
  s3Key: code.key
7448
7554
  });
7449
- resolve(
7555
+ resolve2(
7556
+ JSON.stringify([
7557
+ {
7558
+ name: `container-${id}`,
7559
+ essential: true,
7560
+ image: image2,
7561
+ workingDirectory: "/usr/app",
7562
+ entryPoint: ["sh", "-c"],
7563
+ command: [
7564
+ [
7565
+ ...props.startupCommand?.length ? (() => {
7566
+ const setupHash = createHash4("sha1").update(props.startupCommand.join(" && ")).digest("hex").slice(0, 8);
7567
+ return [`if [ ! -f /root/.setup-done-${setupHash} ]; then ${props.startupCommand.join(" && ")} && touch /root/.setup-done-${setupHash}; fi`];
7568
+ })() : [],
7569
+ `if [ "$(cat /root/.code-hash 2>/dev/null)" != "$CODE_HASH" ]; then command -v aws >/dev/null 2>&1 || dnf install -y awscli && aws s3 cp s3://${s3Bucket}/${s3Key} /root/program.tmp && mv /root/program.tmp /root/program && chmod +x /root/program && echo "$CODE_HASH" > /root/.code-hash; fi`,
7570
+ `exec timeout --kill-after=10 ${toSeconds9(props.timeout)} /root/program`
7571
+ ].join(" && ")
7572
+ ],
7573
+ environment: Object.entries(data).map(([name2, value]) => ({
7574
+ name: name2,
7575
+ value
7576
+ })),
7577
+ ...accessPoint && {
7578
+ mountPoints: [
7579
+ {
7580
+ sourceVolume: "persistent-storage",
7581
+ containerPath: "/root"
7582
+ }
7583
+ ]
7584
+ },
7585
+ ...logGroup && {
7586
+ logConfiguration: {
7587
+ logDriver: "awslogs",
7588
+ options: {
7589
+ "awslogs-group": `/aws/ecs/${name}`,
7590
+ "awslogs-region": ctx.appConfig.region,
7591
+ "awslogs-stream-prefix": "ecs",
7592
+ mode: "non-blocking"
7593
+ }
7594
+ }
7595
+ }
7596
+ }
7597
+ ])
7598
+ );
7599
+ }),
7600
+ tags
7601
+ },
7602
+ {
7603
+ replaceOnChanges: [
7604
+ "containerDefinitions",
7605
+ "cpu",
7606
+ "memory",
7607
+ "runtimePlatform",
7608
+ "executionRoleArn",
7609
+ "taskRoleArn"
7610
+ ],
7611
+ dependsOn: taskDependsOn
7612
+ }
7613
+ );
7614
+ ctx.onEnv((name2, value) => {
7615
+ variables[name2] = value;
7616
+ for (const dep of findInputDeps3([value])) {
7617
+ variableDeps.add(dep);
7618
+ }
7619
+ });
7620
+ variables.APP = ctx.appConfig.name;
7621
+ variables.APP_ID = ctx.appId;
7622
+ variables.AWS_ACCOUNT_ID = ctx.accountId;
7623
+ variables.STACK = ctx.stackConfig.name;
7624
+ variables.CODE_HASH = code.sourceHash;
7625
+ variables.JOB_CONFIG_HASH = createHash4("sha1").update(stringify(props)).digest("hex");
7626
+ if (props.environment) {
7627
+ for (const [key, value] of Object.entries(props.environment)) {
7628
+ variables[key] = value;
7629
+ }
7630
+ }
7631
+ if (ctx.appConfig.defaults.job.permissions) {
7632
+ statements.push(...ctx.appConfig.defaults.job.permissions);
7633
+ }
7634
+ if ("permissions" in local && local.permissions) {
7635
+ statements.push(...local.permissions);
7636
+ }
7637
+ return { name, task: task2, policy, code, group };
7638
+ };
7639
+
7640
+ // src/feature/job/index.ts
7641
+ var typeGenCode10 = `
7642
+ import type { Mock } from 'vitest'
7643
+
7644
+ type Func = (...args: any[]) => any
7645
+
7646
+ type Invoke<N extends string, F extends Func> = Parameters<F> extends []
7647
+ ? InvokeWithoutPayload<N, F>
7648
+ : unknown extends Parameters<F>[0]
7649
+ ? InvokeWithoutPayload<N, F>
7650
+ : InvokeWithPayload<N, F>
7651
+
7652
+ type InvokeWithPayload<Name extends string, F extends Func> = {
7653
+ readonly name: Name
7654
+ (payload: Parameters<F>[0]): Promise<{ taskArn: string | undefined }>
7655
+ }
7656
+
7657
+ type InvokeWithoutPayload<Name extends string, F extends Func> = {
7658
+ readonly name: Name
7659
+ (payload?: Parameters<F>[0]): Promise<{ taskArn: string | undefined }>
7660
+ }
7661
+
7662
+ type MockHandle<F extends Func> = (payload: Parameters<F>[0]) => void | Promise<void>
7663
+ type MockBuilder<F extends Func> = (handle?: MockHandle<F>) => void
7664
+ type MockObject<F extends Func> = Mock<Parameters<F>, ReturnType<F>>
7665
+ `;
7666
+ var jobFeature = defineFeature({
7667
+ name: "job",
7668
+ async onTypeGen(ctx) {
7669
+ const types2 = new TypeFile("awsless");
7670
+ const resources2 = new TypeObject(1);
7671
+ const mocks = new TypeObject(1);
7672
+ const mockResponses = new TypeObject(1);
7673
+ for (const stack of ctx.stackConfigs) {
7674
+ const resource = new TypeObject(2);
7675
+ const mock = new TypeObject(2);
7676
+ const mockResponse = new TypeObject(2);
7677
+ for (const [name, props] of Object.entries(stack.jobs || {})) {
7678
+ const varName = camelCase8(`${stack.name}-${name}`);
7679
+ const funcName = formatLocalResourceName({
7680
+ appName: ctx.appConfig.name,
7681
+ stackName: stack.name,
7682
+ resourceType: "job",
7683
+ resourceName: name
7684
+ });
7685
+ if ("file" in props.code) {
7686
+ const relFile = relative8(directories.types, props.code.file);
7687
+ types2.addImport(varName, relFile);
7688
+ resource.addType(name, `Invoke<'${funcName}', typeof ${varName}>`);
7689
+ mock.addType(name, `MockBuilder<typeof ${varName}>`);
7690
+ mockResponse.addType(name, `MockObject<typeof ${varName}>`);
7691
+ }
7692
+ }
7693
+ mocks.addType(stack.name, mock);
7694
+ resources2.addType(stack.name, resource);
7695
+ mockResponses.addType(stack.name, mockResponse);
7696
+ }
7697
+ types2.addCode(typeGenCode10);
7698
+ types2.addInterface("JobResources", resources2);
7699
+ types2.addInterface("JobMock", mocks);
7700
+ types2.addInterface("JobMockResponse", mockResponses);
7701
+ await ctx.write("job.d.ts", types2, true);
7702
+ },
7703
+ onBefore(ctx) {
7704
+ const group = new Group26(ctx.base, "job", "asset");
7705
+ const bucket = new aws27.s3.Bucket(group, "bucket", {
7706
+ bucket: formatGlobalResourceName({
7707
+ appName: ctx.app.name,
7708
+ resourceType: "job",
7709
+ resourceName: "assets",
7710
+ postfix: ctx.appId
7711
+ }),
7712
+ forceDestroy: true,
7713
+ lifecycleRule: [
7714
+ {
7715
+ id: "expire-payloads",
7716
+ enabled: true,
7717
+ prefix: "payloads/",
7718
+ expiration: { days: 1 }
7719
+ }
7720
+ ]
7721
+ });
7722
+ ctx.shared.set("job", "bucket-name", bucket.bucket);
7723
+ },
7724
+ onApp(ctx) {
7725
+ const found = ctx.stackConfigs.filter((stack) => {
7726
+ return Object.keys(stack.jobs ?? {}).length > 0;
7727
+ });
7728
+ if (found.length === 0) {
7729
+ return;
7730
+ }
7731
+ const group = new Group26(ctx.base, "job", "cluster");
7732
+ const cluster = new aws27.ecs.Cluster(
7733
+ group,
7734
+ "cluster",
7735
+ {
7736
+ name: `${ctx.app.name}-job`
7737
+ },
7738
+ {
7739
+ replaceOnChanges: ["name"]
7740
+ }
7741
+ );
7742
+ ctx.shared.set("job", "cluster-name", cluster.name);
7743
+ ctx.shared.set("job", "cluster-arn", cluster.arn);
7744
+ const securityGroup = new aws27.security.Group(group, "security-group", {
7745
+ name: `${ctx.app.name}-job`,
7746
+ description: "Shared security group for jobs",
7747
+ vpcId: ctx.shared.get("vpc", "id"),
7748
+ revokeRulesOnDelete: true,
7749
+ tags: {
7750
+ APP: ctx.appConfig.name
7751
+ }
7752
+ });
7753
+ new aws27.vpc.SecurityGroupEgressRule(group, "egress-rule", {
7754
+ securityGroupId: securityGroup.id,
7755
+ description: "Allow all outbound traffic from jobs",
7756
+ ipProtocol: "-1",
7757
+ cidrIpv4: "0.0.0.0/0",
7758
+ tags: {
7759
+ APP: ctx.appConfig.name
7760
+ }
7761
+ });
7762
+ ctx.shared.set("job", "security-group-id", securityGroup.id);
7763
+ },
7764
+ onStack(ctx) {
7765
+ const subnets = ctx.shared.get("vpc", "public-subnets");
7766
+ ctx.addEnv(
7767
+ "JOB_SUBNETS",
7768
+ new Output7(new Set(findInputDeps4(subnets)), async (resolve2) => {
7769
+ const resolved = await resolveInputs4(subnets);
7770
+ resolve2(JSON.stringify(resolved));
7771
+ })
7772
+ );
7773
+ ctx.addEnv("JOB_SECURITY_GROUP", ctx.shared.get("job", "security-group-id"));
7774
+ ctx.addEnv("JOB_PAYLOAD_BUCKET", ctx.shared.get("job", "bucket-name"));
7775
+ const jobs = Object.entries(ctx.stackConfig.jobs ?? {});
7776
+ if (jobs.length === 0) return;
7777
+ for (const [id, props] of jobs) {
7778
+ const group = new Group26(ctx.stack, "job", id);
7779
+ createFargateJob(group, ctx, "job", id, props);
7780
+ }
7781
+ ctx.addStackPermission({
7782
+ actions: ["ecs:RunTask"],
7783
+ resources: [
7784
+ `arn:aws:ecs:${ctx.appConfig.region}:*:task-definition/${ctx.app.name}--${ctx.stackConfig.name}--*`
7785
+ ]
7786
+ });
7787
+ ctx.addStackPermission({
7788
+ actions: ["iam:PassRole"],
7789
+ resources: ["*"],
7790
+ conditions: {
7791
+ StringEquals: {
7792
+ "iam:PassedToService": "ecs-tasks.amazonaws.com"
7793
+ }
7794
+ }
7795
+ });
7796
+ ctx.addStackPermission({
7797
+ actions: ["s3:PutObject"],
7798
+ resources: [
7799
+ `arn:aws:s3:::${formatGlobalResourceName({
7800
+ appName: ctx.app.name,
7801
+ resourceType: "job",
7802
+ resourceName: "assets",
7803
+ postfix: ctx.appId
7804
+ })}/payloads/*`
7805
+ ]
7806
+ });
7807
+ }
7808
+ });
7809
+
7810
+ // src/feature/instance/index.ts
7811
+ import { Group as Group28 } from "@terraforge/core";
7812
+ import { aws as aws29 } from "@terraforge/aws";
7813
+
7814
+ // src/feature/instance/util.ts
7815
+ import { toDays as toDays10, toSeconds as toSeconds10 } from "@awsless/duration";
7816
+ import { stringify as stringify2 } from "@awsless/json";
7817
+ import { toMebibytes as toMebibytes6 } from "@awsless/size";
7818
+ import { generateFileHash as generateFileHash3 } from "@awsless/ts-file-cache";
7819
+ import { aws as aws28 } from "@terraforge/aws";
7820
+ import { Group as Group27, Output as Output8, findInputDeps as findInputDeps5, resolveInputs as resolveInputs5 } from "@terraforge/core";
7821
+ import { constantCase as constantCase13, pascalCase as pascalCase4 } from "change-case";
7822
+ import { createHash as createHash6 } from "crypto";
7823
+ import deepmerge5 from "deepmerge";
7824
+ import { join as join19 } from "path";
7825
+
7826
+ // src/feature/instance/build/executable.ts
7827
+ import { createHash as createHash5 } from "crypto";
7828
+ import { readFile as readFile5 } from "fs/promises";
7829
+ import { join as join18 } from "path";
7830
+ var buildExecutable = async (input, outputPath, architecture) => {
7831
+ const filePath = join18(outputPath, "program");
7832
+ const target = architecture === "x86_64" ? "bun-linux-x64" : "bun-linux-arm64";
7833
+ let result;
7834
+ try {
7835
+ result = await Bun.build({
7836
+ entrypoints: [input],
7837
+ compile: {
7838
+ target,
7839
+ outfile: filePath
7840
+ },
7841
+ target: "bun",
7842
+ loader: {
7843
+ ".md": "text",
7844
+ ".txt": "text",
7845
+ ".html": "text",
7846
+ ".css": "text",
7847
+ ".yaml": "text",
7848
+ ".yml": "text",
7849
+ ".xml": "text",
7850
+ ".csv": "text",
7851
+ ".svg": "text",
7852
+ ".png": "file",
7853
+ ".jpg": "file",
7854
+ ".jpeg": "file",
7855
+ ".gif": "file",
7856
+ ".webp": "file",
7857
+ ".wasm": "file"
7858
+ }
7859
+ });
7860
+ } catch (error) {
7861
+ throw new ExpectedError(
7862
+ `Executable build failed: ${error instanceof Error ? error.message : JSON.stringify(error)}`
7863
+ );
7864
+ }
7865
+ if (!result.success) {
7866
+ throw new ExpectedError(`Executable build failed:
7867
+ ${result.logs?.map((log35) => log35.message).join("\n")}`);
7868
+ }
7869
+ const file = await readFile5(filePath);
7870
+ return {
7871
+ hash: createHash5("sha1").update(file).update("x86_64").digest("hex"),
7872
+ file
7873
+ };
7874
+ };
7875
+
7876
+ // src/feature/instance/util.ts
7877
+ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7878
+ const group = new Group27(parentGroup, "instance", ns);
7879
+ const name = formatLocalResourceName({
7880
+ appName: ctx.app.name,
7881
+ stackName: ctx.stack.name,
7882
+ resourceType: ns,
7883
+ resourceName: id
7884
+ });
7885
+ const shortName = shortId(`${ctx.app.name}:${ctx.stack.name}:${ns}:${id}:${ctx.appId}`);
7886
+ const props = deepmerge5(ctx.appConfig.defaults.instance, local);
7887
+ const image2 = props.image || (props.architecture === "arm64" ? "public.ecr.aws/aws-cli/aws-cli:arm64" : "public.ecr.aws/aws-cli/aws-cli:amd64");
7888
+ ctx.registerBuild("instance", name, async (build3, { workspace }) => {
7889
+ const fingerprint = await generateFileHash3(workspace, local.code.file);
7890
+ return build3(fingerprint, async (write) => {
7891
+ const temp = await createTempFolder(`instance--${name}`);
7892
+ const executable = await buildExecutable(local.code.file, temp.path, props.architecture);
7893
+ await Promise.all([
7894
+ //
7895
+ write("HASH", executable.hash),
7896
+ write("program", executable.file),
7897
+ temp.delete()
7898
+ ]);
7899
+ return {
7900
+ size: formatByteSize(executable.file.byteLength)
7901
+ };
7902
+ });
7903
+ });
7904
+ const code = new aws28.s3.BucketObject(group, "code", {
7905
+ bucket: ctx.shared.get("instance", "bucket-name"),
7906
+ key: name,
7907
+ source: relativePath(getBuildPath("instance", name, "program")),
7908
+ sourceHash: $file(getBuildPath("instance", name, "HASH"))
7909
+ });
7910
+ const executionRole = new aws28.iam.Role(group, "execution-role", {
7911
+ name: shortId(`${shortName}:execution-role`),
7912
+ description: name,
7913
+ assumeRolePolicy: JSON.stringify({
7914
+ Version: "2012-10-17",
7915
+ Statement: [
7916
+ {
7917
+ Effect: "Allow",
7918
+ Action: "sts:AssumeRole",
7919
+ Principal: {
7920
+ Service: ["ecs-tasks.amazonaws.com"]
7921
+ }
7922
+ }
7923
+ ]
7924
+ }),
7925
+ managedPolicyArns: ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
7926
+ });
7927
+ const role = new aws28.iam.Role(
7928
+ group,
7929
+ "task-role",
7930
+ {
7931
+ name: shortId(`${shortName}:task-role`),
7932
+ description: name,
7933
+ assumeRolePolicy: JSON.stringify({
7934
+ Version: "2012-10-17",
7935
+ Statement: [
7936
+ {
7937
+ Effect: "Allow",
7938
+ Action: "sts:AssumeRole",
7939
+ Principal: {
7940
+ Service: ["ecs-tasks.amazonaws.com"]
7941
+ }
7942
+ }
7943
+ ]
7944
+ }),
7945
+ inlinePolicy: [
7946
+ {
7947
+ name: "s3-code-access",
7948
+ policy: $resolve([code.bucket, code.key], (bucket, key) => {
7949
+ return JSON.stringify({
7950
+ Version: "2012-10-17",
7951
+ Statement: [
7952
+ {
7953
+ Effect: pascalCase4("allow"),
7954
+ Action: ["s3:getObject", "s3:HeadObject"],
7955
+ Resource: `arn:aws:s3:::${bucket}/${key}`
7956
+ }
7957
+ ]
7958
+ });
7959
+ })
7960
+ }
7961
+ ]
7962
+ },
7963
+ {
7964
+ dependsOn: [code]
7965
+ }
7966
+ );
7967
+ const statements = [];
7968
+ const statementDeps = /* @__PURE__ */ new Set();
7969
+ const policy = new aws28.iam.RolePolicy(group, "policy", {
7970
+ role: role.name,
7971
+ name: "task-policy",
7972
+ policy: new Output8(statementDeps, async (resolve2) => {
7973
+ const list3 = await resolveInputs5(statements);
7974
+ resolve2(
7975
+ JSON.stringify({
7976
+ Version: "2012-10-17",
7977
+ Statement: list3.map((statement) => ({
7978
+ Effect: pascalCase4(statement.effect ?? "allow"),
7979
+ Action: statement.actions,
7980
+ Resource: statement.resources
7981
+ }))
7982
+ })
7983
+ );
7984
+ })
7985
+ });
7986
+ const addPermission = (...permissions) => {
7987
+ statements.push(...permissions);
7988
+ for (const dep of findInputDeps5(permissions)) {
7989
+ statementDeps.add(dep);
7990
+ }
7991
+ };
7992
+ ctx.onPermission((statement) => {
7993
+ addPermission(statement);
7994
+ });
7995
+ let logGroup;
7996
+ if (props.log.retention && props.log.retention.value > 0n) {
7997
+ logGroup = new aws28.cloudwatch.LogGroup(group, "log", {
7998
+ name: `/aws/ecs/${name}`,
7999
+ // name: `/aws/lambda/${name}`,
8000
+ retentionInDays: toDays10(props.log.retention)
8001
+ });
8002
+ if (ctx.shared.has("on-error-log", "subscriber-arn")) {
8003
+ new aws28.cloudwatch.LogSubscriptionFilter(group, "on-error-log", {
8004
+ name: "error-log-subscription",
8005
+ destinationArn: ctx.shared.get("on-error-log", "subscriber-arn"),
8006
+ logGroupName: logGroup.name,
8007
+ filterPattern
8008
+ });
8009
+ }
8010
+ }
8011
+ const tags = {
8012
+ APP: ctx.appConfig.name,
8013
+ APP_ID: ctx.appId,
8014
+ STACK: ctx.stackConfig.name
8015
+ };
8016
+ const variables = {};
8017
+ const variableDeps = /* @__PURE__ */ new Set();
8018
+ const task2 = new aws28.ecs.TaskDefinition(
8019
+ group,
8020
+ "task",
8021
+ {
8022
+ family: name,
8023
+ networkMode: "awsvpc",
8024
+ cpu: props.cpu,
8025
+ memory: toMebibytes6(props.memorySize).toString(),
8026
+ requiresCompatibilities: ["FARGATE"],
8027
+ executionRoleArn: executionRole.arn,
8028
+ taskRoleArn: role.arn,
8029
+ runtimePlatform: {
8030
+ cpuArchitecture: constantCase13(props.architecture),
8031
+ operatingSystemFamily: "LINUX"
8032
+ },
8033
+ trackLatest: true,
8034
+ containerDefinitions: new Output8(variableDeps, async (resolve2) => {
8035
+ const data = await resolveInputs5(variables);
8036
+ const { s3Bucket, s3Key } = await resolveInputs5({
8037
+ s3Bucket: code.bucket,
8038
+ s3Key: code.key
8039
+ });
8040
+ resolve2(
7450
8041
  JSON.stringify([
7451
8042
  {
7452
8043
  name: `container-${id}`,
@@ -7497,12 +8088,12 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7497
8088
  healthCheck: props.healthCheck ? {
7498
8089
  command: [
7499
8090
  "CMD-SHELL",
7500
- `curl -f http://${join18("localhost", props.healthCheck.path)} || exit 1`
8091
+ `curl -f http://${join19("localhost", props.healthCheck.path)} || exit 1`
7501
8092
  ],
7502
- interval: toSeconds9(props.healthCheck.interval),
8093
+ interval: toSeconds10(props.healthCheck.interval),
7503
8094
  retries: props.healthCheck.retries,
7504
- startPeriod: toSeconds9(props.healthCheck.startPeriod),
7505
- timeout: toSeconds9(props.healthCheck.timeout)
8095
+ startPeriod: toSeconds10(props.healthCheck.startPeriod),
8096
+ timeout: toSeconds10(props.healthCheck.timeout)
7506
8097
  } : void 0
7507
8098
  }
7508
8099
  ])
@@ -7522,14 +8113,14 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7522
8113
  dependsOn: [code]
7523
8114
  }
7524
8115
  );
7525
- const securityGroup = new aws26.security.Group(group, "security-group", {
8116
+ const securityGroup = new aws28.security.Group(group, "security-group", {
7526
8117
  name,
7527
8118
  description: "Security group for the instance",
7528
8119
  vpcId: ctx.shared.get("vpc", "id"),
7529
8120
  revokeRulesOnDelete: true,
7530
8121
  tags
7531
8122
  });
7532
- new aws26.vpc.SecurityGroupEgressRule(group, "egress-rule", {
8123
+ new aws28.vpc.SecurityGroupEgressRule(group, "egress-rule", {
7533
8124
  securityGroupId: securityGroup.id,
7534
8125
  description: `Allow all outbound traffic from the ${name} instance`,
7535
8126
  ipProtocol: "-1",
@@ -7538,7 +8129,7 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7538
8129
  });
7539
8130
  const clusterName = ctx.shared.get("instance", "cluster-name");
7540
8131
  const clusterArn = ctx.shared.get("instance", "cluster-arn");
7541
- const service = new aws26.ecs.Service(
8132
+ const service = new aws28.ecs.Service(
7542
8133
  group,
7543
8134
  "service",
7544
8135
  {
@@ -7574,7 +8165,7 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7574
8165
  replaceOnChanges: ["cluster"]
7575
8166
  }
7576
8167
  );
7577
- new aws26.appautoscaling.Target(
8168
+ new aws28.appautoscaling.Target(
7578
8169
  group,
7579
8170
  "autoscaling-target",
7580
8171
  {
@@ -7593,7 +8184,7 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7593
8184
  );
7594
8185
  ctx.onEnv((name2, value) => {
7595
8186
  variables[name2] = value;
7596
- for (const dep of findInputDeps3([value])) {
8187
+ for (const dep of findInputDeps5([value])) {
7597
8188
  variableDeps.add(dep);
7598
8189
  }
7599
8190
  });
@@ -7602,7 +8193,7 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7602
8193
  variables.AWS_ACCOUNT_ID = ctx.accountId;
7603
8194
  variables.STACK = ctx.stackConfig.name;
7604
8195
  variables.CODE_HASH = code.sourceHash;
7605
- variables.INSTANCE_CONFIG_HASH = createHash4("sha1").update(stringify(props)).digest("hex");
8196
+ variables.INSTANCE_CONFIG_HASH = createHash6("sha1").update(stringify2(props)).digest("hex");
7606
8197
  if (props.environment) {
7607
8198
  for (const [key, value] of Object.entries(props.environment)) {
7608
8199
  variables[key] = value;
@@ -7621,8 +8212,8 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7621
8212
  var instanceFeature = defineFeature({
7622
8213
  name: "instance",
7623
8214
  onBefore(ctx) {
7624
- const group = new Group26(ctx.base, "instance", "asset");
7625
- const bucket = new aws27.s3.Bucket(group, "bucket", {
8215
+ const group = new Group28(ctx.base, "instance", "asset");
8216
+ const bucket = new aws29.s3.Bucket(group, "bucket", {
7626
8217
  bucket: formatGlobalResourceName({
7627
8218
  appName: ctx.app.name,
7628
8219
  resourceType: "instance",
@@ -7640,8 +8231,8 @@ var instanceFeature = defineFeature({
7640
8231
  if (found.length === 0) {
7641
8232
  return;
7642
8233
  }
7643
- const group = new Group26(ctx.base, "instance", "cluster");
7644
- const cluster = new aws27.ecs.Cluster(
8234
+ const group = new Group28(ctx.base, "instance", "cluster");
8235
+ const cluster = new aws29.ecs.Cluster(
7645
8236
  group,
7646
8237
  "cluster",
7647
8238
  {
@@ -7656,18 +8247,18 @@ var instanceFeature = defineFeature({
7656
8247
  },
7657
8248
  onStack(ctx) {
7658
8249
  for (const [id, props] of Object.entries(ctx.stackConfig.instances ?? {})) {
7659
- const group = new Group26(ctx.stack, "instance", id);
8250
+ const group = new Group28(ctx.stack, "instance", id);
7660
8251
  createFargateTask(group, ctx, "instance", id, props);
7661
8252
  }
7662
8253
  }
7663
8254
  });
7664
8255
 
7665
8256
  // src/feature/metric/index.ts
7666
- import { Group as Group27 } from "@terraforge/core";
7667
- import { aws as aws28 } from "@terraforge/aws";
7668
- import { kebabCase as kebabCase8, constantCase as constantCase13 } from "change-case";
7669
- import { toSeconds as toSeconds10 } from "@awsless/duration";
7670
- var typeGenCode10 = `
8257
+ import { Group as Group29 } from "@terraforge/core";
8258
+ import { aws as aws30 } from "@terraforge/aws";
8259
+ import { kebabCase as kebabCase8, constantCase as constantCase14 } from "change-case";
8260
+ import { toSeconds as toSeconds11 } from "@awsless/duration";
8261
+ var typeGenCode11 = `
7671
8262
  import { type PutDataProps, putData, batchPutData } from '@awsless/cloudwatch'
7672
8263
  import { type Duration } from '@awsless/duration'
7673
8264
  import { type Size } from '@awsless/size'
@@ -7715,7 +8306,7 @@ var metricFeature = defineFeature({
7715
8306
  resources2.addType(stack.name, stackResources);
7716
8307
  }
7717
8308
  resources2.addType("batch", "Batch");
7718
- gen.addCode(typeGenCode10);
8309
+ gen.addCode(typeGenCode11);
7719
8310
  gen.addInterface("MetricResources", resources2);
7720
8311
  await ctx.write("metric.d.ts", gen, true);
7721
8312
  },
@@ -7732,16 +8323,16 @@ var metricFeature = defineFeature({
7732
8323
  }
7733
8324
  });
7734
8325
  for (const [id, props] of Object.entries(ctx.stackConfig.metrics ?? {})) {
7735
- const group = new Group27(ctx.stack, "metric", id);
7736
- ctx.addEnv(`METRIC_${constantCase13(id)}`, props.type);
8326
+ const group = new Group29(ctx.stack, "metric", id);
8327
+ ctx.addEnv(`METRIC_${constantCase14(id)}`, props.type);
7737
8328
  for (const alarmId in props.alarms ?? []) {
7738
- const alarmGroup = new Group27(group, "alarm", alarmId);
8329
+ const alarmGroup = new Group29(group, "alarm", alarmId);
7739
8330
  const alarmName = kebabCase8(`${id}-${alarmId}`);
7740
8331
  const alarmProps = props.alarms[alarmId];
7741
8332
  let alarmAction;
7742
8333
  let alarmLambda;
7743
8334
  if (Array.isArray(alarmProps.trigger)) {
7744
- const topic = new aws28.sns.Topic(alarmGroup, "alarm-trigger", {
8335
+ const topic = new aws30.sns.Topic(alarmGroup, "alarm-trigger", {
7745
8336
  name: formatLocalResourceName({
7746
8337
  appName: ctx.app.name,
7747
8338
  stackName: ctx.stack.name,
@@ -7751,7 +8342,7 @@ var metricFeature = defineFeature({
7751
8342
  });
7752
8343
  alarmAction = topic.arn;
7753
8344
  for (const email of alarmProps.trigger) {
7754
- new aws28.sns.TopicSubscription(alarmGroup, email, {
8345
+ new aws30.sns.TopicSubscription(alarmGroup, email, {
7755
8346
  topicArn: topic.arn,
7756
8347
  protocol: "email",
7757
8348
  endpoint: email
@@ -7762,7 +8353,7 @@ var metricFeature = defineFeature({
7762
8353
  alarmLambda = lambda;
7763
8354
  alarmAction = lambda.arn;
7764
8355
  }
7765
- const alarm = new aws28.cloudwatch.MetricAlarm(alarmGroup, "alarm", {
8356
+ const alarm = new aws30.cloudwatch.MetricAlarm(alarmGroup, "alarm", {
7766
8357
  namespace,
7767
8358
  metricName: kebabCase8(id),
7768
8359
  alarmName: formatLocalResourceName({
@@ -7774,13 +8365,13 @@ var metricFeature = defineFeature({
7774
8365
  alarmDescription: alarmProps.description,
7775
8366
  statistic: alarmProps.where.stat,
7776
8367
  threshold: alarmProps.where.value,
7777
- period: toSeconds10(alarmProps.period),
8368
+ period: toSeconds11(alarmProps.period),
7778
8369
  evaluationPeriods: alarmProps.minDataPoints,
7779
8370
  comparisonOperator: alarmProps.where.op,
7780
8371
  alarmActions: [alarmAction]
7781
8372
  });
7782
8373
  if (alarmLambda) {
7783
- new aws28.lambda.Permission(alarmGroup, "permission", {
8374
+ new aws30.lambda.Permission(alarmGroup, "permission", {
7784
8375
  action: "lambda:InvokeFunction",
7785
8376
  principal: "lambda.alarms.cloudwatch.amazonaws.com",
7786
8377
  functionName: alarmLambda.functionName,
@@ -7793,10 +8384,10 @@ var metricFeature = defineFeature({
7793
8384
  });
7794
8385
 
7795
8386
  // src/feature/router/index.ts
7796
- import { days as days9, seconds as seconds10, toSeconds as toSeconds11, years } from "@awsless/duration";
7797
- import { Future, Group as Group28 } from "@terraforge/core";
7798
- import { aws as aws29 } from "@terraforge/aws";
7799
- import { camelCase as camelCase8, constantCase as constantCase14 } from "change-case";
8387
+ import { days as days9, seconds as seconds10, toSeconds as toSeconds12, years } from "@awsless/duration";
8388
+ import { Future, Group as Group30 } from "@terraforge/core";
8389
+ import { aws as aws31 } from "@terraforge/aws";
8390
+ import { camelCase as camelCase9, constantCase as constantCase15 } from "change-case";
7800
8391
 
7801
8392
  // src/feature/router/router-code.ts
7802
8393
  var getViewerRequestFunctionCode = (props) => {
@@ -8044,18 +8635,18 @@ async function handler(event) {
8044
8635
  `;
8045
8636
 
8046
8637
  // src/feature/router/index.ts
8047
- import { createHash as createHash5 } from "crypto";
8638
+ import { createHash as createHash7 } from "crypto";
8048
8639
  var routerFeature = defineFeature({
8049
8640
  name: "router",
8050
8641
  onApp(ctx) {
8051
8642
  for (const [id, props] of Object.entries(ctx.appConfig.defaults.router ?? {})) {
8052
- const group = new Group28(ctx.base, "router", id);
8643
+ const group = new Group30(ctx.base, "router", id);
8053
8644
  const name = formatGlobalResourceName({
8054
8645
  appName: ctx.app.name,
8055
8646
  resourceType: "router",
8056
8647
  resourceName: id
8057
8648
  });
8058
- const routeStore = new aws29.cloudfront.KeyValueStore(group, "routes", {
8649
+ const routeStore = new aws31.cloudfront.KeyValueStore(group, "routes", {
8059
8650
  name,
8060
8651
  comment: "Store for routes"
8061
8652
  });
@@ -8085,11 +8676,11 @@ var routerFeature = defineFeature({
8085
8676
  );
8086
8677
  importedRoutes.push(importKeys);
8087
8678
  });
8088
- const cache = new aws29.cloudfront.CachePolicy(group, "cache", {
8679
+ const cache = new aws31.cloudfront.CachePolicy(group, "cache", {
8089
8680
  name,
8090
- minTtl: toSeconds11(seconds10(0)),
8091
- maxTtl: toSeconds11(days9(365)),
8092
- defaultTtl: toSeconds11(days9(0)),
8681
+ minTtl: toSeconds12(seconds10(0)),
8682
+ maxTtl: toSeconds12(days9(365)),
8683
+ defaultTtl: toSeconds12(days9(0)),
8093
8684
  parametersInCacheKeyAndForwardedToOrigin: {
8094
8685
  enableAcceptEncodingBrotli: true,
8095
8686
  enableAcceptEncodingGzip: true,
@@ -8117,10 +8708,10 @@ var routerFeature = defineFeature({
8117
8708
  }
8118
8709
  }
8119
8710
  });
8120
- const originRequest = new aws29.cloudfront.OriginRequestPolicy(group, "request", {
8711
+ const originRequest = new aws31.cloudfront.OriginRequestPolicy(group, "request", {
8121
8712
  name,
8122
8713
  headersConfig: {
8123
- headerBehavior: camelCase8("all-except"),
8714
+ headerBehavior: camelCase9("all-except"),
8124
8715
  headers: {
8125
8716
  items: [
8126
8717
  "host"
@@ -8135,11 +8726,11 @@ var routerFeature = defineFeature({
8135
8726
  queryStringBehavior: "all"
8136
8727
  }
8137
8728
  });
8138
- const responseHeaders = new aws29.cloudfront.ResponseHeadersPolicy(group, "response", {
8729
+ const responseHeaders = new aws31.cloudfront.ResponseHeadersPolicy(group, "response", {
8139
8730
  name,
8140
8731
  corsConfig: {
8141
8732
  originOverride: props.cors?.override ?? true,
8142
- accessControlMaxAgeSec: toSeconds11(props.cors?.maxAge ?? years(1)),
8733
+ accessControlMaxAgeSec: toSeconds12(props.cors?.maxAge ?? years(1)),
8143
8734
  accessControlAllowHeaders: { items: props.cors?.headers ?? ["*"] },
8144
8735
  accessControlAllowMethods: { items: props.cors?.methods ?? ["ALL"] },
8145
8736
  accessControlAllowOrigins: { items: props.cors?.origins ?? ["*"] },
@@ -8164,7 +8755,7 @@ var routerFeature = defineFeature({
8164
8755
  strictTransportSecurity: {
8165
8756
  override: true,
8166
8757
  preload: true,
8167
- accessControlMaxAgeSec: toSeconds11(years(1)),
8758
+ accessControlMaxAgeSec: toSeconds12(years(1)),
8168
8759
  includeSubdomains: true
8169
8760
  },
8170
8761
  xssProtection: {
@@ -8174,7 +8765,7 @@ var routerFeature = defineFeature({
8174
8765
  }
8175
8766
  }
8176
8767
  });
8177
- const viewerRequest = new aws29.cloudfront.Function(group, "viewer-request", {
8768
+ const viewerRequest = new aws31.cloudfront.Function(group, "viewer-request", {
8178
8769
  name,
8179
8770
  runtime: `cloudfront-js-2.0`,
8180
8771
  comment: `Viewer Request - ${name}`,
@@ -8196,7 +8787,7 @@ var routerFeature = defineFeature({
8196
8787
  rateBasedStatement: {
8197
8788
  limit: wafSettingsConfig.rateLimiter.limit,
8198
8789
  aggregateKeyType: "IP",
8199
- evaluationWindowSec: toSeconds11(wafSettingsConfig.rateLimiter.window)
8790
+ evaluationWindowSec: toSeconds12(wafSettingsConfig.rateLimiter.window)
8200
8791
  }
8201
8792
  },
8202
8793
  action: {
@@ -8276,7 +8867,7 @@ var routerFeature = defineFeature({
8276
8867
  }
8277
8868
  let waf;
8278
8869
  if (wafRules.length && wafSettingsConfig) {
8279
- waf = new aws29.wafv2.WebAcl(group, "waf", {
8870
+ waf = new aws31.wafv2.WebAcl(group, "waf", {
8280
8871
  name: `${name}-wafv2`,
8281
8872
  scope: "CLOUDFRONT",
8282
8873
  defaultAction: {
@@ -8286,12 +8877,12 @@ var routerFeature = defineFeature({
8286
8877
  rule: wafRules,
8287
8878
  captchaConfig: {
8288
8879
  immunityTimeProperty: {
8289
- immunityTime: toSeconds11(wafSettingsConfig.captchaImmunityTime)
8880
+ immunityTime: toSeconds12(wafSettingsConfig.captchaImmunityTime)
8290
8881
  }
8291
8882
  },
8292
8883
  challengeConfig: {
8293
8884
  immunityTimeProperty: {
8294
- immunityTime: toSeconds11(wafSettingsConfig.challengeImmunityTime)
8885
+ immunityTime: toSeconds12(wafSettingsConfig.challengeImmunityTime)
8295
8886
  }
8296
8887
  },
8297
8888
  visibilityConfig: {
@@ -8301,7 +8892,7 @@ var routerFeature = defineFeature({
8301
8892
  }
8302
8893
  });
8303
8894
  }
8304
- const distribution = new aws29.cloudfront.MultitenantDistribution(group, "distribution", {
8895
+ const distribution = new aws31.cloudfront.MultitenantDistribution(group, "distribution", {
8305
8896
  tags: {
8306
8897
  name
8307
8898
  },
@@ -8347,7 +8938,7 @@ var routerFeature = defineFeature({
8347
8938
  }
8348
8939
  return {
8349
8940
  errorCode: Number(errorCode),
8350
- errorCachingMinTtl: item.minTTL ? toSeconds11(item.minTTL) : void 0,
8941
+ errorCachingMinTtl: item.minTTL ? toSeconds12(item.minTTL) : void 0,
8351
8942
  responseCode: item.statusCode?.toString() ?? errorCode,
8352
8943
  responsePagePath: item.path
8353
8944
  };
@@ -8395,11 +8986,11 @@ var routerFeature = defineFeature({
8395
8986
  {
8396
8987
  distributionId: distribution.id,
8397
8988
  paths,
8398
- version: new Future((resolve) => {
8989
+ version: new Future((resolve2) => {
8399
8990
  $combine(...versions).then((versions2) => {
8400
8991
  const combined = versions2.filter((v) => !!v).sort().join(",");
8401
- const version = createHash5("sha1").update(combined).digest("hex");
8402
- resolve(version);
8992
+ const version = createHash7("sha1").update(combined).digest("hex");
8993
+ resolve2(version);
8403
8994
  });
8404
8995
  })
8405
8996
  },
@@ -8413,10 +9004,10 @@ var routerFeature = defineFeature({
8413
9004
  const domainName = formatFullDomainName(ctx.appConfig, props.domain, props.subDomain);
8414
9005
  const certificateArn = ctx.shared.entry("domain", `global-certificate-arn`, props.domain);
8415
9006
  const zoneId = ctx.shared.entry("domain", "zone-id", props.domain);
8416
- const connectionGroup = new aws29.cloudfront.ConnectionGroup(group, "connection-group", {
9007
+ const connectionGroup = new aws31.cloudfront.ConnectionGroup(group, "connection-group", {
8417
9008
  name
8418
9009
  });
8419
- new aws29.cloudfront.DistributionTenant(group, `tenant`, {
9010
+ new aws31.cloudfront.DistributionTenant(group, `tenant`, {
8420
9011
  name,
8421
9012
  enabled: true,
8422
9013
  distributionId: distribution.id,
@@ -8424,7 +9015,7 @@ var routerFeature = defineFeature({
8424
9015
  domain: [{ domain: domainName }],
8425
9016
  customizations: [{ certificate: [{ arn: certificateArn }] }]
8426
9017
  });
8427
- new aws29.route53.Record(group, `record`, {
9018
+ new aws31.route53.Record(group, `record`, {
8428
9019
  zoneId,
8429
9020
  type: "A",
8430
9021
  name: domainName,
@@ -8434,7 +9025,7 @@ var routerFeature = defineFeature({
8434
9025
  evaluateTargetHealth: false
8435
9026
  }
8436
9027
  });
8437
- ctx.bind(`ROUTER_${constantCase14(id)}_ENDPOINT`, domainName);
9028
+ ctx.bind(`ROUTER_${constantCase15(id)}_ENDPOINT`, domainName);
8438
9029
  }
8439
9030
  }
8440
9031
  }
@@ -8457,7 +9048,7 @@ var features = [
8457
9048
  // 5
8458
9049
  functionFeature,
8459
9050
  instanceFeature,
8460
- // jobFeature,
9051
+ jobFeature,
8461
9052
  // graphqlFeature,
8462
9053
  configFeature,
8463
9054
  searchFeature,
@@ -9546,14 +10137,14 @@ import wildstring4 from "wildstring";
9546
10137
 
9547
10138
  // src/cli/ui/complex/run-tests.ts
9548
10139
  import { log as log18 } from "@awsless/clui";
9549
- import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
9550
- import { join as join20 } from "path";
10140
+ import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
10141
+ import { join as join21 } from "path";
9551
10142
  import wildstring3 from "wildstring";
9552
- import { parse as parse4, stringify as stringify2 } from "@awsless/json";
10143
+ import { parse as parse4, stringify as stringify3 } from "@awsless/json";
9553
10144
  import { generateFolderHash, loadWorkspace as loadWorkspace2 } from "@awsless/ts-file-cache";
9554
10145
 
9555
10146
  // src/test/start.ts
9556
- import { dirname as dirname9, join as join19 } from "path";
10147
+ import { dirname as dirname9, join as join20 } from "path";
9557
10148
  import { fileURLToPath as fileURLToPath4 } from "url";
9558
10149
  import { configDefaults } from "vitest/config";
9559
10150
  import { startVitest } from "vitest/node";
@@ -9591,7 +10182,7 @@ var startTest = async (props) => {
9591
10182
  // },
9592
10183
  setupFiles: [
9593
10184
  //
9594
- join19(__dirname5, "test-global-setup.js")
10185
+ join20(__dirname5, "test-global-setup.js")
9595
10186
  ]
9596
10187
  // globalSetup: [
9597
10188
  // //
@@ -9785,12 +10376,12 @@ var logTestErrors = (event) => {
9785
10376
  };
9786
10377
  var runTest = async (stack, dir, filters, workspace, opts) => {
9787
10378
  await mkdir4(directories.test, { recursive: true });
9788
- const file = join20(directories.test, `${stack}.json`);
10379
+ const file = join21(directories.test, `${stack}.json`);
9789
10380
  const fingerprint = await generateFolderHash(workspace, dir);
9790
10381
  if (!process.env.NO_CACHE) {
9791
10382
  const exists = await fileExist(file);
9792
10383
  if (exists) {
9793
- const raw = await readFile5(file, { encoding: "utf8" });
10384
+ const raw = await readFile6(file, { encoding: "utf8" });
9794
10385
  const data = parse4(raw);
9795
10386
  if (data.fingerprint === fingerprint) {
9796
10387
  log18.step(
@@ -9833,9 +10424,9 @@ var runTest = async (stack, dir, filters, workspace, opts) => {
9833
10424
  logTestLogs(result);
9834
10425
  }
9835
10426
  logTestErrors(result);
9836
- await writeFile3(
10427
+ await writeFile4(
9837
10428
  file,
9838
- stringify2({
10429
+ stringify3({
9839
10430
  ...result,
9840
10431
  fingerprint
9841
10432
  })
@@ -10373,7 +10964,7 @@ var auth = (program2) => {
10373
10964
  // src/cli/command/bind.ts
10374
10965
  import { log as log24 } from "@awsless/clui";
10375
10966
  import chalk4 from "chalk";
10376
- import { constantCase as constantCase15 } from "change-case";
10967
+ import { constantCase as constantCase16 } from "change-case";
10377
10968
  var bind = (program2) => {
10378
10969
  program2.command("bind").argument("[command...]", "The command to execute").option("--config <string...>", "List of config values that will be accessable", (v) => v.split(",")).description(`Bind your site environment variables to a command`).action(async (commands11 = [], opts) => {
10379
10970
  await layout("bind", async ({ appConfig, stackConfigs }) => {
@@ -10400,10 +10991,10 @@ var bind = (program2) => {
10400
10991
  const configList = opts.config ?? [];
10401
10992
  const configs = {};
10402
10993
  for (const name of configList) {
10403
- configs[`CONFIG_${constantCase15(name)}`] = name;
10994
+ configs[`CONFIG_${constantCase16(name)}`] = name;
10404
10995
  }
10405
10996
  if (configList.length ?? 0 > 0) {
10406
- log24.note("Bind Config", configList.map((v) => color.label(constantCase15(v))).join("\n"));
10997
+ log24.note("Bind Config", configList.map((v) => color.label(constantCase16(v))).join("\n"));
10407
10998
  }
10408
10999
  if (commands11.length === 0) {
10409
11000
  return "No command to execute.";
@@ -10442,7 +11033,7 @@ var bind = (program2) => {
10442
11033
 
10443
11034
  // src/config/load/watch.ts
10444
11035
  import { watch } from "chokidar";
10445
- var watchConfig = async (options, resolve, reject) => {
11036
+ var watchConfig = async (options, resolve2, reject) => {
10446
11037
  await loadAppConfig(options);
10447
11038
  debug("Start watching...");
10448
11039
  const ext = "{json,jsonc,json5}";
@@ -10457,7 +11048,7 @@ var watchConfig = async (options, resolve, reject) => {
10457
11048
  const appConfig = await loadAppConfig(options);
10458
11049
  const stackConfigs = await loadStackConfigs(options);
10459
11050
  validateFeatures({ appConfig, stackConfigs });
10460
- resolve({ appConfig, stackConfigs });
11051
+ resolve2({ appConfig, stackConfigs });
10461
11052
  } catch (error) {
10462
11053
  reject(error);
10463
11054
  }
@@ -10469,8 +11060,8 @@ var watchConfig = async (options, resolve, reject) => {
10469
11060
  import { log as log25 } from "@awsless/clui";
10470
11061
 
10471
11062
  // src/type-gen/generate.ts
10472
- import { mkdir as mkdir5, writeFile as writeFile4 } from "fs/promises";
10473
- import { dirname as dirname10, join as join21, relative as relative8 } from "path";
11063
+ import { mkdir as mkdir5, writeFile as writeFile5 } from "fs/promises";
11064
+ import { dirname as dirname10, join as join22, relative as relative9 } from "path";
10474
11065
  var generateTypes = async (props) => {
10475
11066
  const files = [];
10476
11067
  await Promise.all(
@@ -10479,13 +11070,13 @@ var generateTypes = async (props) => {
10479
11070
  ...props,
10480
11071
  async write(file, data, include = false) {
10481
11072
  const code = data?.toString("utf8");
10482
- const path = join21(directories.types, file);
11073
+ const path = join22(directories.types, file);
10483
11074
  if (code) {
10484
11075
  if (include) {
10485
- files.push(relative8(directories.root, path));
11076
+ files.push(relative9(directories.root, path));
10486
11077
  }
10487
11078
  await mkdir5(dirname10(path), { recursive: true });
10488
- await writeFile4(path, code);
11079
+ await writeFile5(path, code);
10489
11080
  }
10490
11081
  }
10491
11082
  });
@@ -10493,7 +11084,7 @@ var generateTypes = async (props) => {
10493
11084
  );
10494
11085
  if (files.length) {
10495
11086
  const code = files.map((file) => `/// <reference path='${file}' />`).join("\n");
10496
- await writeFile4(join21(directories.root, `awsless.d.ts`), code);
11087
+ await writeFile5(join22(directories.root, `awsless.d.ts`), code);
10497
11088
  }
10498
11089
  };
10499
11090
 
@@ -10517,9 +11108,9 @@ var dev = (program2) => {
10517
11108
  logError(error);
10518
11109
  }
10519
11110
  );
10520
- await new Promise((resolve) => {
10521
- process.once("exit", resolve);
10522
- process.once("SIGINT", resolve);
11111
+ await new Promise((resolve2) => {
11112
+ process.once("exit", resolve2);
11113
+ process.once("SIGINT", resolve2);
10523
11114
  });
10524
11115
  });
10525
11116
  });
@@ -10919,7 +11510,7 @@ var domain = (program2) => {
10919
11510
  // src/cli/command/logs.ts
10920
11511
  import { CloudWatchLogsClient, StartLiveTailCommand } from "@aws-sdk/client-cloudwatch-logs";
10921
11512
  import { log as log30 } from "@awsless/clui";
10922
- import { aws as aws30 } from "@terraforge/aws";
11513
+ import { aws as aws32 } from "@terraforge/aws";
10923
11514
  import chalk6 from "chalk";
10924
11515
  import chunk2 from "chunk";
10925
11516
  import { formatDate } from "date-fns";
@@ -10942,7 +11533,7 @@ var logs = (program2) => {
10942
11533
  for (const stack of app.stacks) {
10943
11534
  if (filters.find((f) => wildstring7.match(f, stack.name))) {
10944
11535
  for (const resource of stack.resources) {
10945
- if (resource instanceof aws30.cloudwatch.LogGroup) {
11536
+ if (resource instanceof aws32.cloudwatch.LogGroup) {
10946
11537
  logGroupArns.push(await resource.arn);
10947
11538
  }
10948
11539
  }