@awsless/cli 0.0.5 → 0.0.6

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,21 +7248,37 @@ 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
@@ -7268,23 +7287,23 @@ var buildExecutable = async (input, outputPath, architecture) => {
7268
7287
  });
7269
7288
  } catch (error) {
7270
7289
  throw new ExpectedError(
7271
- `Executable build failed: ${error instanceof Error ? error.message : JSON.stringify(error)}`
7290
+ `Job executable build failed: ${error instanceof Error ? error.message : JSON.stringify(error)}`
7272
7291
  );
7273
7292
  }
7274
7293
  if (!result.success) {
7275
- throw new ExpectedError(`Executable build failed:
7294
+ throw new ExpectedError(`Job executable build failed:
7276
7295
  ${result.logs?.map((log35) => log35.message).join("\n")}`);
7277
7296
  }
7278
7297
  const file = await readFile4(filePath);
7279
7298
  return {
7280
- hash: createHash3("sha1").update(file).update("x86_64").digest("hex"),
7299
+ hash: createHash3("sha1").update(file).update(target).digest("hex"),
7281
7300
  file
7282
7301
  };
7283
7302
  };
7284
7303
 
7285
- // src/feature/instance/util.ts
7286
- var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7287
- const group = new Group25(parentGroup, "instance", ns);
7304
+ // src/feature/job/util.ts
7305
+ var createFargateJob = (parentGroup, ctx, ns, id, local) => {
7306
+ const group = new Group25(parentGroup, "job", ns);
7288
7307
  const name = formatLocalResourceName({
7289
7308
  appName: ctx.app.name,
7290
7309
  stackName: ctx.stack.name,
@@ -7292,13 +7311,13 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7292
7311
  resourceName: id
7293
7312
  });
7294
7313
  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);
7314
+ const props = deepmerge4(ctx.appConfig.defaults.job, local);
7315
+ const image2 = props.image || "public.ecr.aws/amazonlinux/amazonlinux:2023-minimal";
7316
+ ctx.registerBuild("job", name, async (build3, { workspace }) => {
7317
+ const fingerprint = [await generateFileHash2(workspace, local.code.file), props.architecture].join(":");
7299
7318
  return build3(fingerprint, async (write) => {
7300
- const temp = await createTempFolder(`instance--${name}`);
7301
- const executable = await buildExecutable(local.code.file, temp.path, props.architecture);
7319
+ const temp = await createTempFolder(`job--${name}`);
7320
+ const executable = await buildJobExecutable(local.code.file, temp.path, props.architecture);
7302
7321
  await Promise.all([
7303
7322
  //
7304
7323
  write("HASH", executable.hash),
@@ -7311,10 +7330,10 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7311
7330
  });
7312
7331
  });
7313
7332
  const code = new aws26.s3.BucketObject(group, "code", {
7314
- bucket: ctx.shared.get("instance", "bucket-name"),
7333
+ bucket: ctx.shared.get("job", "bucket-name"),
7315
7334
  key: name,
7316
- source: relativePath(getBuildPath("instance", name, "program")),
7317
- sourceHash: $file(getBuildPath("instance", name, "HASH"))
7335
+ source: relativePath(getBuildPath("job", name, "program")),
7336
+ sourceHash: $file(getBuildPath("job", name, "HASH"))
7318
7337
  });
7319
7338
  const executionRole = new aws26.iam.Role(group, "execution-role", {
7320
7339
  name: shortId(`${shortName}:execution-role`),
@@ -7360,8 +7379,8 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7360
7379
  Statement: [
7361
7380
  {
7362
7381
  Effect: pascalCase3("allow"),
7363
- Action: ["s3:getObject", "s3:HeadObject"],
7364
- Resource: `arn:aws:s3:::${bucket}/${key}`
7382
+ Action: ["s3:GetObject", "s3:HeadObject"],
7383
+ Resource: [`arn:aws:s3:::${bucket}/${key}`, `arn:aws:s3:::${bucket}/payloads/*`]
7365
7384
  }
7366
7385
  ]
7367
7386
  });
@@ -7378,9 +7397,9 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7378
7397
  const policy = new aws26.iam.RolePolicy(group, "policy", {
7379
7398
  role: role.name,
7380
7399
  name: "task-policy",
7381
- policy: new Output6(statementDeps, async (resolve) => {
7400
+ policy: new Output6(statementDeps, async (resolve2) => {
7382
7401
  const list3 = await resolveInputs3(statements);
7383
- resolve(
7402
+ resolve2(
7384
7403
  JSON.stringify({
7385
7404
  Version: "2012-10-17",
7386
7405
  Statement: list3.map((statement) => ({
@@ -7405,7 +7424,6 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7405
7424
  if (props.log.retention && props.log.retention.value > 0n) {
7406
7425
  logGroup = new aws26.cloudwatch.LogGroup(group, "log", {
7407
7426
  name: `/aws/ecs/${name}`,
7408
- // name: `/aws/lambda/${name}`,
7409
7427
  retentionInDays: toDays9(props.log.retention)
7410
7428
  });
7411
7429
  if (ctx.shared.has("on-error-log", "subscriber-arn")) {
@@ -7424,6 +7442,63 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7424
7442
  };
7425
7443
  const variables = {};
7426
7444
  const variableDeps = /* @__PURE__ */ new Set();
7445
+ const taskDependsOn = [code];
7446
+ const accessPoint = props.persistentStorage ? (() => {
7447
+ const persistentStorageSecurityGroup = new aws26.security.Group(
7448
+ group,
7449
+ "persistent-storage-security-group",
7450
+ {
7451
+ name: shortId(`${shortName}:storage-sg`),
7452
+ description: name,
7453
+ vpcId: ctx.shared.get("vpc", "id"),
7454
+ revokeRulesOnDelete: true,
7455
+ tags
7456
+ }
7457
+ );
7458
+ new aws26.vpc.SecurityGroupIngressRule(group, "persistent-storage-ingress-rule", {
7459
+ securityGroupId: persistentStorageSecurityGroup.id,
7460
+ referencedSecurityGroupId: ctx.shared.get("job", "security-group-id"),
7461
+ description: "Allow NFS traffic from this job",
7462
+ ipProtocol: "tcp",
7463
+ fromPort: 2049,
7464
+ toPort: 2049,
7465
+ tags
7466
+ });
7467
+ const fileSystem = new aws26.efs.FileSystem(group, "persistent-storage-file-system", {
7468
+ encrypted: true,
7469
+ performanceMode: "generalPurpose",
7470
+ throughputMode: "elastic",
7471
+ tags: {
7472
+ ...tags,
7473
+ Name: `${name}-storage`
7474
+ }
7475
+ });
7476
+ for (const [index, subnetId] of ctx.shared.get("vpc", "public-subnets").entries()) {
7477
+ const mountTarget = new aws26.efs.MountTarget(group, `mount-target-${index + 1}`, {
7478
+ fileSystemId: fileSystem.id,
7479
+ subnetId,
7480
+ securityGroups: [persistentStorageSecurityGroup.id]
7481
+ });
7482
+ taskDependsOn.push(mountTarget);
7483
+ }
7484
+ const accessPoint2 = new aws26.efs.AccessPoint(group, "access-point", {
7485
+ fileSystemId: fileSystem.id,
7486
+ rootDirectory: {
7487
+ path: `/jobs/${name}`,
7488
+ creationInfo: {
7489
+ ownerUid: 0,
7490
+ ownerGid: 0,
7491
+ permissions: "755"
7492
+ }
7493
+ },
7494
+ tags
7495
+ });
7496
+ taskDependsOn.push(fileSystem, accessPoint2);
7497
+ return {
7498
+ accessPoint: accessPoint2,
7499
+ fileSystem
7500
+ };
7501
+ })() : void 0;
7427
7502
  const task2 = new aws26.ecs.TaskDefinition(
7428
7503
  group,
7429
7504
  "task",
@@ -7440,13 +7515,495 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7440
7515
  operatingSystemFamily: "LINUX"
7441
7516
  },
7442
7517
  trackLatest: true,
7443
- containerDefinitions: new Output6(variableDeps, async (resolve) => {
7518
+ ...accessPoint && {
7519
+ volume: [
7520
+ {
7521
+ name: "persistent-storage",
7522
+ efsVolumeConfiguration: {
7523
+ fileSystemId: accessPoint.fileSystem.id,
7524
+ transitEncryption: "ENABLED",
7525
+ authorizationConfig: {
7526
+ accessPointId: accessPoint.accessPoint.id
7527
+ }
7528
+ }
7529
+ }
7530
+ ]
7531
+ },
7532
+ containerDefinitions: new Output6(variableDeps, async (resolve2) => {
7444
7533
  const data = await resolveInputs3(variables);
7445
7534
  const { s3Bucket, s3Key } = await resolveInputs3({
7446
7535
  s3Bucket: code.bucket,
7447
7536
  s3Key: code.key
7448
7537
  });
7449
- resolve(
7538
+ resolve2(
7539
+ JSON.stringify([
7540
+ {
7541
+ name: `container-${id}`,
7542
+ essential: true,
7543
+ image: image2,
7544
+ workingDirectory: "/usr/app",
7545
+ entryPoint: ["sh", "-c"],
7546
+ command: [
7547
+ [
7548
+ ...props.startupCommand?.length ? (() => {
7549
+ const setupHash = createHash4("sha1").update(props.startupCommand.join(" && ")).digest("hex").slice(0, 8);
7550
+ return [`if [ ! -f /root/.setup-done-${setupHash} ]; then ${props.startupCommand.join(" && ")} && touch /root/.setup-done-${setupHash}; fi`];
7551
+ })() : [],
7552
+ `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`,
7553
+ `exec timeout --kill-after=10 ${toSeconds9(props.timeout)} /root/program`
7554
+ ].join(" && ")
7555
+ ],
7556
+ environment: Object.entries(data).map(([name2, value]) => ({
7557
+ name: name2,
7558
+ value
7559
+ })),
7560
+ ...accessPoint && {
7561
+ mountPoints: [
7562
+ {
7563
+ sourceVolume: "persistent-storage",
7564
+ containerPath: "/root"
7565
+ }
7566
+ ]
7567
+ },
7568
+ ...logGroup && {
7569
+ logConfiguration: {
7570
+ logDriver: "awslogs",
7571
+ options: {
7572
+ "awslogs-group": `/aws/ecs/${name}`,
7573
+ "awslogs-region": ctx.appConfig.region,
7574
+ "awslogs-stream-prefix": "ecs",
7575
+ mode: "non-blocking"
7576
+ }
7577
+ }
7578
+ }
7579
+ }
7580
+ ])
7581
+ );
7582
+ }),
7583
+ tags
7584
+ },
7585
+ {
7586
+ replaceOnChanges: [
7587
+ "containerDefinitions",
7588
+ "cpu",
7589
+ "memory",
7590
+ "runtimePlatform",
7591
+ "executionRoleArn",
7592
+ "taskRoleArn"
7593
+ ],
7594
+ dependsOn: taskDependsOn
7595
+ }
7596
+ );
7597
+ ctx.onEnv((name2, value) => {
7598
+ variables[name2] = value;
7599
+ for (const dep of findInputDeps3([value])) {
7600
+ variableDeps.add(dep);
7601
+ }
7602
+ });
7603
+ variables.APP = ctx.appConfig.name;
7604
+ variables.APP_ID = ctx.appId;
7605
+ variables.AWS_ACCOUNT_ID = ctx.accountId;
7606
+ variables.STACK = ctx.stackConfig.name;
7607
+ variables.CODE_HASH = code.sourceHash;
7608
+ variables.JOB_CONFIG_HASH = createHash4("sha1").update(stringify(props)).digest("hex");
7609
+ if (props.environment) {
7610
+ for (const [key, value] of Object.entries(props.environment)) {
7611
+ variables[key] = value;
7612
+ }
7613
+ }
7614
+ if (ctx.appConfig.defaults.job.permissions) {
7615
+ statements.push(...ctx.appConfig.defaults.job.permissions);
7616
+ }
7617
+ if ("permissions" in local && local.permissions) {
7618
+ statements.push(...local.permissions);
7619
+ }
7620
+ return { name, task: task2, policy, code, group };
7621
+ };
7622
+
7623
+ // src/feature/job/index.ts
7624
+ var typeGenCode10 = `
7625
+ import type { Mock } from 'vitest'
7626
+
7627
+ type Func = (...args: any[]) => any
7628
+
7629
+ type Invoke<N extends string, F extends Func> = Parameters<F> extends []
7630
+ ? InvokeWithoutPayload<N, F>
7631
+ : unknown extends Parameters<F>[0]
7632
+ ? InvokeWithoutPayload<N, F>
7633
+ : InvokeWithPayload<N, F>
7634
+
7635
+ type InvokeWithPayload<Name extends string, F extends Func> = {
7636
+ readonly name: Name
7637
+ (payload: Parameters<F>[0]): Promise<{ taskArn: string | undefined }>
7638
+ }
7639
+
7640
+ type InvokeWithoutPayload<Name extends string, F extends Func> = {
7641
+ readonly name: Name
7642
+ (payload?: Parameters<F>[0]): Promise<{ taskArn: string | undefined }>
7643
+ }
7644
+
7645
+ type MockHandle<F extends Func> = (payload: Parameters<F>[0]) => void | Promise<void>
7646
+ type MockBuilder<F extends Func> = (handle?: MockHandle<F>) => void
7647
+ type MockObject<F extends Func> = Mock<Parameters<F>, ReturnType<F>>
7648
+ `;
7649
+ var jobFeature = defineFeature({
7650
+ name: "job",
7651
+ async onTypeGen(ctx) {
7652
+ const types2 = new TypeFile("awsless");
7653
+ const resources2 = new TypeObject(1);
7654
+ const mocks = new TypeObject(1);
7655
+ const mockResponses = new TypeObject(1);
7656
+ for (const stack of ctx.stackConfigs) {
7657
+ const resource = new TypeObject(2);
7658
+ const mock = new TypeObject(2);
7659
+ const mockResponse = new TypeObject(2);
7660
+ for (const [name, props] of Object.entries(stack.jobs || {})) {
7661
+ const varName = camelCase8(`${stack.name}-${name}`);
7662
+ const funcName = formatLocalResourceName({
7663
+ appName: ctx.appConfig.name,
7664
+ stackName: stack.name,
7665
+ resourceType: "job",
7666
+ resourceName: name
7667
+ });
7668
+ if ("file" in props.code) {
7669
+ const relFile = relative8(directories.types, props.code.file);
7670
+ types2.addImport(varName, relFile);
7671
+ resource.addType(name, `Invoke<'${funcName}', typeof ${varName}>`);
7672
+ mock.addType(name, `MockBuilder<typeof ${varName}>`);
7673
+ mockResponse.addType(name, `MockObject<typeof ${varName}>`);
7674
+ }
7675
+ }
7676
+ mocks.addType(stack.name, mock);
7677
+ resources2.addType(stack.name, resource);
7678
+ mockResponses.addType(stack.name, mockResponse);
7679
+ }
7680
+ types2.addCode(typeGenCode10);
7681
+ types2.addInterface("JobResources", resources2);
7682
+ types2.addInterface("JobMock", mocks);
7683
+ types2.addInterface("JobMockResponse", mockResponses);
7684
+ await ctx.write("job.d.ts", types2, true);
7685
+ },
7686
+ onBefore(ctx) {
7687
+ const group = new Group26(ctx.base, "job", "asset");
7688
+ const bucket = new aws27.s3.Bucket(group, "bucket", {
7689
+ bucket: formatGlobalResourceName({
7690
+ appName: ctx.app.name,
7691
+ resourceType: "job",
7692
+ resourceName: "assets",
7693
+ postfix: ctx.appId
7694
+ }),
7695
+ forceDestroy: true,
7696
+ lifecycleRule: [
7697
+ {
7698
+ id: "expire-payloads",
7699
+ enabled: true,
7700
+ prefix: "payloads/",
7701
+ expiration: { days: 1 }
7702
+ }
7703
+ ]
7704
+ });
7705
+ ctx.shared.set("job", "bucket-name", bucket.bucket);
7706
+ },
7707
+ onApp(ctx) {
7708
+ const found = ctx.stackConfigs.filter((stack) => {
7709
+ return Object.keys(stack.jobs ?? {}).length > 0;
7710
+ });
7711
+ if (found.length === 0) {
7712
+ return;
7713
+ }
7714
+ const group = new Group26(ctx.base, "job", "cluster");
7715
+ const cluster = new aws27.ecs.Cluster(
7716
+ group,
7717
+ "cluster",
7718
+ {
7719
+ name: `${ctx.app.name}-job`
7720
+ },
7721
+ {
7722
+ replaceOnChanges: ["name"]
7723
+ }
7724
+ );
7725
+ ctx.shared.set("job", "cluster-name", cluster.name);
7726
+ ctx.shared.set("job", "cluster-arn", cluster.arn);
7727
+ const securityGroup = new aws27.security.Group(group, "security-group", {
7728
+ name: `${ctx.app.name}-job`,
7729
+ description: "Shared security group for jobs",
7730
+ vpcId: ctx.shared.get("vpc", "id"),
7731
+ revokeRulesOnDelete: true,
7732
+ tags: {
7733
+ APP: ctx.appConfig.name
7734
+ }
7735
+ });
7736
+ new aws27.vpc.SecurityGroupEgressRule(group, "egress-rule", {
7737
+ securityGroupId: securityGroup.id,
7738
+ description: "Allow all outbound traffic from jobs",
7739
+ ipProtocol: "-1",
7740
+ cidrIpv4: "0.0.0.0/0",
7741
+ tags: {
7742
+ APP: ctx.appConfig.name
7743
+ }
7744
+ });
7745
+ ctx.shared.set("job", "security-group-id", securityGroup.id);
7746
+ },
7747
+ onStack(ctx) {
7748
+ const subnets = ctx.shared.get("vpc", "public-subnets");
7749
+ ctx.addEnv(
7750
+ "JOB_SUBNETS",
7751
+ new Output7(new Set(findInputDeps4(subnets)), async (resolve2) => {
7752
+ const resolved = await resolveInputs4(subnets);
7753
+ resolve2(JSON.stringify(resolved));
7754
+ })
7755
+ );
7756
+ ctx.addEnv("JOB_SECURITY_GROUP", ctx.shared.get("job", "security-group-id"));
7757
+ ctx.addEnv("JOB_PAYLOAD_BUCKET", ctx.shared.get("job", "bucket-name"));
7758
+ const jobs = Object.entries(ctx.stackConfig.jobs ?? {});
7759
+ if (jobs.length === 0) return;
7760
+ for (const [id, props] of jobs) {
7761
+ const group = new Group26(ctx.stack, "job", id);
7762
+ createFargateJob(group, ctx, "job", id, props);
7763
+ }
7764
+ ctx.addStackPermission({
7765
+ actions: ["ecs:RunTask"],
7766
+ resources: [
7767
+ `arn:aws:ecs:${ctx.appConfig.region}:*:task-definition/${ctx.app.name}--${ctx.stackConfig.name}--*`
7768
+ ]
7769
+ });
7770
+ ctx.addStackPermission({
7771
+ actions: ["iam:PassRole"],
7772
+ resources: ["*"],
7773
+ conditions: {
7774
+ StringEquals: {
7775
+ "iam:PassedToService": "ecs-tasks.amazonaws.com"
7776
+ }
7777
+ }
7778
+ });
7779
+ ctx.addStackPermission({
7780
+ actions: ["s3:PutObject"],
7781
+ resources: [
7782
+ `arn:aws:s3:::${formatGlobalResourceName({
7783
+ appName: ctx.app.name,
7784
+ resourceType: "job",
7785
+ resourceName: "assets",
7786
+ postfix: ctx.appId
7787
+ })}/payloads/*`
7788
+ ]
7789
+ });
7790
+ }
7791
+ });
7792
+
7793
+ // src/feature/instance/index.ts
7794
+ import { Group as Group28 } from "@terraforge/core";
7795
+ import { aws as aws29 } from "@terraforge/aws";
7796
+
7797
+ // src/feature/instance/util.ts
7798
+ import { toDays as toDays10, toSeconds as toSeconds10 } from "@awsless/duration";
7799
+ import { stringify as stringify2 } from "@awsless/json";
7800
+ import { toMebibytes as toMebibytes6 } from "@awsless/size";
7801
+ import { generateFileHash as generateFileHash3 } from "@awsless/ts-file-cache";
7802
+ import { aws as aws28 } from "@terraforge/aws";
7803
+ import { Group as Group27, Output as Output8, findInputDeps as findInputDeps5, resolveInputs as resolveInputs5 } from "@terraforge/core";
7804
+ import { constantCase as constantCase13, pascalCase as pascalCase4 } from "change-case";
7805
+ import { createHash as createHash6 } from "crypto";
7806
+ import deepmerge5 from "deepmerge";
7807
+ import { join as join19 } from "path";
7808
+
7809
+ // src/feature/instance/build/executable.ts
7810
+ import { createHash as createHash5 } from "crypto";
7811
+ import { readFile as readFile5 } from "fs/promises";
7812
+ import { join as join18 } from "path";
7813
+ var buildExecutable = async (input, outputPath, architecture) => {
7814
+ const filePath = join18(outputPath, "program");
7815
+ const target = architecture === "x86_64" ? "bun-linux-x64" : "bun-linux-arm64";
7816
+ let result;
7817
+ try {
7818
+ result = await Bun.build({
7819
+ entrypoints: [input],
7820
+ compile: {
7821
+ target,
7822
+ outfile: filePath
7823
+ },
7824
+ target: "bun"
7825
+ });
7826
+ } catch (error) {
7827
+ throw new ExpectedError(
7828
+ `Executable build failed: ${error instanceof Error ? error.message : JSON.stringify(error)}`
7829
+ );
7830
+ }
7831
+ if (!result.success) {
7832
+ throw new ExpectedError(`Executable build failed:
7833
+ ${result.logs?.map((log35) => log35.message).join("\n")}`);
7834
+ }
7835
+ const file = await readFile5(filePath);
7836
+ return {
7837
+ hash: createHash5("sha1").update(file).update("x86_64").digest("hex"),
7838
+ file
7839
+ };
7840
+ };
7841
+
7842
+ // src/feature/instance/util.ts
7843
+ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7844
+ const group = new Group27(parentGroup, "instance", ns);
7845
+ const name = formatLocalResourceName({
7846
+ appName: ctx.app.name,
7847
+ stackName: ctx.stack.name,
7848
+ resourceType: ns,
7849
+ resourceName: id
7850
+ });
7851
+ const shortName = shortId(`${ctx.app.name}:${ctx.stack.name}:${ns}:${id}:${ctx.appId}`);
7852
+ const props = deepmerge5(ctx.appConfig.defaults.instance, local);
7853
+ const image2 = props.image || (props.architecture === "arm64" ? "public.ecr.aws/aws-cli/aws-cli:arm64" : "public.ecr.aws/aws-cli/aws-cli:amd64");
7854
+ ctx.registerBuild("instance", name, async (build3, { workspace }) => {
7855
+ const fingerprint = await generateFileHash3(workspace, local.code.file);
7856
+ return build3(fingerprint, async (write) => {
7857
+ const temp = await createTempFolder(`instance--${name}`);
7858
+ const executable = await buildExecutable(local.code.file, temp.path, props.architecture);
7859
+ await Promise.all([
7860
+ //
7861
+ write("HASH", executable.hash),
7862
+ write("program", executable.file),
7863
+ temp.delete()
7864
+ ]);
7865
+ return {
7866
+ size: formatByteSize(executable.file.byteLength)
7867
+ };
7868
+ });
7869
+ });
7870
+ const code = new aws28.s3.BucketObject(group, "code", {
7871
+ bucket: ctx.shared.get("instance", "bucket-name"),
7872
+ key: name,
7873
+ source: relativePath(getBuildPath("instance", name, "program")),
7874
+ sourceHash: $file(getBuildPath("instance", name, "HASH"))
7875
+ });
7876
+ const executionRole = new aws28.iam.Role(group, "execution-role", {
7877
+ name: shortId(`${shortName}:execution-role`),
7878
+ description: name,
7879
+ assumeRolePolicy: JSON.stringify({
7880
+ Version: "2012-10-17",
7881
+ Statement: [
7882
+ {
7883
+ Effect: "Allow",
7884
+ Action: "sts:AssumeRole",
7885
+ Principal: {
7886
+ Service: ["ecs-tasks.amazonaws.com"]
7887
+ }
7888
+ }
7889
+ ]
7890
+ }),
7891
+ managedPolicyArns: ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
7892
+ });
7893
+ const role = new aws28.iam.Role(
7894
+ group,
7895
+ "task-role",
7896
+ {
7897
+ name: shortId(`${shortName}:task-role`),
7898
+ description: name,
7899
+ assumeRolePolicy: JSON.stringify({
7900
+ Version: "2012-10-17",
7901
+ Statement: [
7902
+ {
7903
+ Effect: "Allow",
7904
+ Action: "sts:AssumeRole",
7905
+ Principal: {
7906
+ Service: ["ecs-tasks.amazonaws.com"]
7907
+ }
7908
+ }
7909
+ ]
7910
+ }),
7911
+ inlinePolicy: [
7912
+ {
7913
+ name: "s3-code-access",
7914
+ policy: $resolve([code.bucket, code.key], (bucket, key) => {
7915
+ return JSON.stringify({
7916
+ Version: "2012-10-17",
7917
+ Statement: [
7918
+ {
7919
+ Effect: pascalCase4("allow"),
7920
+ Action: ["s3:getObject", "s3:HeadObject"],
7921
+ Resource: `arn:aws:s3:::${bucket}/${key}`
7922
+ }
7923
+ ]
7924
+ });
7925
+ })
7926
+ }
7927
+ ]
7928
+ },
7929
+ {
7930
+ dependsOn: [code]
7931
+ }
7932
+ );
7933
+ const statements = [];
7934
+ const statementDeps = /* @__PURE__ */ new Set();
7935
+ const policy = new aws28.iam.RolePolicy(group, "policy", {
7936
+ role: role.name,
7937
+ name: "task-policy",
7938
+ policy: new Output8(statementDeps, async (resolve2) => {
7939
+ const list3 = await resolveInputs5(statements);
7940
+ resolve2(
7941
+ JSON.stringify({
7942
+ Version: "2012-10-17",
7943
+ Statement: list3.map((statement) => ({
7944
+ Effect: pascalCase4(statement.effect ?? "allow"),
7945
+ Action: statement.actions,
7946
+ Resource: statement.resources
7947
+ }))
7948
+ })
7949
+ );
7950
+ })
7951
+ });
7952
+ const addPermission = (...permissions) => {
7953
+ statements.push(...permissions);
7954
+ for (const dep of findInputDeps5(permissions)) {
7955
+ statementDeps.add(dep);
7956
+ }
7957
+ };
7958
+ ctx.onPermission((statement) => {
7959
+ addPermission(statement);
7960
+ });
7961
+ let logGroup;
7962
+ if (props.log.retention && props.log.retention.value > 0n) {
7963
+ logGroup = new aws28.cloudwatch.LogGroup(group, "log", {
7964
+ name: `/aws/ecs/${name}`,
7965
+ // name: `/aws/lambda/${name}`,
7966
+ retentionInDays: toDays10(props.log.retention)
7967
+ });
7968
+ if (ctx.shared.has("on-error-log", "subscriber-arn")) {
7969
+ new aws28.cloudwatch.LogSubscriptionFilter(group, "on-error-log", {
7970
+ name: "error-log-subscription",
7971
+ destinationArn: ctx.shared.get("on-error-log", "subscriber-arn"),
7972
+ logGroupName: logGroup.name,
7973
+ filterPattern
7974
+ });
7975
+ }
7976
+ }
7977
+ const tags = {
7978
+ APP: ctx.appConfig.name,
7979
+ APP_ID: ctx.appId,
7980
+ STACK: ctx.stackConfig.name
7981
+ };
7982
+ const variables = {};
7983
+ const variableDeps = /* @__PURE__ */ new Set();
7984
+ const task2 = new aws28.ecs.TaskDefinition(
7985
+ group,
7986
+ "task",
7987
+ {
7988
+ family: name,
7989
+ networkMode: "awsvpc",
7990
+ cpu: props.cpu,
7991
+ memory: toMebibytes6(props.memorySize).toString(),
7992
+ requiresCompatibilities: ["FARGATE"],
7993
+ executionRoleArn: executionRole.arn,
7994
+ taskRoleArn: role.arn,
7995
+ runtimePlatform: {
7996
+ cpuArchitecture: constantCase13(props.architecture),
7997
+ operatingSystemFamily: "LINUX"
7998
+ },
7999
+ trackLatest: true,
8000
+ containerDefinitions: new Output8(variableDeps, async (resolve2) => {
8001
+ const data = await resolveInputs5(variables);
8002
+ const { s3Bucket, s3Key } = await resolveInputs5({
8003
+ s3Bucket: code.bucket,
8004
+ s3Key: code.key
8005
+ });
8006
+ resolve2(
7450
8007
  JSON.stringify([
7451
8008
  {
7452
8009
  name: `container-${id}`,
@@ -7497,12 +8054,12 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7497
8054
  healthCheck: props.healthCheck ? {
7498
8055
  command: [
7499
8056
  "CMD-SHELL",
7500
- `curl -f http://${join18("localhost", props.healthCheck.path)} || exit 1`
8057
+ `curl -f http://${join19("localhost", props.healthCheck.path)} || exit 1`
7501
8058
  ],
7502
- interval: toSeconds9(props.healthCheck.interval),
8059
+ interval: toSeconds10(props.healthCheck.interval),
7503
8060
  retries: props.healthCheck.retries,
7504
- startPeriod: toSeconds9(props.healthCheck.startPeriod),
7505
- timeout: toSeconds9(props.healthCheck.timeout)
8061
+ startPeriod: toSeconds10(props.healthCheck.startPeriod),
8062
+ timeout: toSeconds10(props.healthCheck.timeout)
7506
8063
  } : void 0
7507
8064
  }
7508
8065
  ])
@@ -7522,14 +8079,14 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7522
8079
  dependsOn: [code]
7523
8080
  }
7524
8081
  );
7525
- const securityGroup = new aws26.security.Group(group, "security-group", {
8082
+ const securityGroup = new aws28.security.Group(group, "security-group", {
7526
8083
  name,
7527
8084
  description: "Security group for the instance",
7528
8085
  vpcId: ctx.shared.get("vpc", "id"),
7529
8086
  revokeRulesOnDelete: true,
7530
8087
  tags
7531
8088
  });
7532
- new aws26.vpc.SecurityGroupEgressRule(group, "egress-rule", {
8089
+ new aws28.vpc.SecurityGroupEgressRule(group, "egress-rule", {
7533
8090
  securityGroupId: securityGroup.id,
7534
8091
  description: `Allow all outbound traffic from the ${name} instance`,
7535
8092
  ipProtocol: "-1",
@@ -7538,7 +8095,7 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7538
8095
  });
7539
8096
  const clusterName = ctx.shared.get("instance", "cluster-name");
7540
8097
  const clusterArn = ctx.shared.get("instance", "cluster-arn");
7541
- const service = new aws26.ecs.Service(
8098
+ const service = new aws28.ecs.Service(
7542
8099
  group,
7543
8100
  "service",
7544
8101
  {
@@ -7574,7 +8131,7 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7574
8131
  replaceOnChanges: ["cluster"]
7575
8132
  }
7576
8133
  );
7577
- new aws26.appautoscaling.Target(
8134
+ new aws28.appautoscaling.Target(
7578
8135
  group,
7579
8136
  "autoscaling-target",
7580
8137
  {
@@ -7593,7 +8150,7 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7593
8150
  );
7594
8151
  ctx.onEnv((name2, value) => {
7595
8152
  variables[name2] = value;
7596
- for (const dep of findInputDeps3([value])) {
8153
+ for (const dep of findInputDeps5([value])) {
7597
8154
  variableDeps.add(dep);
7598
8155
  }
7599
8156
  });
@@ -7602,7 +8159,7 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7602
8159
  variables.AWS_ACCOUNT_ID = ctx.accountId;
7603
8160
  variables.STACK = ctx.stackConfig.name;
7604
8161
  variables.CODE_HASH = code.sourceHash;
7605
- variables.INSTANCE_CONFIG_HASH = createHash4("sha1").update(stringify(props)).digest("hex");
8162
+ variables.INSTANCE_CONFIG_HASH = createHash6("sha1").update(stringify2(props)).digest("hex");
7606
8163
  if (props.environment) {
7607
8164
  for (const [key, value] of Object.entries(props.environment)) {
7608
8165
  variables[key] = value;
@@ -7621,8 +8178,8 @@ var createFargateTask = (parentGroup, ctx, ns, id, local) => {
7621
8178
  var instanceFeature = defineFeature({
7622
8179
  name: "instance",
7623
8180
  onBefore(ctx) {
7624
- const group = new Group26(ctx.base, "instance", "asset");
7625
- const bucket = new aws27.s3.Bucket(group, "bucket", {
8181
+ const group = new Group28(ctx.base, "instance", "asset");
8182
+ const bucket = new aws29.s3.Bucket(group, "bucket", {
7626
8183
  bucket: formatGlobalResourceName({
7627
8184
  appName: ctx.app.name,
7628
8185
  resourceType: "instance",
@@ -7640,8 +8197,8 @@ var instanceFeature = defineFeature({
7640
8197
  if (found.length === 0) {
7641
8198
  return;
7642
8199
  }
7643
- const group = new Group26(ctx.base, "instance", "cluster");
7644
- const cluster = new aws27.ecs.Cluster(
8200
+ const group = new Group28(ctx.base, "instance", "cluster");
8201
+ const cluster = new aws29.ecs.Cluster(
7645
8202
  group,
7646
8203
  "cluster",
7647
8204
  {
@@ -7656,18 +8213,18 @@ var instanceFeature = defineFeature({
7656
8213
  },
7657
8214
  onStack(ctx) {
7658
8215
  for (const [id, props] of Object.entries(ctx.stackConfig.instances ?? {})) {
7659
- const group = new Group26(ctx.stack, "instance", id);
8216
+ const group = new Group28(ctx.stack, "instance", id);
7660
8217
  createFargateTask(group, ctx, "instance", id, props);
7661
8218
  }
7662
8219
  }
7663
8220
  });
7664
8221
 
7665
8222
  // 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 = `
8223
+ import { Group as Group29 } from "@terraforge/core";
8224
+ import { aws as aws30 } from "@terraforge/aws";
8225
+ import { kebabCase as kebabCase8, constantCase as constantCase14 } from "change-case";
8226
+ import { toSeconds as toSeconds11 } from "@awsless/duration";
8227
+ var typeGenCode11 = `
7671
8228
  import { type PutDataProps, putData, batchPutData } from '@awsless/cloudwatch'
7672
8229
  import { type Duration } from '@awsless/duration'
7673
8230
  import { type Size } from '@awsless/size'
@@ -7715,7 +8272,7 @@ var metricFeature = defineFeature({
7715
8272
  resources2.addType(stack.name, stackResources);
7716
8273
  }
7717
8274
  resources2.addType("batch", "Batch");
7718
- gen.addCode(typeGenCode10);
8275
+ gen.addCode(typeGenCode11);
7719
8276
  gen.addInterface("MetricResources", resources2);
7720
8277
  await ctx.write("metric.d.ts", gen, true);
7721
8278
  },
@@ -7732,16 +8289,16 @@ var metricFeature = defineFeature({
7732
8289
  }
7733
8290
  });
7734
8291
  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);
8292
+ const group = new Group29(ctx.stack, "metric", id);
8293
+ ctx.addEnv(`METRIC_${constantCase14(id)}`, props.type);
7737
8294
  for (const alarmId in props.alarms ?? []) {
7738
- const alarmGroup = new Group27(group, "alarm", alarmId);
8295
+ const alarmGroup = new Group29(group, "alarm", alarmId);
7739
8296
  const alarmName = kebabCase8(`${id}-${alarmId}`);
7740
8297
  const alarmProps = props.alarms[alarmId];
7741
8298
  let alarmAction;
7742
8299
  let alarmLambda;
7743
8300
  if (Array.isArray(alarmProps.trigger)) {
7744
- const topic = new aws28.sns.Topic(alarmGroup, "alarm-trigger", {
8301
+ const topic = new aws30.sns.Topic(alarmGroup, "alarm-trigger", {
7745
8302
  name: formatLocalResourceName({
7746
8303
  appName: ctx.app.name,
7747
8304
  stackName: ctx.stack.name,
@@ -7751,7 +8308,7 @@ var metricFeature = defineFeature({
7751
8308
  });
7752
8309
  alarmAction = topic.arn;
7753
8310
  for (const email of alarmProps.trigger) {
7754
- new aws28.sns.TopicSubscription(alarmGroup, email, {
8311
+ new aws30.sns.TopicSubscription(alarmGroup, email, {
7755
8312
  topicArn: topic.arn,
7756
8313
  protocol: "email",
7757
8314
  endpoint: email
@@ -7762,7 +8319,7 @@ var metricFeature = defineFeature({
7762
8319
  alarmLambda = lambda;
7763
8320
  alarmAction = lambda.arn;
7764
8321
  }
7765
- const alarm = new aws28.cloudwatch.MetricAlarm(alarmGroup, "alarm", {
8322
+ const alarm = new aws30.cloudwatch.MetricAlarm(alarmGroup, "alarm", {
7766
8323
  namespace,
7767
8324
  metricName: kebabCase8(id),
7768
8325
  alarmName: formatLocalResourceName({
@@ -7774,13 +8331,13 @@ var metricFeature = defineFeature({
7774
8331
  alarmDescription: alarmProps.description,
7775
8332
  statistic: alarmProps.where.stat,
7776
8333
  threshold: alarmProps.where.value,
7777
- period: toSeconds10(alarmProps.period),
8334
+ period: toSeconds11(alarmProps.period),
7778
8335
  evaluationPeriods: alarmProps.minDataPoints,
7779
8336
  comparisonOperator: alarmProps.where.op,
7780
8337
  alarmActions: [alarmAction]
7781
8338
  });
7782
8339
  if (alarmLambda) {
7783
- new aws28.lambda.Permission(alarmGroup, "permission", {
8340
+ new aws30.lambda.Permission(alarmGroup, "permission", {
7784
8341
  action: "lambda:InvokeFunction",
7785
8342
  principal: "lambda.alarms.cloudwatch.amazonaws.com",
7786
8343
  functionName: alarmLambda.functionName,
@@ -7793,10 +8350,10 @@ var metricFeature = defineFeature({
7793
8350
  });
7794
8351
 
7795
8352
  // 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";
8353
+ import { days as days9, seconds as seconds10, toSeconds as toSeconds12, years } from "@awsless/duration";
8354
+ import { Future, Group as Group30 } from "@terraforge/core";
8355
+ import { aws as aws31 } from "@terraforge/aws";
8356
+ import { camelCase as camelCase9, constantCase as constantCase15 } from "change-case";
7800
8357
 
7801
8358
  // src/feature/router/router-code.ts
7802
8359
  var getViewerRequestFunctionCode = (props) => {
@@ -8044,18 +8601,18 @@ async function handler(event) {
8044
8601
  `;
8045
8602
 
8046
8603
  // src/feature/router/index.ts
8047
- import { createHash as createHash5 } from "crypto";
8604
+ import { createHash as createHash7 } from "crypto";
8048
8605
  var routerFeature = defineFeature({
8049
8606
  name: "router",
8050
8607
  onApp(ctx) {
8051
8608
  for (const [id, props] of Object.entries(ctx.appConfig.defaults.router ?? {})) {
8052
- const group = new Group28(ctx.base, "router", id);
8609
+ const group = new Group30(ctx.base, "router", id);
8053
8610
  const name = formatGlobalResourceName({
8054
8611
  appName: ctx.app.name,
8055
8612
  resourceType: "router",
8056
8613
  resourceName: id
8057
8614
  });
8058
- const routeStore = new aws29.cloudfront.KeyValueStore(group, "routes", {
8615
+ const routeStore = new aws31.cloudfront.KeyValueStore(group, "routes", {
8059
8616
  name,
8060
8617
  comment: "Store for routes"
8061
8618
  });
@@ -8085,11 +8642,11 @@ var routerFeature = defineFeature({
8085
8642
  );
8086
8643
  importedRoutes.push(importKeys);
8087
8644
  });
8088
- const cache = new aws29.cloudfront.CachePolicy(group, "cache", {
8645
+ const cache = new aws31.cloudfront.CachePolicy(group, "cache", {
8089
8646
  name,
8090
- minTtl: toSeconds11(seconds10(0)),
8091
- maxTtl: toSeconds11(days9(365)),
8092
- defaultTtl: toSeconds11(days9(0)),
8647
+ minTtl: toSeconds12(seconds10(0)),
8648
+ maxTtl: toSeconds12(days9(365)),
8649
+ defaultTtl: toSeconds12(days9(0)),
8093
8650
  parametersInCacheKeyAndForwardedToOrigin: {
8094
8651
  enableAcceptEncodingBrotli: true,
8095
8652
  enableAcceptEncodingGzip: true,
@@ -8117,10 +8674,10 @@ var routerFeature = defineFeature({
8117
8674
  }
8118
8675
  }
8119
8676
  });
8120
- const originRequest = new aws29.cloudfront.OriginRequestPolicy(group, "request", {
8677
+ const originRequest = new aws31.cloudfront.OriginRequestPolicy(group, "request", {
8121
8678
  name,
8122
8679
  headersConfig: {
8123
- headerBehavior: camelCase8("all-except"),
8680
+ headerBehavior: camelCase9("all-except"),
8124
8681
  headers: {
8125
8682
  items: [
8126
8683
  "host"
@@ -8135,11 +8692,11 @@ var routerFeature = defineFeature({
8135
8692
  queryStringBehavior: "all"
8136
8693
  }
8137
8694
  });
8138
- const responseHeaders = new aws29.cloudfront.ResponseHeadersPolicy(group, "response", {
8695
+ const responseHeaders = new aws31.cloudfront.ResponseHeadersPolicy(group, "response", {
8139
8696
  name,
8140
8697
  corsConfig: {
8141
8698
  originOverride: props.cors?.override ?? true,
8142
- accessControlMaxAgeSec: toSeconds11(props.cors?.maxAge ?? years(1)),
8699
+ accessControlMaxAgeSec: toSeconds12(props.cors?.maxAge ?? years(1)),
8143
8700
  accessControlAllowHeaders: { items: props.cors?.headers ?? ["*"] },
8144
8701
  accessControlAllowMethods: { items: props.cors?.methods ?? ["ALL"] },
8145
8702
  accessControlAllowOrigins: { items: props.cors?.origins ?? ["*"] },
@@ -8164,7 +8721,7 @@ var routerFeature = defineFeature({
8164
8721
  strictTransportSecurity: {
8165
8722
  override: true,
8166
8723
  preload: true,
8167
- accessControlMaxAgeSec: toSeconds11(years(1)),
8724
+ accessControlMaxAgeSec: toSeconds12(years(1)),
8168
8725
  includeSubdomains: true
8169
8726
  },
8170
8727
  xssProtection: {
@@ -8174,7 +8731,7 @@ var routerFeature = defineFeature({
8174
8731
  }
8175
8732
  }
8176
8733
  });
8177
- const viewerRequest = new aws29.cloudfront.Function(group, "viewer-request", {
8734
+ const viewerRequest = new aws31.cloudfront.Function(group, "viewer-request", {
8178
8735
  name,
8179
8736
  runtime: `cloudfront-js-2.0`,
8180
8737
  comment: `Viewer Request - ${name}`,
@@ -8196,7 +8753,7 @@ var routerFeature = defineFeature({
8196
8753
  rateBasedStatement: {
8197
8754
  limit: wafSettingsConfig.rateLimiter.limit,
8198
8755
  aggregateKeyType: "IP",
8199
- evaluationWindowSec: toSeconds11(wafSettingsConfig.rateLimiter.window)
8756
+ evaluationWindowSec: toSeconds12(wafSettingsConfig.rateLimiter.window)
8200
8757
  }
8201
8758
  },
8202
8759
  action: {
@@ -8276,7 +8833,7 @@ var routerFeature = defineFeature({
8276
8833
  }
8277
8834
  let waf;
8278
8835
  if (wafRules.length && wafSettingsConfig) {
8279
- waf = new aws29.wafv2.WebAcl(group, "waf", {
8836
+ waf = new aws31.wafv2.WebAcl(group, "waf", {
8280
8837
  name: `${name}-wafv2`,
8281
8838
  scope: "CLOUDFRONT",
8282
8839
  defaultAction: {
@@ -8286,12 +8843,12 @@ var routerFeature = defineFeature({
8286
8843
  rule: wafRules,
8287
8844
  captchaConfig: {
8288
8845
  immunityTimeProperty: {
8289
- immunityTime: toSeconds11(wafSettingsConfig.captchaImmunityTime)
8846
+ immunityTime: toSeconds12(wafSettingsConfig.captchaImmunityTime)
8290
8847
  }
8291
8848
  },
8292
8849
  challengeConfig: {
8293
8850
  immunityTimeProperty: {
8294
- immunityTime: toSeconds11(wafSettingsConfig.challengeImmunityTime)
8851
+ immunityTime: toSeconds12(wafSettingsConfig.challengeImmunityTime)
8295
8852
  }
8296
8853
  },
8297
8854
  visibilityConfig: {
@@ -8301,7 +8858,7 @@ var routerFeature = defineFeature({
8301
8858
  }
8302
8859
  });
8303
8860
  }
8304
- const distribution = new aws29.cloudfront.MultitenantDistribution(group, "distribution", {
8861
+ const distribution = new aws31.cloudfront.MultitenantDistribution(group, "distribution", {
8305
8862
  tags: {
8306
8863
  name
8307
8864
  },
@@ -8347,7 +8904,7 @@ var routerFeature = defineFeature({
8347
8904
  }
8348
8905
  return {
8349
8906
  errorCode: Number(errorCode),
8350
- errorCachingMinTtl: item.minTTL ? toSeconds11(item.minTTL) : void 0,
8907
+ errorCachingMinTtl: item.minTTL ? toSeconds12(item.minTTL) : void 0,
8351
8908
  responseCode: item.statusCode?.toString() ?? errorCode,
8352
8909
  responsePagePath: item.path
8353
8910
  };
@@ -8395,11 +8952,11 @@ var routerFeature = defineFeature({
8395
8952
  {
8396
8953
  distributionId: distribution.id,
8397
8954
  paths,
8398
- version: new Future((resolve) => {
8955
+ version: new Future((resolve2) => {
8399
8956
  $combine(...versions).then((versions2) => {
8400
8957
  const combined = versions2.filter((v) => !!v).sort().join(",");
8401
- const version = createHash5("sha1").update(combined).digest("hex");
8402
- resolve(version);
8958
+ const version = createHash7("sha1").update(combined).digest("hex");
8959
+ resolve2(version);
8403
8960
  });
8404
8961
  })
8405
8962
  },
@@ -8413,10 +8970,10 @@ var routerFeature = defineFeature({
8413
8970
  const domainName = formatFullDomainName(ctx.appConfig, props.domain, props.subDomain);
8414
8971
  const certificateArn = ctx.shared.entry("domain", `global-certificate-arn`, props.domain);
8415
8972
  const zoneId = ctx.shared.entry("domain", "zone-id", props.domain);
8416
- const connectionGroup = new aws29.cloudfront.ConnectionGroup(group, "connection-group", {
8973
+ const connectionGroup = new aws31.cloudfront.ConnectionGroup(group, "connection-group", {
8417
8974
  name
8418
8975
  });
8419
- new aws29.cloudfront.DistributionTenant(group, `tenant`, {
8976
+ new aws31.cloudfront.DistributionTenant(group, `tenant`, {
8420
8977
  name,
8421
8978
  enabled: true,
8422
8979
  distributionId: distribution.id,
@@ -8424,7 +8981,7 @@ var routerFeature = defineFeature({
8424
8981
  domain: [{ domain: domainName }],
8425
8982
  customizations: [{ certificate: [{ arn: certificateArn }] }]
8426
8983
  });
8427
- new aws29.route53.Record(group, `record`, {
8984
+ new aws31.route53.Record(group, `record`, {
8428
8985
  zoneId,
8429
8986
  type: "A",
8430
8987
  name: domainName,
@@ -8434,7 +8991,7 @@ var routerFeature = defineFeature({
8434
8991
  evaluateTargetHealth: false
8435
8992
  }
8436
8993
  });
8437
- ctx.bind(`ROUTER_${constantCase14(id)}_ENDPOINT`, domainName);
8994
+ ctx.bind(`ROUTER_${constantCase15(id)}_ENDPOINT`, domainName);
8438
8995
  }
8439
8996
  }
8440
8997
  }
@@ -8457,7 +9014,7 @@ var features = [
8457
9014
  // 5
8458
9015
  functionFeature,
8459
9016
  instanceFeature,
8460
- // jobFeature,
9017
+ jobFeature,
8461
9018
  // graphqlFeature,
8462
9019
  configFeature,
8463
9020
  searchFeature,
@@ -9546,14 +10103,14 @@ import wildstring4 from "wildstring";
9546
10103
 
9547
10104
  // src/cli/ui/complex/run-tests.ts
9548
10105
  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";
10106
+ import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
10107
+ import { join as join21 } from "path";
9551
10108
  import wildstring3 from "wildstring";
9552
- import { parse as parse4, stringify as stringify2 } from "@awsless/json";
10109
+ import { parse as parse4, stringify as stringify3 } from "@awsless/json";
9553
10110
  import { generateFolderHash, loadWorkspace as loadWorkspace2 } from "@awsless/ts-file-cache";
9554
10111
 
9555
10112
  // src/test/start.ts
9556
- import { dirname as dirname9, join as join19 } from "path";
10113
+ import { dirname as dirname9, join as join20 } from "path";
9557
10114
  import { fileURLToPath as fileURLToPath4 } from "url";
9558
10115
  import { configDefaults } from "vitest/config";
9559
10116
  import { startVitest } from "vitest/node";
@@ -9591,7 +10148,7 @@ var startTest = async (props) => {
9591
10148
  // },
9592
10149
  setupFiles: [
9593
10150
  //
9594
- join19(__dirname5, "test-global-setup.js")
10151
+ join20(__dirname5, "test-global-setup.js")
9595
10152
  ]
9596
10153
  // globalSetup: [
9597
10154
  // //
@@ -9785,12 +10342,12 @@ var logTestErrors = (event) => {
9785
10342
  };
9786
10343
  var runTest = async (stack, dir, filters, workspace, opts) => {
9787
10344
  await mkdir4(directories.test, { recursive: true });
9788
- const file = join20(directories.test, `${stack}.json`);
10345
+ const file = join21(directories.test, `${stack}.json`);
9789
10346
  const fingerprint = await generateFolderHash(workspace, dir);
9790
10347
  if (!process.env.NO_CACHE) {
9791
10348
  const exists = await fileExist(file);
9792
10349
  if (exists) {
9793
- const raw = await readFile5(file, { encoding: "utf8" });
10350
+ const raw = await readFile6(file, { encoding: "utf8" });
9794
10351
  const data = parse4(raw);
9795
10352
  if (data.fingerprint === fingerprint) {
9796
10353
  log18.step(
@@ -9833,9 +10390,9 @@ var runTest = async (stack, dir, filters, workspace, opts) => {
9833
10390
  logTestLogs(result);
9834
10391
  }
9835
10392
  logTestErrors(result);
9836
- await writeFile3(
10393
+ await writeFile4(
9837
10394
  file,
9838
- stringify2({
10395
+ stringify3({
9839
10396
  ...result,
9840
10397
  fingerprint
9841
10398
  })
@@ -10373,7 +10930,7 @@ var auth = (program2) => {
10373
10930
  // src/cli/command/bind.ts
10374
10931
  import { log as log24 } from "@awsless/clui";
10375
10932
  import chalk4 from "chalk";
10376
- import { constantCase as constantCase15 } from "change-case";
10933
+ import { constantCase as constantCase16 } from "change-case";
10377
10934
  var bind = (program2) => {
10378
10935
  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
10936
  await layout("bind", async ({ appConfig, stackConfigs }) => {
@@ -10400,10 +10957,10 @@ var bind = (program2) => {
10400
10957
  const configList = opts.config ?? [];
10401
10958
  const configs = {};
10402
10959
  for (const name of configList) {
10403
- configs[`CONFIG_${constantCase15(name)}`] = name;
10960
+ configs[`CONFIG_${constantCase16(name)}`] = name;
10404
10961
  }
10405
10962
  if (configList.length ?? 0 > 0) {
10406
- log24.note("Bind Config", configList.map((v) => color.label(constantCase15(v))).join("\n"));
10963
+ log24.note("Bind Config", configList.map((v) => color.label(constantCase16(v))).join("\n"));
10407
10964
  }
10408
10965
  if (commands11.length === 0) {
10409
10966
  return "No command to execute.";
@@ -10442,7 +10999,7 @@ var bind = (program2) => {
10442
10999
 
10443
11000
  // src/config/load/watch.ts
10444
11001
  import { watch } from "chokidar";
10445
- var watchConfig = async (options, resolve, reject) => {
11002
+ var watchConfig = async (options, resolve2, reject) => {
10446
11003
  await loadAppConfig(options);
10447
11004
  debug("Start watching...");
10448
11005
  const ext = "{json,jsonc,json5}";
@@ -10457,7 +11014,7 @@ var watchConfig = async (options, resolve, reject) => {
10457
11014
  const appConfig = await loadAppConfig(options);
10458
11015
  const stackConfigs = await loadStackConfigs(options);
10459
11016
  validateFeatures({ appConfig, stackConfigs });
10460
- resolve({ appConfig, stackConfigs });
11017
+ resolve2({ appConfig, stackConfigs });
10461
11018
  } catch (error) {
10462
11019
  reject(error);
10463
11020
  }
@@ -10469,8 +11026,8 @@ var watchConfig = async (options, resolve, reject) => {
10469
11026
  import { log as log25 } from "@awsless/clui";
10470
11027
 
10471
11028
  // 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";
11029
+ import { mkdir as mkdir5, writeFile as writeFile5 } from "fs/promises";
11030
+ import { dirname as dirname10, join as join22, relative as relative9 } from "path";
10474
11031
  var generateTypes = async (props) => {
10475
11032
  const files = [];
10476
11033
  await Promise.all(
@@ -10479,13 +11036,13 @@ var generateTypes = async (props) => {
10479
11036
  ...props,
10480
11037
  async write(file, data, include = false) {
10481
11038
  const code = data?.toString("utf8");
10482
- const path = join21(directories.types, file);
11039
+ const path = join22(directories.types, file);
10483
11040
  if (code) {
10484
11041
  if (include) {
10485
- files.push(relative8(directories.root, path));
11042
+ files.push(relative9(directories.root, path));
10486
11043
  }
10487
11044
  await mkdir5(dirname10(path), { recursive: true });
10488
- await writeFile4(path, code);
11045
+ await writeFile5(path, code);
10489
11046
  }
10490
11047
  }
10491
11048
  });
@@ -10493,7 +11050,7 @@ var generateTypes = async (props) => {
10493
11050
  );
10494
11051
  if (files.length) {
10495
11052
  const code = files.map((file) => `/// <reference path='${file}' />`).join("\n");
10496
- await writeFile4(join21(directories.root, `awsless.d.ts`), code);
11053
+ await writeFile5(join22(directories.root, `awsless.d.ts`), code);
10497
11054
  }
10498
11055
  };
10499
11056
 
@@ -10517,9 +11074,9 @@ var dev = (program2) => {
10517
11074
  logError(error);
10518
11075
  }
10519
11076
  );
10520
- await new Promise((resolve) => {
10521
- process.once("exit", resolve);
10522
- process.once("SIGINT", resolve);
11077
+ await new Promise((resolve2) => {
11078
+ process.once("exit", resolve2);
11079
+ process.once("SIGINT", resolve2);
10523
11080
  });
10524
11081
  });
10525
11082
  });
@@ -10919,7 +11476,7 @@ var domain = (program2) => {
10919
11476
  // src/cli/command/logs.ts
10920
11477
  import { CloudWatchLogsClient, StartLiveTailCommand } from "@aws-sdk/client-cloudwatch-logs";
10921
11478
  import { log as log30 } from "@awsless/clui";
10922
- import { aws as aws30 } from "@terraforge/aws";
11479
+ import { aws as aws32 } from "@terraforge/aws";
10923
11480
  import chalk6 from "chalk";
10924
11481
  import chunk2 from "chunk";
10925
11482
  import { formatDate } from "date-fns";
@@ -10942,7 +11499,7 @@ var logs = (program2) => {
10942
11499
  for (const stack of app.stacks) {
10943
11500
  if (filters.find((f) => wildstring7.match(f, stack.name))) {
10944
11501
  for (const resource of stack.resources) {
10945
- if (resource instanceof aws30.cloudwatch.LogGroup) {
11502
+ if (resource instanceof aws32.cloudwatch.LogGroup) {
10946
11503
  logGroupArns.push(await resource.arn);
10947
11504
  }
10948
11505
  }
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awsless/cli",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -87,23 +87,23 @@
87
87
  "zod": "^3.24.2",
88
88
  "zod-to-json-schema": "^3.24.3",
89
89
  "@awsless/big-float": "^0.1.6",
90
- "@awsless/clui": "^0.0.8",
91
90
  "@awsless/cloudwatch": "^0.0.1",
92
- "@awsless/json": "^0.0.11",
91
+ "@awsless/clui": "^0.0.8",
93
92
  "@awsless/duration": "^0.0.4",
94
- "@awsless/iot": "^0.0.5",
95
- "@awsless/lambda": "^0.0.42",
96
93
  "@awsless/dynamodb": "^0.3.20",
94
+ "@awsless/iot": "^0.0.5",
95
+ "@awsless/json": "^0.0.11",
97
96
  "@awsless/s3": "^0.0.21",
98
- "@awsless/redis": "^0.0.16",
97
+ "@awsless/scheduler": "^0.0.4",
99
98
  "@awsless/size": "^0.0.2",
99
+ "@awsless/lambda": "^0.0.42",
100
100
  "@awsless/sns": "^0.0.10",
101
- "@awsless/ts-file-cache": "^0.0.12",
102
- "@awsless/validate": "^0.1.7",
103
- "awsless": "^0.0.3",
101
+ "@awsless/redis": "^0.0.16",
104
102
  "@awsless/weak-cache": "^0.0.1",
103
+ "awsless": "^0.0.3",
104
+ "@awsless/ts-file-cache": "^0.0.12",
105
105
  "@awsless/sqs": "^0.0.21",
106
- "@awsless/scheduler": "^0.0.4"
106
+ "@awsless/validate": "^0.1.7"
107
107
  },
108
108
  "devDependencies": {
109
109
  "@hono/node-server": "1.19.9",