hereya-cli 0.55.0 → 0.57.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 (35) hide show
  1. package/README.md +94 -36
  2. package/dist/backend/cloud/cloud-backend.d.ts +5 -1
  3. package/dist/backend/cloud/cloud-backend.js +208 -0
  4. package/dist/backend/common.d.ts +64 -2
  5. package/dist/backend/file.d.ts +5 -1
  6. package/dist/backend/file.js +24 -0
  7. package/dist/commands/add/index.js +2 -1
  8. package/dist/commands/deploy/index.js +50 -24
  9. package/dist/commands/doc/index.d.ts +17 -0
  10. package/dist/commands/doc/index.js +154 -0
  11. package/dist/commands/down/index.js +40 -11
  12. package/dist/commands/import/index.js +2 -1
  13. package/dist/commands/publish/index.d.ts +32 -0
  14. package/dist/commands/publish/index.js +239 -0
  15. package/dist/commands/remove/index.js +16 -4
  16. package/dist/commands/undeploy/index.js +45 -17
  17. package/dist/commands/up/index.js +36 -11
  18. package/dist/commands/workspace/install/index.js +1 -1
  19. package/dist/commands/workspace/uninstall/index.js +18 -5
  20. package/dist/executor/interface.d.ts +5 -0
  21. package/dist/executor/local.js +3 -3
  22. package/dist/infrastructure/index.d.ts +2 -0
  23. package/dist/infrastructure/index.js +8 -8
  24. package/dist/lib/config/common.d.ts +1 -0
  25. package/dist/lib/config/simple.js +2 -2
  26. package/dist/lib/package/cloud.d.ts +19 -0
  27. package/dist/lib/package/cloud.js +226 -0
  28. package/dist/lib/package/common.d.ts +1 -0
  29. package/dist/lib/package/github.d.ts +1 -1
  30. package/dist/lib/package/github.js +33 -6
  31. package/dist/lib/package/index.d.ts +9 -4
  32. package/dist/lib/package/index.js +178 -35
  33. package/dist/lib/package/local.js +1 -0
  34. package/oclif.manifest.json +92 -1
  35. package/package.json +8 -1
@@ -7,13 +7,17 @@ export interface Backend {
7
7
  deleteState(input: DeleteStateInput): Promise<DeleteStateOutput>;
8
8
  deleteWorkspace(input: DeleteWorkspaceInput): Promise<DeleteWorkspaceOutput>;
9
9
  exportBackend(): Promise<ExportBackendOutput>;
10
+ getPackageByVersion?(name: string, version: string): Promise<GetPackageOutput>;
11
+ getPackageLatest?(name: string): Promise<GetPackageOutput>;
10
12
  getProvisioningId(input: GetProvisioningIdInput): Promise<GetProvisioningIdOutput>;
11
13
  getState(input: GetStateInput): Promise<GetStateOutput>;
12
14
  getWorkspace(workspace: string): Promise<GetWorkspaceOutput>;
13
15
  getWorkspaceEnv(input: GetWorkspaceEnvInput): Promise<GetWorkspaceEnvOutput>;
14
16
  importBackend(input: ImportBackendInput): Promise<ImportBackendOutput>;
15
17
  init(options: InitProjectInput): Promise<InitProjectOutput>;
18
+ listPackageVersions?(name: string): Promise<ListPackageVersionsOutput>;
16
19
  listWorkspaces(): Promise<string[]>;
20
+ publishPackage?(input: PublishPackageInput): Promise<PublishPackageOutput>;
17
21
  removePackageFromWorkspace(input: RemovePackageFromWorkspaceInput): Promise<RemovePackageFromWorkspaceOutput>;
18
22
  saveState(config: Config, workspace?: string): Promise<void>;
19
23
  setEnvVar(input: SetEnvVarInput): Promise<SetEnvVarOutput>;
@@ -45,9 +49,9 @@ export declare const WorkspaceSchema: z.ZodObject<{
45
49
  version: string;
46
50
  parameters?: Record<string, any> | undefined;
47
51
  }> | undefined;
52
+ profile?: string | undefined;
48
53
  isDeploy?: boolean | undefined;
49
54
  mirrorOf?: string | undefined;
50
- profile?: string | undefined;
51
55
  }, {
52
56
  name: string;
53
57
  id: string;
@@ -56,9 +60,9 @@ export declare const WorkspaceSchema: z.ZodObject<{
56
60
  version: string;
57
61
  parameters?: Record<string, any> | undefined;
58
62
  }> | undefined;
63
+ profile?: string | undefined;
59
64
  isDeploy?: boolean | undefined;
60
65
  mirrorOf?: string | undefined;
61
- profile?: string | undefined;
62
66
  }>;
63
67
  export type Workspace = z.infer<typeof WorkspaceSchema>;
64
68
  export type AddPackageToWorkspaceInput = {
@@ -227,3 +231,61 @@ export type UpdateWorkspaceOutput = {
227
231
  success: true;
228
232
  workspace: Workspace;
229
233
  };
234
+ export type PublishPackageInput = {
235
+ commit: string;
236
+ description: string;
237
+ doc?: string;
238
+ iac: string;
239
+ infra: string;
240
+ name: string;
241
+ onDeployPkg?: string;
242
+ onDeployVersion?: string;
243
+ repository: string;
244
+ sha256: string;
245
+ version: string;
246
+ visibility?: 'PRIVATE' | 'PUBLIC';
247
+ };
248
+ export type PublishPackageOutput = {
249
+ package: {
250
+ id: string;
251
+ name: string;
252
+ version: string;
253
+ };
254
+ success: true;
255
+ } | {
256
+ reason: string;
257
+ success: false;
258
+ };
259
+ export type RegistryPackage = {
260
+ commit?: string;
261
+ createdAt?: string;
262
+ description: string;
263
+ doc?: string;
264
+ iac?: string;
265
+ id: string;
266
+ infra?: string;
267
+ name: string;
268
+ onDeploy?: {
269
+ pkg: string;
270
+ version: string;
271
+ };
272
+ repository?: string;
273
+ sha256?: string;
274
+ updatedAt?: string;
275
+ version: string;
276
+ visibility?: 'PRIVATE' | 'PUBLIC';
277
+ };
278
+ export type GetPackageOutput = {
279
+ package: RegistryPackage;
280
+ success: true;
281
+ } | {
282
+ reason: string;
283
+ success: false;
284
+ };
285
+ export type ListPackageVersionsOutput = {
286
+ packages: RegistryPackage[];
287
+ success: true;
288
+ } | {
289
+ reason: string;
290
+ success: false;
291
+ };
@@ -1,5 +1,5 @@
1
1
  import { Config } from '../lib/config/common.js';
2
- import { AddPackageToWorkspaceInput, AddPackageToWorkspaceOutput, Backend, CreateWorkspaceInput, CreateWorkspaceOutput, DeleteStateInput, DeleteStateOutput, DeleteWorkspaceInput, DeleteWorkspaceOutput, ExportBackendOutput, GetProvisioningIdInput, GetProvisioningIdOutput, GetStateInput, GetStateOutput, GetWorkspaceEnvInput, GetWorkspaceEnvOutput, GetWorkspaceOutput, ImportBackendInput, ImportBackendOutput, InitProjectInput, InitProjectOutput, RemovePackageFromWorkspaceInput, RemovePackageFromWorkspaceOutput, SetEnvVarInput, SetEnvVarOutput, UnsetEnvVarInput, UnsetEnvVarOutput, UpdateWorkspaceInput, UpdateWorkspaceOutput } from './common.js';
2
+ import { AddPackageToWorkspaceInput, AddPackageToWorkspaceOutput, Backend, CreateWorkspaceInput, CreateWorkspaceOutput, DeleteStateInput, DeleteStateOutput, DeleteWorkspaceInput, DeleteWorkspaceOutput, ExportBackendOutput, GetPackageOutput, GetProvisioningIdInput, GetProvisioningIdOutput, GetStateInput, GetStateOutput, GetWorkspaceEnvInput, GetWorkspaceEnvOutput, GetWorkspaceOutput, ImportBackendInput, ImportBackendOutput, InitProjectInput, InitProjectOutput, ListPackageVersionsOutput, PublishPackageInput, PublishPackageOutput, RemovePackageFromWorkspaceInput, RemovePackageFromWorkspaceOutput, SetEnvVarInput, SetEnvVarOutput, UnsetEnvVarInput, UnsetEnvVarOutput, UpdateWorkspaceInput, UpdateWorkspaceOutput } from './common.js';
3
3
  import { FileStorage } from './file-storage/common.js';
4
4
  export declare class FileBackend implements Backend {
5
5
  private readonly fileStorage;
@@ -9,13 +9,17 @@ export declare class FileBackend implements Backend {
9
9
  deleteState(input: DeleteStateInput): Promise<DeleteStateOutput>;
10
10
  deleteWorkspace(input: DeleteWorkspaceInput): Promise<DeleteWorkspaceOutput>;
11
11
  exportBackend(): Promise<ExportBackendOutput>;
12
+ getPackageByVersion(_: string, __: string): Promise<GetPackageOutput>;
13
+ getPackageLatest(_: string): Promise<GetPackageOutput>;
12
14
  getProvisioningId(input: GetProvisioningIdInput): Promise<GetProvisioningIdOutput>;
13
15
  getState(input: GetStateInput): Promise<GetStateOutput>;
14
16
  getWorkspace(workspace: string): Promise<GetWorkspaceOutput>;
15
17
  getWorkspaceEnv(input: GetWorkspaceEnvInput): Promise<GetWorkspaceEnvOutput>;
16
18
  importBackend(_: ImportBackendInput): Promise<ImportBackendOutput>;
17
19
  init(options: InitProjectInput): Promise<InitProjectOutput>;
20
+ listPackageVersions(_: string): Promise<ListPackageVersionsOutput>;
18
21
  listWorkspaces(): Promise<string[]>;
22
+ publishPackage(_: PublishPackageInput): Promise<PublishPackageOutput>;
19
23
  removePackageFromWorkspace(input: RemovePackageFromWorkspaceInput): Promise<RemovePackageFromWorkspaceOutput>;
20
24
  saveState(config: Config, workspace?: string): Promise<void>;
21
25
  setEnvVar(input: SetEnvVarInput): Promise<SetEnvVarOutput>;
@@ -180,6 +180,18 @@ export class FileBackend {
180
180
  success: false,
181
181
  };
182
182
  }
183
+ async getPackageByVersion(_, __) {
184
+ return {
185
+ reason: 'Package registry operations are only supported with the cloud backend. Please run `hereya login` to use cloud backend.',
186
+ success: false,
187
+ };
188
+ }
189
+ async getPackageLatest(_) {
190
+ return {
191
+ reason: 'Package registry operations are only supported with the cloud backend. Please run `hereya login` to use cloud backend.',
192
+ success: false,
193
+ };
194
+ }
183
195
  async getProvisioningId(input) {
184
196
  const idPaths = [`provisioning/${input.logicalId}.yaml`, `provisioning/${input.logicalId}.yml`];
185
197
  const newId = `p-${randomUUID()}`;
@@ -331,6 +343,12 @@ export class FileBackend {
331
343
  },
332
344
  };
333
345
  }
346
+ async listPackageVersions(_) {
347
+ return {
348
+ reason: 'Package registry operations are only supported with the cloud backend. Please run `hereya login` to use cloud backend.',
349
+ success: false,
350
+ };
351
+ }
334
352
  async listWorkspaces() {
335
353
  const workspaces$ = await this.fileStorage.listFileNames({ directory: 'state/workspaces' });
336
354
  if (workspaces$.success) {
@@ -341,6 +359,12 @@ export class FileBackend {
341
359
  }
342
360
  throw new Error(`Could not list workspaces: ${workspaces$.error}`);
343
361
  }
362
+ async publishPackage(_) {
363
+ return {
364
+ reason: 'Package registry operations are only supported with the cloud backend. Please run `hereya login` to use cloud backend.',
365
+ success: false,
366
+ };
367
+ }
344
368
  async removePackageFromWorkspace(input) {
345
369
  const workspace$ = await this.getWorkspace(input.workspace);
346
370
  if (!workspace$.found) {
@@ -147,8 +147,9 @@ export default class Add extends Command {
147
147
  const configManager = getConfigManager();
148
148
  await configManager.addPackage({
149
149
  metadata: ctx.provisionOutput.metadata,
150
- package: ctx.package,
150
+ package: ctx.provisionOutput.pkgName,
151
151
  projectRootDir,
152
+ version: ctx.provisionOutput.version,
152
153
  });
153
154
  await delay(500);
154
155
  },
@@ -54,8 +54,20 @@ export default class Deploy extends Command {
54
54
  async task(ctx) {
55
55
  // Config already loaded above for validation
56
56
  ctx.configOutput = loadConfigOutput;
57
- ctx.deployPackages = Object.keys(loadConfigOutput.config.deploy ?? {});
58
- ctx.packages = Object.keys(loadConfigOutput.config.packages ?? {});
57
+ // Get deploy packages with their versions
58
+ const deployPackages = loadConfigOutput.config.deploy ?? {};
59
+ ctx.deployPackages = Object.entries(deployPackages).map(([name, info]) => ({
60
+ name,
61
+ packageSpec: info.version ? `${name}@${info.version}` : name,
62
+ version: info.version || ''
63
+ }));
64
+ // Get regular packages with their versions
65
+ const packages = loadConfigOutput.config.packages ?? {};
66
+ ctx.packages = Object.entries(packages).map(([name, info]) => ({
67
+ name,
68
+ packageSpec: info.version ? `${name}@${info.version}` : name,
69
+ version: info.version || ''
70
+ }));
59
71
  ctx.workspace = flags.workspace;
60
72
  await delay(500);
61
73
  },
@@ -97,10 +109,24 @@ export default class Deploy extends Command {
97
109
  },
98
110
  {
99
111
  async task(ctx) {
100
- const savedDeployPackages = Object.keys(ctx.savedStateOutput?.config.deploy ?? {});
101
- ctx.removedDeployPackages = savedDeployPackages.filter((packageName) => !ctx.deployPackages.includes(packageName));
102
- const savedPackages = Object.keys(ctx.savedStateOutput?.config.packages ?? {});
103
- ctx.removedPackages = savedPackages.filter((packageName) => !ctx.packages.includes(packageName));
112
+ const savedDeployPackages = ctx.savedStateOutput?.config.deploy ?? {};
113
+ const currentDeployPackageNames = new Set(ctx.deployPackages.map(pkg => pkg.name));
114
+ ctx.removedDeployPackages = Object.entries(savedDeployPackages)
115
+ .filter(([name]) => !currentDeployPackageNames.has(name))
116
+ .map(([name, info]) => ({
117
+ name,
118
+ packageSpec: info.version ? `${name}@${info.version}` : name,
119
+ version: info.version || ''
120
+ }));
121
+ const savedPackages = ctx.savedStateOutput?.config.packages ?? {};
122
+ const currentPackageNames = new Set(ctx.packages.map(pkg => pkg.name));
123
+ ctx.removedPackages = Object.entries(savedPackages)
124
+ .filter(([name]) => !currentPackageNames.has(name))
125
+ .map(([name, info]) => ({
126
+ name,
127
+ packageSpec: info.version ? `${name}@${info.version}` : name,
128
+ version: info.version || ''
129
+ }));
104
130
  await delay(500);
105
131
  },
106
132
  title: 'Identifying removed packages',
@@ -108,7 +134,7 @@ export default class Deploy extends Command {
108
134
  {
109
135
  skip: (ctx) => ctx.removedDeployPackages.length === 0,
110
136
  task(ctx, task) {
111
- return task.newListr(ctx.removedDeployPackages.map((packageName) => ({
137
+ return task.newListr(ctx.removedDeployPackages.map((pkg) => ({
112
138
  rendererOptions: {
113
139
  persistentOutput: isDebug(),
114
140
  },
@@ -117,7 +143,7 @@ export default class Deploy extends Command {
117
143
  const backend = await getBackend();
118
144
  const profile = await getProfileFromWorkspace(backend, ctx.workspace, ctx.configOutput.config.project);
119
145
  const { parameters } = await parameterManager.getPackageParameters({
120
- package: packageName,
146
+ package: pkg.name,
121
147
  profile,
122
148
  projectRootDir,
123
149
  });
@@ -129,7 +155,7 @@ export default class Deploy extends Command {
129
155
  const destroyOutput = await executor.destroy({
130
156
  isDeploying: true,
131
157
  logger: getLogger(task),
132
- package: packageName,
158
+ package: pkg.packageSpec,
133
159
  parameters,
134
160
  project: ctx.configOutput.config.project,
135
161
  projectEnv: ctx.projectEnv,
@@ -140,7 +166,7 @@ export default class Deploy extends Command {
140
166
  throw new Error(destroyOutput.reason);
141
167
  }
142
168
  },
143
- title: `Destroying package ${packageName}`,
169
+ title: `Destroying package ${pkg.name}`,
144
170
  })), { concurrent: true, rendererOptions: { collapseSubtasks: !isDebug() } });
145
171
  },
146
172
  title: 'Destroying removed deployment packages',
@@ -148,7 +174,7 @@ export default class Deploy extends Command {
148
174
  {
149
175
  skip: (ctx) => ctx.removedPackages.length === 0,
150
176
  task(ctx, task) {
151
- return task.newListr(ctx.removedPackages.map((packageName) => ({
177
+ return task.newListr(ctx.removedPackages.map((pkg) => ({
152
178
  rendererOptions: {
153
179
  persistentOutput: isDebug(),
154
180
  },
@@ -157,7 +183,7 @@ export default class Deploy extends Command {
157
183
  const backend = await getBackend();
158
184
  const profile = await getProfileFromWorkspace(backend, ctx.workspace, ctx.configOutput.config.project);
159
185
  const { parameters } = await parameterManager.getPackageParameters({
160
- package: packageName,
186
+ package: pkg.name,
161
187
  profile,
162
188
  projectRootDir,
163
189
  });
@@ -169,7 +195,7 @@ export default class Deploy extends Command {
169
195
  const destroyOutput = await executor.destroy({
170
196
  isDeploying: true,
171
197
  logger: getLogger(task),
172
- package: packageName,
198
+ package: pkg.packageSpec,
173
199
  parameters,
174
200
  project: ctx.configOutput.config.project,
175
201
  projectRootDir,
@@ -180,10 +206,10 @@ export default class Deploy extends Command {
180
206
  }
181
207
  const { env, metadata } = destroyOutput;
182
208
  const output = ctx.removed || [];
183
- output.push({ env, metadata, packageName });
209
+ output.push({ env, metadata, packageName: pkg.name });
184
210
  ctx.removed = output;
185
211
  },
186
- title: `Destroying ${packageName}`,
212
+ title: `Destroying ${pkg.name}`,
187
213
  })), { concurrent: true, rendererOptions: { collapseSubtasks: !isDebug() } });
188
214
  },
189
215
  title: 'Destroying removed packages',
@@ -194,7 +220,7 @@ export default class Deploy extends Command {
194
220
  if (!ctx.packages || ctx.packages.length === 0) {
195
221
  return;
196
222
  }
197
- return task.newListr(ctx.packages.map((packageName) => ({
223
+ return task.newListr(ctx.packages.map((pkg) => ({
198
224
  rendererOptions: {
199
225
  persistentOutput: isDebug(),
200
226
  },
@@ -203,7 +229,7 @@ export default class Deploy extends Command {
203
229
  const backend = await getBackend();
204
230
  const profile = await getProfileFromWorkspace(backend, ctx.workspace, ctx.configOutput.config.project);
205
231
  const { parameters } = await parameterManager.getPackageParameters({
206
- package: packageName,
232
+ package: pkg.name,
207
233
  profile,
208
234
  projectRootDir,
209
235
  });
@@ -215,7 +241,7 @@ export default class Deploy extends Command {
215
241
  const provisionOutput = await executor.provision({
216
242
  isDeploying: true,
217
243
  logger: getLogger(task),
218
- package: packageName,
244
+ package: pkg.packageSpec,
219
245
  parameters,
220
246
  project: ctx.configOutput.config.project,
221
247
  projectRootDir,
@@ -226,10 +252,10 @@ export default class Deploy extends Command {
226
252
  }
227
253
  const { env, metadata } = provisionOutput;
228
254
  const output = ctx.added || [];
229
- output.push({ env, metadata, packageName });
255
+ output.push({ env, metadata, packageName: pkg.name });
230
256
  ctx.added = output;
231
257
  },
232
- title: `Provisioning ${packageName}`,
258
+ title: `Provisioning ${pkg.name}`,
233
259
  })), { concurrent: true, rendererOptions: { collapseSubtasks: !isDebug() } });
234
260
  },
235
261
  title: `Provisioning packages`,
@@ -300,7 +326,7 @@ export default class Deploy extends Command {
300
326
  throw new Error(getProjectEnvOutput.reason);
301
327
  }
302
328
  const { env: projectEnv } = getProjectEnvOutput;
303
- return task.newListr(ctx.deployPackages.map((packageName) => ({
329
+ return task.newListr(ctx.deployPackages.map((pkg) => ({
304
330
  rendererOptions: {
305
331
  persistentOutput: isDebug(),
306
332
  },
@@ -309,7 +335,7 @@ export default class Deploy extends Command {
309
335
  const backend = await getBackend();
310
336
  const profile = await getProfileFromWorkspace(backend, ctx.workspace, ctx.configOutput.config.project);
311
337
  const { parameters } = await parameterManager.getPackageParameters({
312
- package: packageName,
338
+ package: pkg.name,
313
339
  profile,
314
340
  projectRootDir,
315
341
  });
@@ -321,7 +347,7 @@ export default class Deploy extends Command {
321
347
  const provisionOutput = await executor.provision({
322
348
  isDeploying: true,
323
349
  logger: getLogger(task),
324
- package: packageName,
350
+ package: pkg.packageSpec,
325
351
  parameters,
326
352
  project: ctx.configOutput.config.project,
327
353
  projectEnv,
@@ -335,7 +361,7 @@ export default class Deploy extends Command {
335
361
  .map(([key, value]) => `${key}=${value}`)
336
362
  .join('\n'));
337
363
  },
338
- title: `Provisioning package ${packageName}`,
364
+ title: `Provisioning package ${pkg.name}`,
339
365
  })), { concurrent: true, rendererOptions: { collapseSubtasks: !isDebug() } });
340
366
  },
341
367
  title: 'Provisioning deployment packages',
@@ -0,0 +1,17 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Doc extends Command {
3
+ static args: {
4
+ package: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ chdir: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ 'no-doc': import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
+ };
13
+ run(): Promise<void>;
14
+ private displayMarkdown;
15
+ private displayPackage;
16
+ private parsePackageSpec;
17
+ }
@@ -0,0 +1,154 @@
1
+ import { Args, Command, Flags } from '@oclif/core';
2
+ import chalk from 'chalk';
3
+ import { marked } from 'marked';
4
+ import { markedTerminal } from 'marked-terminal';
5
+ import { getBackend } from '../../backend/index.js';
6
+ // Configure marked with terminal renderer
7
+ marked.use(markedTerminal({
8
+ reflowText: true,
9
+ showSectionPrefix: false,
10
+ width: 80,
11
+ }));
12
+ export default class Doc extends Command {
13
+ static args = {
14
+ package: Args.string({
15
+ description: 'Package name with optional version (e.g., my-package or my-package@1.0.0)',
16
+ required: true,
17
+ }),
18
+ };
19
+ static description = 'Display documentation for a package from the registry';
20
+ static examples = [
21
+ '<%= config.bin %> <%= command.id %> my-package',
22
+ '<%= config.bin %> <%= command.id %> my-package@1.0.0',
23
+ '<%= config.bin %> <%= command.id %> my-package --json',
24
+ '<%= config.bin %> <%= command.id %> my-package --no-doc',
25
+ ];
26
+ static flags = {
27
+ chdir: Flags.directory({
28
+ char: 'C',
29
+ default: '.',
30
+ description: 'directory to run command in',
31
+ helpGroup: 'global',
32
+ }),
33
+ json: Flags.boolean({
34
+ char: 'j',
35
+ description: 'Output in JSON format',
36
+ }),
37
+ 'no-doc': Flags.boolean({
38
+ description: 'Show only metadata without the full documentation',
39
+ }),
40
+ };
41
+ async run() {
42
+ const { args, flags } = await this.parse(Doc);
43
+ // Change to specified directory
44
+ if (flags.chdir !== '.') {
45
+ process.chdir(flags.chdir);
46
+ }
47
+ // Parse package name and version
48
+ const { name, version } = this.parsePackageSpec(args.package);
49
+ // Get backend
50
+ const backend = await getBackend();
51
+ // Check if backend supports registry operations
52
+ if (!backend.getPackageLatest || !backend.getPackageByVersion) {
53
+ this.error('Package registry operations are only supported with the cloud backend. Please run `hereya login` to use cloud backend.');
54
+ }
55
+ try {
56
+ // Fetch package information
57
+ const result = version
58
+ ? await backend.getPackageByVersion(name, version)
59
+ : await backend.getPackageLatest(name);
60
+ if (!result.success) {
61
+ this.error(result.reason);
62
+ }
63
+ const pkg = result.package;
64
+ // Output JSON if requested
65
+ if (flags.json) {
66
+ this.log(JSON.stringify(pkg, null, 2));
67
+ return;
68
+ }
69
+ // Display package information
70
+ this.displayPackage(pkg, flags['no-doc']);
71
+ }
72
+ catch (error) {
73
+ this.error(error instanceof Error ? error.message : 'Failed to fetch package documentation');
74
+ }
75
+ }
76
+ displayMarkdown(content) {
77
+ // Render markdown for terminal display
78
+ const rendered = marked(content);
79
+ // Remove any trailing newlines to avoid extra spacing
80
+ this.log(rendered.trimEnd());
81
+ }
82
+ displayPackage(pkg, noDoc) {
83
+ // Header with package name, version, and visibility
84
+ const visibilityBadge = pkg.visibility === 'PUBLIC'
85
+ ? chalk.green('PUBLIC')
86
+ : chalk.yellow('PRIVATE');
87
+ this.log(chalk.bold.cyan(`📦 ${pkg.name}@${pkg.version}`) + ' ' + visibilityBadge);
88
+ this.log('━'.repeat(50));
89
+ // Description
90
+ if (pkg.description) {
91
+ this.log(chalk.bold('Description:') + ' ' + pkg.description);
92
+ this.log();
93
+ }
94
+ // Repository and commit info
95
+ if (pkg.repository) {
96
+ this.log(chalk.bold('Repository:') + ' ' + chalk.blue.underline(pkg.repository));
97
+ }
98
+ if (pkg.commit) {
99
+ this.log(chalk.bold('Commit:') + ' ' + chalk.gray(pkg.commit));
100
+ }
101
+ if (pkg.sha256) {
102
+ this.log(chalk.bold('SHA256:') + ' ' + chalk.gray(pkg.sha256.slice(0, 16) + '...'));
103
+ }
104
+ // Infrastructure details
105
+ if (pkg.iac || pkg.infra) {
106
+ this.log();
107
+ this.log(`${chalk.bold('Infrastructure:')} ${pkg.iac || 'Unknown'} on ${pkg.infra || 'Unknown'}`);
108
+ }
109
+ // OnDeploy hook
110
+ if (pkg.onDeploy) {
111
+ this.log();
112
+ this.log(`${chalk.bold('On Deploy Hook:')} ${pkg.onDeploy.pkg}@${pkg.onDeploy.version}`);
113
+ }
114
+ // Timestamps
115
+ if (pkg.createdAt || pkg.updatedAt) {
116
+ this.log();
117
+ if (pkg.createdAt) {
118
+ const created = new Date(pkg.createdAt).toLocaleString();
119
+ this.log(`${chalk.bold('Created:')} ${chalk.gray(created)}`);
120
+ }
121
+ if (pkg.updatedAt) {
122
+ const updated = new Date(pkg.updatedAt).toLocaleString();
123
+ this.log(`${chalk.bold('Updated:')} ${chalk.gray(updated)}`);
124
+ }
125
+ }
126
+ // Documentation content
127
+ if (!noDoc && pkg.doc) {
128
+ this.log();
129
+ this.log(chalk.bold.blue('━━━ Documentation ━━━'));
130
+ this.log();
131
+ this.displayMarkdown(pkg.doc);
132
+ }
133
+ else if (!noDoc && !pkg.doc) {
134
+ this.log();
135
+ this.log(chalk.gray('(No documentation available)'));
136
+ }
137
+ }
138
+ parsePackageSpec(spec) {
139
+ const atIndex = spec.lastIndexOf('@');
140
+ // Handle scoped packages like @org/package
141
+ if (atIndex > 0) {
142
+ const possibleVersion = spec.slice(atIndex + 1);
143
+ // Check if what comes after @ looks like a version
144
+ if (/^\d+\.\d+\.\d+/.test(possibleVersion)) {
145
+ return {
146
+ name: spec.slice(0, atIndex),
147
+ version: possibleVersion,
148
+ };
149
+ }
150
+ }
151
+ // No version specified
152
+ return { name: spec };
153
+ }
154
+ }
@@ -71,21 +71,50 @@ export default class Down extends Command {
71
71
  },
72
72
  {
73
73
  async task(ctx) {
74
- let packages = Object.keys(ctx.configOutput.config.packages ?? {});
74
+ // Get packages with their versions from config
75
+ const configPackages = ctx.configOutput.config.packages ?? {};
76
+ let packagesWithVersions = Object.entries(configPackages).map(([name, info]) => ({
77
+ name,
78
+ packageSpec: info.version ? `${name}@${info.version}` : name,
79
+ version: info.version || ''
80
+ }));
75
81
  const backend = await getBackend();
76
82
  const savedStateOutput = await backend.getState({
77
83
  project: ctx.configOutput.config.project,
78
84
  workspace: ctx.workspace,
79
85
  });
80
- let removedPackages = [];
86
+ let removedPackagesWithVersions = [];
81
87
  if (savedStateOutput.found) {
82
- const savedPackages = Object.keys(savedStateOutput.config.packages ?? {});
83
- removedPackages = savedPackages.filter((packageName) => !packages.includes(packageName));
88
+ const savedPackages = savedStateOutput.config.packages ?? {};
89
+ removedPackagesWithVersions = Object.entries(savedPackages)
90
+ .filter(([name]) => !configPackages[name])
91
+ .map(([name, info]) => ({
92
+ name,
93
+ packageSpec: info.version ? `${name}@${info.version}` : name,
94
+ version: info.version || ''
95
+ }));
84
96
  }
97
+ // Handle --select flag with version validation
85
98
  if (flags.select.length > 0) {
86
- packages = packages.filter((packageName) => flags.select.includes(packageName));
99
+ const selectedPackages = [];
100
+ for (const selectedPkg of flags.select) {
101
+ // Parse the selected package to extract name and version
102
+ const [selectedName, selectedVersion] = selectedPkg.split('@');
103
+ // Find the package in the config
104
+ const configPkg = packagesWithVersions.find(pkg => pkg.name === selectedName);
105
+ if (!configPkg) {
106
+ throw new Error(`Package ${selectedName} not found in project`);
107
+ }
108
+ // If user specified a version, validate it matches the installed version
109
+ if (selectedVersion && selectedVersion !== configPkg.version) {
110
+ throw new Error(`Package ${selectedName} version mismatch: installed version is ${configPkg.version || 'unspecified'}, but you specified ${selectedVersion}. ` +
111
+ `Please use 'hereya down -s ${selectedName}' to use the installed version.`);
112
+ }
113
+ selectedPackages.push(configPkg);
114
+ }
115
+ packagesWithVersions = selectedPackages;
87
116
  }
88
- ctx.packages = [...packages, ...removedPackages];
117
+ ctx.packages = [...packagesWithVersions, ...removedPackagesWithVersions];
89
118
  await delay(500);
90
119
  },
91
120
  title: 'Identifying packages to destroy',
@@ -96,7 +125,7 @@ export default class Down extends Command {
96
125
  if (!ctx.packages || ctx.packages.length === 0) {
97
126
  return;
98
127
  }
99
- return task.newListr(ctx.packages.map((packageName) => ({
128
+ return task.newListr(ctx.packages.map((pkg) => ({
100
129
  rendererOptions: {
101
130
  persistentOutput: isDebug(),
102
131
  },
@@ -105,7 +134,7 @@ export default class Down extends Command {
105
134
  const backend = await getBackend();
106
135
  const profile = await getProfileFromWorkspace(backend, ctx.workspace, ctx.configOutput.config.project);
107
136
  const { parameters } = await parameterManager.getPackageParameters({
108
- package: packageName,
137
+ package: pkg.name,
109
138
  profile,
110
139
  projectRootDir,
111
140
  });
@@ -117,7 +146,7 @@ export default class Down extends Command {
117
146
  const destroyOutput = await executor.destroy({
118
147
  isDeploying: flags.deploy,
119
148
  logger: getLogger(task),
120
- package: packageName,
149
+ package: pkg.packageSpec,
121
150
  parameters,
122
151
  project: ctx.configOutput.config.project,
123
152
  projectRootDir,
@@ -128,10 +157,10 @@ export default class Down extends Command {
128
157
  }
129
158
  const { env, metadata } = destroyOutput;
130
159
  const output = ctx.destroyed || [];
131
- output.push({ env, metadata, packageName });
160
+ output.push({ env, metadata, packageName: pkg.name });
132
161
  ctx.destroyed = output;
133
162
  },
134
- title: `Destroying ${packageName}`,
163
+ title: `Destroying ${pkg.name}`,
135
164
  })), { concurrent: true, rendererOptions: { collapseSubtasks: !isDebug() } });
136
165
  },
137
166
  title: `Destroying packages`,