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.
- package/README.md +94 -36
- package/dist/backend/cloud/cloud-backend.d.ts +5 -1
- package/dist/backend/cloud/cloud-backend.js +208 -0
- package/dist/backend/common.d.ts +64 -2
- package/dist/backend/file.d.ts +5 -1
- package/dist/backend/file.js +24 -0
- package/dist/commands/add/index.js +2 -1
- package/dist/commands/deploy/index.js +50 -24
- package/dist/commands/doc/index.d.ts +17 -0
- package/dist/commands/doc/index.js +154 -0
- package/dist/commands/down/index.js +40 -11
- package/dist/commands/import/index.js +2 -1
- package/dist/commands/publish/index.d.ts +32 -0
- package/dist/commands/publish/index.js +239 -0
- package/dist/commands/remove/index.js +16 -4
- package/dist/commands/undeploy/index.js +45 -17
- package/dist/commands/up/index.js +36 -11
- package/dist/commands/workspace/install/index.js +1 -1
- package/dist/commands/workspace/uninstall/index.js +18 -5
- package/dist/executor/interface.d.ts +5 -0
- package/dist/executor/local.js +3 -3
- package/dist/infrastructure/index.d.ts +2 -0
- package/dist/infrastructure/index.js +8 -8
- package/dist/lib/config/common.d.ts +1 -0
- package/dist/lib/config/simple.js +2 -2
- package/dist/lib/package/cloud.d.ts +19 -0
- package/dist/lib/package/cloud.js +226 -0
- package/dist/lib/package/common.d.ts +1 -0
- package/dist/lib/package/github.d.ts +1 -1
- package/dist/lib/package/github.js +33 -6
- package/dist/lib/package/index.d.ts +9 -4
- package/dist/lib/package/index.js +178 -35
- package/dist/lib/package/local.js +1 -0
- package/oclif.manifest.json +92 -1
- package/package.json +8 -1
package/dist/executor/local.js
CHANGED
|
@@ -17,11 +17,11 @@ export class LocalExecutor {
|
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
19
|
async import(input) {
|
|
20
|
-
const resolvePackageOutput = await resolvePackage({ package: input.package, projectRootDir: input.projectRootDir });
|
|
20
|
+
const resolvePackageOutput = await resolvePackage({ logger: input.logger, package: input.package, projectRootDir: input.projectRootDir });
|
|
21
21
|
if (!resolvePackageOutput.found) {
|
|
22
22
|
return { reason: resolvePackageOutput.reason, success: false };
|
|
23
23
|
}
|
|
24
|
-
const { canonicalName, metadata, pkgName } = resolvePackageOutput;
|
|
24
|
+
const { canonicalName, metadata, pkgName, version } = resolvePackageOutput;
|
|
25
25
|
const backend = await getBackend();
|
|
26
26
|
const id$ = await backend.getProvisioningId({
|
|
27
27
|
logicalId: getProvisioningLogicalId({ pkg: pkgName, project: input.project, workspace: input.workspace }),
|
|
@@ -46,7 +46,7 @@ export class LocalExecutor {
|
|
|
46
46
|
if (!uploadStateFileOutput.success) {
|
|
47
47
|
return { reason: uploadStateFileOutput.reason, success: false };
|
|
48
48
|
}
|
|
49
|
-
return { metadata, success: true };
|
|
49
|
+
return { metadata, pkgName, success: true, version };
|
|
50
50
|
}
|
|
51
51
|
async provision(input) {
|
|
52
52
|
const getWorkspaceEnvOutput = await this.getWorkspaceEnv({
|
|
@@ -29,17 +29,17 @@ export function getInfrastructure(input) {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
export async function destroyPackage(input) {
|
|
32
|
-
const resolvePackageOutput = await resolvePackage({ isDeploying: input.isDeploying, package: input.package, projectRootDir: input.projectRootDir });
|
|
32
|
+
const resolvePackageOutput = await resolvePackage({ isDeploying: input.isDeploying, logger: input.logger, package: input.package, projectRootDir: input.projectRootDir });
|
|
33
33
|
if (!resolvePackageOutput.found) {
|
|
34
34
|
return { reason: resolvePackageOutput.reason, success: false };
|
|
35
35
|
}
|
|
36
|
-
const { canonicalName, metadata, packageUri, pkgName } = resolvePackageOutput;
|
|
36
|
+
const { canonicalName, metadata, packageUri, pkgName, version } = resolvePackageOutput;
|
|
37
37
|
const infrastructure$ = getInfrastructure({ type: metadata.infra });
|
|
38
38
|
if (!infrastructure$.supported) {
|
|
39
39
|
return { reason: infrastructure$.reason, success: false };
|
|
40
40
|
}
|
|
41
41
|
if (metadata.deploy && input.skipDeploy) {
|
|
42
|
-
return { env: {}, metadata, success: true };
|
|
42
|
+
return { env: {}, metadata, pkgName, success: true, version: version || '' };
|
|
43
43
|
}
|
|
44
44
|
const { infrastructure } = infrastructure$;
|
|
45
45
|
const backend = await getBackend();
|
|
@@ -92,20 +92,20 @@ export async function destroyPackage(input) {
|
|
|
92
92
|
success: false,
|
|
93
93
|
};
|
|
94
94
|
}
|
|
95
|
-
return { env: destroyOutput.env, metadata, success: true };
|
|
95
|
+
return { env: destroyOutput.env, metadata, pkgName, success: true, version: version || '' };
|
|
96
96
|
}
|
|
97
97
|
export async function provisionPackage(input) {
|
|
98
|
-
const resolvePackageOutput = await resolvePackage({ isDeploying: input.isDeploying, package: input.package, projectRootDir: input.projectRootDir });
|
|
98
|
+
const resolvePackageOutput = await resolvePackage({ isDeploying: input.isDeploying, logger: input.logger, package: input.package, projectRootDir: input.projectRootDir });
|
|
99
99
|
if (!resolvePackageOutput.found) {
|
|
100
100
|
return { reason: resolvePackageOutput.reason, success: false };
|
|
101
101
|
}
|
|
102
|
-
const { canonicalName, metadata, packageUri, pkgName } = resolvePackageOutput;
|
|
102
|
+
const { canonicalName, metadata, packageUri, pkgName, version } = resolvePackageOutput;
|
|
103
103
|
const infrastructure$ = getInfrastructure({ type: metadata.infra });
|
|
104
104
|
if (!infrastructure$.supported) {
|
|
105
105
|
return { reason: infrastructure$.reason, success: false };
|
|
106
106
|
}
|
|
107
107
|
if (metadata.deploy && input.skipDeploy) {
|
|
108
|
-
return { env: {}, metadata, success: true };
|
|
108
|
+
return { env: {}, metadata, pkgName, success: true, version: version || '' };
|
|
109
109
|
}
|
|
110
110
|
const dependencies = metadata.dependencies ?? {};
|
|
111
111
|
const depsOutput = await Promise.all(Object.entries(dependencies).map(async ([depName]) => provisionPackage({
|
|
@@ -167,7 +167,7 @@ export async function provisionPackage(input) {
|
|
|
167
167
|
if (!provisionOutput.success) {
|
|
168
168
|
return { reason: provisionOutput.reason, success: false };
|
|
169
169
|
}
|
|
170
|
-
return { env: provisionOutput.env, metadata, success: true };
|
|
170
|
+
return { env: provisionOutput.env, metadata, pkgName, success: true, version: version || '' };
|
|
171
171
|
}
|
|
172
172
|
export function getProvisioningLogicalId({ pkg, project, workspace }) {
|
|
173
173
|
const projectName = project ? stripOrgPrefix(project) : project;
|
|
@@ -11,7 +11,7 @@ export class SimpleConfigManager {
|
|
|
11
11
|
deploy: {
|
|
12
12
|
...config.deploy,
|
|
13
13
|
[input.package]: {
|
|
14
|
-
version: '',
|
|
14
|
+
version: input.version || '',
|
|
15
15
|
},
|
|
16
16
|
},
|
|
17
17
|
}
|
|
@@ -19,7 +19,7 @@ export class SimpleConfigManager {
|
|
|
19
19
|
packages: {
|
|
20
20
|
...config.packages,
|
|
21
21
|
[input.package]: {
|
|
22
|
-
version: '',
|
|
22
|
+
version: input.version || '',
|
|
23
23
|
...(input.metadata.onDeploy
|
|
24
24
|
? {
|
|
25
25
|
onDeploy: {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Backend, RegistryPackage } from '../../backend/common.js';
|
|
2
|
+
import type { Logger } from '../log.js';
|
|
3
|
+
import type { GetRepoContentInput, GetRepoContentOutput, PackageManager } from './common.js';
|
|
4
|
+
export interface CloudPackageInfo {
|
|
5
|
+
name: string;
|
|
6
|
+
registryPackage: RegistryPackage;
|
|
7
|
+
version: string;
|
|
8
|
+
}
|
|
9
|
+
export declare class CloudPackageManager implements PackageManager {
|
|
10
|
+
private readonly backend;
|
|
11
|
+
private readonly logger?;
|
|
12
|
+
constructor(backend: Backend, logger?: Logger | undefined);
|
|
13
|
+
downloadAndValidatePackage(registryPackage: RegistryPackage, destPath: string): Promise<string>;
|
|
14
|
+
downloadPackage(pkgUrl: string, destPath: string): Promise<string>;
|
|
15
|
+
getFileFromRepository(repoUrl: string, commit: string | undefined, filePath: string, sha256?: string): Promise<GetRepoContentOutput>;
|
|
16
|
+
getRepoContent({ owner, path: filePath, repo, version }: GetRepoContentInput): Promise<GetRepoContentOutput>;
|
|
17
|
+
resolvePackage(packageSpec: string): Promise<CloudPackageInfo | null>;
|
|
18
|
+
private parsePackageSpec;
|
|
19
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileExists, isNotEmpty } from '../filesystem.js';
|
|
6
|
+
export class CloudPackageManager {
|
|
7
|
+
backend;
|
|
8
|
+
logger;
|
|
9
|
+
constructor(backend, logger) {
|
|
10
|
+
this.backend = backend;
|
|
11
|
+
this.logger = logger;
|
|
12
|
+
}
|
|
13
|
+
async downloadAndValidatePackage(registryPackage, destPath) {
|
|
14
|
+
if (!registryPackage.repository) {
|
|
15
|
+
throw new Error(`No repository URL for package ${registryPackage.name}`);
|
|
16
|
+
}
|
|
17
|
+
if (await isNotEmpty(destPath)) {
|
|
18
|
+
return destPath;
|
|
19
|
+
}
|
|
20
|
+
await fs.mkdir(destPath, { recursive: true });
|
|
21
|
+
const tmpFolder = path.join(os.homedir(), '.hereya', 'downloads', randomUUID());
|
|
22
|
+
try {
|
|
23
|
+
// Use simple-git to clone the repository at specific commit
|
|
24
|
+
const { simpleGit } = await import('simple-git');
|
|
25
|
+
const git = simpleGit();
|
|
26
|
+
if (registryPackage.commit) {
|
|
27
|
+
// Clone repository and checkout specific commit
|
|
28
|
+
await git.clone(registryPackage.repository, tmpFolder, ['--no-checkout']);
|
|
29
|
+
const repoGit = simpleGit(tmpFolder);
|
|
30
|
+
await repoGit.checkout(registryPackage.commit);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
// Clone repository at default branch
|
|
34
|
+
await git.clone(registryPackage.repository, tmpFolder, ['--depth=1']);
|
|
35
|
+
}
|
|
36
|
+
// If checksum is provided, validate it
|
|
37
|
+
if (registryPackage.sha256) {
|
|
38
|
+
// Create tar archive from the cloned repository for checksum validation
|
|
39
|
+
const repoGit = simpleGit(tmpFolder);
|
|
40
|
+
const archiveBuffer = await repoGit.raw(['archive', '--format=tar', registryPackage.commit || 'HEAD']);
|
|
41
|
+
const hash = createHash('sha256');
|
|
42
|
+
hash.update(archiveBuffer);
|
|
43
|
+
const actualChecksum = hash.digest('hex');
|
|
44
|
+
if (actualChecksum !== registryPackage.sha256) {
|
|
45
|
+
throw new Error(`Checksum mismatch for package ${registryPackage.name}@${registryPackage.version}. Expected: ${registryPackage.sha256}, Got: ${actualChecksum}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Move files from temp folder to destination
|
|
49
|
+
const files = await fs.readdir(tmpFolder);
|
|
50
|
+
await Promise.all(files.map(async (file) => {
|
|
51
|
+
if (file !== '.git') {
|
|
52
|
+
await fs.rename(path.join(tmpFolder, file), path.join(destPath, file));
|
|
53
|
+
}
|
|
54
|
+
}));
|
|
55
|
+
// Clean up temp folder
|
|
56
|
+
await fs.rm(tmpFolder, { force: true, recursive: true });
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
// Clean up on error
|
|
60
|
+
try {
|
|
61
|
+
await fs.rm(tmpFolder, { force: true, recursive: true });
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// Ignore cleanup errors
|
|
65
|
+
}
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
return destPath;
|
|
69
|
+
}
|
|
70
|
+
async downloadPackage(pkgUrl, destPath) {
|
|
71
|
+
// Parse the package URL to extract repository and commit
|
|
72
|
+
// Format: <repository_url>#<commit_sha>
|
|
73
|
+
const [repoUrl, commitSha] = pkgUrl.split('#');
|
|
74
|
+
// For downloading packages during provision/deploy, we use downloadAndValidatePackage
|
|
75
|
+
// but we don't have SHA256 here. The validation happens in getRepoContent instead
|
|
76
|
+
// where we have access to the full registry metadata
|
|
77
|
+
const registryPackage = {
|
|
78
|
+
commit: commitSha,
|
|
79
|
+
description: '',
|
|
80
|
+
id: '',
|
|
81
|
+
name: '',
|
|
82
|
+
repository: repoUrl,
|
|
83
|
+
version: '',
|
|
84
|
+
// SHA256 not available here - validation happens in getRepoContent
|
|
85
|
+
};
|
|
86
|
+
return this.downloadAndValidatePackage(registryPackage, destPath);
|
|
87
|
+
}
|
|
88
|
+
async getFileFromRepository(repoUrl, commit, filePath, sha256) {
|
|
89
|
+
this.logger?.debug(`getFileFromRepository: trying to get ${filePath} from ${repoUrl}${commit ? `@${commit}` : ''}`);
|
|
90
|
+
const tmpFolder = path.join(os.homedir(), '.hereya', 'downloads', randomUUID());
|
|
91
|
+
try {
|
|
92
|
+
const { simpleGit } = await import('simple-git');
|
|
93
|
+
const git = simpleGit();
|
|
94
|
+
this.logger?.debug(`getFileFromRepository: cloning ${repoUrl}...`);
|
|
95
|
+
if (commit) {
|
|
96
|
+
// Clone repository and checkout specific commit
|
|
97
|
+
await git.clone(repoUrl, tmpFolder, ['--no-checkout']);
|
|
98
|
+
const repoGit = simpleGit(tmpFolder);
|
|
99
|
+
await repoGit.checkout(commit);
|
|
100
|
+
// Validate checksum if provided (for cloud packages)
|
|
101
|
+
if (sha256) {
|
|
102
|
+
this.logger?.debug('Validating package checksum...');
|
|
103
|
+
const archiveBuffer = await repoGit.raw(['archive', '--format=tar', commit]);
|
|
104
|
+
const hash = createHash('sha256');
|
|
105
|
+
hash.update(archiveBuffer);
|
|
106
|
+
const actualChecksum = hash.digest('hex');
|
|
107
|
+
if (actualChecksum !== sha256) {
|
|
108
|
+
await fs.rm(tmpFolder, { recursive: true });
|
|
109
|
+
return {
|
|
110
|
+
found: false,
|
|
111
|
+
reason: `Checksum validation failed for repository ${repoUrl}@${commit}. Expected: ${sha256}, Got: ${actualChecksum}. This may indicate the package has been tampered with or corrupted.`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
this.logger?.debug('Checksum validation successful');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// Clone repository at default branch
|
|
119
|
+
await git.clone(repoUrl, tmpFolder, ['--depth=1']);
|
|
120
|
+
}
|
|
121
|
+
const fullFilePath = path.join(tmpFolder, filePath);
|
|
122
|
+
this.logger?.debug(`getFileFromRepository: checking if file exists at ${fullFilePath}`);
|
|
123
|
+
if (await fileExists(fullFilePath)) {
|
|
124
|
+
this.logger?.debug(`getFileFromRepository: file found, reading content...`);
|
|
125
|
+
const content = await fs.readFile(fullFilePath, 'utf8');
|
|
126
|
+
await fs.rm(tmpFolder, { recursive: true });
|
|
127
|
+
return {
|
|
128
|
+
content,
|
|
129
|
+
found: true,
|
|
130
|
+
pkgUrl: commit ? `${repoUrl}#${commit}` : repoUrl,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
// List files to debug what's actually in the package
|
|
134
|
+
const files = await fs.readdir(tmpFolder);
|
|
135
|
+
this.logger?.debug(`getFileFromRepository: file ${filePath} not found. Available files: ${files.join(', ')}`);
|
|
136
|
+
await fs.rm(tmpFolder, { recursive: true });
|
|
137
|
+
return {
|
|
138
|
+
found: false,
|
|
139
|
+
reason: `File '${filePath}' not found in repository '${repoUrl}'. Available files: ${files.join(', ')}. This package may not be compatible with hereya or may be missing required metadata files.`,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
// Clean up on error
|
|
144
|
+
try {
|
|
145
|
+
await fs.rm(tmpFolder, { recursive: true });
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// Ignore cleanup errors
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
found: false,
|
|
152
|
+
reason: `Failed to download repository '${repoUrl}': ${error.message}. This could be due to network issues, repository access problems, or authentication issues.`,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async getRepoContent({ owner, path: filePath, repo, version }) {
|
|
157
|
+
// For cloud packages, owner/repo represent the package name in the registry
|
|
158
|
+
// The actual repository URL comes from the registry metadata
|
|
159
|
+
// Reconstruct package name from owner/repo
|
|
160
|
+
// - If owner is empty → package name is just 'repo' (simple name)
|
|
161
|
+
// - Otherwise → package name is 'owner/repo' (org/name format)
|
|
162
|
+
const packageName = owner ? `${owner}/${repo}` : repo;
|
|
163
|
+
// Include version if provided
|
|
164
|
+
const packageSpec = version ? `${packageName}@${version}` : packageName;
|
|
165
|
+
this.logger?.debug(`getRepoContent: trying to get ${filePath} from registry package '${packageSpec}'`);
|
|
166
|
+
// Resolve package from registry to get repository URL
|
|
167
|
+
const packageInfo = await this.resolvePackage(packageSpec);
|
|
168
|
+
if (!packageInfo) {
|
|
169
|
+
this.logger?.debug(`getRepoContent: package '${packageName}' not found in registry`);
|
|
170
|
+
return {
|
|
171
|
+
found: false,
|
|
172
|
+
reason: `Package '${packageName}' not found in registry`,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
// Now we have the actual repository URL and commit from registry
|
|
176
|
+
const repoUrl = packageInfo.registryPackage.repository;
|
|
177
|
+
const { commit } = packageInfo.registryPackage;
|
|
178
|
+
if (!repoUrl) {
|
|
179
|
+
return {
|
|
180
|
+
found: false,
|
|
181
|
+
reason: `Package '${packageName}' in registry has no repository URL`,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
this.logger?.debug(`getRepoContent: downloading from ${repoUrl}${commit ? `@${commit}` : ''}`);
|
|
185
|
+
// Use the getFileFromRepository method to download from the actual repository
|
|
186
|
+
// Pass SHA256 for checksum validation if available
|
|
187
|
+
return this.getFileFromRepository(repoUrl, commit, filePath, packageInfo.registryPackage.sha256);
|
|
188
|
+
}
|
|
189
|
+
async resolvePackage(packageSpec) {
|
|
190
|
+
// Parse package specification: name, org/name, name@version, or org/name@version
|
|
191
|
+
const { packageName, version } = this.parsePackageSpec(packageSpec);
|
|
192
|
+
try {
|
|
193
|
+
// Try to get package from registry
|
|
194
|
+
// Registry API accepts both 'name' and 'org/name' formats
|
|
195
|
+
const result = version
|
|
196
|
+
? await this.backend.getPackageByVersion(packageName, version)
|
|
197
|
+
: await this.backend.getPackageLatest(packageName);
|
|
198
|
+
if (result.success && result.package) {
|
|
199
|
+
return {
|
|
200
|
+
name: packageName,
|
|
201
|
+
registryPackage: result.package,
|
|
202
|
+
version: result.package.version,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
// Package not found in registry or API error
|
|
206
|
+
if (!result.success) {
|
|
207
|
+
this.logger?.debug(`Registry API returned error for ${packageName}: ${result.reason}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
// Network error or other exception
|
|
212
|
+
this.logger?.debug(`Registry API exception for ${packageName}: ${error.message}`);
|
|
213
|
+
}
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
parsePackageSpec(spec) {
|
|
217
|
+
// Parse package specification
|
|
218
|
+
// Formats: name, org/name, name@version, org/name@version
|
|
219
|
+
const match = spec.match(/^([^@]+)(?:@(.+))?$/);
|
|
220
|
+
if (!match) {
|
|
221
|
+
return { packageName: spec };
|
|
222
|
+
}
|
|
223
|
+
const [, packageName, version] = match;
|
|
224
|
+
return { packageName, version };
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -3,5 +3,5 @@ export declare class GitHubPackageManager implements PackageManager {
|
|
|
3
3
|
private readonly registryUrl;
|
|
4
4
|
constructor(registryUrl?: string);
|
|
5
5
|
downloadPackage(pkgUrl: string, destPath: string): Promise<string>;
|
|
6
|
-
getRepoContent({ owner, path: filePath, repo }: GetRepoContentInput): Promise<GetRepoContentOutput>;
|
|
6
|
+
getRepoContent({ owner, path: filePath, repo, version }: GetRepoContentInput): Promise<GetRepoContentOutput>;
|
|
7
7
|
}
|
|
@@ -16,30 +16,57 @@ export class GitHubPackageManager {
|
|
|
16
16
|
await fs.mkdir(destPath, { recursive: true });
|
|
17
17
|
// Initialize simple-git
|
|
18
18
|
const git = simpleGit();
|
|
19
|
-
//
|
|
20
|
-
|
|
19
|
+
// Parse version from URL if present (format: url#version)
|
|
20
|
+
const [repoUrl, version] = pkgUrl.split('#');
|
|
21
|
+
if (version) {
|
|
22
|
+
// Clone repository and checkout specific version tag
|
|
23
|
+
await git.clone(repoUrl, destPath, ['--no-checkout']);
|
|
24
|
+
const repoGit = simpleGit(destPath);
|
|
25
|
+
try {
|
|
26
|
+
// Try to checkout the version as a tag (e.g., v1.2.3 or 1.2.3)
|
|
27
|
+
await repoGit.checkout(`v${version}`);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
try {
|
|
31
|
+
// If v-prefixed tag doesn't exist, try without prefix
|
|
32
|
+
await repoGit.checkout(version);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// If neither tag exists, fail with clear error
|
|
36
|
+
throw new Error(`Version '${version}' not found in repository ${repoUrl}. Available tags can be found at ${repoUrl}/tags`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// Clone repository at default branch
|
|
42
|
+
await git.clone(pkgUrl, destPath, ['--depth=1']);
|
|
43
|
+
}
|
|
21
44
|
return destPath;
|
|
22
45
|
}
|
|
23
|
-
async getRepoContent({ owner, path: filePath, repo }) {
|
|
46
|
+
async getRepoContent({ owner, path: filePath, repo, version }) {
|
|
24
47
|
const pkgUrl = `${this.registryUrl}/${owner}/${repo}`;
|
|
25
48
|
const tmpFolder = path.join(os.homedir(), '.hereya', 'downloads', randomUUID());
|
|
26
49
|
try {
|
|
27
|
-
|
|
50
|
+
// Build URL with version if provided
|
|
51
|
+
const versionedPkgUrl = version ? `${pkgUrl}#${version}` : pkgUrl;
|
|
52
|
+
const destPath = await this.downloadPackage(versionedPkgUrl, tmpFolder);
|
|
28
53
|
if (await fileExists(path.join(destPath, filePath))) {
|
|
29
54
|
const content = await fs.readFile(path.join(destPath, filePath), 'utf8');
|
|
30
55
|
// remove the tmp folder
|
|
31
56
|
await fs.rm(destPath, { recursive: true });
|
|
57
|
+
// Include version in the pkgUrl if provided
|
|
58
|
+
const finalPkgUrl = version ? `${pkgUrl}#${version}` : pkgUrl;
|
|
32
59
|
return {
|
|
33
60
|
content,
|
|
34
61
|
found: true,
|
|
35
|
-
pkgUrl,
|
|
62
|
+
pkgUrl: finalPkgUrl,
|
|
36
63
|
};
|
|
37
64
|
}
|
|
38
65
|
// remove the tmp folder
|
|
39
66
|
await fs.rm(destPath, { recursive: true });
|
|
40
67
|
return {
|
|
41
68
|
found: false,
|
|
42
|
-
reason: `File ${filePath} not found in ${pkgUrl}`,
|
|
69
|
+
reason: `File ${filePath} not found in ${pkgUrl}${version ? ` at version ${version}` : ''}`,
|
|
43
70
|
};
|
|
44
71
|
}
|
|
45
72
|
catch (error) {
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { Backend } from '../../backend/common.js';
|
|
2
3
|
import { IacType } from '../../iac/common.js';
|
|
3
4
|
import { InfrastructureType } from '../../infrastructure/common.js';
|
|
5
|
+
import { Logger } from '../log.js';
|
|
4
6
|
import { PackageManager } from './common.js';
|
|
5
7
|
import { LocalPackageManager } from './local.js';
|
|
6
|
-
export declare const
|
|
8
|
+
export declare const githubPackageManager: PackageManager;
|
|
7
9
|
export declare const localPackageManager: LocalPackageManager;
|
|
8
|
-
export declare function getPackageManager(protocol: string): PackageManager
|
|
10
|
+
export declare function getPackageManager(protocol: string, logger?: Logger): Promise<PackageManager>;
|
|
9
11
|
export declare function resolvePackage(input: ResolvePackageInput): Promise<ResolvePackageOutput>;
|
|
10
12
|
export declare function getPackageCanonicalName(packageName: string): string;
|
|
11
13
|
export declare function downloadPackage(pkgUrl: string, destPath: string): Promise<string>;
|
|
12
14
|
export type ResolvePackageInput = {
|
|
15
|
+
backend?: Backend;
|
|
13
16
|
isDeploying?: boolean;
|
|
17
|
+
logger?: Logger;
|
|
14
18
|
package: string;
|
|
15
19
|
projectRootDir?: string;
|
|
16
20
|
};
|
|
@@ -20,6 +24,7 @@ export type ResolvePackageOutput = {
|
|
|
20
24
|
metadata: z.infer<typeof PackageMetadata>;
|
|
21
25
|
packageUri: string;
|
|
22
26
|
pkgName: string;
|
|
27
|
+
version?: string;
|
|
23
28
|
} | {
|
|
24
29
|
found: false;
|
|
25
30
|
reason: string;
|
|
@@ -41,8 +46,8 @@ export declare const PackageMetadata: z.ZodObject<{
|
|
|
41
46
|
}>>;
|
|
42
47
|
originalInfra: z.ZodOptional<z.ZodNativeEnum<typeof InfrastructureType>>;
|
|
43
48
|
}, "strip", z.ZodTypeAny, {
|
|
44
|
-
iac: IacType;
|
|
45
49
|
infra: InfrastructureType;
|
|
50
|
+
iac: IacType;
|
|
46
51
|
dependencies?: Record<string, string> | undefined;
|
|
47
52
|
deploy?: boolean | undefined;
|
|
48
53
|
onDeploy?: {
|
|
@@ -51,8 +56,8 @@ export declare const PackageMetadata: z.ZodObject<{
|
|
|
51
56
|
} | undefined;
|
|
52
57
|
originalInfra?: InfrastructureType | undefined;
|
|
53
58
|
}, {
|
|
54
|
-
iac: IacType;
|
|
55
59
|
infra: InfrastructureType;
|
|
60
|
+
iac: IacType;
|
|
56
61
|
dependencies?: Record<string, string> | undefined;
|
|
57
62
|
deploy?: boolean | undefined;
|
|
58
63
|
onDeploy?: {
|