hereya-cli 0.87.0 → 0.89.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 (54) hide show
  1. package/README.md +111 -163
  2. package/dist/backend/cloud/cloud-backend.d.ts +2 -1
  3. package/dist/backend/cloud/cloud-backend.js +36 -0
  4. package/dist/backend/common.d.ts +17 -0
  5. package/dist/backend/index.js +0 -24
  6. package/dist/commands/app/deploy/index.d.ts +0 -2
  7. package/dist/commands/app/deploy/index.js +0 -17
  8. package/dist/commands/app/deployments/index.d.ts +0 -4
  9. package/dist/commands/app/deployments/index.js +1 -20
  10. package/dist/commands/app/destroy/index.d.ts +0 -2
  11. package/dist/commands/app/destroy/index.js +0 -17
  12. package/dist/commands/app/env/index.d.ts +0 -2
  13. package/dist/commands/app/env/index.js +0 -17
  14. package/dist/commands/app/list/index.d.ts +0 -4
  15. package/dist/commands/app/list/index.js +1 -20
  16. package/dist/commands/app/status/index.d.ts +0 -2
  17. package/dist/commands/app/status/index.js +0 -17
  18. package/dist/commands/clone/index.js +2 -2
  19. package/dist/commands/deploy/index.d.ts +0 -2
  20. package/dist/commands/deploy/index.js +56 -69
  21. package/dist/commands/import-repo/index.d.ts +15 -0
  22. package/dist/commands/import-repo/index.js +111 -0
  23. package/dist/commands/init/index.d.ts +0 -2
  24. package/dist/commands/init/index.js +10 -70
  25. package/dist/commands/login/index.js +0 -5
  26. package/dist/commands/publish/index.d.ts +0 -2
  27. package/dist/commands/publish/index.js +0 -17
  28. package/dist/commands/run/index.d.ts +0 -2
  29. package/dist/commands/run/index.js +0 -17
  30. package/dist/commands/search/index.d.ts +0 -2
  31. package/dist/commands/search/index.js +21 -38
  32. package/dist/commands/undeploy/index.d.ts +0 -2
  33. package/dist/commands/undeploy/index.js +14 -27
  34. package/dist/commands/up/index.d.ts +0 -2
  35. package/dist/commands/up/index.js +1 -17
  36. package/dist/commands/workspace/executor/install/index.d.ts +0 -2
  37. package/dist/commands/workspace/executor/install/index.js +1 -17
  38. package/dist/commands/workspace/executor/token/index.d.ts +0 -2
  39. package/dist/commands/workspace/executor/token/index.js +0 -17
  40. package/dist/commands/workspace/executor/uninstall/index.d.ts +0 -2
  41. package/dist/commands/workspace/executor/uninstall/index.js +1 -17
  42. package/dist/executor/context.js +15 -12
  43. package/dist/lib/clone-and-configure.d.ts +43 -0
  44. package/dist/lib/clone-and-configure.js +77 -0
  45. package/dist/lib/hereya-token.js +11 -10
  46. package/dist/lib/package/index.js +24 -20
  47. package/oclif.manifest.json +145 -254
  48. package/package.json +1 -1
  49. package/dist/commands/git/index.d.ts +0 -12
  50. package/dist/commands/git/index.js +0 -116
  51. package/dist/lib/active-cloud.d.ts +0 -31
  52. package/dist/lib/active-cloud.js +0 -55
  53. package/dist/lib/ephemeral-token.d.ts +0 -45
  54. package/dist/lib/ephemeral-token.js +0 -89
@@ -1,15 +1,9 @@
1
1
  import { Command, Flags } from '@oclif/core';
2
2
  import { CloudBackend } from '../../../../backend/cloud/cloud-backend.js';
3
3
  import { getBackend } from '../../../../backend/index.js';
4
- import { withEphemeralToken } from '../../../../lib/ephemeral-token.js';
5
- import { getDefaultLogger } from '../../../../lib/log.js';
6
4
  export default class WorkspaceExecutorToken extends Command {
7
5
  static description = 'Generate a workspace-scoped executor token for the remote executor';
8
6
  static flags = {
9
- token: Flags.string({
10
- description: 'Ephemeral cloud access token used for this invocation only. Held in memory; never written to the keychain or `~/.hereya/secrets/`. Takes precedence over the HEREYA_TOKEN environment variable.',
11
- required: false,
12
- }),
13
7
  workspace: Flags.string({
14
8
  char: 'w',
15
9
  description: 'name of the workspace',
@@ -17,17 +11,6 @@ export default class WorkspaceExecutorToken extends Command {
17
11
  }),
18
12
  };
19
13
  async run() {
20
- const { flags } = await this.parse(WorkspaceExecutorToken);
21
- // Precedence: --token flag > HEREYA_TOKEN env var > keychain.
22
- if (flags.token) {
23
- if (process.env.HEREYA_TOKEN) {
24
- getDefaultLogger().debug('[workspace executor token] --token flag overrides HEREYA_TOKEN environment variable.');
25
- }
26
- return withEphemeralToken(flags.token, () => this.runInner());
27
- }
28
- return this.runInner();
29
- }
30
- async runInner() {
31
14
  const { flags } = await this.parse(WorkspaceExecutorToken);
32
15
  const backend = await getBackend();
33
16
  if (!(backend instanceof CloudBackend)) {
@@ -3,9 +3,7 @@ export default class WorkspaceExecutorUninstall extends Command {
3
3
  static description: string;
4
4
  static flags: {
5
5
  debug: import("@oclif/core/interfaces").BooleanFlag<boolean>;
6
- token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
6
  workspace: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
8
7
  };
9
8
  run(): Promise<void>;
10
- private runInner;
11
9
  }
@@ -3,18 +3,13 @@ import { Listr, ListrLogger, ListrLogLevels } from 'listr2';
3
3
  import { CloudBackend } from '../../../../backend/cloud/cloud-backend.js';
4
4
  import { getBackend } from '../../../../backend/index.js';
5
5
  import { getExecutor } from '../../../../executor/index.js';
6
- import { withEphemeralToken } from '../../../../lib/ephemeral-token.js';
7
- import { getDefaultLogger, getLogger, getLogPath, isDebug, setDebug } from '../../../../lib/log.js';
6
+ import { getLogger, getLogPath, isDebug, setDebug } from '../../../../lib/log.js';
8
7
  import { delay } from '../../../../lib/shell.js';
9
8
  const DEFAULT_EXECUTOR_PACKAGE = 'hereya/remote-executor-aws';
10
9
  export default class WorkspaceExecutorUninstall extends Command {
11
10
  static description = 'Uninstall the remote executor from a workspace';
12
11
  static flags = {
13
12
  debug: Flags.boolean({ default: false, description: 'enable debug mode' }),
14
- token: Flags.string({
15
- description: 'Ephemeral cloud access token used for this invocation only. Held in memory; never written to the keychain or `~/.hereya/secrets/`. Takes precedence over the HEREYA_TOKEN environment variable.',
16
- required: false,
17
- }),
18
13
  workspace: Flags.string({
19
14
  char: 'w',
20
15
  description: 'name of the workspace',
@@ -22,17 +17,6 @@ export default class WorkspaceExecutorUninstall extends Command {
22
17
  }),
23
18
  };
24
19
  async run() {
25
- const { flags } = await this.parse(WorkspaceExecutorUninstall);
26
- // Precedence: --token flag > HEREYA_TOKEN env var > keychain.
27
- if (flags.token) {
28
- if (process.env.HEREYA_TOKEN) {
29
- getDefaultLogger().debug('[workspace executor uninstall] --token flag overrides HEREYA_TOKEN environment variable.');
30
- }
31
- return withEphemeralToken(flags.token, () => this.runInner());
32
- }
33
- return this.runInner();
34
- }
35
- async runInner() {
36
20
  const { flags } = await this.parse(WorkspaceExecutorUninstall);
37
21
  setDebug(flags.debug);
38
22
  const myLogger = new ListrLogger({ useIcons: false });
@@ -1,17 +1,16 @@
1
- import { getBackend } from '../backend/index.js';
2
- import { getActiveCloudAuth } from '../lib/active-cloud.js';
1
+ import { getCloudCredentials, loadBackendConfig } from '../backend/config.js';
2
+ import { BackendType, getBackend } from '../backend/index.js';
3
3
  import { resolveWorkspaceName } from '../lib/workspace-utils.js';
4
4
  import { getExecutor } from './index.js';
5
5
  export async function getExecutorForWorkspace(workspaceName, project) {
6
6
  if (!workspaceName) {
7
7
  return getExecutor();
8
8
  }
9
- // Honors both `hereya login` (persisted on disk) and an in-scope
10
- // ephemeral `--token`. Without this, `--token` invocations silently
11
- // fell through to the local executor regardless of whether the
12
- // workspace actually has a remote executor.
13
- const auth = await getActiveCloudAuth();
14
- if (!auth) {
9
+ const backendConfig = await loadBackendConfig();
10
+ if (backendConfig.current !== BackendType.Cloud) {
11
+ return getExecutor();
12
+ }
13
+ if (!backendConfig.cloud) {
15
14
  return getExecutor();
16
15
  }
17
16
  const resolvedName = resolveWorkspaceName(workspaceName, project);
@@ -24,12 +23,16 @@ export async function getExecutorForWorkspace(workspaceName, project) {
24
23
  if (!workspace.hasExecutor) {
25
24
  return getExecutor();
26
25
  }
26
+ const credentials = await getCloudCredentials(backendConfig.cloud.clientId);
27
+ if (!credentials) {
28
+ return getExecutor();
29
+ }
27
30
  return getExecutor({
28
31
  cloudConfig: {
29
- accessToken: auth.accessToken,
30
- clientId: auth.clientId ?? '',
31
- refreshToken: auth.refreshToken ?? '',
32
- url: auth.cloudUrl,
32
+ accessToken: credentials.accessToken,
33
+ clientId: backendConfig.cloud.clientId,
34
+ refreshToken: credentials.refreshToken,
35
+ url: backendConfig.cloud.url,
33
36
  },
34
37
  workspace: resolvedName,
35
38
  });
@@ -0,0 +1,43 @@
1
+ import { ListrTask } from 'listr2';
2
+ export interface CloneAndConfigureCtx {
3
+ resolvedGitEnv: Record<string, string>;
4
+ }
5
+ export interface CloneAndConfigureProjectInput<Ctx extends CloneAndConfigureCtx = CloneAndConfigureCtx> {
6
+ /**
7
+ * Returns the env map that should be inspected for `hereyaGit*` markers.
8
+ * Called when the "Resolving git credentials" task runs, so callers can
9
+ * read up-to-date values from the Listr context (e.g. `ctx.prefixedEnv`)
10
+ * after earlier tasks have populated it.
11
+ */
12
+ getEnv(ctx: Ctx): Promise<Record<string, string>> | Record<string, string>;
13
+ /**
14
+ * Project name to use for env resolution / config persistence / state save.
15
+ * Can be a value or a callback that reads it from ctx (useful when the
16
+ * canonical name comes from a previous task, e.g. `ctx.initOutput.project.name`).
17
+ */
18
+ getProject(ctx: Ctx): Promise<string> | string;
19
+ /**
20
+ * Workspace name to use for env resolution / config persistence / state save.
21
+ */
22
+ getWorkspace(ctx: Ctx): Promise<string> | string;
23
+ hereyaBinPath: string;
24
+ /**
25
+ * Optional override for the error thrown when `hereyaGitRemoteUrl` is missing
26
+ * from the resolved git env. Lets call sites surface a flow-specific message
27
+ * (e.g. "Template did not provide hereyaGitRemoteUrl").
28
+ */
29
+ missingRemoteUrlError?: string;
30
+ targetDir: string;
31
+ }
32
+ /**
33
+ * Returns Listr tasks that:
34
+ * 1. Filter `hereyaGit*` env vars (from a caller-supplied env map) and resolve
35
+ * them via the workspace executor (this exercises the github-app marker path).
36
+ * 2. Clone the repository using the resolved git credentials and the hereya
37
+ * credential helper.
38
+ * 3. Persist the local hereya.yaml, set up the credential helper for the
39
+ * cloned repo, and save the project state on the cloud backend.
40
+ *
41
+ * Used by both `hereya init` (template flow) and `hereya import-repo --clone`.
42
+ */
43
+ export declare function cloneAndConfigureProjectTasks<Ctx extends CloneAndConfigureCtx = CloneAndConfigureCtx>(input: CloneAndConfigureProjectInput<Ctx>): ListrTask<Ctx>[];
@@ -0,0 +1,77 @@
1
+ import { getBackend } from '../backend/index.js';
2
+ import { getExecutorForWorkspace } from '../executor/context.js';
3
+ import { getConfigManager } from './config/index.js';
4
+ import { gitUtils } from './git-utils.js';
5
+ /**
6
+ * Returns Listr tasks that:
7
+ * 1. Filter `hereyaGit*` env vars (from a caller-supplied env map) and resolve
8
+ * them via the workspace executor (this exercises the github-app marker path).
9
+ * 2. Clone the repository using the resolved git credentials and the hereya
10
+ * credential helper.
11
+ * 3. Persist the local hereya.yaml, set up the credential helper for the
12
+ * cloned repo, and save the project state on the cloud backend.
13
+ *
14
+ * Used by both `hereya init` (template flow) and `hereya import-repo --clone`.
15
+ */
16
+ export function cloneAndConfigureProjectTasks(input) {
17
+ const { getEnv, getProject, getWorkspace, hereyaBinPath, missingRemoteUrlError, targetDir } = input;
18
+ return [
19
+ {
20
+ async task(ctx) {
21
+ const env = await getEnv(ctx);
22
+ const project = await getProject(ctx);
23
+ const workspace = await getWorkspace(ctx);
24
+ const gitEnvToResolve = {};
25
+ for (const [key, value] of Object.entries(env || {})) {
26
+ if (key.startsWith('hereyaGit'))
27
+ gitEnvToResolve[key] = value;
28
+ }
29
+ const executor$ = await getExecutorForWorkspace(workspace);
30
+ if (!executor$.success)
31
+ throw new Error(executor$.reason);
32
+ ctx.resolvedGitEnv = await executor$.executor.resolveEnvValues({
33
+ env: gitEnvToResolve,
34
+ project,
35
+ workspace,
36
+ });
37
+ if (!ctx.resolvedGitEnv.hereyaGitRemoteUrl) {
38
+ throw new Error(missingRemoteUrlError
39
+ ?? 'Project metadata does not contain hereyaGitRemoteUrl.');
40
+ }
41
+ },
42
+ title: 'Resolving git credentials',
43
+ },
44
+ {
45
+ async task(ctx) {
46
+ const cloneResult = await gitUtils.cloneWithCredentialHelper({
47
+ gitUrl: ctx.resolvedGitEnv.hereyaGitRemoteUrl,
48
+ hereyaBinPath,
49
+ password: ctx.resolvedGitEnv.hereyaGitPassword,
50
+ targetDir,
51
+ username: ctx.resolvedGitEnv.hereyaGitUsername,
52
+ });
53
+ if (!cloneResult.success)
54
+ throw new Error(cloneResult.reason);
55
+ },
56
+ title: 'Cloning project repository',
57
+ },
58
+ {
59
+ async task(ctx) {
60
+ const project = await getProject(ctx);
61
+ const workspace = await getWorkspace(ctx);
62
+ const configManager = getConfigManager();
63
+ const existing = await configManager.loadConfig({ projectRootDir: targetDir });
64
+ const config = {
65
+ ...(existing.found ? existing.config : {}),
66
+ project,
67
+ workspace,
68
+ };
69
+ await configManager.saveConfig({ config, projectRootDir: targetDir });
70
+ await gitUtils.setupCredentialHelper({ hereyaBinPath, projectDir: targetDir });
71
+ const backend = await getBackend();
72
+ await backend.saveState(config, workspace);
73
+ },
74
+ title: 'Setting up project',
75
+ },
76
+ ];
77
+ }
@@ -1,21 +1,22 @@
1
- import { getActiveCloudAuth } from './active-cloud.js';
1
+ import { getCloudCredentials, loadBackendConfig } from '../backend/config.js';
2
2
  export const hereyaTokenUtils = {
3
3
  async generateHereyaToken(description) {
4
- // Honors both `hereya login` (persisted) and an in-scope ephemeral
5
- // `--token`. Note: workspace-scoped CLI tokens (kind:"cli") are
6
- // intentionally rejected by the cloud's /api/personal-tokens route —
7
- // only user-level tokens can mint long-lived personal tokens.
8
- const auth = await getActiveCloudAuth();
9
- if (!auth) {
4
+ const backendConfig = await loadBackendConfig();
5
+ if (!backendConfig.cloud) {
6
+ return null;
7
+ }
8
+ const { clientId, url } = backendConfig.cloud;
9
+ const credentials = await getCloudCredentials(clientId);
10
+ if (!credentials) {
10
11
  return null;
11
12
  }
12
13
  const formData = new FormData();
13
14
  formData.append('description', description);
14
15
  formData.append('expiresInDays', '365');
15
- const response = await fetch(`${auth.cloudUrl}/api/personal-tokens`, {
16
+ const response = await fetch(`${url}/api/personal-tokens`, {
16
17
  body: formData,
17
18
  headers: {
18
- Authorization: `Bearer ${auth.accessToken}`,
19
+ Authorization: `Bearer ${credentials.accessToken}`,
19
20
  },
20
21
  method: 'POST',
21
22
  });
@@ -23,6 +24,6 @@ export const hereyaTokenUtils = {
23
24
  return null;
24
25
  }
25
26
  const result = await response.json();
26
- return { cloudUrl: auth.cloudUrl, token: result.data.token };
27
+ return { cloudUrl: url, token: result.data.token };
27
28
  },
28
29
  };
@@ -1,9 +1,9 @@
1
1
  import * as yaml from 'yaml';
2
2
  import { z } from 'zod';
3
- import { getBackend } from '../../backend/index.js';
3
+ import { getCurrentBackendType } from '../../backend/config.js';
4
+ import { BackendType, getBackend } from '../../backend/index.js';
4
5
  import { IacType } from '../../iac/common.js';
5
6
  import { InfrastructureType } from '../../infrastructure/common.js';
6
- import { isCloudBackendActive } from '../active-cloud.js';
7
7
  import { CloudPackageManager } from './cloud.js';
8
8
  import { GitHubPackageManager } from './github.js';
9
9
  import { LocalPackageManager } from './local.js';
@@ -31,21 +31,23 @@ export async function resolvePackage(input) {
31
31
  return { found: false, reason: 'Invalid package format. Package name cannot contain dots (.) nor double dashes (--)' };
32
32
  }
33
33
  const isLocal = packageName.startsWith('local/');
34
- // Try cloud first if not local and backend is cloud (or an ephemeral
35
- // --token is in scope, which acts as Cloud regardless of on-disk config).
36
- if (!isLocal && await isCloudBackendActive()) {
37
- const result = await tryCloudResolution(input);
38
- if (result.found) {
39
- return result; // Success
34
+ // Try cloud first if not local and backend is cloud
35
+ if (!isLocal) {
36
+ const backendType = await getCurrentBackendType();
37
+ if (backendType === BackendType.Cloud) {
38
+ const result = await tryCloudResolution(input);
39
+ if (result.found) {
40
+ return result; // Success
41
+ }
42
+ // Failed - check if we should fallback to GitHub or return the error
43
+ if (!packageName.includes('/')) {
44
+ // Simple package names can't fallback to GitHub, return the detailed error
45
+ return result;
46
+ }
47
+ // For owner/repo format, we can fallback to GitHub
48
+ input.logger?.debug(`Package ${packageName} not found in registry, trying GitHub...`);
49
+ input.logger?.debug(`Registry error: ${result.reason}`);
40
50
  }
41
- // Failed - check if we should fallback to GitHub or return the error
42
- if (!packageName.includes('/')) {
43
- // Simple package names can't fallback to GitHub, return the detailed error
44
- return result;
45
- }
46
- // For owner/repo format, we can fallback to GitHub
47
- input.logger?.debug(`Package ${packageName} not found in registry, trying GitHub...`);
48
- input.logger?.debug(`Registry error: ${result.reason}`);
49
51
  }
50
52
  // Determine protocol for standard resolution
51
53
  const protocol = isLocal ? 'local' : '';
@@ -61,10 +63,12 @@ export async function downloadPackage(pkgUrl, destPath) {
61
63
  if (pkgUrl.startsWith('local/')) {
62
64
  protocol = 'local';
63
65
  }
64
- else if (pkgUrl.includes('#') && await isCloudBackendActive()) {
65
- // Registry package URLs contain commit SHA. Cloud branch fires when
66
- // backend.yaml says Cloud OR an ephemeral --token is in scope.
67
- protocol = 'cloud';
66
+ else if (pkgUrl.includes('#')) {
67
+ // Registry package URLs contain commit SHA
68
+ const backendType = await getCurrentBackendType();
69
+ if (backendType === BackendType.Cloud) {
70
+ protocol = 'cloud';
71
+ }
68
72
  }
69
73
  // Otherwise defaults to GitHub
70
74
  const packageManager = await getPackageManager(protocol);