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
|
@@ -135,8 +135,9 @@ export default class Import extends Command {
|
|
|
135
135
|
const configManager = getConfigManager();
|
|
136
136
|
await configManager.addPackage({
|
|
137
137
|
metadata: ctx.importOutput.metadata,
|
|
138
|
-
package: ctx.
|
|
138
|
+
package: ctx.importOutput.pkgName,
|
|
139
139
|
projectRootDir,
|
|
140
|
+
version: ctx.importOutput.version,
|
|
140
141
|
});
|
|
141
142
|
await delay(500);
|
|
142
143
|
},
|
|
@@ -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
|
+
}
|
|
@@ -68,9 +68,21 @@ export default class Remove extends Command {
|
|
|
68
68
|
throw new Error(validation.message);
|
|
69
69
|
}
|
|
70
70
|
const { config } = loadConfigOutput;
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
// Parse package name to extract name and version
|
|
72
|
+
const [packageNameWithoutVersion, userSpecifiedVersion] = ctx.package.split('@');
|
|
73
|
+
// Check if package exists in config (using clean name without version)
|
|
74
|
+
const packageInfo = config.packages?.[packageNameWithoutVersion] || config.deploy?.[packageNameWithoutVersion];
|
|
75
|
+
if (!packageInfo) {
|
|
76
|
+
throw new Error(`Package ${packageNameWithoutVersion} not found in the project.`);
|
|
73
77
|
}
|
|
78
|
+
// Get the installed version from config
|
|
79
|
+
const installedVersion = packageInfo.version || '';
|
|
80
|
+
// If user specified a version, validate it matches the installed version
|
|
81
|
+
if (userSpecifiedVersion && userSpecifiedVersion !== installedVersion) {
|
|
82
|
+
throw new Error(`Package ${packageNameWithoutVersion} version mismatch: installed version is ${installedVersion || 'unspecified'}, but you specified ${userSpecifiedVersion}. ` +
|
|
83
|
+
`Please use 'hereya remove ${packageNameWithoutVersion}' to remove the installed version.`);
|
|
84
|
+
}
|
|
85
|
+
ctx.packageWithInstalledVersion = installedVersion ? `${packageNameWithoutVersion}@${installedVersion}` : packageNameWithoutVersion;
|
|
74
86
|
await delay(500);
|
|
75
87
|
},
|
|
76
88
|
title: 'Loading project config',
|
|
@@ -102,7 +114,7 @@ export default class Remove extends Command {
|
|
|
102
114
|
const { executor } = executor$;
|
|
103
115
|
const destroyOutput = await executor.destroy({
|
|
104
116
|
logger: getLogger(task),
|
|
105
|
-
package: ctx.
|
|
117
|
+
package: ctx.packageWithInstalledVersion,
|
|
106
118
|
parameters: ctx.parametersOutput.parameters,
|
|
107
119
|
project: ctx.configOutput.config.project,
|
|
108
120
|
projectRootDir,
|
|
@@ -134,7 +146,7 @@ export default class Remove extends Command {
|
|
|
134
146
|
const configManager = getConfigManager();
|
|
135
147
|
await configManager.removePackage({
|
|
136
148
|
metadata: ctx.destroyOutput.metadata,
|
|
137
|
-
package: ctx.
|
|
149
|
+
package: ctx.destroyOutput.pkgName,
|
|
138
150
|
projectRootDir,
|
|
139
151
|
});
|
|
140
152
|
await delay(500);
|
|
@@ -98,14 +98,42 @@ export default class Undeploy extends Command {
|
|
|
98
98
|
},
|
|
99
99
|
{
|
|
100
100
|
async task(ctx) {
|
|
101
|
-
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
101
|
+
// Get deploy packages with versions from config
|
|
102
|
+
const configDeployPackages = ctx.configOutput.config.deploy ?? {};
|
|
103
|
+
const deployPackagesWithVersions = Object.entries(configDeployPackages).map(([name, info]) => ({
|
|
104
|
+
name,
|
|
105
|
+
packageSpec: info.version ? `${name}@${info.version}` : name,
|
|
106
|
+
version: info.version || ''
|
|
107
|
+
}));
|
|
108
|
+
// Get removed deploy packages with versions from saved state
|
|
109
|
+
const savedDeployPackages = ctx.savedStateOutput?.config.deploy ?? {};
|
|
110
|
+
const currentDeployPackageNames = Object.keys(configDeployPackages);
|
|
111
|
+
const removedDeployPackages = Object.entries(savedDeployPackages)
|
|
112
|
+
.filter(([name]) => !currentDeployPackageNames.includes(name))
|
|
113
|
+
.map(([name, info]) => ({
|
|
114
|
+
name,
|
|
115
|
+
packageSpec: info.version ? `${name}@${info.version}` : name,
|
|
116
|
+
version: info.version || ''
|
|
117
|
+
}));
|
|
118
|
+
ctx.deployPackages = [...removedDeployPackages, ...deployPackagesWithVersions];
|
|
119
|
+
// Get regular packages with versions from config
|
|
120
|
+
const configPackages = ctx.configOutput.config.packages ?? {};
|
|
121
|
+
const packagesWithVersions = Object.entries(configPackages).map(([name, info]) => ({
|
|
122
|
+
name,
|
|
123
|
+
packageSpec: info.version ? `${name}@${info.version}` : name,
|
|
124
|
+
version: info.version || ''
|
|
125
|
+
}));
|
|
126
|
+
// Get removed packages with versions from saved state
|
|
127
|
+
const savedPackages = ctx.savedStateOutput?.config.packages ?? {};
|
|
128
|
+
const currentPackageNames = Object.keys(configPackages);
|
|
129
|
+
const removedPackages = Object.entries(savedPackages)
|
|
130
|
+
.filter(([name]) => !currentPackageNames.includes(name))
|
|
131
|
+
.map(([name, info]) => ({
|
|
132
|
+
name,
|
|
133
|
+
packageSpec: info.version ? `${name}@${info.version}` : name,
|
|
134
|
+
version: info.version || ''
|
|
135
|
+
}));
|
|
136
|
+
ctx.packages = [...removedPackages, ...packagesWithVersions];
|
|
109
137
|
await delay(500);
|
|
110
138
|
},
|
|
111
139
|
title: 'Identifying removed packages',
|
|
@@ -113,7 +141,7 @@ export default class Undeploy extends Command {
|
|
|
113
141
|
{
|
|
114
142
|
skip: (ctx) => ctx.deployPackages.length === 0,
|
|
115
143
|
async task(ctx, task) {
|
|
116
|
-
return task.newListr(ctx.deployPackages.map((
|
|
144
|
+
return task.newListr(ctx.deployPackages.map((pkg) => ({
|
|
117
145
|
rendererOptions: {
|
|
118
146
|
persistentOutput: isDebug(),
|
|
119
147
|
},
|
|
@@ -122,7 +150,7 @@ export default class Undeploy extends Command {
|
|
|
122
150
|
const backend = await getBackend();
|
|
123
151
|
const profile = await getProfileFromWorkspace(backend, ctx.workspace, ctx.configOutput.config.project);
|
|
124
152
|
const { parameters } = await parameterManager.getPackageParameters({
|
|
125
|
-
package:
|
|
153
|
+
package: pkg.name,
|
|
126
154
|
profile,
|
|
127
155
|
projectRootDir,
|
|
128
156
|
});
|
|
@@ -134,7 +162,7 @@ export default class Undeploy extends Command {
|
|
|
134
162
|
const destroyOutput = await executor.destroy({
|
|
135
163
|
isDeploying: true,
|
|
136
164
|
logger: getLogger(task),
|
|
137
|
-
package:
|
|
165
|
+
package: pkg.packageSpec,
|
|
138
166
|
parameters,
|
|
139
167
|
project: ctx.configOutput.config.project,
|
|
140
168
|
projectEnv: ctx.projectEnv,
|
|
@@ -145,7 +173,7 @@ export default class Undeploy extends Command {
|
|
|
145
173
|
throw new Error(destroyOutput.reason);
|
|
146
174
|
}
|
|
147
175
|
},
|
|
148
|
-
title: `Destroying package ${
|
|
176
|
+
title: `Destroying package ${pkg.name}`,
|
|
149
177
|
})), { concurrent: false, rendererOptions: { collapseSubtasks: !isDebug() } });
|
|
150
178
|
},
|
|
151
179
|
title: 'Destroying deployment packages',
|
|
@@ -153,7 +181,7 @@ export default class Undeploy extends Command {
|
|
|
153
181
|
{
|
|
154
182
|
skip: (ctx) => !ctx.packages || ctx.packages.length === 0,
|
|
155
183
|
async task(ctx, task) {
|
|
156
|
-
return task.newListr(ctx.packages.map((
|
|
184
|
+
return task.newListr(ctx.packages.map((pkg) => ({
|
|
157
185
|
rendererOptions: {
|
|
158
186
|
persistentOutput: isDebug(),
|
|
159
187
|
},
|
|
@@ -162,7 +190,7 @@ export default class Undeploy extends Command {
|
|
|
162
190
|
const backend = await getBackend();
|
|
163
191
|
const profile = await getProfileFromWorkspace(backend, ctx.workspace, ctx.configOutput.config.project);
|
|
164
192
|
const { parameters } = await parameterManager.getPackageParameters({
|
|
165
|
-
package:
|
|
193
|
+
package: pkg.name,
|
|
166
194
|
profile,
|
|
167
195
|
projectRootDir,
|
|
168
196
|
});
|
|
@@ -174,7 +202,7 @@ export default class Undeploy extends Command {
|
|
|
174
202
|
const destroyOutput = await executor.destroy({
|
|
175
203
|
isDeploying: true,
|
|
176
204
|
logger: getLogger(task),
|
|
177
|
-
package:
|
|
205
|
+
package: pkg.packageSpec,
|
|
178
206
|
parameters,
|
|
179
207
|
project: ctx.configOutput.config.project,
|
|
180
208
|
projectRootDir,
|
|
@@ -185,10 +213,10 @@ export default class Undeploy extends Command {
|
|
|
185
213
|
}
|
|
186
214
|
const { env, metadata } = destroyOutput;
|
|
187
215
|
const output = ctx.destroyed || [];
|
|
188
|
-
output.push({ env, metadata, packageName });
|
|
216
|
+
output.push({ env, metadata, packageName: pkg.name });
|
|
189
217
|
ctx.destroyed = output;
|
|
190
218
|
},
|
|
191
|
-
title: `Destroying ${
|
|
219
|
+
title: `Destroying ${pkg.name}`,
|
|
192
220
|
})), { concurrent: true, rendererOptions: { collapseSubtasks: !isDebug() } });
|
|
193
221
|
},
|
|
194
222
|
title: `Destroying packages`,
|
|
@@ -85,14 +85,39 @@ export default class Up extends Command {
|
|
|
85
85
|
},
|
|
86
86
|
{
|
|
87
87
|
async task(ctx) {
|
|
88
|
-
|
|
88
|
+
// Get packages with their versions from config
|
|
89
|
+
const configPackages = ctx.configOutput.config.packages ?? {};
|
|
90
|
+
const packagesWithVersions = Object.entries(configPackages).map(([name, info]) => ({
|
|
91
|
+
name,
|
|
92
|
+
packageSpec: info.version ? `${name}@${info.version}` : name,
|
|
93
|
+
version: info.version || ''
|
|
94
|
+
}));
|
|
89
95
|
const savedPackages = Object.keys(ctx.savedStateOutput?.config?.packages ?? {});
|
|
90
|
-
const removedPackages = savedPackages.filter((packageName) => !
|
|
96
|
+
const removedPackages = savedPackages.filter((packageName) => !configPackages[packageName]);
|
|
91
97
|
ctx.removedPackages = removedPackages;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
98
|
+
// Handle --select flag with version validation
|
|
99
|
+
if (flags.select.length > 0) {
|
|
100
|
+
const selectedPackages = [];
|
|
101
|
+
for (const selectedPkg of flags.select) {
|
|
102
|
+
// Parse the selected package to extract name and version
|
|
103
|
+
const [selectedName, selectedVersion] = selectedPkg.split('@');
|
|
104
|
+
// Find the package in the config
|
|
105
|
+
const configPkg = packagesWithVersions.find(pkg => pkg.name === selectedName);
|
|
106
|
+
if (!configPkg) {
|
|
107
|
+
throw new Error(`Package ${selectedName} not found in project`);
|
|
108
|
+
}
|
|
109
|
+
// If user specified a version, validate it matches the installed version
|
|
110
|
+
if (selectedVersion && selectedVersion !== configPkg.version) {
|
|
111
|
+
throw new Error(`Package ${selectedName} version mismatch: installed version is ${configPkg.version || 'unspecified'}, but you specified ${selectedVersion}. ` +
|
|
112
|
+
`Please use 'hereya up -s ${selectedName}' to use the installed version, or update the package to version ${selectedVersion} first.`);
|
|
113
|
+
}
|
|
114
|
+
selectedPackages.push(configPkg);
|
|
115
|
+
}
|
|
116
|
+
ctx.packages = selectedPackages;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
ctx.packages = packagesWithVersions;
|
|
120
|
+
}
|
|
96
121
|
await delay(500);
|
|
97
122
|
},
|
|
98
123
|
title: 'Searching for removed packages',
|
|
@@ -150,7 +175,7 @@ export default class Up extends Command {
|
|
|
150
175
|
if (!ctx.packages || ctx.packages.length === 0) {
|
|
151
176
|
return;
|
|
152
177
|
}
|
|
153
|
-
return task.newListr(ctx.packages.map((
|
|
178
|
+
return task.newListr(ctx.packages.map((pkg) => ({
|
|
154
179
|
rendererOptions: {
|
|
155
180
|
persistentOutput: isDebug(),
|
|
156
181
|
},
|
|
@@ -159,7 +184,7 @@ export default class Up extends Command {
|
|
|
159
184
|
const backend = await getBackend();
|
|
160
185
|
const profile = await getProfileFromWorkspace(backend, ctx.workspace, ctx.configOutput.config.project);
|
|
161
186
|
const { parameters } = await parameterManager.getPackageParameters({
|
|
162
|
-
package:
|
|
187
|
+
package: pkg.name,
|
|
163
188
|
profile,
|
|
164
189
|
projectRootDir,
|
|
165
190
|
});
|
|
@@ -171,7 +196,7 @@ export default class Up extends Command {
|
|
|
171
196
|
const provisionOutput = await executor.provision({
|
|
172
197
|
isDeploying: flags.deploy,
|
|
173
198
|
logger: getLogger(task),
|
|
174
|
-
package:
|
|
199
|
+
package: pkg.packageSpec,
|
|
175
200
|
parameters,
|
|
176
201
|
project: ctx.configOutput.config.project,
|
|
177
202
|
projectRootDir,
|
|
@@ -182,10 +207,10 @@ export default class Up extends Command {
|
|
|
182
207
|
}
|
|
183
208
|
const { env, metadata } = provisionOutput;
|
|
184
209
|
const output = ctx.added || [];
|
|
185
|
-
output.push({ env, metadata, packageName });
|
|
210
|
+
output.push({ env, metadata, packageName: pkg.name });
|
|
186
211
|
ctx.added = output;
|
|
187
212
|
},
|
|
188
|
-
title: `Provisioning ${
|
|
213
|
+
title: `Provisioning ${pkg.name}`,
|
|
189
214
|
})), { concurrent: true, rendererOptions: { collapseSubtasks: !isDebug() } });
|
|
190
215
|
},
|
|
191
216
|
title: `Provisioning packages`,
|
|
@@ -106,7 +106,7 @@ export default class WorkspaceInstall extends Command {
|
|
|
106
106
|
const output = await backend.addPackageToWorkspace({
|
|
107
107
|
env,
|
|
108
108
|
infra: metadata.infra,
|
|
109
|
-
package:
|
|
109
|
+
package: ctx.provisionOutput.pkgName,
|
|
110
110
|
parameters: ctx.parameters,
|
|
111
111
|
workspace: flags.workspace,
|
|
112
112
|
});
|
|
@@ -54,9 +54,20 @@ export default class WorkspaceUninstall extends Command {
|
|
|
54
54
|
if (loadWorkspaceOutput.workspace.mirrorOf) {
|
|
55
55
|
throw new Error(`Workspace ${flags.workspace} is a mirror of ${loadWorkspaceOutput.workspace.mirrorOf}`);
|
|
56
56
|
}
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
// Parse package name to extract name and version
|
|
58
|
+
const [packageNameWithoutVersion, userSpecifiedVersion] = args.package.split('@');
|
|
59
|
+
const packageInfo = loadWorkspaceOutput.workspace.packages?.[packageNameWithoutVersion];
|
|
60
|
+
if (!packageInfo) {
|
|
61
|
+
throw new Error(`Package ${packageNameWithoutVersion} not found in workspace ${flags.workspace}`);
|
|
59
62
|
}
|
|
63
|
+
// Get the installed version from workspace
|
|
64
|
+
const installedVersion = packageInfo.version || '';
|
|
65
|
+
// If user specified a version, validate it matches the installed version
|
|
66
|
+
if (userSpecifiedVersion && userSpecifiedVersion !== installedVersion) {
|
|
67
|
+
throw new Error(`Package ${packageNameWithoutVersion} version mismatch: installed version is ${installedVersion || 'unspecified'}, but you specified ${userSpecifiedVersion}. ` +
|
|
68
|
+
`Please use 'hereya workspace uninstall ${packageNameWithoutVersion} -w ${flags.workspace}' to uninstall the installed version.`);
|
|
69
|
+
}
|
|
70
|
+
ctx.packageWithInstalledVersion = installedVersion ? `${packageNameWithoutVersion}@${installedVersion}` : packageNameWithoutVersion;
|
|
60
71
|
ctx.workspace = loadWorkspaceOutput;
|
|
61
72
|
await delay(500);
|
|
62
73
|
},
|
|
@@ -73,8 +84,10 @@ export default class WorkspaceUninstall extends Command {
|
|
|
73
84
|
}
|
|
74
85
|
parametersFromFile = data;
|
|
75
86
|
}
|
|
87
|
+
// Use the clean package name to get stored parameters
|
|
88
|
+
const packageNameWithoutVersion = args.package.split('@')[0];
|
|
76
89
|
const parameters = {
|
|
77
|
-
...ctx.workspace.workspace.packages?.[
|
|
90
|
+
...ctx.workspace.workspace.packages?.[packageNameWithoutVersion].parameters,
|
|
78
91
|
...parametersFromFile,
|
|
79
92
|
...parametersInCmdline,
|
|
80
93
|
};
|
|
@@ -95,7 +108,7 @@ export default class WorkspaceUninstall extends Command {
|
|
|
95
108
|
const { executor } = executor$;
|
|
96
109
|
const destroyOutput = await executor.destroy({
|
|
97
110
|
logger: getLogger(task),
|
|
98
|
-
package:
|
|
111
|
+
package: ctx.packageWithInstalledVersion,
|
|
99
112
|
parameters: ctx.parameters,
|
|
100
113
|
workspace: flags.workspace,
|
|
101
114
|
});
|
|
@@ -113,7 +126,7 @@ export default class WorkspaceUninstall extends Command {
|
|
|
113
126
|
const output = await backend.removePackageFromWorkspace({
|
|
114
127
|
env,
|
|
115
128
|
infra: metadata.infra,
|
|
116
|
-
package:
|
|
129
|
+
package: ctx.destroyOutput.pkgName,
|
|
117
130
|
workspace: flags.workspace,
|
|
118
131
|
});
|
|
119
132
|
if (!output.success) {
|
|
@@ -21,7 +21,9 @@ export type ExecutorProvisionOutput = {
|
|
|
21
21
|
[key: string]: string;
|
|
22
22
|
};
|
|
23
23
|
metadata: IPackageMetadata;
|
|
24
|
+
pkgName: string;
|
|
24
25
|
success: true;
|
|
26
|
+
version?: string;
|
|
25
27
|
} | {
|
|
26
28
|
reason: string;
|
|
27
29
|
success: false;
|
|
@@ -61,6 +63,7 @@ export type ExecutorUnsetEnvVarInput = {
|
|
|
61
63
|
workspace: string;
|
|
62
64
|
};
|
|
63
65
|
export type ExecutorImportInput = {
|
|
66
|
+
logger?: Logger;
|
|
64
67
|
package: string;
|
|
65
68
|
project: string;
|
|
66
69
|
projectRootDir?: string;
|
|
@@ -69,7 +72,9 @@ export type ExecutorImportInput = {
|
|
|
69
72
|
};
|
|
70
73
|
export type ExecutorImportOutput = {
|
|
71
74
|
metadata: IPackageMetadata;
|
|
75
|
+
pkgName: string;
|
|
72
76
|
success: true;
|
|
77
|
+
version?: string;
|
|
73
78
|
} | {
|
|
74
79
|
reason: string;
|
|
75
80
|
success: false;
|