hereya-cli 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +24 -24
  2. package/bin/dev.js +1 -1
  3. package/dist/backend/common.d.ts +4 -4
  4. package/dist/backend/local.js +10 -2
  5. package/dist/commands/add/index.d.ts +4 -4
  6. package/dist/commands/add/index.js +12 -21
  7. package/dist/commands/bootstrap/index.d.ts +2 -2
  8. package/dist/commands/deploy/index.d.ts +3 -3
  9. package/dist/commands/deploy/index.js +45 -36
  10. package/dist/commands/down/index.d.ts +4 -4
  11. package/dist/commands/down/index.js +10 -21
  12. package/dist/commands/env/index.d.ts +4 -4
  13. package/dist/commands/env/index.js +6 -2
  14. package/dist/commands/env/set/index.d.ts +4 -4
  15. package/dist/commands/init/index.d.ts +3 -3
  16. package/dist/commands/remove/index.d.ts +3 -3
  17. package/dist/commands/remove/index.js +9 -19
  18. package/dist/commands/run/index.d.ts +3 -3
  19. package/dist/commands/run/index.js +7 -3
  20. package/dist/commands/unbootstrap/index.d.ts +2 -2
  21. package/dist/commands/undeploy/index.d.ts +3 -3
  22. package/dist/commands/undeploy/index.js +21 -24
  23. package/dist/commands/up/index.d.ts +4 -4
  24. package/dist/commands/up/index.js +24 -32
  25. package/dist/commands/workspace/create/index.d.ts +1 -1
  26. package/dist/commands/workspace/delete/index.d.ts +1 -1
  27. package/dist/commands/workspace/env/index.d.ts +3 -3
  28. package/dist/commands/workspace/env/set/index.d.ts +5 -5
  29. package/dist/commands/workspace/env/unset/index.d.ts +2 -2
  30. package/dist/commands/workspace/install/index.d.ts +5 -5
  31. package/dist/commands/workspace/install/index.js +8 -3
  32. package/dist/commands/workspace/uninstall/index.d.ts +5 -5
  33. package/dist/commands/workspace/uninstall/index.js +8 -3
  34. package/dist/executor/index.d.ts +11 -0
  35. package/dist/executor/index.js +8 -0
  36. package/dist/executor/interface.d.ts +41 -0
  37. package/dist/executor/interface.js +1 -0
  38. package/dist/executor/local.d.ts +7 -0
  39. package/dist/executor/local.js +56 -0
  40. package/dist/iac/cdk.js +5 -5
  41. package/dist/iac/index.js +5 -5
  42. package/dist/iac/terraform.js +7 -7
  43. package/dist/infrastructure/aws.js +1 -1
  44. package/dist/infrastructure/common.d.ts +2 -2
  45. package/dist/infrastructure/index.d.ts +3 -3
  46. package/dist/infrastructure/index.js +6 -6
  47. package/dist/infrastructure/local.js +1 -1
  48. package/dist/lib/env/index.d.ts +4 -0
  49. package/dist/lib/env/index.js +11 -3
  50. package/dist/lib/env-utils.d.ts +0 -7
  51. package/dist/lib/env-utils.js +0 -18
  52. package/dist/lib/log.js +1 -1
  53. package/dist/lib/package/common.d.ts +1 -0
  54. package/dist/lib/package/index.d.ts +1 -0
  55. package/dist/lib/package/index.js +3 -3
  56. package/dist/lib/package/local.d.ts +1 -1
  57. package/dist/lib/package/local.js +17 -4
  58. package/dist/lib/shell.d.ts +17 -7
  59. package/dist/lib/shell.js +73 -14
  60. package/dist/lib/yaml-utils.js +1 -1
  61. package/oclif.manifest.json +72 -72
  62. package/package.json +37 -37
@@ -0,0 +1,41 @@
1
+ import { IPackageMetadata } from '../lib/package/index.js';
2
+ export type ExecutorProvisionInput = {
3
+ isDeploying?: boolean;
4
+ package: string;
5
+ parameters?: {
6
+ [key: string]: string;
7
+ };
8
+ project?: string;
9
+ projectEnv?: {
10
+ [key: string]: string;
11
+ };
12
+ projectRootDir?: string;
13
+ skipDeploy?: boolean;
14
+ workspace?: string;
15
+ };
16
+ export type ExecutorProvisionOutput = {
17
+ env: {
18
+ [key: string]: string;
19
+ };
20
+ metadata: IPackageMetadata;
21
+ success: true;
22
+ } | {
23
+ reason: string;
24
+ success: false;
25
+ };
26
+ export type ExecutorDestroyInput = ExecutorProvisionInput;
27
+ export type ExecutorDestroyOutput = ExecutorProvisionOutput;
28
+ export type ExecutorResolveEnvValuesInput = {
29
+ env: {
30
+ [key: string]: string;
31
+ };
32
+ markSecret?: boolean;
33
+ };
34
+ export type ExecutorResolveEnvValuesOutput = {
35
+ [key: string]: string;
36
+ };
37
+ export interface Executor {
38
+ destroy(input: ExecutorDestroyInput): Promise<ExecutorDestroyOutput>;
39
+ provision(input: ExecutorProvisionInput): Promise<ExecutorProvisionOutput>;
40
+ resolveEnvValues(input: ExecutorResolveEnvValuesInput): Promise<ExecutorResolveEnvValuesOutput>;
41
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ import { Executor, ExecutorDestroyInput, ExecutorDestroyOutput, ExecutorProvisionInput, ExecutorProvisionOutput, ExecutorResolveEnvValuesInput, ExecutorResolveEnvValuesOutput } from './interface.js';
2
+ export declare class LocalExecutor implements Executor {
3
+ destroy(input: ExecutorDestroyInput): Promise<ExecutorDestroyOutput>;
4
+ provision(input: ExecutorProvisionInput): Promise<ExecutorProvisionOutput>;
5
+ resolveEnvValues(input: ExecutorResolveEnvValuesInput): Promise<ExecutorResolveEnvValuesOutput>;
6
+ private getWorkspaceEnv;
7
+ }
@@ -0,0 +1,56 @@
1
+ import { getBackend } from '../backend/index.js';
2
+ import { destroyPackage, getInfrastructure, provisionPackage } from '../infrastructure/index.js';
3
+ export class LocalExecutor {
4
+ async destroy(input) {
5
+ const getWorkspaceEnvOutput = await this.getWorkspaceEnv({
6
+ project: input.project,
7
+ workspace: input.workspace,
8
+ });
9
+ if (!getWorkspaceEnvOutput.success) {
10
+ return { reason: getWorkspaceEnvOutput.reason, success: false };
11
+ }
12
+ return destroyPackage({
13
+ ...input,
14
+ env: getWorkspaceEnvOutput.env,
15
+ });
16
+ }
17
+ async provision(input) {
18
+ const getWorkspaceEnvOutput = await this.getWorkspaceEnv({
19
+ project: input.project,
20
+ workspace: input.workspace,
21
+ });
22
+ if (!getWorkspaceEnvOutput.success) {
23
+ return { reason: getWorkspaceEnvOutput.reason, success: false };
24
+ }
25
+ return provisionPackage({
26
+ ...input,
27
+ env: getWorkspaceEnvOutput.env,
28
+ });
29
+ }
30
+ async resolveEnvValues(input) {
31
+ return Object.fromEntries(await Promise.all(Object.entries(input.env).map(async ([key, value]) => {
32
+ const infraType = value.split(':')[0];
33
+ const infra$ = getInfrastructure({ type: infraType });
34
+ if (!infra$.supported) {
35
+ throw new Error(infra$.reason);
36
+ }
37
+ const { infrastructure } = infra$;
38
+ const valueWithoutInfra = value.split(':').slice(1).join(':');
39
+ const { isSecret, value: resolvedValue } = await infrastructure.resolveEnv({
40
+ value: valueWithoutInfra,
41
+ });
42
+ const finalValue = input.markSecret && isSecret ? `secret://${resolvedValue}` : resolvedValue;
43
+ return [key, finalValue];
44
+ })));
45
+ }
46
+ async getWorkspaceEnv({ project, workspace, }) {
47
+ if (!workspace || !project) {
48
+ return { env: {}, success: true };
49
+ }
50
+ const backend = await getBackend();
51
+ return backend.getWorkspaceEnv({
52
+ project,
53
+ workspace,
54
+ });
55
+ }
56
+ }
package/dist/iac/cdk.js CHANGED
@@ -7,7 +7,7 @@ export class Cdk {
7
7
  async apply(input) {
8
8
  try {
9
9
  const { remainingEnv, serializedParameters, serializedWorkspaceEnv } = await this.serializedParametersAndContext(input);
10
- runShell('npx', [
10
+ await runShell('npx', [
11
11
  'aws-cdk', 'deploy', '--require-approval', 'never', ...serializedWorkspaceEnv, ...serializedParameters,
12
12
  ], {
13
13
  directory: input.pkgPath,
@@ -27,7 +27,7 @@ export class Cdk {
27
27
  try {
28
28
  const env = await this.getEnv(input.id);
29
29
  const { remainingEnv, serializedParameters, serializedWorkspaceEnv } = await this.serializedParametersAndContext(input);
30
- runShell('npx', [
30
+ await runShell('npx', [
31
31
  'aws-cdk', 'destroy', '--force', ...serializedWorkspaceEnv, ...serializedParameters
32
32
  ], {
33
33
  directory: input.pkgPath,
@@ -52,14 +52,14 @@ export class Cdk {
52
52
  }
53
53
  async getParameterNames(input) {
54
54
  const workDir = input.pkgPath;
55
- runShell('npm', ['install'], { directory: workDir });
56
- runShell('npx', [
55
+ await runShell('npm', ['install'], { directory: workDir });
56
+ await runShell('npx', [
57
57
  'aws-cdk', 'synth',
58
58
  ], {
59
59
  directory: workDir,
60
60
  env: { ...input.env, ...input.parameters },
61
61
  });
62
- const result = runShell('npx', [
62
+ const result = await runShell('npx', [
63
63
  'aws-cdk', 'synth',
64
64
  ], {
65
65
  directory: workDir,
package/dist/iac/index.js CHANGED
@@ -5,21 +5,21 @@ export const terraform = new Terraform();
5
5
  export const cdk = new Cdk();
6
6
  export function getIac({ type }) {
7
7
  switch (type) {
8
- case IacType.terraform: {
8
+ case IacType.cdk: {
9
+ return { iac: cdk, supported: true };
10
+ }
11
+ case IacType.opentf: {
9
12
  return { iac: terraform, supported: true };
10
13
  }
11
14
  case IacType.opentofu: {
12
15
  return { iac: terraform, supported: true };
13
16
  }
14
- case IacType.opentf: {
17
+ case IacType.terraform: {
15
18
  return { iac: terraform, supported: true };
16
19
  }
17
20
  case IacType.tofu: {
18
21
  return { iac: terraform, supported: true };
19
22
  }
20
- case IacType.cdk: {
21
- return { iac: cdk, supported: true };
22
- }
23
23
  default: {
24
24
  return { reason: `Iac type ${type} is not supported yet!`, supported: false };
25
25
  }
@@ -27,7 +27,7 @@ export class Terraform {
27
27
  }
28
28
  try {
29
29
  const terraform = await this.getTerraformBinary();
30
- runShell(terraform, ['init'], {
30
+ await runShell(terraform, ['init'], {
31
31
  directory: input.pkgPath,
32
32
  env: {
33
33
  ...mapObject(input.env ?? {}, (key, value) => [
@@ -41,7 +41,7 @@ export class Terraform {
41
41
  ]),
42
42
  },
43
43
  });
44
- runShell(terraform, ['apply', '-auto-approve'], {
44
+ await runShell(terraform, ['apply', '-auto-approve'], {
45
45
  directory: input.pkgPath,
46
46
  env: {
47
47
  ...mapObject(input.env ?? {}, (key, value) => [
@@ -71,7 +71,7 @@ export class Terraform {
71
71
  async destroy(input) {
72
72
  const terraform = await this.getTerraformBinary();
73
73
  try {
74
- runShell(terraform, ['init'], {
74
+ await runShell(terraform, ['init'], {
75
75
  directory: input.pkgPath,
76
76
  env: {
77
77
  ...mapObject(input.env ?? {}, (key, value) => [
@@ -86,7 +86,7 @@ export class Terraform {
86
86
  },
87
87
  });
88
88
  const env = await this.getEnv(input.pkgPath);
89
- runShell(terraform, ['destroy', '-auto-approve'], {
89
+ await runShell(terraform, ['destroy', '-auto-approve'], {
90
90
  directory: input.pkgPath,
91
91
  env: {
92
92
  ...mapObject(input.env ?? {}, (key, value) => [
@@ -119,8 +119,8 @@ export class Terraform {
119
119
  ['freebsd_arm', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_freebsd_arm.zip'],
120
120
  ['freebsd_ia32', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_freebsd_386.zip'],
121
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
122
  ['linux_arm64', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_linux_arm64.zip'],
123
+ ['linux_arm', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_linux_arm.zip'],
124
124
  ['linux_ia32', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_linux_386.zip'],
125
125
  ['linux_x64', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_linux_amd64.zip'],
126
126
  ['openbsd_ia32', 'https://github.com/opentofu/opentofu/releases/download/v1.9.0/tofu_1.9.0_openbsd_386.zip'],
@@ -170,11 +170,11 @@ export class Terraform {
170
170
  }
171
171
  async getEnv(pkgPath) {
172
172
  const terraform = await this.getTerraformBinary();
173
- const resourceOut = runShell(terraform, ['output', '--json'], {
173
+ const resourceOut = await runShell(terraform, ['output', '--json'], {
174
174
  directory: pkgPath,
175
175
  stdio: 'pipe',
176
176
  });
177
- let outStr = resourceOut.output.toString().trim();
177
+ let outStr = resourceOut.stdout.toString().trim();
178
178
  const start = outStr.indexOf('{');
179
179
  const end = outStr.lastIndexOf('}');
180
180
  outStr = outStr.slice(start, end + 1);
@@ -15,7 +15,7 @@ export class AwsInfrastructure {
15
15
  const stsClient = new STSClient({});
16
16
  const { Account: accountId } = await stsClient.send(new GetCallerIdentityCommand({}));
17
17
  const region = process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION;
18
- runShell('npx', ['cdk', 'bootstrap', `aws://${accountId}/${region}`]);
18
+ await runShell('npx', ['cdk', 'bootstrap', `aws://${accountId}/${region}`]);
19
19
  const bootstrapPackage = 'hereya/bootstrap-aws-stack';
20
20
  const output = await provisionPackage({ package: bootstrapPackage });
21
21
  if (!output.success) {
@@ -44,12 +44,12 @@ export type ProvisionOutput = {
44
44
  };
45
45
  export type DestroyInput = ProvisionInput;
46
46
  export type DestroyOutput = ProvisionOutput;
47
- export type DeployInput = {
47
+ export type DeployInput = ProvisionInput & {
48
48
  projectEnv: {
49
49
  [key: string]: string;
50
50
  };
51
51
  projectRootDir: string;
52
- } & ProvisionInput;
52
+ };
53
53
  export type DeployOutput = ProvisionOutput;
54
54
  export type UndeployInput = DeployInput;
55
55
  export type UndeployOutput = DeployOutput;
@@ -41,10 +41,10 @@ export type GetInfrastructureInput = {
41
41
  type: InfrastructureType;
42
42
  };
43
43
  export type GetInfrastructureOutput = {
44
- reason: string;
45
- supported: false;
46
- } | {
47
44
  infrastructure: Infrastructure;
48
45
  supported: true;
46
+ } | {
47
+ reason: string;
48
+ supported: false;
49
49
  };
50
50
  export type PackageMetadata = z.infer<typeof PackageMetadata>;
@@ -7,15 +7,15 @@ export const localInfrastructure = new LocalInfrastructure();
7
7
  export const awsInfrastructure = new AwsInfrastructure();
8
8
  export function getInfrastructure(input) {
9
9
  switch (input.type) {
10
- case InfrastructureType.local: {
10
+ case InfrastructureType.aws: {
11
11
  return {
12
- infrastructure: localInfrastructure,
12
+ infrastructure: awsInfrastructure,
13
13
  supported: true,
14
14
  };
15
15
  }
16
- case InfrastructureType.aws: {
16
+ case InfrastructureType.local: {
17
17
  return {
18
- infrastructure: awsInfrastructure,
18
+ infrastructure: localInfrastructure,
19
19
  supported: true,
20
20
  };
21
21
  }
@@ -28,7 +28,7 @@ export function getInfrastructure(input) {
28
28
  }
29
29
  }
30
30
  export async function destroyPackage(input) {
31
- const resolvePackageOutput = await resolvePackage({ isDeploying: input.isDeploying, package: input.package });
31
+ const resolvePackageOutput = await resolvePackage({ isDeploying: input.isDeploying, package: input.package, projectRootDir: input.projectRootDir });
32
32
  if (!resolvePackageOutput.found) {
33
33
  return { reason: resolvePackageOutput.reason, success: false };
34
34
  }
@@ -91,7 +91,7 @@ export async function destroyPackage(input) {
91
91
  return { env: destroyOutput.env, metadata, success: true };
92
92
  }
93
93
  export async function provisionPackage(input) {
94
- const resolvePackageOutput = await resolvePackage({ isDeploying: input.isDeploying, package: input.package });
94
+ const resolvePackageOutput = await resolvePackage({ isDeploying: input.isDeploying, package: input.package, projectRootDir: input.projectRootDir });
95
95
  if (!resolvePackageOutput.found) {
96
96
  return { reason: resolvePackageOutput.reason, success: false };
97
97
  }
@@ -1,6 +1,6 @@
1
1
  import * as fs from 'node:fs/promises';
2
2
  import * as os from 'node:os';
3
- import * as path from 'node:path';
3
+ import path from 'node:path';
4
4
  import { getIac } from '../iac/index.js';
5
5
  import { downloadPackage } from '../lib/package/index.js';
6
6
  export class LocalInfrastructure {
@@ -26,4 +26,8 @@ export type GetProjectEnvOutput = {
26
26
  env: {
27
27
  [key: string]: string;
28
28
  };
29
+ success: true;
30
+ } | {
31
+ reason: string;
32
+ success: false;
29
33
  };
@@ -1,5 +1,5 @@
1
1
  import path from 'node:path';
2
- import { resolveEnvValues } from '../env-utils.js';
2
+ import { getExecutor } from '../../executor/index.js';
3
3
  import { getAnyPath } from '../filesystem.js';
4
4
  import { load, save } from '../yaml-utils.js';
5
5
  export class EnvManager {
@@ -14,8 +14,16 @@ export class EnvManager {
14
14
  const envPath = await this.getEnvPath(input);
15
15
  const { data: env, found } = await load(envPath);
16
16
  let resolvedEnv = {};
17
+ const executor$ = getExecutor();
18
+ if (!executor$.success) {
19
+ return {
20
+ reason: executor$.reason,
21
+ success: false,
22
+ };
23
+ }
24
+ const { executor } = executor$;
17
25
  if (found) {
18
- resolvedEnv = await resolveEnvValues(env, { markSecret: input.markSecret });
26
+ resolvedEnv = await executor.resolveEnvValues({ env, markSecret: input.markSecret });
19
27
  }
20
28
  const userEnvs = await this.getUserEnvPaths(input).then((paths) => Promise.all(paths.map((path) => load(path))));
21
29
  let userMergedEnv = {};
@@ -23,7 +31,7 @@ export class EnvManager {
23
31
  userMergedEnv = { ...userMergedEnv, ...data };
24
32
  }
25
33
  const finalEnv = { ...resolvedEnv, ...userMergedEnv };
26
- return { env: finalEnv };
34
+ return { env: finalEnv, success: true };
27
35
  }
28
36
  async removeProjectEnv(input) {
29
37
  const envPath = await this.getEnvPath(input);
@@ -1,10 +1,3 @@
1
1
  export declare function logEnv(env: {
2
2
  [key: string]: string;
3
3
  }, logFn?: (_: string) => void): void;
4
- export declare function resolveEnvValues(env: {
5
- [key: string]: string;
6
- }, options?: {
7
- markSecret?: boolean;
8
- }): Promise<{
9
- [key: string]: string;
10
- }>;
@@ -1,23 +1,5 @@
1
- import { getInfrastructure } from '../infrastructure/index.js';
2
1
  export function logEnv(env, logFn = console.log) {
3
2
  for (const [key, value] of Object.entries(env)) {
4
3
  logFn(`${key}=${value}`);
5
4
  }
6
5
  }
7
- export async function resolveEnvValues(env, options = {}) {
8
- return Object.fromEntries(await Promise.all(Object.entries(env)
9
- .map(async ([key, value]) => {
10
- const infraType = value.split(':')[0];
11
- const infra$ = getInfrastructure({ type: infraType });
12
- if (!infra$.supported) {
13
- throw new Error(infra$.reason);
14
- }
15
- const { infrastructure } = infra$;
16
- const valueWithoutInfra = value.split(':').slice(1).join(':');
17
- const { isSecret, value: resolvedValue } = await infrastructure.resolveEnv({
18
- value: valueWithoutInfra
19
- });
20
- const finalValue = options.markSecret && isSecret ? `secret://${resolvedValue}` : resolvedValue;
21
- return [key, finalValue];
22
- })));
23
- }
package/dist/lib/log.js CHANGED
@@ -1,4 +1,4 @@
1
- import { ListrLogLevels, ListrLogger } from 'listr2';
1
+ import { ListrLogger, ListrLogLevels } from 'listr2';
2
2
  const myLogger = new ListrLogger({ useIcons: false });
3
3
  const logger = {
4
4
  done(message) {
@@ -5,6 +5,7 @@ export interface PackageManager {
5
5
  export type GetRepoContentInput = {
6
6
  owner: string;
7
7
  path: string;
8
+ projectRootDir?: string;
8
9
  repo: string;
9
10
  };
10
11
  export type GetRepoContentOutput = {
@@ -12,6 +12,7 @@ export declare function downloadPackage(pkgUrl: string, destPath: string): Promi
12
12
  export type ResolvePackageInput = {
13
13
  isDeploying?: boolean;
14
14
  package: string;
15
+ projectRootDir?: string;
15
16
  };
16
17
  export type ResolvePackageOutput = {
17
18
  canonicalName: string;
@@ -29,8 +29,8 @@ export async function resolvePackage(input) {
29
29
  }
30
30
  const packageManager = getPackageManager(isLocal ? 'local' : '');
31
31
  const metadataContentCandidates = (await Promise.all([
32
- packageManager.getRepoContent({ owner, path: 'hereyarc.yaml', repo }),
33
- packageManager.getRepoContent({ owner, path: 'hereyarc.yml', repo }),
32
+ packageManager.getRepoContent({ owner, path: 'hereyarc.yaml', projectRootDir: input.projectRootDir, repo }),
33
+ packageManager.getRepoContent({ owner, path: 'hereyarc.yml', projectRootDir: input.projectRootDir, repo }),
34
34
  ])).filter((content$) => content$.found);
35
35
  if (metadataContentCandidates.length === 0) {
36
36
  return { found: false, reason: `No hereya metadata file found in ${input.package}` };
@@ -42,7 +42,7 @@ export async function resolvePackage(input) {
42
42
  return { found: false, reason: 'Package has dependencies but is not a deploy package' };
43
43
  }
44
44
  if (input.isDeploying && metadata.onDeploy) {
45
- return resolvePackage({ package: metadata.onDeploy.pkg });
45
+ return resolvePackage({ package: metadata.onDeploy.pkg, projectRootDir: input.projectRootDir });
46
46
  }
47
47
  return {
48
48
  canonicalName: getPackageCanonicalName(input.package),
@@ -1,5 +1,5 @@
1
1
  import { GetRepoContentInput, GetRepoContentOutput, PackageManager } from './common.js';
2
2
  export declare class LocalPackageManager implements PackageManager {
3
3
  downloadPackage(pkgUrl: string, destPath: string): Promise<string>;
4
- getRepoContent({ path: filePath, repo }: GetRepoContentInput): Promise<GetRepoContentOutput>;
4
+ getRepoContent({ path: filePath, projectRootDir, repo }: GetRepoContentInput): Promise<GetRepoContentOutput>;
5
5
  }
@@ -3,18 +3,19 @@ import path from 'node:path';
3
3
  export class LocalPackageManager {
4
4
  async downloadPackage(pkgUrl, destPath) {
5
5
  const [, source] = pkgUrl.split('://');
6
- await fs.cp(source, destPath, { recursive: true });
6
+ await copyRecursive(source, destPath);
7
7
  return destPath;
8
8
  }
9
- async getRepoContent({ path: filePath, repo }) {
9
+ async getRepoContent({ path: filePath, projectRootDir, repo }) {
10
10
  try {
11
- const resolvedPath = path.join(repo, filePath);
11
+ const pkgRootDir = path.join(projectRootDir ?? process.cwd(), repo);
12
+ const resolvedPath = path.join(pkgRootDir, filePath);
12
13
  if (await fs.stat(resolvedPath)) {
13
14
  const content = await fs.readFile(resolvedPath, 'utf8');
14
15
  return {
15
16
  content,
16
17
  found: true,
17
- pkgUrl: `local://${repo}`,
18
+ pkgUrl: `local://${pkgRootDir}`,
18
19
  };
19
20
  }
20
21
  return {
@@ -30,3 +31,15 @@ export class LocalPackageManager {
30
31
  }
31
32
  }
32
33
  }
34
+ async function copyRecursive(src, dest) {
35
+ // Ensure the destination directory exists.
36
+ await fs.mkdir(dest, { recursive: true });
37
+ // Read the contents of the source directory.
38
+ const entries = await fs.readdir(src, { withFileTypes: true });
39
+ // Loop through each entry in the directory.
40
+ await Promise.all(entries.map(async (entry) => {
41
+ const srcPath = path.join(src, entry.name);
42
+ const destPath = path.join(dest, entry.name);
43
+ return entry.isDirectory() ? copyRecursive(srcPath, destPath) : fs.copyFile(srcPath, destPath);
44
+ }));
45
+ }
@@ -1,12 +1,22 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
- /// <reference types="node" resolution-mode="require"/>
3
- import { StdioOptions, spawnSync } from 'node:child_process';
1
+ import { SpawnOptions } from 'node:child_process';
4
2
  export declare function setDebug(value: boolean): void;
5
3
  export declare function isDebug(): boolean;
6
- export type RunShellOptions = {
4
+ export interface RunShellOptions {
7
5
  directory?: string;
8
6
  env?: NodeJS.ProcessEnv;
9
- stdio?: StdioOptions;
10
- };
11
- export declare function runShell(cmd: string, args: string[], options?: RunShellOptions): ReturnType<typeof spawnSync>;
7
+ logger?: typeof defaultLogger;
8
+ stdio?: SpawnOptions['stdio'];
9
+ }
10
+ export declare function runShell(cmd: string, args: string[], options?: RunShellOptions): Promise<{
11
+ pid: number;
12
+ signal: NodeJS.Signals | null;
13
+ status: null | number;
14
+ stderr: string;
15
+ stdout: string;
16
+ }>;
12
17
  export declare function delay(ms: number): Promise<unknown> | undefined;
18
+ export declare const defaultLogger: {
19
+ debug(message: string): void;
20
+ error(message: string): void;
21
+ info(message: string): void;
22
+ };
package/dist/lib/shell.js CHANGED
@@ -1,4 +1,4 @@
1
- import { spawnSync } from 'node:child_process';
1
+ import { spawn } from 'node:child_process';
2
2
  let debug = false;
3
3
  export function setDebug(value) {
4
4
  debug = value;
@@ -6,20 +6,62 @@ export function setDebug(value) {
6
6
  export function isDebug() {
7
7
  return debug;
8
8
  }
9
- export function runShell(cmd, args, options = {}) {
10
- // Run the command
11
- const result = spawnSync(cmd, args, {
12
- cwd: options.directory ?? process.cwd(),
13
- encoding: 'utf8',
14
- env: { ...process.env, ...options.env },
15
- shell: true,
16
- stdio: options.stdio ?? (isDebug() ? 'inherit' : 'ignore'),
9
+ // Asynchronous runShell that logs output using the provided logger.
10
+ export async function runShell(cmd, args, options = {}) {
11
+ return new Promise((resolve, reject) => {
12
+ // Use the provided logger or default to console.log
13
+ const logger = options.logger ?? defaultLogger;
14
+ const spawnOptions = {
15
+ cwd: options.directory ?? process.cwd(),
16
+ env: { ...process.env, ...options.env },
17
+ shell: true,
18
+ // If not explicitly set, use 'inherit' when debugging, or 'pipe' otherwise
19
+ stdio: options.stdio ?? (isDebug() ? 'inherit' : 'pipe'),
20
+ };
21
+ logger.debug(`Executing: ${cmd} ${args.join(' ')}`);
22
+ const child = spawn(cmd, args, spawnOptions);
23
+ // Buffers to accumulate output from the process.
24
+ let stdout = '';
25
+ let stderr = '';
26
+ if (child.stdout) {
27
+ child.stdout.on('data', (data) => {
28
+ const chunk = data.toString();
29
+ stdout += chunk;
30
+ logger.info(chunk);
31
+ });
32
+ }
33
+ if (child.stderr) {
34
+ child.stderr.on('data', (data) => {
35
+ const chunk = data.toString();
36
+ stderr += chunk;
37
+ logger.info(chunk);
38
+ });
39
+ }
40
+ child.on('error', (err) => {
41
+ logger.error(`Error: ${err.message}`);
42
+ reject(err);
43
+ });
44
+ let exitCode = null;
45
+ let exitSignal = null;
46
+ child.on('exit', (code, signal) => {
47
+ exitCode = code;
48
+ exitSignal = signal;
49
+ });
50
+ child.on('close', () => {
51
+ if (exitCode !== 0) {
52
+ const errorMsg = `Command "${cmd} ${args.join(' ')}" failed with exit code "${exitCode}"`;
53
+ logger.error(errorMsg);
54
+ return reject(new Error(errorMsg));
55
+ }
56
+ resolve({
57
+ pid: child.pid,
58
+ signal: exitSignal,
59
+ status: exitCode,
60
+ stderr,
61
+ stdout,
62
+ });
63
+ });
17
64
  });
18
- // Throw an error if the command failed
19
- if (result.status !== 0) {
20
- throw new Error(`Command "${cmd} ${args.join(' ')}" failed with exit code "${result.status}"`);
21
- }
22
- return result;
23
65
  }
24
66
  export function delay(ms) {
25
67
  if (process.env.NODE_ENV === 'test') {
@@ -29,3 +71,20 @@ export function delay(ms) {
29
71
  setTimeout(resolve, ms);
30
72
  });
31
73
  }
74
+ export const defaultLogger = {
75
+ debug(message) {
76
+ if (isDebug()) {
77
+ console.debug(message);
78
+ }
79
+ },
80
+ error(message) {
81
+ if (isDebug()) {
82
+ console.error(message);
83
+ }
84
+ },
85
+ info(message) {
86
+ if (isDebug()) {
87
+ console.info(message);
88
+ }
89
+ },
90
+ };
@@ -1,5 +1,5 @@
1
1
  import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
- import * as path from 'node:path';
2
+ import path from 'node:path';
3
3
  import { parse, stringify } from 'yaml';
4
4
  export async function save(content, file) {
5
5
  try {