hereya-cli 0.64.1 → 0.64.3

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 (41) hide show
  1. package/README.md +133 -43
  2. package/dist/backend/cloud/cloud-backend.d.ts +70 -0
  3. package/dist/backend/cloud/cloud-backend.js +96 -0
  4. package/dist/backend/common.d.ts +4 -0
  5. package/dist/backend/common.js +1 -0
  6. package/dist/backend/index.d.ts +5 -1
  7. package/dist/backend/index.js +62 -22
  8. package/dist/commands/add/index.js +109 -2
  9. package/dist/commands/deploy/index.js +8 -2
  10. package/dist/commands/docker/run/index.js +1 -0
  11. package/dist/commands/down/index.js +111 -3
  12. package/dist/commands/env/index.js +1 -0
  13. package/dist/commands/executor/start/index.d.ts +11 -0
  14. package/dist/commands/executor/start/index.js +176 -0
  15. package/dist/commands/remove/index.js +138 -4
  16. package/dist/commands/run/index.js +1 -0
  17. package/dist/commands/undeploy/index.js +4 -1
  18. package/dist/commands/up/index.js +102 -5
  19. package/dist/commands/workspace/executor/install/index.d.ts +9 -0
  20. package/dist/commands/workspace/executor/install/index.js +110 -0
  21. package/dist/commands/workspace/executor/token/index.d.ts +8 -0
  22. package/dist/commands/workspace/executor/token/index.js +40 -0
  23. package/dist/commands/workspace/executor/uninstall/index.d.ts +9 -0
  24. package/dist/commands/workspace/executor/uninstall/index.js +102 -0
  25. package/dist/executor/context.d.ts +2 -0
  26. package/dist/executor/context.js +39 -0
  27. package/dist/executor/delegating.d.ts +15 -0
  28. package/dist/executor/delegating.js +50 -0
  29. package/dist/executor/index.d.ts +12 -3
  30. package/dist/executor/index.js +13 -2
  31. package/dist/executor/remote.d.ts +16 -0
  32. package/dist/executor/remote.js +168 -0
  33. package/dist/infrastructure/index.js +55 -22
  34. package/dist/lib/config/common.d.ts +5 -0
  35. package/dist/lib/config/simple.js +43 -24
  36. package/dist/lib/env/index.d.ts +9 -0
  37. package/dist/lib/env/index.js +101 -15
  38. package/dist/lib/package/index.d.ts +12 -0
  39. package/dist/lib/package/index.js +4 -0
  40. package/oclif.manifest.json +183 -25
  41. package/package.json +1 -1
@@ -41,6 +41,10 @@ export async function destroyPackage(input) {
41
41
  if (metadata.deploy && input.skipDeploy) {
42
42
  return { env: {}, metadata, pkgName, success: true, version: version || '' };
43
43
  }
44
+ // devDeploy packages are NOT skipped during remove (unlike deploy packages)
45
+ if (metadata.devDeploy && input.isDeploying) {
46
+ return { env: {}, metadata, pkgName, success: true, version: version || '' };
47
+ }
44
48
  const { infrastructure } = infrastructure$;
45
49
  const backend = await getBackend();
46
50
  const id$ = await backend.getProvisioningId({
@@ -67,17 +71,29 @@ export async function destroyPackage(input) {
67
71
  projectRootDir: input.projectRootDir,
68
72
  version,
69
73
  })
70
- : await infrastructure.destroy({
71
- canonicalName,
72
- env: input.env,
73
- iacType: metadata.iac,
74
- id,
75
- logger: input.logger,
76
- parameters: input.parameters,
77
- pkgName,
78
- pkgUrl: packageUri,
79
- version,
80
- });
74
+ : metadata.devDeploy
75
+ ? await infrastructure.destroy({
76
+ canonicalName,
77
+ env: { ...input.env, ...input.projectEnv },
78
+ iacType: metadata.iac,
79
+ id,
80
+ logger: input.logger,
81
+ parameters: input.parameters,
82
+ pkgName,
83
+ pkgUrl: packageUri,
84
+ version,
85
+ })
86
+ : await infrastructure.destroy({
87
+ canonicalName,
88
+ env: input.env,
89
+ iacType: metadata.iac,
90
+ id,
91
+ logger: input.logger,
92
+ parameters: input.parameters,
93
+ pkgName,
94
+ pkgUrl: packageUri,
95
+ version,
96
+ });
81
97
  if (!destroyOutput.success) {
82
98
  return { reason: destroyOutput.reason, success: false };
83
99
  }
@@ -109,6 +125,11 @@ export async function provisionPackage(input) {
109
125
  if (metadata.deploy && input.skipDeploy) {
110
126
  return { env: {}, metadata, pkgName, success: true, version: version || '' };
111
127
  }
128
+ // devDeploy packages are skipped during the regular provision step (skipDeploy=true)
129
+ // and ignored during deploy/undeploy (deploy workspaces)
130
+ if (metadata.devDeploy && (input.skipDeploy || input.isDeploying)) {
131
+ return { env: {}, metadata, pkgName, success: true, version: version || '' };
132
+ }
112
133
  const dependencies = metadata.dependencies ?? {};
113
134
  const depsOutput = await Promise.all(Object.entries(dependencies).map(async ([depName]) => provisionPackage({
114
135
  ...input,
@@ -157,17 +178,29 @@ export async function provisionPackage(input) {
157
178
  projectRootDir: input.projectRootDir,
158
179
  version,
159
180
  })
160
- : await infrastructure.provision({
161
- canonicalName,
162
- env: input.env,
163
- iacType: metadata.iac,
164
- id,
165
- logger: input.logger,
166
- parameters: input.parameters,
167
- pkgName,
168
- pkgUrl: packageUri,
169
- version,
170
- });
181
+ : metadata.devDeploy
182
+ ? await infrastructure.provision({
183
+ canonicalName,
184
+ env: { ...input.env, ...depsEnv, ...input.projectEnv },
185
+ iacType: metadata.iac,
186
+ id,
187
+ logger: input.logger,
188
+ parameters: input.parameters,
189
+ pkgName,
190
+ pkgUrl: packageUri,
191
+ version,
192
+ })
193
+ : await infrastructure.provision({
194
+ canonicalName,
195
+ env: input.env,
196
+ iacType: metadata.iac,
197
+ id,
198
+ logger: input.logger,
199
+ parameters: input.parameters,
200
+ pkgName,
201
+ pkgUrl: packageUri,
202
+ version,
203
+ });
171
204
  if (!provisionOutput.success) {
172
205
  return { reason: provisionOutput.reason, success: false };
173
206
  }
@@ -21,6 +21,11 @@ export interface Config {
21
21
  version: string;
22
22
  };
23
23
  };
24
+ devDeploy?: {
25
+ [key: string]: {
26
+ version: string;
27
+ };
28
+ };
24
29
  packages?: {
25
30
  [key: string]: {
26
31
  version: string;
@@ -4,32 +4,46 @@ import * as yaml from '../yaml-utils.js';
4
4
  export class SimpleConfigManager {
5
5
  async addPackage(input) {
6
6
  const { config } = await this.loadConfig({ projectRootDir: input.projectRootDir });
7
+ const packageEntry = {
8
+ version: input.version || '',
9
+ };
10
+ let sectionUpdate;
11
+ if (input.metadata.deploy) {
12
+ sectionUpdate = {
13
+ deploy: {
14
+ ...config.deploy,
15
+ [input.package]: packageEntry,
16
+ },
17
+ };
18
+ }
19
+ else if (input.metadata.devDeploy) {
20
+ sectionUpdate = {
21
+ devDeploy: {
22
+ ...config.devDeploy,
23
+ [input.package]: packageEntry,
24
+ },
25
+ };
26
+ }
27
+ else {
28
+ sectionUpdate = {
29
+ packages: {
30
+ ...config.packages,
31
+ [input.package]: {
32
+ ...packageEntry,
33
+ ...(input.metadata.onDeploy
34
+ ? {
35
+ onDeploy: {
36
+ ...input.metadata.onDeploy,
37
+ },
38
+ }
39
+ : {}),
40
+ },
41
+ },
42
+ };
43
+ }
7
44
  await yaml.save({
8
45
  ...config,
9
- ...(input.metadata.deploy
10
- ? {
11
- deploy: {
12
- ...config.deploy,
13
- [input.package]: {
14
- version: input.version || '',
15
- },
16
- },
17
- }
18
- : {
19
- packages: {
20
- ...config.packages,
21
- [input.package]: {
22
- version: input.version || '',
23
- ...(input.metadata.onDeploy
24
- ? {
25
- onDeploy: {
26
- ...input.metadata.onDeploy,
27
- },
28
- }
29
- : {}),
30
- },
31
- },
32
- }),
46
+ ...sectionUpdate,
33
47
  }, await this.getConfigPath(input.projectRootDir));
34
48
  }
35
49
  async loadConfig(input) {
@@ -44,15 +58,20 @@ export class SimpleConfigManager {
44
58
  const { config } = await this.loadConfig({ projectRootDir: input.projectRootDir });
45
59
  const newPackages = { ...config.packages };
46
60
  const newDeploy = { ...config.deploy };
61
+ const newDevDeploy = { ...config.devDeploy };
47
62
  if (input.metadata.deploy) {
48
63
  delete newDeploy[input.package];
49
64
  }
65
+ else if (input.metadata.devDeploy) {
66
+ delete newDevDeploy[input.package];
67
+ }
50
68
  else {
51
69
  delete newPackages[input.package];
52
70
  }
53
71
  await yaml.save({
54
72
  ...config,
55
73
  deploy: newDeploy,
74
+ devDeploy: newDevDeploy,
56
75
  packages: newPackages,
57
76
  }, await this.getConfigPath(input.projectRootDir));
58
77
  }
@@ -7,9 +7,11 @@ export declare class EnvManager {
7
7
  private extractProfileSections;
8
8
  private getEnvPath;
9
9
  private getUserEnvPaths;
10
+ private loadAllDocuments;
10
11
  private loadDotEnvFiles;
11
12
  private loadFirstDocument;
12
13
  private parseDotEnv;
14
+ private saveAllDocuments;
13
15
  }
14
16
  export declare const envManager: EnvManager;
15
17
  export declare function getEnvManager(): EnvManager;
@@ -18,13 +20,19 @@ export type AddEnvInput = {
18
20
  [key: string]: string;
19
21
  };
20
22
  infra: InfrastructureType;
23
+ isDevDeploy?: boolean;
24
+ pkg?: string;
21
25
  projectRootDir?: string;
26
+ snakeCase?: boolean;
22
27
  workspace: string;
23
28
  };
24
29
  export type RemoveEnvInput = AddEnvInput;
25
30
  export type GetProjectEnvInput = {
31
+ excludeDevDeploy?: boolean;
32
+ excludePkg?: string;
26
33
  markSecret?: boolean;
27
34
  profile: string;
35
+ project?: string;
28
36
  projectRootDir?: string;
29
37
  workspace: string;
30
38
  };
@@ -37,3 +45,4 @@ export type GetProjectEnvOutput = {
37
45
  reason: string;
38
46
  success: false;
39
47
  };
48
+ export declare function camelToUpperSnake(str: string): string;
@@ -1,22 +1,64 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { getExecutor } from '../../executor/index.js';
3
+ import { stringify } from 'yaml';
4
+ import { getExecutorForWorkspace } from '../../executor/context.js';
4
5
  import { getAnyPath } from '../filesystem.js';
5
6
  import { stripOrgPrefix } from '../org-utils.js';
6
- import { load, parseYaml, save } from '../yaml-utils.js';
7
+ import { load, parseYaml } from '../yaml-utils.js';
7
8
  export class EnvManager {
8
9
  async addProjectEnv(input) {
9
10
  const envPath = await this.getEnvPath(input);
10
- const { data: existingEnv } = await load(envPath);
11
+ const documents = await this.loadAllDocuments(envPath);
12
+ const newDoc = {};
13
+ if (input.pkg) {
14
+ newDoc.pkg = input.pkg;
15
+ }
16
+ if (input.isDevDeploy) {
17
+ newDoc.devDeploy = 'true';
18
+ }
11
19
  const newEnv = Object.fromEntries(Object.entries(input.env).map(([key, value]) => [key, `${input.infra}:${value}`]));
12
- const finalEnv = { ...existingEnv, ...newEnv };
13
- await save(finalEnv, envPath);
20
+ Object.assign(newDoc, newEnv);
21
+ if (input.snakeCase) {
22
+ for (const [key, value] of Object.entries(newEnv)) {
23
+ const snakeKey = camelToUpperSnake(key);
24
+ if (snakeKey !== key) {
25
+ newDoc[snakeKey] = value;
26
+ }
27
+ }
28
+ }
29
+ if (input.pkg) {
30
+ const idx = documents.findIndex(d => d.pkg === input.pkg);
31
+ if (idx === -1) {
32
+ documents.push(newDoc);
33
+ }
34
+ else {
35
+ documents[idx] = newDoc;
36
+ }
37
+ }
38
+ else {
39
+ documents[0] = { ...documents[0], ...newDoc };
40
+ }
41
+ await this.saveAllDocuments(documents, envPath);
14
42
  }
15
43
  async getProjectEnv(input) {
16
44
  const envPath = await this.getEnvPath(input);
17
- const { data: env, found } = await load(envPath);
45
+ const documents = await this.loadAllDocuments(envPath);
46
+ const mergedEnv = {};
47
+ for (const doc of documents) {
48
+ if (doc.pkg) {
49
+ if (input.excludeDevDeploy && doc.devDeploy === 'true')
50
+ continue;
51
+ if (input.excludePkg && doc.pkg === input.excludePkg)
52
+ continue;
53
+ }
54
+ for (const [key, value] of Object.entries(doc)) {
55
+ if (key === 'pkg' || key === 'devDeploy')
56
+ continue;
57
+ mergedEnv[key] = value;
58
+ }
59
+ }
18
60
  let resolvedEnv = {};
19
- const executor$ = getExecutor();
61
+ const executor$ = await getExecutorForWorkspace(input.workspace, input.project);
20
62
  if (!executor$.success) {
21
63
  return {
22
64
  reason: executor$.reason,
@@ -30,8 +72,8 @@ export class EnvManager {
30
72
  if (input.markSecret && Object.keys(dotEnv).length > 0) {
31
73
  dotEnv = Object.fromEntries(Object.entries(dotEnv).map(([key, value]) => [key, `secret://${value}`]));
32
74
  }
33
- if (found) {
34
- resolvedEnv = await executor.resolveEnvValues({ env, markSecret: input.markSecret });
75
+ if (Object.keys(mergedEnv).length > 0) {
76
+ resolvedEnv = await executor.resolveEnvValues({ env: mergedEnv, markSecret: input.markSecret });
35
77
  }
36
78
  const userEnvPaths = await this.getUserEnvPaths(input.profile, input.projectRootDir);
37
79
  let userMergedEnv = {};
@@ -64,13 +106,29 @@ export class EnvManager {
64
106
  }
65
107
  async removeProjectEnv(input) {
66
108
  const envPath = await this.getEnvPath(input);
67
- const { data: existingEnv, found } = await load(envPath);
68
- if (!found) {
69
- return;
109
+ const documents = await this.loadAllDocuments(envPath);
110
+ if (input.pkg) {
111
+ // Remove tagged document
112
+ const filtered = documents.filter(d => d.pkg !== input.pkg);
113
+ // Also remove matching keys from untagged (legacy) document for backward compat
114
+ if (filtered.length > 0 && !filtered[0].pkg) {
115
+ const envKeysToRemove = Object.keys(input.env);
116
+ const allKeysToRemove = input.snakeCase
117
+ ? new Set([...envKeysToRemove, ...envKeysToRemove.map(k => camelToUpperSnake(k))])
118
+ : new Set(envKeysToRemove);
119
+ filtered[0] = Object.fromEntries(Object.entries(filtered[0]).filter(([key]) => !allKeysToRemove.has(key)));
120
+ }
121
+ await this.saveAllDocuments(filtered, envPath);
122
+ }
123
+ else {
124
+ // Legacy: filter individual keys from first document
125
+ const envKeysToRemove = Object.keys(input.env);
126
+ const allKeysToRemove = input.snakeCase
127
+ ? new Set([...envKeysToRemove, ...envKeysToRemove.map(k => camelToUpperSnake(k))])
128
+ : new Set(envKeysToRemove);
129
+ documents[0] = Object.fromEntries(Object.entries(documents[0]).filter(([key]) => !allKeysToRemove.has(key)));
130
+ await this.saveAllDocuments(documents, envPath);
70
131
  }
71
- const envKeysToRemove = Object.keys(input.env);
72
- const finalEnv = Object.fromEntries(Object.entries(existingEnv).filter(([key]) => !envKeysToRemove.includes(key)));
73
- await save(finalEnv, envPath);
74
132
  }
75
133
  async extractProfileSections(filePath, targetProfile) {
76
134
  if (!filePath)
@@ -113,6 +171,23 @@ export class EnvManager {
113
171
  ]);
114
172
  return paths;
115
173
  }
174
+ async loadAllDocuments(envPath) {
175
+ try {
176
+ const content = await fs.readFile(envPath, 'utf8');
177
+ const rawDocs = content.split(/^---$/m);
178
+ const parsedDocs = await Promise.all(rawDocs
179
+ .filter(raw => raw.trim())
180
+ .map(async (raw) => {
181
+ const { data } = await parseYaml(raw);
182
+ return data && Object.keys(data).length > 0 ? data : null;
183
+ }));
184
+ const documents = parsedDocs.filter((d) => d !== null);
185
+ return documents.length > 0 ? documents : [{}];
186
+ }
187
+ catch {
188
+ return [{}];
189
+ }
190
+ }
116
191
  // Load .env files from project root
117
192
  async loadDotEnvFiles(profile, projectRootDir) {
118
193
  const rootDir = projectRootDir ?? process.cwd();
@@ -175,8 +250,19 @@ export class EnvManager {
175
250
  }
176
251
  return result;
177
252
  }
253
+ async saveAllDocuments(documents, envPath) {
254
+ await fs.mkdir(path.dirname(envPath), { recursive: true });
255
+ const yamlDocs = documents
256
+ .filter(d => Object.keys(d).length > 0)
257
+ .map(d => stringify(d));
258
+ const content = yamlDocs.length > 0 ? yamlDocs.join('---\n') : stringify({});
259
+ await fs.writeFile(envPath, content, 'utf8');
260
+ }
178
261
  }
179
262
  export const envManager = new EnvManager();
180
263
  export function getEnvManager() {
181
264
  return envManager;
182
265
  }
266
+ export function camelToUpperSnake(str) {
267
+ return str.replaceAll(/([a-z0-9])([A-Z])/g, '$1_$2').toUpperCase();
268
+ }
@@ -32,8 +32,10 @@ export type ResolvePackageOutput = {
32
32
  export declare const PackageMetadata: z.ZodObject<{
33
33
  dependencies: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
34
34
  deploy: z.ZodOptional<z.ZodBoolean>;
35
+ devDeploy: z.ZodOptional<z.ZodBoolean>;
35
36
  iac: z.ZodNativeEnum<typeof IacType>;
36
37
  infra: z.ZodNativeEnum<typeof InfrastructureType>;
38
+ inputs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{}, "passthrough", z.ZodTypeAny, z.objectOutputType<{}, z.ZodTypeAny, "passthrough">, z.objectInputType<{}, z.ZodTypeAny, "passthrough">>>>;
37
39
  onDeploy: z.ZodOptional<z.ZodObject<{
38
40
  pkg: z.ZodString;
39
41
  version: z.ZodString;
@@ -45,25 +47,35 @@ export declare const PackageMetadata: z.ZodObject<{
45
47
  pkg: string;
46
48
  }>>;
47
49
  originalInfra: z.ZodOptional<z.ZodNativeEnum<typeof InfrastructureType>>;
50
+ outputs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{}, "passthrough", z.ZodTypeAny, z.objectOutputType<{}, z.ZodTypeAny, "passthrough">, z.objectInputType<{}, z.ZodTypeAny, "passthrough">>>>;
51
+ snakeCase: z.ZodOptional<z.ZodBoolean>;
48
52
  }, "strip", z.ZodTypeAny, {
49
53
  infra: InfrastructureType;
50
54
  iac: IacType;
51
55
  dependencies?: Record<string, string> | undefined;
52
56
  deploy?: boolean | undefined;
57
+ devDeploy?: boolean | undefined;
58
+ inputs?: Record<string, z.objectOutputType<{}, z.ZodTypeAny, "passthrough">> | undefined;
53
59
  onDeploy?: {
54
60
  version: string;
55
61
  pkg: string;
56
62
  } | undefined;
57
63
  originalInfra?: InfrastructureType | undefined;
64
+ outputs?: Record<string, z.objectOutputType<{}, z.ZodTypeAny, "passthrough">> | undefined;
65
+ snakeCase?: boolean | undefined;
58
66
  }, {
59
67
  infra: InfrastructureType;
60
68
  iac: IacType;
61
69
  dependencies?: Record<string, string> | undefined;
62
70
  deploy?: boolean | undefined;
71
+ devDeploy?: boolean | undefined;
72
+ inputs?: Record<string, z.objectInputType<{}, z.ZodTypeAny, "passthrough">> | undefined;
63
73
  onDeploy?: {
64
74
  version: string;
65
75
  pkg: string;
66
76
  } | undefined;
67
77
  originalInfra?: InfrastructureType | undefined;
78
+ outputs?: Record<string, z.objectInputType<{}, z.ZodTypeAny, "passthrough">> | undefined;
79
+ snakeCase?: boolean | undefined;
68
80
  }>;
69
81
  export type IPackageMetadata = z.infer<typeof PackageMetadata>;
@@ -77,8 +77,10 @@ export async function downloadPackage(pkgUrl, destPath) {
77
77
  export const PackageMetadata = z.object({
78
78
  dependencies: z.record(z.string()).optional(),
79
79
  deploy: z.boolean().optional(),
80
+ devDeploy: z.boolean().optional(),
80
81
  iac: z.nativeEnum(IacType),
81
82
  infra: z.nativeEnum(InfrastructureType),
83
+ inputs: z.record(z.object({}).passthrough()).optional(),
82
84
  onDeploy: z
83
85
  .object({
84
86
  pkg: z.string(),
@@ -86,6 +88,8 @@ export const PackageMetadata = z.object({
86
88
  })
87
89
  .optional(),
88
90
  originalInfra: z.nativeEnum(InfrastructureType).optional(),
91
+ outputs: z.record(z.object({}).passthrough()).optional(),
92
+ snakeCase: z.boolean().optional(),
89
93
  });
90
94
  // Helper function to parse package spec into name and version
91
95
  function parsePackageSpec(spec) {