hereya-cli 0.55.0 → 0.56.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.
@@ -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>;
@@ -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) {
@@ -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
+ }
@@ -0,0 +1,32 @@
1
+ import { Command } from '@oclif/core';
2
+ interface HereyarcConfig {
3
+ description: string;
4
+ iac: string;
5
+ infra: string;
6
+ name: string;
7
+ onDeploy?: {
8
+ pkg: string;
9
+ version: string;
10
+ };
11
+ version: string;
12
+ visibility?: 'PRIVATE' | 'private' | 'PUBLIC' | 'public';
13
+ }
14
+ export default class Publish extends Command {
15
+ static description: string;
16
+ static examples: string[];
17
+ static flags: {
18
+ chdir: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
+ };
20
+ calculateGitArchiveSha256(packageDir: string): Promise<string>;
21
+ displayPublishError(reason: string, config: HereyarcConfig): void;
22
+ getGitInfo(packageDir: string): Promise<{
23
+ commit: string;
24
+ repository: string;
25
+ sha256: string;
26
+ }>;
27
+ loadConfig(packageDir: string): Promise<HereyarcConfig>;
28
+ loadReadme(packageDir: string): string | undefined;
29
+ run(): Promise<void>;
30
+ validateConfig(config: HereyarcConfig): void;
31
+ }
32
+ export {};
@@ -0,0 +1,239 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { simpleGit } from 'simple-git';
5
+ import * as yaml from 'yaml';
6
+ import { getBackend } from '../../backend/index.js';
7
+ export default class Publish extends Command {
8
+ static description = 'Publish a package to the Hereya registry';
9
+ static examples = [
10
+ '$ hereya publish',
11
+ '$ hereya publish --chdir=/path/to/package',
12
+ ];
13
+ static flags = {
14
+ chdir: Flags.string({
15
+ description: `
16
+ Directory where the command will be executed.
17
+ If not specified, it defaults to the current working directory.
18
+ `,
19
+ required: false,
20
+ }),
21
+ };
22
+ async calculateGitArchiveSha256(packageDir) {
23
+ const { exec } = await import('node:child_process');
24
+ const { promisify } = await import('node:util');
25
+ const execAsync = promisify(exec);
26
+ try {
27
+ // Create a tar archive of HEAD and calculate its SHA256
28
+ const { stdout } = await execAsync('git archive --format=tar HEAD | shasum -a 256', {
29
+ cwd: packageDir,
30
+ encoding: 'utf8',
31
+ maxBuffer: 50 * 1024 * 1024 // 50MB buffer for large repos
32
+ });
33
+ // The shasum output is "hash -" so we take the first part
34
+ const sha256 = stdout.trim().split(' ')[0];
35
+ if (!sha256 || sha256.length !== 64) {
36
+ this.error('Failed to calculate SHA256 hash of the repository');
37
+ }
38
+ return sha256;
39
+ }
40
+ catch (error) {
41
+ this.error(`Failed to calculate SHA256: ${error instanceof Error ? error.message : String(error)}`);
42
+ }
43
+ }
44
+ displayPublishError(reason, config) {
45
+ this.log('\nāŒ Failed to publish package\n');
46
+ // Handle specific error cases with helpful messages
47
+ if (reason.toLowerCase().includes('already exists')) {
48
+ this.log(`Package ${config.name}@${config.version} already exists in the registry.`);
49
+ this.log('\nPossible solutions:');
50
+ this.log(' • Increment the version number in hereyarc.yaml');
51
+ this.log(' • Use a different package name');
52
+ this.log(' • If you own this package, delete the existing version first');
53
+ }
54
+ else if (reason.toLowerCase().includes('permission')) {
55
+ this.log('You do not have permission to publish this package.');
56
+ this.log('\nPossible reasons:');
57
+ this.log(' • The package namespace is owned by another user/organization');
58
+ this.log(' • Your account does not have publishing privileges');
59
+ this.log(' • Contact the namespace owner for access');
60
+ }
61
+ else if (reason.toLowerCase().includes('authentication')) {
62
+ this.log('Authentication failed.');
63
+ this.log('\nPlease run: hereya login');
64
+ }
65
+ else if (reason.includes('\n')) {
66
+ // Multiple validation errors
67
+ this.log('Validation errors:');
68
+ const errors = reason.split('\n');
69
+ for (const error of errors) {
70
+ this.log(` • ${error}`);
71
+ }
72
+ }
73
+ else {
74
+ // Single error message
75
+ this.log(`Error: ${reason}`);
76
+ }
77
+ this.log(''); // Empty line for spacing
78
+ this.error('Package publication failed', { exit: 1 });
79
+ }
80
+ async getGitInfo(packageDir) {
81
+ const git = simpleGit(packageDir);
82
+ // Check if it's a git repository
83
+ const isRepo = await git.checkIsRepo();
84
+ if (!isRepo) {
85
+ this.error('The current directory is not a git repository');
86
+ }
87
+ // Get current commit SHA
88
+ const commit = await git.revparse(['HEAD']);
89
+ if (!commit) {
90
+ this.error('Could not get current commit SHA');
91
+ }
92
+ // Get repository URL
93
+ const remotes = await git.getRemotes(true);
94
+ if (remotes.length === 0) {
95
+ this.error('No git remotes found');
96
+ }
97
+ // Use origin remote by default, or the first one available
98
+ const origin = remotes.find((r) => r.name === 'origin') || remotes[0];
99
+ if (!origin || !origin.refs.fetch) {
100
+ this.error('Could not find repository URL');
101
+ }
102
+ let repository = origin.refs.fetch;
103
+ // Convert SSH URLs to HTTPS
104
+ if (repository.startsWith('git@github.com:')) {
105
+ repository = repository.replace('git@github.com:', 'https://github.com/');
106
+ }
107
+ // Remove .git suffix if present
108
+ if (repository.endsWith('.git')) {
109
+ repository = repository.slice(0, -4);
110
+ }
111
+ // Calculate SHA256 of the git archive
112
+ const sha256 = await this.calculateGitArchiveSha256(packageDir);
113
+ return { commit: commit.trim(), repository, sha256 };
114
+ }
115
+ async loadConfig(packageDir) {
116
+ // Look for hereyarc.yaml or hereyarc.yml
117
+ let configPath = join(packageDir, 'hereyarc.yaml');
118
+ if (!existsSync(configPath)) {
119
+ configPath = join(packageDir, 'hereyarc.yml');
120
+ if (!existsSync(configPath)) {
121
+ this.error('No hereyarc.yaml or hereyarc.yml file found in the current directory');
122
+ }
123
+ }
124
+ // Parse the configuration file
125
+ const configContent = readFileSync(configPath, 'utf8');
126
+ const config = yaml.parse(configContent);
127
+ // Validate required fields
128
+ this.validateConfig(config);
129
+ return config;
130
+ }
131
+ loadReadme(packageDir) {
132
+ // Check for README files in various formats
133
+ const readmeVariants = [
134
+ 'README.md',
135
+ 'readme.md',
136
+ 'Readme.md',
137
+ 'README.MD',
138
+ 'README',
139
+ 'readme',
140
+ 'README.txt',
141
+ 'readme.txt',
142
+ ];
143
+ for (const variant of readmeVariants) {
144
+ const readmePath = join(packageDir, variant);
145
+ if (existsSync(readmePath)) {
146
+ try {
147
+ const content = readFileSync(readmePath, 'utf8');
148
+ if (content.trim()) {
149
+ this.log(` README: Found ${variant}`);
150
+ return content;
151
+ }
152
+ }
153
+ catch {
154
+ // Ignore read errors and try next variant
155
+ continue;
156
+ }
157
+ }
158
+ }
159
+ this.log(' README: Not found (optional)');
160
+ return undefined;
161
+ }
162
+ async run() {
163
+ const { flags } = await this.parse(Publish);
164
+ const packageDir = flags.chdir || process.cwd();
165
+ try {
166
+ // Load and validate configuration
167
+ const config = await this.loadConfig(packageDir);
168
+ // Display package info
169
+ this.log(`\nšŸ“¦ Preparing package ${config.name}@${config.version}`);
170
+ this.log(` Description: ${config.description}`);
171
+ this.log(` Infrastructure: ${config.infra} (${config.iac})`);
172
+ if (config.visibility) {
173
+ this.log(` Visibility: ${config.visibility}`);
174
+ }
175
+ // Get git information (including SHA256 of git archive)
176
+ this.log('\nšŸ” Analyzing repository...');
177
+ const gitInfo = await this.getGitInfo(packageDir);
178
+ const { commit, repository, sha256 } = gitInfo;
179
+ this.log(` Repository: ${repository}`);
180
+ this.log(` Commit: ${commit.slice(0, 8)}`);
181
+ this.log(` SHA256: ${sha256.slice(0, 16)}...`);
182
+ // Load README content if available
183
+ const readmeContent = this.loadReadme(packageDir);
184
+ // Load backend configuration and check if it's cloud backend
185
+ this.log('\nšŸ” Checking authentication...');
186
+ const backend = await getBackend();
187
+ if (!backend.publishPackage) {
188
+ this.log('\nāŒ Authentication failed\n');
189
+ this.error('Publishing packages is only supported with the cloud backend. Please run `hereya login` first.');
190
+ }
191
+ this.log(' āœ“ Authenticated with Hereya Cloud');
192
+ // Prepare the publish input (normalize visibility to uppercase)
193
+ const publishInput = {
194
+ commit: commit.trim(),
195
+ description: config.description,
196
+ doc: readmeContent,
197
+ iac: config.iac,
198
+ infra: config.infra,
199
+ name: config.name,
200
+ onDeployPkg: config.onDeploy?.pkg,
201
+ onDeployVersion: config.onDeploy?.version,
202
+ repository,
203
+ sha256,
204
+ version: config.version,
205
+ visibility: config.visibility?.toUpperCase(),
206
+ };
207
+ this.log('\nšŸ“¤ Publishing to registry...');
208
+ // Publish the package
209
+ const result = await backend.publishPackage(publishInput);
210
+ if (!result.success) {
211
+ this.displayPublishError(result.reason, config);
212
+ return;
213
+ }
214
+ this.log(`\nāœ… Successfully published ${result.package.name}@${result.package.version}`);
215
+ this.log(` Package ID: ${result.package.id}`);
216
+ this.log('');
217
+ }
218
+ catch (error) {
219
+ this.error(error instanceof Error ? error.message : String(error));
220
+ }
221
+ }
222
+ validateConfig(config) {
223
+ if (!config.name) {
224
+ this.error('Missing required field "name" in hereyarc.yaml');
225
+ }
226
+ if (!config.version) {
227
+ this.error('Missing required field "version" in hereyarc.yaml');
228
+ }
229
+ if (!config.description) {
230
+ this.error('Missing required field "description" in hereyarc.yaml');
231
+ }
232
+ if (!config.iac) {
233
+ this.error('Missing required field "iac" in hereyarc.yaml');
234
+ }
235
+ if (!config.infra) {
236
+ this.error('Missing required field "infra" in hereyarc.yaml');
237
+ }
238
+ }
239
+ }