hereya-cli 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -20,7 +20,7 @@ $ npm install -g hereya-cli
20
20
  $ hereya COMMAND
21
21
  running command...
22
22
  $ hereya (--version)
23
- hereya-cli/0.10.0 linux-x64 node-v20.15.0
23
+ hereya-cli/0.12.0 linux-x64 node-v22.12.0
24
24
  $ hereya --help [COMMAND]
25
25
  USAGE
26
26
  $ hereya COMMAND
@@ -36,7 +36,6 @@ USAGE
36
36
  * [`hereya env [NAME]`](#hereya-env-name)
37
37
  * [`hereya help [COMMAND]`](#hereya-help-command)
38
38
  * [`hereya init PROJECT`](#hereya-init-project)
39
- * [`hereya remote exec [PKGPATH]`](#hereya-remote-exec-pkgpath)
40
39
  * [`hereya remove PACKAGE`](#hereya-remove-package)
41
40
  * [`hereya run CMD`](#hereya-run-cmd)
42
41
  * [`hereya unbootstrap INFRASTRUCTURETYPE`](#hereya-unbootstrap-infrastructuretype)
@@ -73,7 +72,7 @@ EXAMPLES
73
72
  $ hereya add cloudy/docker_postgres
74
73
  ```
75
74
 
76
- _See code: [src/commands/add/index.ts](https://github.com/hereya/hereya-cli/blob/v0.10.0/src/commands/add/index.ts)_
75
+ _See code: [src/commands/add/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/add/index.ts)_
77
76
 
78
77
  ## `hereya bootstrap INFRASTRUCTURETYPE`
79
78
 
@@ -98,7 +97,7 @@ EXAMPLES
98
97
  $ hereya bootstrap local
99
98
  ```
100
99
 
101
- _See code: [src/commands/bootstrap/index.ts](https://github.com/hereya/hereya-cli/blob/v0.10.0/src/commands/bootstrap/index.ts)_
100
+ _See code: [src/commands/bootstrap/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/bootstrap/index.ts)_
102
101
 
103
102
  ## `hereya deploy`
104
103
 
@@ -119,7 +118,7 @@ EXAMPLES
119
118
  $ hereya deploy
120
119
  ```
121
120
 
122
- _See code: [src/commands/deploy/index.ts](https://github.com/hereya/hereya-cli/blob/v0.10.0/src/commands/deploy/index.ts)_
121
+ _See code: [src/commands/deploy/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/deploy/index.ts)_
123
122
 
124
123
  ## `hereya down`
125
124
 
@@ -141,7 +140,7 @@ EXAMPLES
141
140
  $ hereya down
142
141
  ```
143
142
 
144
- _See code: [src/commands/down/index.ts](https://github.com/hereya/hereya-cli/blob/v0.10.0/src/commands/down/index.ts)_
143
+ _See code: [src/commands/down/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/down/index.ts)_
145
144
 
146
145
  ## `hereya env [NAME]`
147
146
 
@@ -172,7 +171,7 @@ EXAMPLES
172
171
  $ hereya env -w dev -l
173
172
  ```
174
173
 
175
- _See code: [src/commands/env/index.ts](https://github.com/hereya/hereya-cli/blob/v0.10.0/src/commands/env/index.ts)_
174
+ _See code: [src/commands/env/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/env/index.ts)_
176
175
 
177
176
  ## `hereya help [COMMAND]`
178
177
 
@@ -218,31 +217,7 @@ EXAMPLES
218
217
  $ hereya init myProject -w=defaultWorkspace --chdir=./myProject
219
218
  ```
220
219
 
221
- _See code: [src/commands/init/index.ts](https://github.com/hereya/hereya-cli/blob/v0.10.0/src/commands/init/index.ts)_
222
-
223
- ## `hereya remote exec [PKGPATH]`
224
-
225
- remotely provision or destroy a package
226
-
227
- ```
228
- USAGE
229
- $ hereya remote exec [PKGPATH] [-o <value>] [-s <value>]
230
-
231
- ARGUMENTS
232
- PKGPATH The path to the package to provision or destroy
233
-
234
- FLAGS
235
- -o, --output=<value> The path to store the output env in
236
- -s, --source=<value> The source of the project to provision or destroy the package for
237
-
238
- DESCRIPTION
239
- remotely provision or destroy a package
240
-
241
- EXAMPLES
242
- $ hereya remote exec
243
- ```
244
-
245
- _See code: [src/commands/remote/exec/index.ts](https://github.com/hereya/hereya-cli/blob/v0.10.0/src/commands/remote/exec/index.ts)_
220
+ _See code: [src/commands/init/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/init/index.ts)_
246
221
 
247
222
  ## `hereya remove PACKAGE`
248
223
 
@@ -265,7 +240,7 @@ EXAMPLES
265
240
  $ hereya remove cloudy/docker_postgres
266
241
  ```
267
242
 
268
- _See code: [src/commands/remove/index.ts](https://github.com/hereya/hereya-cli/blob/v0.10.0/src/commands/remove/index.ts)_
243
+ _See code: [src/commands/remove/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/remove/index.ts)_
269
244
 
270
245
  ## `hereya run CMD`
271
246
 
@@ -291,7 +266,7 @@ EXAMPLES
291
266
  $ hereya run -w uat -- node index.js
292
267
  ```
293
268
 
294
- _See code: [src/commands/run/index.ts](https://github.com/hereya/hereya-cli/blob/v0.10.0/src/commands/run/index.ts)_
269
+ _See code: [src/commands/run/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/run/index.ts)_
295
270
 
296
271
  ## `hereya unbootstrap INFRASTRUCTURETYPE`
297
272
 
@@ -316,7 +291,7 @@ EXAMPLES
316
291
  $ hereya unbootstrap local
317
292
  ```
318
293
 
319
- _See code: [src/commands/unbootstrap/index.ts](https://github.com/hereya/hereya-cli/blob/v0.10.0/src/commands/unbootstrap/index.ts)_
294
+ _See code: [src/commands/unbootstrap/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/unbootstrap/index.ts)_
320
295
 
321
296
  ## `hereya undeploy`
322
297
 
@@ -337,7 +312,7 @@ EXAMPLES
337
312
  $ hereya undeploy
338
313
  ```
339
314
 
340
- _See code: [src/commands/undeploy/index.ts](https://github.com/hereya/hereya-cli/blob/v0.10.0/src/commands/undeploy/index.ts)_
315
+ _See code: [src/commands/undeploy/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/undeploy/index.ts)_
341
316
 
342
317
  ## `hereya up`
343
318
 
@@ -359,7 +334,7 @@ EXAMPLES
359
334
  $ hereya up
360
335
  ```
361
336
 
362
- _See code: [src/commands/up/index.ts](https://github.com/hereya/hereya-cli/blob/v0.10.0/src/commands/up/index.ts)_
337
+ _See code: [src/commands/up/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/up/index.ts)_
363
338
 
364
339
  ## `hereya workspace create NAME`
365
340
 
@@ -379,7 +354,7 @@ EXAMPLES
379
354
  $ hereya workspace create dev
380
355
  ```
381
356
 
382
- _See code: [src/commands/workspace/create/index.ts](https://github.com/hereya/hereya-cli/blob/v0.10.0/src/commands/workspace/create/index.ts)_
357
+ _See code: [src/commands/workspace/create/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/workspace/create/index.ts)_
383
358
 
384
359
  ## `hereya workspace delete NAME`
385
360
 
@@ -399,7 +374,7 @@ EXAMPLES
399
374
  $ hereya workspace delete dev
400
375
  ```
401
376
 
402
- _See code: [src/commands/workspace/delete/index.ts](https://github.com/hereya/hereya-cli/blob/v0.10.0/src/commands/workspace/delete/index.ts)_
377
+ _See code: [src/commands/workspace/delete/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/workspace/delete/index.ts)_
403
378
 
404
379
  ## `hereya workspace env [NAME]`
405
380
 
@@ -425,7 +400,7 @@ EXAMPLES
425
400
  $ hereya workspace env myEnv -w dev
426
401
  ```
427
402
 
428
- _See code: [src/commands/workspace/env/index.ts](https://github.com/hereya/hereya-cli/blob/v0.10.0/src/commands/workspace/env/index.ts)_
403
+ _See code: [src/commands/workspace/env/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/workspace/env/index.ts)_
429
404
 
430
405
  ## `hereya workspace env set`
431
406
 
@@ -449,7 +424,7 @@ EXAMPLES
449
424
  $ hereya workspace env set -w my-workspace -n myVar -v my-value -i aws -s
450
425
  ```
451
426
 
452
- _See code: [src/commands/workspace/env/set/index.ts](https://github.com/hereya/hereya-cli/blob/v0.10.0/src/commands/workspace/env/set/index.ts)_
427
+ _See code: [src/commands/workspace/env/set/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/workspace/env/set/index.ts)_
453
428
 
454
429
  ## `hereya workspace env unset`
455
430
 
@@ -470,7 +445,7 @@ EXAMPLES
470
445
  $ hereya workspace env unset -w my-workspace -n myVar
471
446
  ```
472
447
 
473
- _See code: [src/commands/workspace/env/unset/index.ts](https://github.com/hereya/hereya-cli/blob/v0.10.0/src/commands/workspace/env/unset/index.ts)_
448
+ _See code: [src/commands/workspace/env/unset/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/workspace/env/unset/index.ts)_
474
449
 
475
450
  ## `hereya workspace install PACKAGE`
476
451
 
@@ -496,7 +471,7 @@ EXAMPLES
496
471
  $ hereya workspace install hereya/aws-cognito
497
472
  ```
498
473
 
499
- _See code: [src/commands/workspace/install/index.ts](https://github.com/hereya/hereya-cli/blob/v0.10.0/src/commands/workspace/install/index.ts)_
474
+ _See code: [src/commands/workspace/install/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/workspace/install/index.ts)_
500
475
 
501
476
  ## `hereya workspace uninstall PACKAGE`
502
477
 
@@ -522,5 +497,5 @@ EXAMPLES
522
497
  $ hereya workspace uninstall hereya/aws-cognito
523
498
  ```
524
499
 
525
- _See code: [src/commands/workspace/uninstall/index.ts](https://github.com/hereya/hereya-cli/blob/v0.10.0/src/commands/workspace/uninstall/index.ts)_
500
+ _See code: [src/commands/workspace/uninstall/index.ts](https://github.com/hereya/hereya-cli/blob/v0.12.0/src/commands/workspace/uninstall/index.ts)_
526
501
  <!-- commandsstop -->
@@ -4,13 +4,21 @@ export interface Iac {
4
4
  }
5
5
  export declare enum IacType {
6
6
  cdk = "cdk",
7
- terraform = "terraform"
7
+ opentf = "opentf",
8
+ opentofu = "opentofu",
9
+ terraform = "terraform",
10
+ tofu = "tofu"
8
11
  }
9
12
  export type ApplyInput = {
10
13
  env: {
11
14
  [key: string]: string;
12
15
  };
13
16
  id: string;
17
+ infraConfig?: {
18
+ terraformStateBucketName: string;
19
+ terraformStateBucketRegion?: string;
20
+ terraformStateLockTableName: string;
21
+ };
14
22
  parameters?: {
15
23
  [key: string]: string;
16
24
  };
@@ -1,5 +1,8 @@
1
1
  export var IacType;
2
2
  (function (IacType) {
3
3
  IacType["cdk"] = "cdk";
4
+ IacType["opentf"] = "opentf";
5
+ IacType["opentofu"] = "opentofu";
4
6
  IacType["terraform"] = "terraform";
7
+ IacType["tofu"] = "tofu";
5
8
  })(IacType || (IacType = {}));
package/dist/iac/index.js CHANGED
@@ -8,6 +8,15 @@ export function getIac({ type }) {
8
8
  case IacType.terraform: {
9
9
  return { iac: terraform, supported: true };
10
10
  }
11
+ case IacType.opentofu: {
12
+ return { iac: terraform, supported: true };
13
+ }
14
+ case IacType.opentf: {
15
+ return { iac: terraform, supported: true };
16
+ }
17
+ case IacType.tofu: {
18
+ return { iac: terraform, supported: true };
19
+ }
11
20
  case IacType.cdk: {
12
21
  return { iac: cdk, supported: true };
13
22
  }
@@ -2,8 +2,9 @@ import { ApplyInput, ApplyOutput, DestroyInput, DestroyOutput, Iac } from './com
2
2
  export declare class Terraform implements Iac {
3
3
  apply(input: ApplyInput): Promise<ApplyOutput>;
4
4
  destroy(input: DestroyInput): Promise<DestroyOutput>;
5
- private downloadTerraform;
5
+ downloadTerraform(): Promise<boolean>;
6
6
  private getEnv;
7
7
  private getTerraformBinary;
8
+ private getWithRedirect;
8
9
  private terraformBinPath;
9
10
  }
@@ -8,34 +8,63 @@ import { mapObject } from '../lib/object-utils.js';
8
8
  import { runShell } from '../lib/shell.js';
9
9
  export class Terraform {
10
10
  async apply(input) {
11
+ if (input.infraConfig &&
12
+ input.infraConfig.terraformStateBucketName &&
13
+ input.infraConfig.terraformStateLockTableName &&
14
+ input.infraConfig.terraformStateBucketRegion) {
15
+ const backendConfig = `
16
+ terraform {
17
+ backend "s3" {
18
+ bucket = "${input.infraConfig.terraformStateBucketName}"
19
+ key = "${input.id}/terraform.tfstate"
20
+ region = "${input.infraConfig.terraformStateBucketRegion}"
21
+ dynamodb_table = "${input.infraConfig.terraformStateLockTableName}"
22
+ }
23
+ }
24
+ `;
25
+ const backendFile = path.join(input.pkgPath, 'hereya_terraform_backend.tf');
26
+ await fs.promises.writeFile(backendFile, backendConfig);
27
+ }
11
28
  try {
12
29
  const terraform = await this.getTerraformBinary();
13
30
  runShell(terraform, ['init'], {
14
31
  directory: input.pkgPath,
15
32
  env: {
16
- ...mapObject(input.env ?? {}, (key, value) => [`TF_VAR_${key}`, typeof value === "object" ? JSON.stringify(value) : value]),
33
+ ...mapObject(input.env ?? {}, (key, value) => [
34
+ `TF_VAR_${key}`,
35
+ typeof value === 'object' ? JSON.stringify(value) : value,
36
+ ]),
17
37
  ...input.env,
18
- ...mapObject(input.parameters ?? {}, (key, value) => [`TF_VAR_${key}`, typeof value === "object" ? JSON.stringify(value) : value])
19
- }
38
+ ...mapObject(input.parameters ?? {}, (key, value) => [
39
+ `TF_VAR_${key}`,
40
+ typeof value === 'object' ? JSON.stringify(value) : value,
41
+ ]),
42
+ },
20
43
  });
21
44
  runShell(terraform, ['apply', '-auto-approve'], {
22
45
  directory: input.pkgPath,
23
46
  env: {
24
- ...mapObject(input.env ?? {}, (key, value) => [`TF_VAR_${key}`, typeof value === "object" ? JSON.stringify(value) : value]),
47
+ ...mapObject(input.env ?? {}, (key, value) => [
48
+ `TF_VAR_${key}`,
49
+ typeof value === 'object' ? JSON.stringify(value) : value,
50
+ ]),
25
51
  ...input.env,
26
- ...mapObject(input.parameters ?? {}, (key, value) => [`TF_VAR_${key}`, typeof value === "object" ? JSON.stringify(value) : value])
27
- }
52
+ ...mapObject(input.parameters ?? {}, (key, value) => [
53
+ `TF_VAR_${key}`,
54
+ typeof value === 'object' ? JSON.stringify(value) : value,
55
+ ]),
56
+ },
28
57
  });
29
58
  const env = await this.getEnv(input.pkgPath);
30
59
  return {
31
60
  env,
32
- success: true
61
+ success: true,
33
62
  };
34
63
  }
35
64
  catch (error) {
36
65
  return {
37
66
  reason: error.message,
38
- success: false
67
+ success: false,
39
68
  };
40
69
  }
41
70
  }
@@ -45,48 +74,60 @@ export class Terraform {
45
74
  runShell(terraform, ['init'], {
46
75
  directory: input.pkgPath,
47
76
  env: {
48
- ...mapObject(input.env ?? {}, (key, value) => [`TF_VAR_${key}`, typeof value === "object" ? JSON.stringify(value) : value]),
77
+ ...mapObject(input.env ?? {}, (key, value) => [
78
+ `TF_VAR_${key}`,
79
+ typeof value === 'object' ? JSON.stringify(value) : value,
80
+ ]),
49
81
  ...input.env,
50
- ...mapObject(input.parameters ?? {}, (key, value) => [`TF_VAR_${key}`, typeof value === "object" ? JSON.stringify(value) : value])
51
- }
82
+ ...mapObject(input.parameters ?? {}, (key, value) => [
83
+ `TF_VAR_${key}`,
84
+ typeof value === 'object' ? JSON.stringify(value) : value,
85
+ ]),
86
+ },
52
87
  });
53
88
  const env = await this.getEnv(input.pkgPath);
54
89
  runShell(terraform, ['destroy', '-auto-approve'], {
55
90
  directory: input.pkgPath,
56
91
  env: {
57
- ...mapObject(input.env ?? {}, (key, value) => [`TF_VAR_${key}`, typeof value === "object" ? JSON.stringify(value) : value]),
92
+ ...mapObject(input.env ?? {}, (key, value) => [
93
+ `TF_VAR_${key}`,
94
+ typeof value === 'object' ? JSON.stringify(value) : value,
95
+ ]),
58
96
  ...input.env,
59
- ...mapObject(input.parameters ?? {}, (key, value) => [`TF_VAR_${key}`, typeof value === "object" ? JSON.stringify(value) : value])
60
- }
97
+ ...mapObject(input.parameters ?? {}, (key, value) => [
98
+ `TF_VAR_${key}`,
99
+ typeof value === 'object' ? JSON.stringify(value) : value,
100
+ ]),
101
+ },
61
102
  });
62
103
  return {
63
104
  env,
64
- success: true
105
+ success: true,
65
106
  };
66
107
  }
67
108
  catch (error) {
68
109
  return {
69
110
  reason: error.message,
70
- success: false
111
+ success: false,
71
112
  };
72
113
  }
73
114
  }
74
115
  async downloadTerraform() {
75
116
  const TERRAFORM_DOWNLOAD_URLS = new Map([
76
- ['darwin_arm64', 'https://releases.hashicorp.com/terraform/1.5.7/terraform_1.5.7_darwin_arm64.zip'],
77
- ['darwin_x64', 'https://releases.hashicorp.com/terraform/1.5.7/terraform_1.5.7_darwin_amd64.zip'],
78
- ['freebsd_arm', 'https://releases.hashicorp.com/terraform/1.5.7/terraform_1.5.7_freebsd_arm.zip'],
79
- ['freebsd_ia32', 'https://releases.hashicorp.com/terraform/1.5.7/terraform_1.5.7_freebsd_386.zip'],
80
- ['freebsd_x64', 'https://releases.hashicorp.com/terraform/1.5.7/terraform_1.5.7_freebsd_amd64.zip'],
81
- ['linux_arm', 'https://releases.hashicorp.com/terraform/1.5.7/terraform_1.5.7_linux_arm.zip'],
82
- ['linux_arm64', 'https://releases.hashicorp.com/terraform/1.5.7/terraform_1.5.7_linux_arm64.zip'],
83
- ['linux_ia32', 'https://releases.hashicorp.com/terraform/1.5.7/terraform_1.5.7_linux_386.zip'],
84
- ['linux_x64', 'https://releases.hashicorp.com/terraform/1.5.7/terraform_1.5.7_linux_amd64.zip'],
85
- ['openbsd_ia32', 'https://releases.hashicorp.com/terraform/1.5.7/terraform_1.5.7_openbsd_386.zip'],
86
- ['openbsd_x64', 'https://releases.hashicorp.com/terraform/1.5.7/terraform_1.5.7_openbsd_amd64.zip'],
87
- ['sunos_x64', 'https://releases.hashicorp.com/terraform/1.5.7/terraform_1.5.7_solaris_amd64.zip'],
88
- ['win32_ia32', 'https://releases.hashicorp.com/terraform/1.5.7/terraform_1.5.7_windows_386.zip'],
89
- ['win32_x64', 'https://releases.hashicorp.com/terraform/1.5.7/terraform_1.5.7_windows_amd64.zip'],
117
+ ['darwin_arm64', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_darwin_arm64.zip'],
118
+ ['darwin_x64', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_darwin_amd64.zip'],
119
+ ['freebsd_arm', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_freebsd_arm.zip'],
120
+ ['freebsd_ia32', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_freebsd_386.zip'],
121
+ ['freebsd_x64', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_freebsd_amd64.zip'],
122
+ ['linux_arm', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_linux_arm.zip'],
123
+ ['linux_arm64', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_linux_arm64.zip'],
124
+ ['linux_ia32', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_linux_386.zip'],
125
+ ['linux_x64', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_linux_amd64.zip'],
126
+ ['openbsd_ia32', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_openbsd_386.zip'],
127
+ ['openbsd_x64', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_openbsd_amd64.zip'],
128
+ ['sunos_x64', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_solaris_amd64.zip'],
129
+ ['win32_ia32', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_windows_386.zip'],
130
+ ['win32_x64', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_windows_amd64.zip'],
90
131
  ]);
91
132
  const tfPath = this.terraformBinPath();
92
133
  try {
@@ -114,7 +155,7 @@ export class Terraform {
114
155
  }
115
156
  }
116
157
  await new Promise((resolve, reject) => {
117
- https.get(url, async (response) => {
158
+ this.getWithRedirect(url, async (response) => {
118
159
  try {
119
160
  await pipeline(response, decompress({ path: path.dirname(tfPath) }));
120
161
  await fs.promises.chmod(tfPath, '0755');
@@ -123,7 +164,7 @@ export class Terraform {
123
164
  throw new Error(`could not download terraform: ${error}`);
124
165
  }
125
166
  resolve(null);
126
- }).on('error', error => reject(error));
167
+ }, reject);
127
168
  });
128
169
  return true;
129
170
  }
@@ -131,7 +172,7 @@ export class Terraform {
131
172
  const terraform = await this.getTerraformBinary();
132
173
  const resourceOut = runShell(terraform, ['output', '--json'], {
133
174
  directory: pkgPath,
134
- stdio: 'pipe'
175
+ stdio: 'pipe',
135
176
  });
136
177
  let outStr = resourceOut.output.toString().trim();
137
178
  const start = outStr.indexOf('{');
@@ -149,7 +190,22 @@ export class Terraform {
149
190
  await this.downloadTerraform();
150
191
  return this.terraformBinPath();
151
192
  }
193
+ async getWithRedirect(url, handler, reject) {
194
+ https
195
+ .get(url, (response) => {
196
+ if (response.statusCode === 302) {
197
+ const { location } = response.headers;
198
+ if (location) {
199
+ this.getWithRedirect(location, handler, reject);
200
+ }
201
+ }
202
+ else {
203
+ handler(response);
204
+ }
205
+ })
206
+ .on('error', (error) => reject(error));
207
+ }
152
208
  terraformBinPath() {
153
- return path.join(os.homedir(), '.hereya', 'iac', 'terraform', os.platform() === 'win32' ? 'terraform.exe' : 'terraform');
209
+ return path.join(os.homedir(), '.hereya', 'iac', 'terraform', 'tofu_1', os.platform() === 'win32' ? 'tofu.exe' : 'tofu');
154
210
  }
155
211
  }
@@ -1,5 +1,6 @@
1
1
  import { BootstrapInput, DeployInput, DeployOutput, DestroyInput, DestroyOutput, Infrastructure, ProvisionInput, ProvisionOutput, ResolveEnvInput, ResolveEnvOutput, SaveEnvInput, SaveEnvOutput, StoreEnvInput, StoreEnvOutput, UndeployInput, UndeployOutput, UnstoreEnvInput, UnstoreEnvOutput } from './common.js';
2
2
  export declare class AwsInfrastructure implements Infrastructure {
3
+ private configKey;
3
4
  bootstrap(_: BootstrapInput): Promise<void>;
4
5
  deploy(input: DeployInput): Promise<DeployOutput>;
5
6
  destroy(input: DestroyInput): Promise<DestroyOutput>;
@@ -10,9 +11,5 @@ export declare class AwsInfrastructure implements Infrastructure {
10
11
  unbootstrap(_: BootstrapInput): Promise<void>;
11
12
  undeploy(input: UndeployInput): Promise<UndeployOutput>;
12
13
  unstoreEnv(input: UnstoreEnvInput): Promise<UnstoreEnvOutput>;
13
- private getEnv;
14
- private getFilesToUpload;
15
- private removeEnv;
16
- private runCodeBuild;
17
- private uploadProjectFiles;
14
+ private getConfig;
18
15
  }
@@ -1,20 +1,16 @@
1
- import { BatchGetBuildsCommand, CodeBuildClient, StartBuildCommand } from '@aws-sdk/client-codebuild';
2
- import { DeleteObjectsCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
3
1
  import { GetSecretValueCommand, SecretsManagerClient } from '@aws-sdk/client-secrets-manager';
4
2
  import { DeleteParameterCommand, GetParameterCommand, PutParameterCommand, SSMClient } from '@aws-sdk/client-ssm';
5
3
  import { GetCallerIdentityCommand, STSClient } from '@aws-sdk/client-sts';
6
- import { glob } from 'glob';
7
- import ignore from 'ignore';
8
4
  import { randomUUID } from 'node:crypto';
9
5
  import fs from 'node:fs/promises';
6
+ import os from 'node:os';
10
7
  import path from 'node:path';
11
- import { IacType } from '../iac/common.js';
12
- import { fileExists, getAnyPath } from '../lib/filesystem.js';
13
- import { objectToBase64 } from '../lib/object-utils.js';
8
+ import { getIac } from '../iac/index.js';
9
+ import { downloadPackage } from '../lib/package/index.js';
14
10
  import { runShell } from '../lib/shell.js';
15
- import { InfrastructureType, } from './common.js';
16
11
  import { destroyPackage, provisionPackage } from './index.js';
17
12
  export class AwsInfrastructure {
13
+ configKey = '/hereya-bootstrap/config';
18
14
  async bootstrap(_) {
19
15
  const stsClient = new STSClient({});
20
16
  const { Account: accountId } = await stsClient.send(new GetCallerIdentityCommand({}));
@@ -26,7 +22,7 @@ export class AwsInfrastructure {
26
22
  throw new Error(output.reason);
27
23
  }
28
24
  const { env } = output;
29
- const key = '/hereya-bootstrap/config';
25
+ const key = this.configKey;
30
26
  const ssmClient = new SSMClient({});
31
27
  const value = JSON.stringify(env);
32
28
  await ssmClient.send(new PutParameterCommand({
@@ -37,55 +33,77 @@ export class AwsInfrastructure {
37
33
  }));
38
34
  }
39
35
  async deploy(input) {
40
- let files = [];
41
- let s3Bucket = '';
42
- let s3Client = new S3Client({});
43
- let s3Key = '';
44
- try {
45
- ;
46
- ({ files, s3Bucket, s3Client, s3Key } = await this.uploadProjectFiles(input));
47
- input.parameters = {
48
- ...input.parameters,
49
- hereyaProjectEnv: objectToBase64(input.projectEnv),
50
- };
51
- const output = await this.runCodeBuild({
52
- ...input,
53
- deploy: true,
54
- sourceS3Key: s3Key,
55
- });
56
- if (!output.success) {
57
- return output;
58
- }
59
- const env = await this.getEnv(input.id);
60
- return { env, success: true };
61
- }
62
- finally {
63
- if (s3Key && files.length > 0) {
64
- await s3Client.send(new DeleteObjectsCommand({
65
- Bucket: s3Bucket,
66
- Delete: {
67
- Objects: files.map((file) => ({ Key: `${s3Key}/${file}` })),
68
- },
69
- }));
70
- }
71
- }
36
+ input.parameters = {
37
+ ...input.parameters,
38
+ hereyaProjectEnv: JSON.stringify(input.projectEnv ?? {}),
39
+ hereyaProjectRootDir: input.projectRootDir,
40
+ };
41
+ return this.provision(input);
72
42
  }
73
43
  async destroy(input) {
74
- const env = await this.getEnv(input.id);
75
- const output = await this.runCodeBuild({ ...input, destroy: true });
44
+ const destPath = path.join(os.homedir(), '.hereya', input.id, input.canonicalName);
45
+ const downloadPath = await downloadPackage(input.pkgUrl, destPath);
46
+ const region = process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION;
47
+ const infraConfig = {
48
+ ...await this.getConfig(),
49
+ region,
50
+ };
51
+ if (!infraConfig.terraformStateBucketName || !infraConfig.terraformStateLockTableName) {
52
+ return {
53
+ reason: 'could not find AWS infrastructure config. Did you run `hereya bootstrap aws`?',
54
+ success: false,
55
+ };
56
+ }
57
+ const iac$ = getIac({ type: input.iacType });
58
+ if (!iac$.supported) {
59
+ return { reason: iac$.reason, success: false };
60
+ }
61
+ const { iac } = iac$;
62
+ const output = await iac.destroy({
63
+ env: input.env ?? {},
64
+ id: input.id,
65
+ infraConfig,
66
+ parameters: input.parameters,
67
+ pkgPath: downloadPath,
68
+ });
76
69
  if (!output.success) {
77
- return output;
70
+ return { reason: output.reason, success: false };
78
71
  }
79
- await this.removeEnv(input.id);
80
- return { env, success: true };
72
+ // Remove downloaded package
73
+ await fs.rm(downloadPath, { recursive: true });
74
+ return { env: output.env, success: true };
81
75
  }
82
76
  async provision(input) {
83
- const output = await this.runCodeBuild(input);
77
+ const destPath = path.join(os.homedir(), '.hereya', input.id, input.canonicalName);
78
+ const downloadPath = await downloadPackage(input.pkgUrl, destPath);
79
+ const config = await this.getConfig();
80
+ const terraformStateBucketRegion = config.terraformStateBucketRegion || process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION;
81
+ const infraConfig = {
82
+ ...config,
83
+ terraformStateBucketRegion,
84
+ };
85
+ if (!infraConfig.terraformStateBucketName || !infraConfig.terraformStateLockTableName) {
86
+ return {
87
+ reason: 'could not find AWS infrastructure config. Did you run `hereya bootstrap aws`?',
88
+ success: false,
89
+ };
90
+ }
91
+ const iac$ = getIac({ type: input.iacType });
92
+ if (!iac$.supported) {
93
+ return { reason: iac$.reason, success: false };
94
+ }
95
+ const { iac } = iac$;
96
+ const output = await iac.apply({
97
+ env: input.env ?? {},
98
+ id: input.id,
99
+ infraConfig,
100
+ parameters: input.parameters,
101
+ pkgPath: downloadPath,
102
+ });
84
103
  if (!output.success) {
85
- return output;
104
+ return { reason: output.reason, success: false };
86
105
  }
87
- const env = await this.getEnv(input.id);
88
- return { env, success: true };
106
+ return { env: output.env, success: true };
89
107
  }
90
108
  async resolveEnv(input) {
91
109
  try {
@@ -175,45 +193,12 @@ export class AwsInfrastructure {
175
193
  }
176
194
  }
177
195
  async undeploy(input) {
178
- let files = [];
179
- let s3Bucket = '';
180
- let s3Client = new S3Client({});
181
- let s3Key = '';
182
- let env = {};
183
- try {
184
- env = await this.getEnv(input.id);
185
- }
186
- catch (error) {
187
- console.log(`Could not get env for ${input.id}: ${error.message}. Continuing with undeployment...`);
188
- }
189
- try {
190
- ;
191
- ({ files, s3Bucket, s3Client, s3Key } = await this.uploadProjectFiles(input));
192
- input.parameters = {
193
- ...input.parameters,
194
- hereyaProjectEnv: objectToBase64(input.projectEnv),
195
- };
196
- const output = await this.runCodeBuild({
197
- ...input,
198
- deploy: true,
199
- destroy: true,
200
- sourceS3Key: s3Key,
201
- });
202
- if (!output.success) {
203
- return output;
204
- }
205
- return { env, success: true };
206
- }
207
- finally {
208
- if (s3Key && files.length > 0) {
209
- await s3Client.send(new DeleteObjectsCommand({
210
- Bucket: s3Bucket,
211
- Delete: {
212
- Objects: files.map((file) => ({ Key: `${s3Key}/${file}` })),
213
- },
214
- }));
215
- }
216
- }
196
+ input.parameters = {
197
+ ...input.parameters,
198
+ hereyaProjectEnv: JSON.stringify(input.projectEnv ?? {}),
199
+ hereyaProjectRootDir: input.projectRootDir,
200
+ };
201
+ return this.destroy(input);
217
202
  }
218
203
  async unstoreEnv(input) {
219
204
  const parameterStoreArnPattern = /^arn:aws:ssm:[\da-z-]+:\d{12}:parameter\/[\w./-]+$/;
@@ -233,171 +218,11 @@ export class AwsInfrastructure {
233
218
  }));
234
219
  return { success: true };
235
220
  }
236
- async getEnv(id) {
237
- const ssmClient = new SSMClient({});
238
- const ssmParameterName = `/hereya/${id}`;
239
- try {
240
- const ssmParameter = await ssmClient.send(new GetParameterCommand({
241
- Name: ssmParameterName,
242
- }));
243
- return JSON.parse(ssmParameter.Parameter?.Value ?? '{}');
244
- }
245
- catch (error) {
246
- if (error.name === 'ParameterNotFound') {
247
- console.debug(`Parameter "${ssmParameterName}" does not exist.`);
248
- return {};
249
- }
250
- throw error;
251
- }
252
- }
253
- async getFilesToUpload(rootDir) {
254
- const ig = ignore.default();
255
- const ignoreFilePath = await getAnyPath(`${rootDir}/.hereyaignore`, `${rootDir}/.gitignore`);
256
- if (await fileExists(ignoreFilePath)) {
257
- const ignoreFileContent = await fs.readFile(ignoreFilePath, 'utf8');
258
- ig.add(ignoreFileContent);
259
- }
260
- const files = glob.sync('**/*', { cwd: rootDir, nodir: true });
261
- return files.filter((file) => !ig.ignores(file));
262
- }
263
- async removeEnv(id) {
264
- const ssmClient = new SSMClient({});
265
- const ssmParameterName = `/hereya/${id}`;
266
- await ssmClient.send(new DeleteParameterCommand({
267
- Name: ssmParameterName,
268
- }));
269
- }
270
- async runCodeBuild(input) {
271
- const codebuildClient = new CodeBuildClient({});
272
- let codebuildProjectName = '';
273
- switch (input.iacType) {
274
- case IacType.cdk: {
275
- codebuildProjectName = 'hereyaCdk';
276
- break;
277
- }
278
- case IacType.terraform: {
279
- codebuildProjectName = 'hereyaTerraform';
280
- break;
281
- }
282
- default: {
283
- return { reason: `IAC type ${input.iacType} is not supported yet!`, success: false };
284
- }
285
- }
286
- const ssmClient = new SSMClient({});
287
- const parameterName = `/hereya/package-parameters/${input.id}`;
288
- const parameterValue = Object.entries(input.parameters ?? {})
289
- .map(([key, value]) => `${key}=${typeof value === 'object' ? objectToBase64(value) : value}`)
290
- .join(',');
291
- if (parameterValue) {
292
- await ssmClient.send(new PutParameterCommand({
293
- Name: parameterName,
294
- Overwrite: true,
295
- Type: 'SecureString',
296
- Value: parameterValue,
297
- }));
298
- }
299
- const response = await codebuildClient.send(new StartBuildCommand({
300
- environmentVariablesOverride: [
301
- {
302
- name: 'HEREYA_ID',
303
- type: 'PLAINTEXT',
304
- value: input.id,
305
- },
306
- {
307
- name: 'HEREYA_IAC_TYPE',
308
- type: 'PLAINTEXT',
309
- value: input.iacType,
310
- },
311
- {
312
- name: 'HEREYA_INFRA_TYPE',
313
- type: 'PLAINTEXT',
314
- value: InfrastructureType.aws,
315
- },
316
- {
317
- name: 'HEREYA_PARAMETERS',
318
- type: parameterValue ? 'PARAMETER_STORE' : 'PLAINTEXT',
319
- value: parameterValue ? parameterName : '',
320
- },
321
- {
322
- name: 'HEREYA_WORKSPACE_ENV',
323
- type: 'PLAINTEXT',
324
- value: Object.entries(input.env ?? {})
325
- .map(([key, value]) => `${key}=${typeof value === 'object' ? objectToBase64(value) : value}`)
326
- .join(','),
327
- },
328
- {
329
- name: 'PKG_REPO_URL',
330
- type: 'PLAINTEXT',
331
- value: input.pkgUrl,
332
- },
333
- {
334
- name: 'HEREYA_DESTROY',
335
- type: 'PLAINTEXT',
336
- value: input.destroy ? 'true' : '',
337
- },
338
- {
339
- name: 'HEREYA_DEPLOY',
340
- type: 'PLAINTEXT',
341
- value: input.deploy ? 'true' : '',
342
- },
343
- {
344
- name: 'HEREYA_PROJECT_S3_KEY',
345
- type: 'PLAINTEXT',
346
- value: input.deploy ? input.sourceS3Key : '',
347
- },
348
- ],
349
- projectName: codebuildProjectName,
350
- }));
351
- console.log(`Deployment ${response.build?.id} started successfully.`);
352
- const command = new BatchGetBuildsCommand({
353
- ids: [response.build?.id ?? ''],
354
- });
355
- const deploymentResult = await new Promise((resolve) => {
356
- const handle = setInterval(async () => {
357
- const buildResponse = await codebuildClient.send(command);
358
- const build = buildResponse.builds?.[0];
359
- if (build?.buildStatus === 'IN_PROGRESS') {
360
- console.log(`Deployment ${response.build?.id} still in progress...`);
361
- return;
362
- }
363
- clearInterval(handle);
364
- console.log(`Deployment ${response.build?.id} finished with status ${build?.buildStatus}.`);
365
- resolve(build);
366
- }, 10_000); // 10 seconds
367
- });
368
- if (deploymentResult?.buildStatus !== 'SUCCEEDED') {
369
- return { reason: `Deployment failed with status ${deploymentResult?.buildStatus}`, success: false };
370
- }
371
- // remove the parameter
372
- if (parameterValue) {
373
- await ssmClient.send(new DeleteParameterCommand({
374
- Name: parameterName,
375
- }));
376
- }
377
- return { success: true };
378
- }
379
- async uploadProjectFiles(input) {
380
- const key = '/hereya-bootstrap/config';
221
+ async getConfig() {
381
222
  const ssmClient = new SSMClient({});
382
- const response = await ssmClient.send(new GetParameterCommand({
383
- Name: key,
384
- }));
385
- const bootstrapConfig = JSON.parse(response.Parameter?.Value ?? '{}');
386
- if (!bootstrapConfig.hereyaSourceCodeBucketName) {
387
- throw new Error('hereyaSourceCodeBucketName not found in bootstrap config');
388
- }
389
- const s3Key = `${input.id}/${randomUUID()}`;
390
- const s3Bucket = bootstrapConfig.hereyaSourceCodeBucketName;
391
- const files = await this.getFilesToUpload(input.projectRootDir);
392
- const s3Client = new S3Client({});
393
- await Promise.all(files.map(async (file) => {
394
- console.log(`Uploading ${file} to s3://${s3Bucket}/${s3Key}`);
395
- await s3Client.send(new PutObjectCommand({
396
- Body: await fs.readFile(path.join(input.projectRootDir, file)),
397
- Bucket: s3Bucket,
398
- Key: `${s3Key}/${file}`,
399
- }));
223
+ const ssmParameter = await ssmClient.send(new GetParameterCommand({
224
+ Name: this.configKey,
400
225
  }));
401
- return { files, s3Bucket, s3Client, s3Key };
226
+ return JSON.parse(ssmParameter.Parameter?.Value ?? '{}');
402
227
  }
403
228
  }
@@ -14,6 +14,4 @@ export declare class LocalInfrastructure implements Infrastructure {
14
14
  unbootstrap(): Promise<void>;
15
15
  undeploy(input: UndeployInput): Promise<UndeployOutput>;
16
16
  unstoreEnv(_: UnstoreEnvInput): Promise<UnstoreEnvOutput>;
17
- private download;
18
- private isNotEmpty;
19
17
  }
@@ -1,8 +1,8 @@
1
1
  import * as fs from 'node:fs/promises';
2
2
  import * as os from 'node:os';
3
3
  import * as path from 'node:path';
4
- import { simpleGit } from 'simple-git';
5
4
  import { getIac } from '../iac/index.js';
5
+ import { downloadPackage } from '../lib/package/index.js';
6
6
  export class LocalInfrastructure {
7
7
  async bootstrap() {
8
8
  console.log('Bootstrapping local infrastructure');
@@ -18,7 +18,7 @@ export class LocalInfrastructure {
18
18
  async destroy(input) {
19
19
  // noinspection DuplicatedCode
20
20
  const destPath = path.join(os.homedir(), '.hereya', input.id, input.canonicalName);
21
- const downloadPath = await this.download(input.pkgUrl, destPath);
21
+ const downloadPath = await downloadPackage(input.pkgUrl, destPath);
22
22
  const iac$ = getIac({ type: input.iacType });
23
23
  if (!iac$.supported) {
24
24
  return { reason: iac$.reason, success: false };
@@ -40,7 +40,7 @@ export class LocalInfrastructure {
40
40
  async provision(input) {
41
41
  // noinspection DuplicatedCode
42
42
  const destPath = path.join(os.homedir(), '.hereya', input.id, input.canonicalName);
43
- const downloadPath = await this.download(input.pkgUrl, destPath);
43
+ const downloadPath = await downloadPackage(input.pkgUrl, destPath);
44
44
  const iac$ = getIac({ type: input.iacType });
45
45
  if (!iac$.supported) {
46
46
  return { reason: iac$.reason, success: false };
@@ -81,26 +81,4 @@ export class LocalInfrastructure {
81
81
  async unstoreEnv(_) {
82
82
  return { success: true };
83
83
  }
84
- async download(pkgUrl, destPath) {
85
- if (await this.isNotEmpty(destPath)) {
86
- console.log(`Package already downloaded at ${destPath}`);
87
- return destPath;
88
- }
89
- await fs.mkdir(destPath, { recursive: true });
90
- console.log(`Downloading package from ${pkgUrl}`);
91
- // Initialize simple-git
92
- const git = simpleGit();
93
- // Clone repository into temp directory
94
- await git.clone(pkgUrl, destPath, ['--depth=1']);
95
- return destPath;
96
- }
97
- async isNotEmpty(directoryPath) {
98
- try {
99
- const files = await fs.readdir(directoryPath);
100
- return files.length > 0;
101
- }
102
- catch {
103
- return false; // or you can handle the error as needed
104
- }
105
- }
106
84
  }
@@ -1,2 +1,3 @@
1
1
  export declare function getAnyPath(...candidates: string[]): Promise<string>;
2
2
  export declare function fileExists(filePath: string): Promise<boolean>;
3
+ export declare function isNotEmpty(directoryPath: string): Promise<boolean>;
@@ -1,4 +1,4 @@
1
- import { access, constants } from 'node:fs/promises';
1
+ import { access, constants, readdir } from 'node:fs/promises';
2
2
  export async function getAnyPath(...candidates) {
3
3
  const checkAccess = async (index) => {
4
4
  if (index >= candidates.length)
@@ -22,3 +22,12 @@ export async function fileExists(filePath) {
22
22
  return false;
23
23
  }
24
24
  }
25
+ export async function isNotEmpty(directoryPath) {
26
+ try {
27
+ const files = await readdir(directoryPath);
28
+ return files.length > 0;
29
+ }
30
+ catch {
31
+ return false;
32
+ }
33
+ }
@@ -6,6 +6,7 @@ export declare const packageManager: PackageManager;
6
6
  export declare function getPackageManager(): PackageManager;
7
7
  export declare function resolvePackage(input: ResolvePackageInput): Promise<ResolvePackageOutput>;
8
8
  export declare function getPackageCanonicalName(packageName: string): string;
9
+ export declare function downloadPackage(pkgUrl: string, destPath: string): Promise<string>;
9
10
  export type ResolvePackageInput = {
10
11
  isDeploying?: boolean;
11
12
  package: string;
@@ -1,7 +1,10 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import { simpleGit } from 'simple-git';
1
3
  import * as yaml from 'yaml';
2
4
  import { z } from 'zod';
3
5
  import { IacType } from '../../iac/common.js';
4
6
  import { InfrastructureType } from '../../infrastructure/common.js';
7
+ import { isNotEmpty } from '../filesystem.js';
5
8
  import { GitHubPackageManager } from './github.js';
6
9
  export const packageManager = new GitHubPackageManager();
7
10
  export function getPackageManager() {
@@ -18,7 +21,7 @@ export async function resolvePackage(input) {
18
21
  const metadataContentCandidates = (await Promise.all([
19
22
  packageManager.getRepoContent({ owner, path: 'hereyarc.yaml', repo }),
20
23
  packageManager.getRepoContent({ owner, path: 'hereyarc.yml', repo }),
21
- ])).filter(content$ => content$.found);
24
+ ])).filter((content$) => content$.found);
22
25
  if (metadataContentCandidates.length === 0) {
23
26
  return { found: false, reason: `No hereya metadata file found in ${pkgUrl}` };
24
27
  }
@@ -31,16 +34,16 @@ export async function resolvePackage(input) {
31
34
  if (input.isDeploying && metadata.onDeploy) {
32
35
  return resolvePackage({ package: metadata.onDeploy.pkg });
33
36
  }
34
- if (process.env.HEREYA_OVERRIDE_INFRA) {
35
- metadata.originalInfra = metadata.infra;
36
- metadata.infra = process.env.HEREYA_OVERRIDE_INFRA;
37
- }
37
+ // if (process.env.HEREYA_OVERRIDE_INFRA) {
38
+ // metadata.originalInfra = metadata.infra
39
+ // metadata.infra = process.env.HEREYA_OVERRIDE_INFRA as InfrastructureType
40
+ // }
38
41
  return {
39
42
  canonicalName: getPackageCanonicalName(input.package),
40
43
  found: true,
41
44
  metadata,
42
45
  packageUri: pkgUrl,
43
- pkgName: input.package
46
+ pkgName: input.package,
44
47
  };
45
48
  }
46
49
  catch (error) {
@@ -50,14 +53,29 @@ export async function resolvePackage(input) {
50
53
  export function getPackageCanonicalName(packageName) {
51
54
  return packageName.replace('/', '-');
52
55
  }
56
+ export async function downloadPackage(pkgUrl, destPath) {
57
+ if (await isNotEmpty(destPath)) {
58
+ console.log(`Package already downloaded at ${destPath}`);
59
+ return destPath;
60
+ }
61
+ await fs.mkdir(destPath, { recursive: true });
62
+ console.log(`Downloading package from ${pkgUrl}`);
63
+ // Initialize simple-git
64
+ const git = simpleGit();
65
+ // Clone repository into temp directory
66
+ await git.clone(pkgUrl, destPath, ['--depth=1']);
67
+ return destPath;
68
+ }
53
69
  export const PackageMetadata = z.object({
54
70
  dependencies: z.record(z.string()).optional(),
55
71
  deploy: z.boolean().optional(),
56
72
  iac: z.nativeEnum(IacType),
57
73
  infra: z.nativeEnum(InfrastructureType),
58
- onDeploy: z.object({
74
+ onDeploy: z
75
+ .object({
59
76
  pkg: z.string(),
60
77
  version: z.string(),
61
- }).optional(),
78
+ })
79
+ .optional(),
62
80
  originalInfra: z.nativeEnum(InfrastructureType).optional(),
63
81
  });
@@ -504,56 +504,6 @@
504
504
  "index.js"
505
505
  ]
506
506
  },
507
- "remote:exec": {
508
- "aliases": [],
509
- "args": {
510
- "pkgPath": {
511
- "description": "The path to the package to provision or destroy",
512
- "name": "pkgPath",
513
- "required": false
514
- }
515
- },
516
- "description": "remotely provision or destroy a package",
517
- "examples": [
518
- "<%= config.bin %> <%= command.id %>"
519
- ],
520
- "flags": {
521
- "output": {
522
- "char": "o",
523
- "description": "The path to store the output env in",
524
- "name": "output",
525
- "required": false,
526
- "hasDynamicHelp": false,
527
- "multiple": false,
528
- "type": "option"
529
- },
530
- "source": {
531
- "char": "s",
532
- "description": "The source of the project to provision or destroy the package for",
533
- "name": "source",
534
- "required": false,
535
- "hasDynamicHelp": false,
536
- "multiple": false,
537
- "type": "option"
538
- }
539
- },
540
- "hasDynamicHelp": false,
541
- "hiddenAliases": [],
542
- "id": "remote:exec",
543
- "pluginAlias": "hereya-cli",
544
- "pluginName": "hereya-cli",
545
- "pluginType": "core",
546
- "strict": true,
547
- "enableJsonFlag": false,
548
- "isESM": true,
549
- "relativePath": [
550
- "dist",
551
- "commands",
552
- "remote",
553
- "exec",
554
- "index.js"
555
- ]
556
- },
557
507
  "workspace:create": {
558
508
  "aliases": [],
559
509
  "args": {
@@ -898,5 +848,5 @@
898
848
  ]
899
849
  }
900
850
  },
901
- "version": "0.10.0"
851
+ "version": "0.12.0"
902
852
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hereya-cli",
3
3
  "description": "Infrastructure as Package",
4
- "version": "0.10.0",
4
+ "version": "0.12.0",
5
5
  "author": "Hereya Developers",
6
6
  "bin": {
7
7
  "hereya": "./bin/run.js"
@@ -1,13 +0,0 @@
1
- import { Command } from '@oclif/core';
2
- export default class RemoteExec extends Command {
3
- static args: {
4
- pkgPath: import("@oclif/core/lib/interfaces/parser.js").Arg<string | undefined, Record<string, unknown>>;
5
- };
6
- static description: string;
7
- static examples: string[];
8
- static flags: {
9
- output: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
10
- source: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
11
- };
12
- run(): Promise<void>;
13
- }
@@ -1,90 +0,0 @@
1
- import { Args, Command, Flags } from '@oclif/core';
2
- import path from 'node:path';
3
- import { getIac } from '../../../iac/index.js';
4
- import { getInfrastructure } from '../../../infrastructure/index.js';
5
- import { tryBase64ToJSONString } from '../../../lib/object-utils.js';
6
- import { save } from '../../../lib/yaml-utils.js';
7
- export default class RemoteExec extends Command {
8
- static args = {
9
- pkgPath: Args.string({
10
- description: 'The path to the package to provision or destroy',
11
- required: false,
12
- }),
13
- };
14
- static description = 'remotely provision or destroy a package';
15
- static examples = ['<%= config.bin %> <%= command.id %>'];
16
- static flags = {
17
- output: Flags.string({
18
- char: 'o',
19
- description: 'The path to store the output env in',
20
- required: false,
21
- }),
22
- source: Flags.string({
23
- char: 's',
24
- description: 'The source of the project to provision or destroy the package for',
25
- required: false,
26
- }),
27
- };
28
- async run() {
29
- const { args, flags } = await this.parse(RemoteExec);
30
- const workspaceEnv = Object.fromEntries((process.env.HEREYA_WORKSPACE_ENV?.split(',') ?? [])
31
- .filter((param) => param.trim())
32
- .map((param) => param.split('='))
33
- .map(([key, value]) => [key, tryBase64ToJSONString(value)]));
34
- const parameters = Object.fromEntries((process.env.HEREYA_PARAMETERS?.split(',') ?? [])
35
- .filter((param) => param.trim())
36
- .map((param) => param.split('='))
37
- .map(([key, value]) => [key, tryBase64ToJSONString(value)]));
38
- const id = process.env.HEREYA_ID;
39
- const iacType = process.env.HEREYA_IAC_TYPE;
40
- const destroy = process.env.HEREYA_DESTROY === 'true';
41
- const infraType = process.env.HEREYA_INFRA_TYPE;
42
- const deploy = process.env.HEREYA_DEPLOY === 'true';
43
- const source = flags.source ? path.resolve(flags.source) : '';
44
- if (deploy && !source) {
45
- return this.error('Deploy packages provisioning requires a source path');
46
- }
47
- if (!id || !iacType || !infraType) {
48
- return this.error(`
49
- Missing required environment variables:
50
- HEREYA_ID: ${id}
51
- HEREYA_IAC_TYPE: ${iacType}
52
- HEREYA_INFRA_TYPE: ${infraType}
53
- `);
54
- }
55
- const input = {
56
- env: workspaceEnv,
57
- id,
58
- parameters,
59
- pkgPath: args.pkgPath || process.cwd(),
60
- };
61
- if (deploy) {
62
- input.parameters = {
63
- ...input.parameters,
64
- hereyaProjectRootDir: source,
65
- };
66
- }
67
- const iac$ = getIac({ type: iacType });
68
- if (!iac$.supported) {
69
- return this.error(iac$.reason);
70
- }
71
- const { iac } = iac$;
72
- const output = await (destroy ? iac.destroy(input) : iac.apply(input));
73
- if (!output.success) {
74
- return this.error(output.reason);
75
- }
76
- if (flags.output) {
77
- await save(output.env, flags.output);
78
- this.log(`Output env saved to ${flags.output}`);
79
- }
80
- const infra$ = getInfrastructure({ type: infraType });
81
- if (!infra$.supported) {
82
- return this.error(infra$.reason);
83
- }
84
- const { infrastructure } = infra$;
85
- const saveOutput = await infrastructure.saveEnv({ env: output.env, id });
86
- if (!saveOutput.success) {
87
- return this.error(saveOutput.reason);
88
- }
89
- }
90
- }