hereya-cli 0.45.0 → 0.47.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.
@@ -0,0 +1,49 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import { getBackend } from '../../backend/index.js';
3
+ import { getConfigManager } from '../../lib/config/index.js';
4
+ export default class DeleteState extends Command {
5
+ static description = 'Delete the remote state of a project for a given workspace';
6
+ static examples = [
7
+ '<%= config.bin %> <%= command.id %> -w dev',
8
+ '<%= config.bin %> <%= command.id %> --workspace staging',
9
+ ];
10
+ static flags = {
11
+ chdir: Flags.string({
12
+ description: `
13
+ Directory where the command will be executed.
14
+ If not specified, it defaults to the current working directory.
15
+ Alternatively, you can define the project root by setting the HEREYA_PROJECT_ROOT_DIR environment variable.
16
+ `,
17
+ required: false,
18
+ }),
19
+ workspace: Flags.string({
20
+ char: 'w',
21
+ description: 'workspace name',
22
+ required: true,
23
+ }),
24
+ };
25
+ async run() {
26
+ const { flags } = await this.parse(DeleteState);
27
+ const projectRootDir = flags.chdir || process.env.HEREYA_PROJECT_ROOT_DIR;
28
+ const configManager = getConfigManager();
29
+ const loadConfigOutput = await configManager.loadConfig({ projectRootDir });
30
+ if (!loadConfigOutput.found) {
31
+ this.error("Project not initialized. Run 'hereya init' first.");
32
+ }
33
+ const { config } = loadConfigOutput;
34
+ const backend = await getBackend();
35
+ const output = await backend.deleteState({
36
+ project: config.project,
37
+ workspace: flags.workspace,
38
+ });
39
+ if (!output.success) {
40
+ this.error(`Failed to delete state: ${output.reason}`);
41
+ }
42
+ if (output.message) {
43
+ this.log(output.message);
44
+ }
45
+ else {
46
+ this.log(`State for project ${config.project} in workspace ${flags.workspace} deleted successfully`);
47
+ }
48
+ }
49
+ }
@@ -3,6 +3,7 @@ import path from 'node:path';
3
3
  import { getConfigManager } from '../../../lib/config/index.js';
4
4
  import { getEnvManager } from '../../../lib/env/index.js';
5
5
  import { getAnyPath } from '../../../lib/filesystem.js';
6
+ import { stripOrgPrefix } from '../../../lib/org-utils.js';
6
7
  import { load, save } from '../../../lib/yaml-utils.js';
7
8
  export default class EnvSet extends Command {
8
9
  static args = {
@@ -43,8 +44,9 @@ export default class EnvSet extends Command {
43
44
  }
44
45
  const envManager = getEnvManager();
45
46
  const envDir = await envManager.getStaticEnvDir(projectRootDir);
46
- const candidates = flags.workspace
47
- ? [path.join(envDir, `env.${flags.workspace}.yaml`), path.join(envDir, `env.${flags.workspace}.yml`)]
47
+ const workspaceName = flags.workspace ? stripOrgPrefix(flags.workspace) : null;
48
+ const candidates = workspaceName
49
+ ? [path.join(envDir, `env.${workspaceName}.yaml`), path.join(envDir, `env.${workspaceName}.yml`)]
48
50
  : [path.join(envDir, `env.yaml`), path.join(envDir, `env.yml`)];
49
51
  const envFile = await getAnyPath(...candidates);
50
52
  const { data: env } = await load(envFile);
@@ -0,0 +1,14 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class FlowDown extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ chdir: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ debug: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ deploy: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ pin: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ select: import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
12
+ };
13
+ run(): Promise<void>;
14
+ }
@@ -0,0 +1,110 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import { getConfigManager } from '../../../lib/config/index.js';
3
+ import { gitUtils } from '../../../lib/git-utils.js';
4
+ import { setDebug } from '../../../lib/log.js';
5
+ import DeleteState from '../../delete-state/index.js';
6
+ import Down from '../../down/index.js';
7
+ import WorkspaceCreate from '../../workspace/create/index.js';
8
+ import WorkspaceDelete from '../../workspace/delete/index.js';
9
+ export default class FlowDown extends Command {
10
+ static description = 'Create a git branch-based workspace and destroy all packages';
11
+ static examples = [
12
+ '<%= config.bin %> <%= command.id %>',
13
+ '<%= config.bin %> <%= command.id %> --debug',
14
+ '<%= config.bin %> <%= command.id %> --profile staging',
15
+ '<%= config.bin %> <%= command.id %> --pin',
16
+ ];
17
+ static flags = {
18
+ chdir: Flags.string({
19
+ description: `
20
+ Directory where the command will be executed.
21
+ If not specified, it defaults to the current working directory.
22
+ Alternatively, you can define the project root by setting the HEREYA_PROJECT_ROOT_DIR environment variable.
23
+ `,
24
+ required: false,
25
+ }),
26
+ debug: Flags.boolean({
27
+ default: false,
28
+ description: 'enable debug mode',
29
+ }),
30
+ deploy: Flags.boolean({
31
+ description: 'destroy deployment companion packages',
32
+ required: false,
33
+ }),
34
+ pin: Flags.boolean({
35
+ description: 'append git commit SHA to workspace name for commit-specific isolation',
36
+ required: false,
37
+ }),
38
+ profile: Flags.string({
39
+ description: 'profile to use for the workspace (will be appended to workspace name)',
40
+ required: false,
41
+ }),
42
+ select: Flags.string({
43
+ char: 's',
44
+ default: [],
45
+ description: 'select the packages to destroy',
46
+ multiple: true,
47
+ }),
48
+ };
49
+ async run() {
50
+ const { flags } = await this.parse(FlowDown);
51
+ setDebug(flags.debug);
52
+ const projectRootDir = flags.chdir || process.env.HEREYA_PROJECT_ROOT_DIR;
53
+ // Load project config
54
+ const configManager = getConfigManager();
55
+ const loadConfigOutput = await configManager.loadConfig({ projectRootDir });
56
+ if (!loadConfigOutput.found) {
57
+ this.error("Project not initialized. Run 'hereya init' first.");
58
+ }
59
+ // Get current git branch
60
+ const gitBranch = await gitUtils.getCurrentGitBranch(projectRootDir);
61
+ // Sanitize branch name
62
+ const sanitizedBranch = gitUtils.sanitizeBranchName(gitBranch);
63
+ if (!sanitizedBranch) {
64
+ this.error('Branch name contains only special characters and cannot be used for workspace name');
65
+ }
66
+ // Get commit SHA if pin flag is set
67
+ const commitSHA = flags.pin ? await gitUtils.getShortCommitSHA(projectRootDir) : null;
68
+ // Build workspace name parts
69
+ const parts = [loadConfigOutput.config.project, sanitizedBranch];
70
+ if (flags.profile)
71
+ parts.push(flags.profile);
72
+ if (commitSHA)
73
+ parts.push(commitSHA);
74
+ const workspaceName = parts.join('---');
75
+ // Create workspace with mirror
76
+ const createArgs = [workspaceName, '--mirror', loadConfigOutput.config.workspace];
77
+ if (projectRootDir) {
78
+ createArgs.push('--chdir', projectRootDir);
79
+ }
80
+ if (flags.debug) {
81
+ createArgs.push('--debug');
82
+ }
83
+ if (flags.profile) {
84
+ createArgs.push('--profile', flags.profile);
85
+ }
86
+ await WorkspaceCreate.run(createArgs);
87
+ // Run down command with the new workspace and all flags
88
+ const downArgs = ['--workspace', workspaceName];
89
+ // Pass through all flags
90
+ if (flags.chdir)
91
+ downArgs.push('--chdir', flags.chdir);
92
+ if (flags.debug)
93
+ downArgs.push('--debug');
94
+ if (flags.deploy)
95
+ downArgs.push('--deploy');
96
+ for (const pkg of flags.select) {
97
+ downArgs.push('--select', pkg);
98
+ }
99
+ await Down.run(downArgs);
100
+ // Delete state
101
+ const deleteStateArgs = ['--workspace', workspaceName];
102
+ if (projectRootDir) {
103
+ deleteStateArgs.push('--chdir', projectRootDir);
104
+ }
105
+ await DeleteState.run(deleteStateArgs);
106
+ // Delete workspace
107
+ const deleteWorkspaceArgs = [workspaceName];
108
+ await WorkspaceDelete.run(deleteWorkspaceArgs);
109
+ }
110
+ }
@@ -0,0 +1,14 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class FlowUp extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ chdir: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ debug: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ deploy: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ pin: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ select: import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
12
+ };
13
+ run(): Promise<void>;
14
+ }
@@ -0,0 +1,99 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import { getConfigManager } from '../../../lib/config/index.js';
3
+ import { gitUtils } from '../../../lib/git-utils.js';
4
+ import { setDebug } from '../../../lib/log.js';
5
+ import Up from '../../up/index.js';
6
+ import WorkspaceCreate from '../../workspace/create/index.js';
7
+ export default class FlowUp extends Command {
8
+ static description = 'Create a git branch-based workspace and provision all packages';
9
+ static examples = [
10
+ '<%= config.bin %> <%= command.id %>',
11
+ '<%= config.bin %> <%= command.id %> --debug',
12
+ '<%= config.bin %> <%= command.id %> --profile staging',
13
+ '<%= config.bin %> <%= command.id %> --pin',
14
+ ];
15
+ static flags = {
16
+ chdir: Flags.string({
17
+ description: `
18
+ Directory where the command will be executed.
19
+ If not specified, it defaults to the current working directory.
20
+ Alternatively, you can define the project root by setting the HEREYA_PROJECT_ROOT_DIR environment variable.
21
+ `,
22
+ required: false,
23
+ }),
24
+ debug: Flags.boolean({
25
+ default: false,
26
+ description: 'enable debug mode',
27
+ }),
28
+ deploy: Flags.boolean({
29
+ description: 'provision deployment companion packages',
30
+ required: false,
31
+ }),
32
+ pin: Flags.boolean({
33
+ description: 'append git commit SHA to workspace name for commit-specific isolation',
34
+ required: false,
35
+ }),
36
+ profile: Flags.string({
37
+ description: 'profile to use for the workspace (will be appended to workspace name)',
38
+ required: false,
39
+ }),
40
+ select: Flags.string({
41
+ char: 's',
42
+ default: [],
43
+ description: 'select the packages to provision',
44
+ multiple: true,
45
+ }),
46
+ };
47
+ async run() {
48
+ const { flags } = await this.parse(FlowUp);
49
+ setDebug(flags.debug);
50
+ const projectRootDir = flags.chdir || process.env.HEREYA_PROJECT_ROOT_DIR;
51
+ // Load project config
52
+ const configManager = getConfigManager();
53
+ const loadConfigOutput = await configManager.loadConfig({ projectRootDir });
54
+ if (!loadConfigOutput.found) {
55
+ this.error("Project not initialized. Run 'hereya init' first.");
56
+ }
57
+ // Get current git branch
58
+ const gitBranch = await gitUtils.getCurrentGitBranch(projectRootDir);
59
+ // Sanitize branch name
60
+ const sanitizedBranch = gitUtils.sanitizeBranchName(gitBranch);
61
+ if (!sanitizedBranch) {
62
+ this.error('Branch name contains only special characters and cannot be used for workspace name');
63
+ }
64
+ // Get commit SHA if pin flag is set
65
+ const commitSHA = flags.pin ? await gitUtils.getShortCommitSHA(projectRootDir) : null;
66
+ // Build workspace name parts
67
+ const parts = [loadConfigOutput.config.project, sanitizedBranch];
68
+ if (flags.profile)
69
+ parts.push(flags.profile);
70
+ if (commitSHA)
71
+ parts.push(commitSHA);
72
+ const workspaceName = parts.join('---');
73
+ // Create workspace with mirror
74
+ const createArgs = [workspaceName, '--mirror', loadConfigOutput.config.workspace];
75
+ if (projectRootDir) {
76
+ createArgs.push('--chdir', projectRootDir);
77
+ }
78
+ if (flags.debug) {
79
+ createArgs.push('--debug');
80
+ }
81
+ if (flags.profile) {
82
+ createArgs.push('--profile', flags.profile);
83
+ }
84
+ await WorkspaceCreate.run(createArgs);
85
+ // Run up command with the new workspace and all flags
86
+ const upArgs = ['--workspace', workspaceName];
87
+ // Pass through all flags
88
+ if (flags.chdir)
89
+ upArgs.push('--chdir', flags.chdir);
90
+ if (flags.debug)
91
+ upArgs.push('--debug');
92
+ if (flags.deploy)
93
+ upArgs.push('--deploy');
94
+ for (const pkg of flags.select) {
95
+ upArgs.push('--select', pkg);
96
+ }
97
+ await Up.run(upArgs);
98
+ }
99
+ }
@@ -8,6 +8,7 @@ export default class Remove extends Command {
8
8
  static flags: {
9
9
  chdir: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
10
  debug: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
12
  };
12
13
  run(): Promise<void>;
13
14
  }
@@ -30,6 +30,11 @@ export default class Remove extends Command {
30
30
  default: false,
31
31
  description: 'enable debug mode',
32
32
  }),
33
+ workspace: Flags.string({
34
+ char: 'w',
35
+ description: 'name of the workspace to remove the package from (defaults to current workspace)',
36
+ required: false,
37
+ }),
33
38
  };
34
39
  async run() {
35
40
  const { args, flags } = await this.parse(Remove);
@@ -53,6 +58,8 @@ export default class Remove extends Command {
53
58
  throw new Error("Project not initialized. Run 'hereya init' first.");
54
59
  }
55
60
  ctx.configOutput = loadConfigOutput;
61
+ // Use workspace from flag if provided, otherwise use config workspace
62
+ ctx.workspace = flags.workspace || loadConfigOutput.config.workspace;
56
63
  const { config } = loadConfigOutput;
57
64
  if (!(ctx.package in (config.packages ?? {})) && !(ctx.package in (config.deploy ?? {}))) {
58
65
  throw new Error(`Package ${ctx.package} not found in the project.`);
@@ -65,7 +72,7 @@ export default class Remove extends Command {
65
72
  async task(ctx) {
66
73
  const parameterManager = getParameterManager();
67
74
  const backend = await getBackend();
68
- const profile = await getProfileFromWorkspace(backend, ctx.configOutput.config.workspace);
75
+ const profile = await getProfileFromWorkspace(backend, ctx.workspace);
69
76
  const parametersOutput = await parameterManager.getPackageParameters({
70
77
  package: ctx.package,
71
78
  profile,
@@ -93,7 +100,7 @@ export default class Remove extends Command {
93
100
  project: ctx.configOutput.config.project,
94
101
  projectRootDir,
95
102
  skipDeploy: true,
96
- workspace: ctx.configOutput.config.workspace,
103
+ workspace: ctx.workspace,
97
104
  });
98
105
  if (!destroyOutput.success) {
99
106
  throw new Error(destroyOutput.reason);
@@ -109,7 +116,7 @@ export default class Remove extends Command {
109
116
  env: ctx.destroyOutput.env,
110
117
  infra: ctx.destroyOutput.metadata.infra,
111
118
  projectRootDir,
112
- workspace: ctx.configOutput.config.workspace,
119
+ workspace: ctx.workspace,
113
120
  });
114
121
  await delay(500);
115
122
  },
@@ -128,11 +135,11 @@ export default class Remove extends Command {
128
135
  title: 'Removing package from hereya manifest',
129
136
  },
130
137
  {
131
- async task() {
138
+ async task(ctx) {
132
139
  const backend = await getBackend();
133
140
  const configManager = getConfigManager();
134
141
  const { config: newConfig } = await configManager.loadConfig({ projectRootDir });
135
- await backend.saveState(newConfig);
142
+ await backend.saveState(newConfig, ctx.workspace);
136
143
  await delay(500);
137
144
  },
138
145
  title: 'Saving state',
@@ -2,6 +2,7 @@ import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { getExecutor } from '../../executor/index.js';
4
4
  import { getAnyPath } from '../filesystem.js';
5
+ import { stripOrgPrefix } from '../org-utils.js';
5
6
  import { load, parseYaml, save } from '../yaml-utils.js';
6
7
  export class EnvManager {
7
8
  async addProjectEnv(input) {
@@ -92,7 +93,8 @@ export class EnvManager {
92
93
  return null;
93
94
  }
94
95
  async getEnvPath(input) {
95
- return getAnyPath(path.join(input.projectRootDir ?? process.cwd(), '.hereya', `env.${input.workspace}.yaml`), path.join(input.projectRootDir ?? process.cwd(), '.hereya', `env.${input.workspace}.yml`));
96
+ const workspaceName = stripOrgPrefix(input.workspace);
97
+ return getAnyPath(path.join(input.projectRootDir ?? process.cwd(), '.hereya', `env.${workspaceName}.yaml`), path.join(input.projectRootDir ?? process.cwd(), '.hereya', `env.${workspaceName}.yml`));
96
98
  }
97
99
  async getUserEnvPaths(profile, projectRootDir) {
98
100
  const paths = await Promise.all([
@@ -0,0 +1,29 @@
1
+ export declare const gitUtils: {
2
+ /**
3
+ * Gets the current git branch name.
4
+ * @param cwd The working directory to execute git command in
5
+ * @returns The current branch name
6
+ * @throws Error if not in a git repository or git command fails
7
+ */
8
+ getCurrentGitBranch(cwd?: string): Promise<string>;
9
+ /**
10
+ * Gets the short commit SHA of the current HEAD.
11
+ * @param cwd The working directory to execute git command in
12
+ * @returns The short commit SHA (7 characters by default)
13
+ * @throws Error if not in a git repository or git command fails
14
+ */
15
+ getShortCommitSHA(cwd?: string): Promise<string>;
16
+ /**
17
+ * Checks if the current directory is a git repository.
18
+ * @param cwd The working directory to check
19
+ * @returns true if in a git repository, false otherwise
20
+ */
21
+ isGitRepository(cwd?: string): Promise<boolean>;
22
+ /**
23
+ * Sanitizes a git branch name for use in workspace names.
24
+ * Keeps only alphanumeric characters, hyphens (-), and underscores (_).
25
+ * @param branch The branch name to sanitize
26
+ * @returns The sanitized branch name
27
+ */
28
+ sanitizeBranchName(branch: string): string;
29
+ };
@@ -0,0 +1,62 @@
1
+ import { runShell } from './shell.js';
2
+ export const gitUtils = {
3
+ /**
4
+ * Gets the current git branch name.
5
+ * @param cwd The working directory to execute git command in
6
+ * @returns The current branch name
7
+ * @throws Error if not in a git repository or git command fails
8
+ */
9
+ async getCurrentGitBranch(cwd) {
10
+ try {
11
+ const { stdout } = await runShell('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { directory: cwd });
12
+ return stdout.trim();
13
+ }
14
+ catch (error) {
15
+ if (error.message.includes('not a git repository') || error.message.includes('exit code "128"')) {
16
+ throw new Error('Not in a git repository. The flow commands require git integration.');
17
+ }
18
+ throw new Error(`Failed to get current git branch: ${error.message}`);
19
+ }
20
+ },
21
+ /**
22
+ * Gets the short commit SHA of the current HEAD.
23
+ * @param cwd The working directory to execute git command in
24
+ * @returns The short commit SHA (7 characters by default)
25
+ * @throws Error if not in a git repository or git command fails
26
+ */
27
+ async getShortCommitSHA(cwd) {
28
+ try {
29
+ const { stdout } = await runShell('git', ['rev-parse', '--short', 'HEAD'], { directory: cwd });
30
+ return stdout.trim();
31
+ }
32
+ catch (error) {
33
+ if (error.message.includes('not a git repository') || error.message.includes('exit code "128"')) {
34
+ throw new Error('Not in a git repository. The flow commands require git integration.');
35
+ }
36
+ throw new Error(`Failed to get commit SHA: ${error.message}`);
37
+ }
38
+ },
39
+ /**
40
+ * Checks if the current directory is a git repository.
41
+ * @param cwd The working directory to check
42
+ * @returns true if in a git repository, false otherwise
43
+ */
44
+ async isGitRepository(cwd) {
45
+ try {
46
+ await runShell('git', ['rev-parse', '--git-dir'], { directory: cwd });
47
+ return true;
48
+ }
49
+ catch {
50
+ return false;
51
+ }
52
+ },
53
+ /**
54
+ * Sanitizes a git branch name for use in workspace names.
55
+ * Keeps only alphanumeric characters, hyphens (-), and underscores (_).
56
+ * @param branch The branch name to sanitize
57
+ * @returns The sanitized branch name
58
+ */
59
+ sanitizeBranchName(branch) {
60
+ return branch.replaceAll(/[^a-zA-Z0-9_-]/g, '');
61
+ }
62
+ };