hereya-cli 0.90.1 → 0.91.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.
@@ -5,11 +5,19 @@ import { getBackend } from '../../../../backend/index.js';
5
5
  import { getExecutor } from '../../../../executor/index.js';
6
6
  import { getLogger, getLogPath, isDebug, setDebug } from '../../../../lib/log.js';
7
7
  import { delay } from '../../../../lib/shell.js';
8
- const DEFAULT_EXECUTOR_PACKAGE = 'hereya/remote-executor-aws';
8
+ const ALWAYS_ON_PACKAGE = 'hereya/remote-executor-aws';
9
+ const EPHEMERAL_PACKAGE = 'hereya/aws-executor-broker';
9
10
  export default class WorkspaceExecutorUninstall extends Command {
10
- static description = 'Uninstall the remote executor from a workspace';
11
+ static description = `Uninstall the remote executor from a workspace.
12
+
13
+ The --mode flag must match the mode used at install time (default: ephemeral).`;
11
14
  static flags = {
12
15
  debug: Flags.boolean({ default: false, description: 'enable debug mode' }),
16
+ mode: Flags.string({
17
+ default: 'ephemeral',
18
+ description: 'executor mode used at install time',
19
+ options: ['ephemeral', 'always-on'],
20
+ }),
13
21
  workspace: Flags.string({
14
22
  char: 'w',
15
23
  description: 'name of the workspace',
@@ -20,6 +28,8 @@ export default class WorkspaceExecutorUninstall extends Command {
20
28
  const { flags } = await this.parse(WorkspaceExecutorUninstall);
21
29
  setDebug(flags.debug);
22
30
  const myLogger = new ListrLogger({ useIcons: false });
31
+ const isEphemeral = flags.mode === 'ephemeral';
32
+ const executorPackage = isEphemeral ? EPHEMERAL_PACKAGE : ALWAYS_ON_PACKAGE;
23
33
  const task = new Listr([
24
34
  {
25
35
  async task(_ctx, task) {
@@ -56,7 +66,7 @@ export default class WorkspaceExecutorUninstall extends Command {
56
66
  const { executor } = executor$;
57
67
  const destroyOutput = await executor.destroy({
58
68
  logger: getLogger(task),
59
- package: DEFAULT_EXECUTOR_PACKAGE,
69
+ package: executorPackage,
60
70
  skipDeploy: true,
61
71
  workspace: flags.workspace,
62
72
  });
@@ -76,6 +86,22 @@ export default class WorkspaceExecutorUninstall extends Command {
76
86
  },
77
87
  title: 'Revoking executor token',
78
88
  },
89
+ {
90
+ async task() {
91
+ const backend = await getBackend();
92
+ // Always attempt to unregister the broker. The endpoint
93
+ // gracefully no-ops on 404 so this is safe for legacy
94
+ // always-on installs that never registered a broker.
95
+ const result = await backend.unregisterExecutorBroker({
96
+ workspace: flags.workspace,
97
+ });
98
+ if (!result.success) {
99
+ throw new Error(`Failed to unregister executor broker: ${result.reason}`);
100
+ }
101
+ await delay(500);
102
+ },
103
+ title: 'Unregistering executor broker',
104
+ },
79
105
  {
80
106
  async task() {
81
107
  const backend = await getBackend();
@@ -89,7 +115,7 @@ export default class WorkspaceExecutorUninstall extends Command {
89
115
  },
90
116
  ], { concurrent: false, rendererOptions: { collapseSubtasks: !isDebug() } });
91
117
  },
92
- title: `Uninstalling executor from workspace ${flags.workspace}`,
118
+ title: `Uninstalling executor from workspace ${flags.workspace} (mode: ${flags.mode})`,
93
119
  },
94
120
  ], { concurrent: false });
95
121
  try {
@@ -7,5 +7,4 @@ export declare class LocalExecutor implements Executor {
7
7
  setEnvVar(input: ExecutorSetEnvVarInput): Promise<ExecutorSetEnvVarOutput>;
8
8
  unsetEnvVar(input: ExecutorUnsetEnvVarInput): Promise<ExecutorUnsetEnvVarOutput>;
9
9
  private getWorkspaceEnv;
10
- private resolveGithubAppMarkers;
11
10
  }
@@ -2,6 +2,7 @@ import { getBackend } from '../backend/index.js';
2
2
  import { destroyPackage, getInfrastructure, getProvisioningLogicalId, provisionPackage, resolveEnvValues as resolveEnvValuesSimple } from '../infrastructure/index.js';
3
3
  import { mintInstallationToken } from '../lib/github-app.js';
4
4
  import { resolvePackage } from '../lib/package/index.js';
5
+ import { resolveEnvValues as resolveEnvValuesPure } from './resolve-env.js';
5
6
  export class LocalExecutor {
6
7
  async destroy(input) {
7
8
  // For apps, we use `app` as the project-name source for workspace env resolution
@@ -70,28 +71,15 @@ export class LocalExecutor {
70
71
  });
71
72
  }
72
73
  async resolveEnvValues(input) {
73
- const resolved = Object.fromEntries(await Promise.all(Object.entries(input.env).map(async ([key, value]) => {
74
- // Check if value has infra prefix (contains ':' and first part is valid infra)
75
- const colonIndex = value.indexOf(':');
76
- if (colonIndex === -1) {
77
- // No colon - plain value, return as-is
78
- return [key, value];
79
- }
80
- const infraType = value.slice(0, colonIndex);
81
- const infra$ = getInfrastructure({ type: infraType });
82
- if (!infra$.supported) {
83
- // Unknown infra type - treat as plain value, return as-is
84
- return [key, value];
85
- }
86
- const { infrastructure } = infra$;
87
- const valueWithoutInfra = value.slice(colonIndex + 1);
88
- const { isSecret, value: resolvedValue } = await infrastructure.resolveEnv({
89
- value: valueWithoutInfra,
90
- });
91
- const finalValue = input.markSecret && isSecret ? `secret://${resolvedValue}` : resolvedValue;
92
- return [key, finalValue];
93
- })));
94
- return this.resolveGithubAppMarkers(resolved, input);
74
+ return resolveEnvValuesPure(input, {
75
+ getInfrastructure,
76
+ async getWorkspaceEnv(args) {
77
+ const backend = await getBackend();
78
+ return backend.getWorkspaceEnv(args);
79
+ },
80
+ mintInstallationToken,
81
+ resolveSimpleEnv: resolveEnvValuesSimple,
82
+ });
95
83
  }
96
84
  async setEnvVar(input) {
97
85
  const backend = await getBackend();
@@ -187,44 +175,4 @@ export class LocalExecutor {
187
175
  success: true,
188
176
  };
189
177
  }
190
- async resolveGithubAppMarkers(resolved, input) {
191
- const needsToken = Object.values(resolved).some((v) => typeof v === 'string' && v.startsWith('github-app:'));
192
- if (!needsToken)
193
- return resolved;
194
- if (!input.workspace || !input.project)
195
- return resolved;
196
- const backend = await getBackend();
197
- const wsEnv$ = await backend.getWorkspaceEnv({
198
- project: input.project,
199
- workspace: input.workspace,
200
- });
201
- if (!wsEnv$.success)
202
- return resolved;
203
- // Use the simple infrastructure resolver - resolves aws: prefixes only and
204
- // does NOT recurse into github-app: markers, so no circularity.
205
- const wsResolved = await resolveEnvValuesSimple(wsEnv$.env);
206
- const appId = wsResolved.hereyaGithubAppId;
207
- const installationIdFromEnv = wsResolved.hereyaGithubAppInstallationId;
208
- const privateKey = wsResolved.hereyaGithubAppPrivateKey;
209
- if (!appId || !privateKey) {
210
- console.warn('hereya: github-app marker present but workspace env missing hereyaGithubAppId / hereyaGithubAppPrivateKey; leaving marker unresolved');
211
- return resolved;
212
- }
213
- const out = { ...resolved };
214
- for (const [k, v] of Object.entries(resolved)) {
215
- if (typeof v !== 'string' || !v.startsWith('github-app:'))
216
- continue;
217
- const installationId = v.slice('github-app:'.length) || installationIdFromEnv;
218
- if (!installationId)
219
- continue;
220
- try {
221
- // eslint-disable-next-line no-await-in-loop
222
- out[k] = await mintInstallationToken({ appId, installationId, privateKey });
223
- }
224
- catch (error) {
225
- console.warn(`hereya: failed to mint github-app installation token: ${error.message}`);
226
- }
227
- }
228
- return out;
229
- }
230
178
  }
@@ -0,0 +1,88 @@
1
+ import type { GetInfrastructureOutput } from '../infrastructure/registry.js';
2
+ /**
3
+ * Pure-function env-value resolver, factored out of `LocalExecutor.resolveEnvValues`.
4
+ *
5
+ * The functions in this module are deliberately free of any class state
6
+ * (`this`) or hard-coded global lookups so they can be invoked from contexts
7
+ * that do not include the full CLI runtime — e.g. the broker Lambda that
8
+ * imports `hereya-cli` as a published npm dependency.
9
+ *
10
+ * Dependencies that the resolver needs (the infrastructure-provider lookup,
11
+ * the workspace-env lookup, and the GitHub-app installation-token minter) are
12
+ * all injected via the `providers` argument so callers can supply only the
13
+ * pieces they have available.
14
+ */
15
+ import { InfrastructureType } from '../infrastructure/common.js';
16
+ export type ResolveEnvValuesInput = {
17
+ env: {
18
+ [key: string]: string;
19
+ };
20
+ markSecret?: boolean;
21
+ project?: string;
22
+ workspace?: string;
23
+ };
24
+ export type ResolveEnvValuesOutput = {
25
+ [key: string]: string;
26
+ };
27
+ /**
28
+ * Look up an infrastructure provider by type. The Lambda passes a thin wrapper
29
+ * over its registered `aws` provider; the CLI passes the full registry.
30
+ */
31
+ export type GetInfrastructureFn = (input: {
32
+ type: InfrastructureType;
33
+ }) => GetInfrastructureOutput;
34
+ /**
35
+ * Fetch a workspace's stored env values (used to find the GitHub-app
36
+ * configuration when resolving `github-app:` markers).
37
+ */
38
+ export type GetWorkspaceEnvFn = (input: {
39
+ project: string;
40
+ workspace: string;
41
+ }) => Promise<{
42
+ env: {
43
+ [key: string]: string;
44
+ };
45
+ success: true;
46
+ } | {
47
+ reason: string;
48
+ success: false;
49
+ }>;
50
+ /**
51
+ * Resolve a workspace env map without recursing into github-app markers.
52
+ * Used to dereference the `aws:` (or other infra) values for the GitHub-app
53
+ * config keys.
54
+ */
55
+ export type ResolveSimpleEnvFn = (env: Record<string, string>) => Promise<Record<string, string>>;
56
+ /**
57
+ * Mint a GitHub-app installation token.
58
+ */
59
+ export type MintInstallationTokenFn = (input: {
60
+ appId: string;
61
+ installationId: string;
62
+ privateKey: string;
63
+ }) => Promise<string>;
64
+ export interface ResolveEnvProviders {
65
+ /** Infrastructure-provider lookup — required. */
66
+ getInfrastructure: GetInfrastructureFn;
67
+ /**
68
+ * Optional GitHub-app dependencies. If any of them is missing, `github-app:`
69
+ * markers are left unresolved (the Lambda path can choose not to wire
70
+ * GitHub-app support at all).
71
+ */
72
+ getWorkspaceEnv?: GetWorkspaceEnvFn;
73
+ mintInstallationToken?: MintInstallationTokenFn;
74
+ resolveSimpleEnv?: ResolveSimpleEnvFn;
75
+ }
76
+ /**
77
+ * Resolve `infra:` prefixes and `github-app:` markers in an env map. Pure
78
+ * function — no module-level state, no `this`, side effects are limited to
79
+ * what the injected providers do.
80
+ */
81
+ export declare function resolveEnvValues(input: ResolveEnvValuesInput, providers: ResolveEnvProviders): Promise<ResolveEnvValuesOutput>;
82
+ /**
83
+ * Walk the resolved env, replace any `github-app:<installationId?>` marker
84
+ * with a freshly-minted installation token. Reads the GitHub-app config from
85
+ * the workspace env (`hereyaGithubAppId`, `hereyaGithubAppPrivateKey`,
86
+ * optional `hereyaGithubAppInstallationId`).
87
+ */
88
+ export declare function resolveGithubAppMarkers(resolved: Record<string, string>, input: ResolveEnvValuesInput, providers: ResolveEnvProviders): Promise<Record<string, string>>;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Resolve `infra:` prefixes and `github-app:` markers in an env map. Pure
3
+ * function — no module-level state, no `this`, side effects are limited to
4
+ * what the injected providers do.
5
+ */
6
+ export async function resolveEnvValues(input, providers) {
7
+ const resolved = Object.fromEntries(await Promise.all(Object.entries(input.env).map(async ([key, value]) => {
8
+ // Check if value has infra prefix (contains ':' and first part is valid infra)
9
+ const colonIndex = value.indexOf(':');
10
+ if (colonIndex === -1) {
11
+ // No colon - plain value, return as-is
12
+ return [key, value];
13
+ }
14
+ const infraType = value.slice(0, colonIndex);
15
+ const infra$ = providers.getInfrastructure({ type: infraType });
16
+ if (!infra$.supported) {
17
+ // Unknown infra type - treat as plain value, return as-is
18
+ return [key, value];
19
+ }
20
+ const { infrastructure } = infra$;
21
+ const valueWithoutInfra = value.slice(colonIndex + 1);
22
+ const { isSecret, value: resolvedValue } = await infrastructure.resolveEnv({
23
+ value: valueWithoutInfra,
24
+ });
25
+ const finalValue = input.markSecret && isSecret ? `secret://${resolvedValue}` : resolvedValue;
26
+ return [key, finalValue];
27
+ })));
28
+ return resolveGithubAppMarkers(resolved, input, providers);
29
+ }
30
+ /**
31
+ * Walk the resolved env, replace any `github-app:<installationId?>` marker
32
+ * with a freshly-minted installation token. Reads the GitHub-app config from
33
+ * the workspace env (`hereyaGithubAppId`, `hereyaGithubAppPrivateKey`,
34
+ * optional `hereyaGithubAppInstallationId`).
35
+ */
36
+ export async function resolveGithubAppMarkers(resolved, input, providers) {
37
+ const needsToken = Object.values(resolved).some((v) => typeof v === 'string' && v.startsWith('github-app:'));
38
+ if (!needsToken)
39
+ return resolved;
40
+ if (!input.workspace || !input.project)
41
+ return resolved;
42
+ const { getWorkspaceEnv, mintInstallationToken, resolveSimpleEnv } = providers;
43
+ if (!getWorkspaceEnv || !mintInstallationToken || !resolveSimpleEnv)
44
+ return resolved;
45
+ const wsEnv$ = await getWorkspaceEnv({
46
+ project: input.project,
47
+ workspace: input.workspace,
48
+ });
49
+ if (!wsEnv$.success)
50
+ return resolved;
51
+ // Use the simple infrastructure resolver - resolves aws: prefixes only and
52
+ // does NOT recurse into github-app: markers, so no circularity.
53
+ const wsResolved = await resolveSimpleEnv(wsEnv$.env);
54
+ const appId = wsResolved.hereyaGithubAppId;
55
+ const installationIdFromEnv = wsResolved.hereyaGithubAppInstallationId;
56
+ const privateKey = wsResolved.hereyaGithubAppPrivateKey;
57
+ if (!appId || !privateKey) {
58
+ console.warn('hereya: github-app marker present but workspace env missing hereyaGithubAppId / hereyaGithubAppPrivateKey; leaving marker unresolved');
59
+ return resolved;
60
+ }
61
+ const out = { ...resolved };
62
+ for (const [k, v] of Object.entries(resolved)) {
63
+ if (typeof v !== 'string' || !v.startsWith('github-app:'))
64
+ continue;
65
+ const installationId = v.slice('github-app:'.length) || installationIdFromEnv;
66
+ if (!installationId)
67
+ continue;
68
+ try {
69
+ // eslint-disable-next-line no-await-in-loop
70
+ out[k] = await mintInstallationToken({ appId, installationId, privateKey });
71
+ }
72
+ catch (error) {
73
+ console.warn(`hereya: failed to mint github-app installation token: ${error.message}`);
74
+ }
75
+ }
76
+ return out;
77
+ }
package/dist/index.d.ts CHANGED
@@ -1 +1,4 @@
1
+ export { type GetInfrastructureFn, type GetWorkspaceEnvFn, type MintInstallationTokenFn, type ResolveEnvProviders, resolveEnvValues, type ResolveEnvValuesInput, type ResolveEnvValuesOutput, resolveGithubAppMarkers, type ResolveSimpleEnvFn, } from './executor/resolve-env.js';
2
+ export { InfrastructureType } from './infrastructure/common.js';
3
+ export { awsProviderFactory, getInfrastructure, type GetInfrastructureInput, type GetInfrastructureOutput, type InfrastructureProviderFactory, registerInfrastructureProvider, resetInfrastructureProviders, } from './infrastructure/registry.js';
1
4
  export { run } from '@oclif/core';
package/dist/index.js CHANGED
@@ -1 +1,8 @@
1
+ // Public API for external consumers (e.g. the broker Lambda) that import
2
+ // `hereya-cli` as a published npm dependency. None of these re-exports pull
3
+ // in the `local` infrastructure provider or the rest of the CLI runtime — the
4
+ // Lambda bundle stays slim.
5
+ export { resolveEnvValues, resolveGithubAppMarkers, } from './executor/resolve-env.js';
6
+ export { InfrastructureType } from './infrastructure/common.js';
7
+ export { awsProviderFactory, getInfrastructure, registerInfrastructureProvider, resetInfrastructureProviders, } from './infrastructure/registry.js';
1
8
  export { run } from '@oclif/core';
@@ -1,13 +1,19 @@
1
1
  import { z } from 'zod';
2
2
  import { Logger } from '../lib/log.js';
3
3
  import { PackageMetadata } from '../lib/package/index.js';
4
- import { AwsInfrastructure } from './aws.js';
5
- import { Infrastructure, InfrastructureType } from './common.js';
6
- import { LocalInfrastructure } from './local.js';
7
- export declare const localInfrastructure: LocalInfrastructure;
8
- export declare const awsInfrastructure: AwsInfrastructure;
4
+ import { Infrastructure } from './common.js';
5
+ import { type InfrastructureProviderFactory } from './registry.js';
6
+ export { awsProviderFactory, getInfrastructure, type GetInfrastructureInput, type GetInfrastructureOutput, type InfrastructureProviderFactory, registerInfrastructureProvider, resetInfrastructureProviders, } from './registry.js';
7
+ /**
8
+ * Default factory for the local-filesystem provider. Registered eagerly when
9
+ * this module is imported (the CLI's standard entry point). The broker
10
+ * Lambda never imports this module, so `LocalInfrastructure` and its
11
+ * filesystem deps are tree-shaken out of the Lambda bundle.
12
+ */
13
+ export declare const localProviderFactory: InfrastructureProviderFactory;
14
+ export declare const awsInfrastructure: Infrastructure;
15
+ export declare const localInfrastructure: Infrastructure;
9
16
  export declare function resolveEnvValues(env: Record<string, string>): Promise<Record<string, string>>;
10
- export declare function getInfrastructure(input: GetInfrastructureInput): GetInfrastructureOutput;
11
17
  export declare function destroyPackage(input: DestroyPackageInput): Promise<DestroyPackageOutput>;
12
18
  export declare function provisionPackage(input: ProvisionPackageInput): Promise<ProvisionPackageOutput>;
13
19
  export declare function getProvisioningLogicalId({ app, pkg, project, workspace }: {
@@ -49,14 +55,4 @@ export type ProvisionPackageOutput = {
49
55
  reason: string;
50
56
  success: false;
51
57
  };
52
- export type GetInfrastructureInput = {
53
- type: InfrastructureType;
54
- };
55
- export type GetInfrastructureOutput = {
56
- infrastructure: Infrastructure;
57
- supported: true;
58
- } | {
59
- reason: string;
60
- supported: false;
61
- };
62
58
  export type PackageMetadata = z.infer<typeof PackageMetadata>;
@@ -1,11 +1,26 @@
1
1
  import { getBackend } from '../backend/index.js';
2
2
  import { stripOrgPrefix } from '../lib/org-utils.js';
3
3
  import { resolvePackage } from '../lib/package/index.js';
4
- import { AwsInfrastructure } from './aws.js';
5
4
  import { InfrastructureType } from './common.js';
6
5
  import { LocalInfrastructure } from './local.js';
7
- export const localInfrastructure = new LocalInfrastructure();
8
- export const awsInfrastructure = new AwsInfrastructure();
6
+ import { getInfrastructure, getOrCreateProvider, registerInfrastructureProvider, } from './registry.js';
7
+ // Re-export the registry surface so existing imports
8
+ // (`from '../infrastructure/index.js'`) keep working.
9
+ export { awsProviderFactory, getInfrastructure, registerInfrastructureProvider, resetInfrastructureProviders, } from './registry.js';
10
+ /**
11
+ * Default factory for the local-filesystem provider. Registered eagerly when
12
+ * this module is imported (the CLI's standard entry point). The broker
13
+ * Lambda never imports this module, so `LocalInfrastructure` and its
14
+ * filesystem deps are tree-shaken out of the Lambda bundle.
15
+ */
16
+ export const localProviderFactory = () => new LocalInfrastructure();
17
+ registerInfrastructureProvider(InfrastructureType.local, localProviderFactory);
18
+ // Backwards-compatible singleton exports. These remain stable references
19
+ // (`sinon.stub(awsInfrastructure, 'foo')` continues to work) because the
20
+ // default registrations above run at import time and the cached instances
21
+ // never change identity for the lifetime of the CLI process.
22
+ export const awsInfrastructure = getOrCreateProvider(InfrastructureType.aws);
23
+ export const localInfrastructure = getOrCreateProvider(InfrastructureType.local);
9
24
  export async function resolveEnvValues(env) {
10
25
  return Object.fromEntries(await Promise.all(Object.entries(env).map(async ([key, value]) => {
11
26
  const colonIndex = value.indexOf(':');
@@ -21,28 +36,6 @@ export async function resolveEnvValues(env) {
21
36
  return [key, resolvedValue];
22
37
  })));
23
38
  }
24
- export function getInfrastructure(input) {
25
- switch (input.type) {
26
- case InfrastructureType.aws: {
27
- return {
28
- infrastructure: awsInfrastructure,
29
- supported: true,
30
- };
31
- }
32
- case InfrastructureType.local: {
33
- return {
34
- infrastructure: localInfrastructure,
35
- supported: true,
36
- };
37
- }
38
- default: {
39
- return {
40
- reason: `Invalid infrastructure type: ${input.type}. Valid options are: aws, local`,
41
- supported: false,
42
- };
43
- }
44
- }
45
- }
46
39
  /**
47
40
  * When `input.app` is set, scope the provisioning row by `app` (workspace-scoped).
48
41
  * Otherwise, scope by `project`. Apps and projects are mutually exclusive scopes
@@ -0,0 +1,38 @@
1
+ import { Infrastructure, InfrastructureType } from './common.js';
2
+ /**
3
+ * A factory that lazily constructs an `Infrastructure` provider singleton.
4
+ * Registered factories are invoked at most once per type — the resulting
5
+ * instance is cached so external consumers (and tests that stub methods on
6
+ * `awsInfrastructure` / `localInfrastructure`) see the same reference every
7
+ * time.
8
+ */
9
+ export type InfrastructureProviderFactory = () => Infrastructure;
10
+ /**
11
+ * Default factory for the AWS provider. Both the CLI and the broker Lambda
12
+ * register this; it is the only factory the Lambda bundle pulls in.
13
+ */
14
+ export declare const awsProviderFactory: InfrastructureProviderFactory;
15
+ /**
16
+ * Register (or replace) the factory for a given infrastructure type. Resets
17
+ * any cached instance for that type so the next `getInfrastructure` call
18
+ * rebuilds it from the new factory.
19
+ */
20
+ export declare function registerInfrastructureProvider(type: InfrastructureType, factory: InfrastructureProviderFactory): void;
21
+ /**
22
+ * Clear all registered providers and cached instances. Primarily useful for
23
+ * tests and for the Lambda bundle that wants to start with a clean slate
24
+ * before registering only `aws`.
25
+ */
26
+ export declare function resetInfrastructureProviders(): void;
27
+ export declare function getOrCreateProvider(type: InfrastructureType): Infrastructure;
28
+ export declare function getInfrastructure(input: GetInfrastructureInput): GetInfrastructureOutput;
29
+ export type GetInfrastructureInput = {
30
+ type: InfrastructureType;
31
+ };
32
+ export type GetInfrastructureOutput = {
33
+ infrastructure: Infrastructure;
34
+ supported: true;
35
+ } | {
36
+ reason: string;
37
+ supported: false;
38
+ };
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Infrastructure provider registry. This module is the slim, Lambda-friendly
3
+ * entry point: it never imports the local-filesystem provider. The CLI's
4
+ * `./index.ts` extends this registry with `LocalInfrastructure`; the broker
5
+ * Lambda imports only this registry (via the `hereya-cli` package root) and
6
+ * registers `aws` only.
7
+ */
8
+ import { AwsInfrastructure } from './aws.js';
9
+ import { InfrastructureType } from './common.js';
10
+ /**
11
+ * Default factory for the AWS provider. Both the CLI and the broker Lambda
12
+ * register this; it is the only factory the Lambda bundle pulls in.
13
+ */
14
+ export const awsProviderFactory = () => new AwsInfrastructure();
15
+ const factories = new Map();
16
+ const instances = new Map();
17
+ /**
18
+ * Register (or replace) the factory for a given infrastructure type. Resets
19
+ * any cached instance for that type so the next `getInfrastructure` call
20
+ * rebuilds it from the new factory.
21
+ */
22
+ export function registerInfrastructureProvider(type, factory) {
23
+ factories.set(type, factory);
24
+ instances.delete(type);
25
+ }
26
+ /**
27
+ * Clear all registered providers and cached instances. Primarily useful for
28
+ * tests and for the Lambda bundle that wants to start with a clean slate
29
+ * before registering only `aws`.
30
+ */
31
+ export function resetInfrastructureProviders() {
32
+ factories.clear();
33
+ instances.clear();
34
+ }
35
+ export function getOrCreateProvider(type) {
36
+ const cached = instances.get(type);
37
+ if (cached)
38
+ return cached;
39
+ const factory = factories.get(type);
40
+ if (!factory) {
41
+ throw new Error(`Infrastructure provider not registered: ${type}. Call registerInfrastructureProvider(${type}, factory) first.`);
42
+ }
43
+ const instance = factory();
44
+ instances.set(type, instance);
45
+ return instance;
46
+ }
47
+ export function getInfrastructure(input) {
48
+ if (!factories.has(input.type)) {
49
+ return {
50
+ reason: `Invalid infrastructure type: ${input.type}. Valid options are: ${[...factories.keys()].join(', ') || '<none registered>'}`,
51
+ supported: false,
52
+ };
53
+ }
54
+ return {
55
+ infrastructure: getOrCreateProvider(input.type),
56
+ supported: true,
57
+ };
58
+ }
59
+ // Register the AWS provider eagerly. The CLI's `infrastructure/index.ts`
60
+ // additionally registers the `local` provider; the broker Lambda does not.
61
+ registerInfrastructureProvider(InfrastructureType.aws, awsProviderFactory);
@@ -37,20 +37,20 @@ export declare const ParameterSpec: z.ZodEffects<z.ZodObject<{
37
37
  description: z.ZodOptional<z.ZodString>;
38
38
  mandatory: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
39
39
  }, "strip", z.ZodTypeAny, {
40
- description?: string | undefined;
41
40
  default?: string | undefined;
41
+ description?: string | undefined;
42
42
  mandatory?: boolean | undefined;
43
43
  }, {
44
- description?: string | undefined;
45
44
  default?: string | undefined;
45
+ description?: string | undefined;
46
46
  mandatory?: boolean | undefined;
47
47
  }>, {
48
- description?: string | undefined;
49
48
  default?: string | undefined;
49
+ description?: string | undefined;
50
50
  mandatory?: boolean | undefined;
51
51
  }, {
52
- description?: string | undefined;
53
52
  default?: string | undefined;
53
+ description?: string | undefined;
54
54
  mandatory?: boolean | undefined;
55
55
  }>;
56
56
  export type IParameterSpec = z.infer<typeof ParameterSpec>;
@@ -79,42 +79,37 @@ export declare const PackageMetadata: z.ZodObject<{
79
79
  description: z.ZodOptional<z.ZodString>;
80
80
  mandatory: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
81
81
  }, "strip", z.ZodTypeAny, {
82
- description?: string | undefined;
83
82
  default?: string | undefined;
83
+ description?: string | undefined;
84
84
  mandatory?: boolean | undefined;
85
85
  }, {
86
- description?: string | undefined;
87
86
  default?: string | undefined;
87
+ description?: string | undefined;
88
88
  mandatory?: boolean | undefined;
89
89
  }>, {
90
- description?: string | undefined;
91
90
  default?: string | undefined;
91
+ description?: string | undefined;
92
92
  mandatory?: boolean | undefined;
93
93
  }, {
94
- description?: string | undefined;
95
94
  default?: string | undefined;
95
+ description?: string | undefined;
96
96
  mandatory?: boolean | undefined;
97
97
  }>>>, Record<string, {
98
- description?: string | undefined;
99
98
  default?: string | undefined;
99
+ description?: string | undefined;
100
100
  mandatory?: boolean | undefined;
101
101
  }> | undefined, Record<string, {
102
- description?: string | undefined;
103
102
  default?: string | undefined;
103
+ description?: string | undefined;
104
104
  mandatory?: boolean | undefined;
105
105
  }> | undefined>;
106
106
  snakeCase: z.ZodOptional<z.ZodBoolean>;
107
107
  }, "strip", z.ZodTypeAny, {
108
108
  iac: IacType;
109
109
  infra: InfrastructureType;
110
- dependencies?: Record<string, string> | undefined;
111
110
  kind?: "app" | "package" | undefined;
112
- parameters?: Record<string, {
113
- description?: string | undefined;
114
- default?: string | undefined;
115
- mandatory?: boolean | undefined;
116
- }> | undefined;
117
111
  deploy?: boolean | undefined;
112
+ dependencies?: Record<string, string> | undefined;
118
113
  devDeploy?: boolean | undefined;
119
114
  inputs?: Record<string, z.objectOutputType<{}, z.ZodTypeAny, "passthrough">> | undefined;
120
115
  onDeploy?: {
@@ -123,18 +118,18 @@ export declare const PackageMetadata: z.ZodObject<{
123
118
  } | undefined;
124
119
  originalInfra?: InfrastructureType | undefined;
125
120
  outputs?: Record<string, z.objectOutputType<{}, z.ZodTypeAny, "passthrough">> | undefined;
121
+ parameters?: Record<string, {
122
+ default?: string | undefined;
123
+ description?: string | undefined;
124
+ mandatory?: boolean | undefined;
125
+ }> | undefined;
126
126
  snakeCase?: boolean | undefined;
127
127
  }, {
128
128
  iac: IacType;
129
129
  infra: InfrastructureType;
130
- dependencies?: Record<string, string> | undefined;
131
130
  kind?: "app" | "package" | undefined;
132
- parameters?: Record<string, {
133
- description?: string | undefined;
134
- default?: string | undefined;
135
- mandatory?: boolean | undefined;
136
- }> | undefined;
137
131
  deploy?: boolean | undefined;
132
+ dependencies?: Record<string, string> | undefined;
138
133
  devDeploy?: boolean | undefined;
139
134
  inputs?: Record<string, z.objectInputType<{}, z.ZodTypeAny, "passthrough">> | undefined;
140
135
  onDeploy?: {
@@ -143,6 +138,11 @@ export declare const PackageMetadata: z.ZodObject<{
143
138
  } | undefined;
144
139
  originalInfra?: InfrastructureType | undefined;
145
140
  outputs?: Record<string, z.objectInputType<{}, z.ZodTypeAny, "passthrough">> | undefined;
141
+ parameters?: Record<string, {
142
+ default?: string | undefined;
143
+ description?: string | undefined;
144
+ mandatory?: boolean | undefined;
145
+ }> | undefined;
146
146
  snakeCase?: boolean | undefined;
147
147
  }>;
148
148
  export type IPackageMetadata = z.infer<typeof PackageMetadata>;