hereya-cli 0.54.1 ā 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.
- package/README.md +94 -36
- package/dist/backend/cloud/cloud-backend.d.ts +5 -1
- package/dist/backend/cloud/cloud-backend.js +214 -0
- package/dist/backend/common.d.ts +62 -0
- package/dist/backend/file.d.ts +5 -1
- package/dist/backend/file.js +24 -0
- package/dist/commands/doc/index.d.ts +17 -0
- package/dist/commands/doc/index.js +154 -0
- package/dist/commands/publish/index.d.ts +32 -0
- package/dist/commands/publish/index.js +239 -0
- package/oclif.manifest.json +92 -1
- package/package.json +5 -1
package/dist/backend/common.d.ts
CHANGED
|
@@ -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
|
+
};
|
package/dist/backend/file.d.ts
CHANGED
|
@@ -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>;
|
package/dist/backend/file.js
CHANGED
|
@@ -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
|
+
}
|