hereya-cli 0.85.4 → 0.85.6
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 +78 -67
- package/dist/backend/cloud/cloud-backend.d.ts +1 -0
- package/dist/backend/cloud/cloud-backend.js +9 -1
- package/dist/backend/common.d.ts +1 -0
- package/dist/commands/app/deploy/index.d.ts +4 -0
- package/dist/commands/app/deploy/index.js +211 -6
- package/dist/commands/app/destroy/index.d.ts +5 -0
- package/dist/commands/app/destroy/index.js +154 -3
- package/dist/commands/executor/start/index.d.ts +2 -4
- package/dist/commands/executor/start/index.js +89 -332
- package/dist/executor/interface.d.ts +1 -0
- package/dist/executor/local.js +6 -2
- package/dist/infrastructure/index.d.ts +3 -1
- package/dist/infrastructure/index.js +22 -15
- package/dist/lib/app-parameters.d.ts +23 -0
- package/dist/lib/app-parameters.js +53 -0
- package/oclif.manifest.json +52 -15
- package/package.json +1 -1
|
@@ -1,10 +1,66 @@
|
|
|
1
1
|
import { Args, Command, Flags } from '@oclif/core';
|
|
2
2
|
import { ListrLogger, ListrLogLevels } from 'listr2';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import * as yaml from 'yaml';
|
|
3
6
|
import { CloudBackend } from '../../../backend/cloud/cloud-backend.js';
|
|
4
7
|
import { getBackend } from '../../../backend/index.js';
|
|
5
|
-
import {
|
|
8
|
+
import { LocalExecutor } from '../../../executor/local.js';
|
|
9
|
+
import { resolveAndSubstituteAppParams, resolveAppParameters } from '../../../lib/app-parameters.js';
|
|
10
|
+
import { getEnvManager } from '../../../lib/env/index.js';
|
|
6
11
|
import { arrayOfStringToObject } from '../../../lib/object-utils.js';
|
|
12
|
+
import { stripOrgPrefix } from '../../../lib/org-utils.js';
|
|
13
|
+
import { getProfileFromWorkspace } from '../../../lib/profile-utils.js';
|
|
7
14
|
import { pollExecutorJob } from '../../../lib/remote-job-utils.js';
|
|
15
|
+
import { shellUtils } from '../../../lib/shell.js';
|
|
16
|
+
function normalizePackageList(raw) {
|
|
17
|
+
if (!raw)
|
|
18
|
+
return [];
|
|
19
|
+
if (Array.isArray(raw)) {
|
|
20
|
+
return raw
|
|
21
|
+
.filter((entry) => typeof entry === 'string' && entry.length > 0)
|
|
22
|
+
.map((entry) => {
|
|
23
|
+
const [name, version] = entry.split('@');
|
|
24
|
+
return {
|
|
25
|
+
name,
|
|
26
|
+
spec: entry,
|
|
27
|
+
version,
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
if (typeof raw === 'object') {
|
|
32
|
+
return Object.entries(raw).map(([name, info]) => {
|
|
33
|
+
const version = info?.version;
|
|
34
|
+
return {
|
|
35
|
+
name,
|
|
36
|
+
spec: version ? `${name}@${version}` : name,
|
|
37
|
+
version,
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
async function getAnyExisting(...candidates) {
|
|
44
|
+
for (const candidate of candidates) {
|
|
45
|
+
try {
|
|
46
|
+
// eslint-disable-next-line no-await-in-loop
|
|
47
|
+
await fs.access(candidate);
|
|
48
|
+
return candidate;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// try next
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Workspaces look like `org/name`, which is not filesystem-safe. The
|
|
58
|
+
* corresponding read-side in the executor wrapper MUST use the same
|
|
59
|
+
* sanitization — see `executeAppJob` in `commands/executor/start/index.ts`.
|
|
60
|
+
*/
|
|
61
|
+
function sanitizeWorkspaceForFilename(workspace) {
|
|
62
|
+
return workspace.replaceAll('/', '-');
|
|
63
|
+
}
|
|
8
64
|
export default class AppDeploy extends Command {
|
|
9
65
|
static args = {
|
|
10
66
|
name: Args.string({ description: 'app name in org/name format', required: true }),
|
|
@@ -16,6 +72,16 @@ export default class AppDeploy extends Command {
|
|
|
16
72
|
'<%= config.bin %> <%= command.id %> my-org/my-app -w prod -p organizationId=org-123',
|
|
17
73
|
];
|
|
18
74
|
static flags = {
|
|
75
|
+
chdir: Flags.string({
|
|
76
|
+
description: `Directory where the command will be executed.
|
|
77
|
+
If not specified, it defaults to the current working directory.
|
|
78
|
+
Alternatively, you can define the project root by setting the HEREYA_PROJECT_ROOT_DIR environment variable.`,
|
|
79
|
+
required: false,
|
|
80
|
+
}),
|
|
81
|
+
local: Flags.boolean({
|
|
82
|
+
default: false,
|
|
83
|
+
description: 'force local execution (skip remote executor)',
|
|
84
|
+
}),
|
|
19
85
|
parameter: Flags.string({
|
|
20
86
|
char: 'p',
|
|
21
87
|
default: [],
|
|
@@ -33,6 +99,145 @@ export default class AppDeploy extends Command {
|
|
|
33
99
|
};
|
|
34
100
|
async run() {
|
|
35
101
|
const { args, flags } = await this.parse(AppDeploy);
|
|
102
|
+
const forceLocal = flags.local || process.env.HEREYA_LOCAL_EXECUTION === 'true';
|
|
103
|
+
if (forceLocal) {
|
|
104
|
+
await this.runLocal({ appName: args.name, flags });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
await this.runRemote({ appName: args.name, flags });
|
|
108
|
+
}
|
|
109
|
+
async runLocal(input) {
|
|
110
|
+
const { appName, flags } = input;
|
|
111
|
+
const rootDir = path.resolve(flags.chdir || process.env.HEREYA_PROJECT_ROOT_DIR || process.cwd());
|
|
112
|
+
const myLogger = new ListrLogger({ useIcons: false });
|
|
113
|
+
const logger = {
|
|
114
|
+
debug: (msg) => myLogger.log(ListrLogLevels.OUTPUT, msg),
|
|
115
|
+
error: (msg) => myLogger.log(ListrLogLevels.OUTPUT, msg),
|
|
116
|
+
info: (msg) => myLogger.log(ListrLogLevels.OUTPUT, msg),
|
|
117
|
+
};
|
|
118
|
+
// Read hereya.yaml
|
|
119
|
+
const hereyaYamlPath = await getAnyExisting(path.join(rootDir, 'hereya.yaml'), path.join(rootDir, 'hereya.yml'));
|
|
120
|
+
if (!hereyaYamlPath) {
|
|
121
|
+
this.error(`No hereya.yaml found in ${rootDir}`);
|
|
122
|
+
}
|
|
123
|
+
const parsedHereyaYaml = (yaml.parse(await fs.readFile(hereyaYamlPath, 'utf8')) ?? {});
|
|
124
|
+
const packages = normalizePackageList(parsedHereyaYaml.packages);
|
|
125
|
+
const deployPackages = normalizePackageList(parsedHereyaYaml.deployPackages ?? parsedHereyaYaml.deploy);
|
|
126
|
+
// Parse user-provided parameters
|
|
127
|
+
const provided = arrayOfStringToObject(flags.parameter);
|
|
128
|
+
// Resolve params + substitute hereyavars files
|
|
129
|
+
const paramsResult = await resolveAndSubstituteAppParams({
|
|
130
|
+
logger,
|
|
131
|
+
provided,
|
|
132
|
+
rootDir,
|
|
133
|
+
});
|
|
134
|
+
if (!paramsResult.success) {
|
|
135
|
+
this.error(paramsResult.reason);
|
|
136
|
+
}
|
|
137
|
+
const { parameters: resolvedParameters } = paramsResult;
|
|
138
|
+
// Run preDeployCommand if any
|
|
139
|
+
if (parsedHereyaYaml.preDeployCommand) {
|
|
140
|
+
logger.info('Running pre-deploy command...');
|
|
141
|
+
try {
|
|
142
|
+
await shellUtils.runShell(parsedHereyaYaml.preDeployCommand, [], { directory: rootDir, logger });
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
this.error(`pre-deploy command failed: ${error.message}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const backend = await getBackend();
|
|
149
|
+
const { profile } = await getProfileFromWorkspace(backend, flags.workspace, appName);
|
|
150
|
+
const executor = new LocalExecutor();
|
|
151
|
+
const envMgr = getEnvManager();
|
|
152
|
+
const deployEnv = {};
|
|
153
|
+
// Mirror project-deploy env machinery exactly:
|
|
154
|
+
// 1. Each regular pkg is provisioned in isolation. LocalExecutor.provision
|
|
155
|
+
// auto-loads workspace env; pkg outputs are persisted via
|
|
156
|
+
// envManager.addProjectEnv to .hereya/env.<workspace>.yaml.
|
|
157
|
+
// 2. projectEnv for deploy pkgs is built from envManager.getProjectEnv
|
|
158
|
+
// (.hereya/env.<workspace>.yaml + .env + hereyaconfig/hereyastaticenv)
|
|
159
|
+
// — explicitly NOT including workspace env. That keeps the deploy
|
|
160
|
+
// Lambda env block under AWS's 4 KB limit.
|
|
161
|
+
// 3. deployEnv accumulates only deploy-package outputs and is the
|
|
162
|
+
// AppDeployment's public env surface (cloud UI + `hereya app env`).
|
|
163
|
+
/* eslint-disable no-await-in-loop */
|
|
164
|
+
for (const pkg of packages) {
|
|
165
|
+
myLogger.log(ListrLogLevels.STARTED, `Provisioning ${pkg.name}...`);
|
|
166
|
+
const result = await executor.provision({
|
|
167
|
+
app: appName,
|
|
168
|
+
isDeploying: true,
|
|
169
|
+
logger,
|
|
170
|
+
package: pkg.spec,
|
|
171
|
+
parameters: resolvedParameters,
|
|
172
|
+
projectRootDir: rootDir,
|
|
173
|
+
workspace: flags.workspace,
|
|
174
|
+
});
|
|
175
|
+
if (!result.success) {
|
|
176
|
+
this.error(`Failed to provision package ${pkg.name}: ${result.reason}`);
|
|
177
|
+
}
|
|
178
|
+
await envMgr.addProjectEnv({
|
|
179
|
+
env: result.env,
|
|
180
|
+
infra: result.metadata.originalInfra ?? result.metadata.infra,
|
|
181
|
+
pkg: result.pkgName,
|
|
182
|
+
projectRootDir: rootDir,
|
|
183
|
+
snakeCase: result.metadata.snakeCase,
|
|
184
|
+
workspace: flags.workspace,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
// Build projectEnv for deploy packages from saved per-package outputs.
|
|
188
|
+
const projectEnvResult = await envMgr.getProjectEnv({
|
|
189
|
+
markSecret: true,
|
|
190
|
+
profile: profile ?? stripOrgPrefix(flags.workspace),
|
|
191
|
+
project: appName,
|
|
192
|
+
projectRootDir: rootDir,
|
|
193
|
+
workspace: flags.workspace,
|
|
194
|
+
});
|
|
195
|
+
if (!projectEnvResult.success) {
|
|
196
|
+
this.error(`Failed to build projectEnv: ${projectEnvResult.reason}`);
|
|
197
|
+
}
|
|
198
|
+
const projectEnv = projectEnvResult.env;
|
|
199
|
+
for (const pkg of deployPackages) {
|
|
200
|
+
myLogger.log(ListrLogLevels.STARTED, `Provisioning deploy package ${pkg.name}...`);
|
|
201
|
+
const result = await executor.provision({
|
|
202
|
+
app: appName,
|
|
203
|
+
isDeploying: true,
|
|
204
|
+
logger,
|
|
205
|
+
package: pkg.spec,
|
|
206
|
+
parameters: resolvedParameters,
|
|
207
|
+
projectEnv,
|
|
208
|
+
projectRootDir: rootDir,
|
|
209
|
+
workspace: flags.workspace,
|
|
210
|
+
});
|
|
211
|
+
if (!result.success) {
|
|
212
|
+
this.error(`Failed to provision deploy package ${pkg.name}: ${result.reason}`);
|
|
213
|
+
}
|
|
214
|
+
Object.assign(deployEnv, result.env);
|
|
215
|
+
}
|
|
216
|
+
/* eslint-enable no-await-in-loop */
|
|
217
|
+
// Write deploy env file for executor wrapper to PATCH back to the cloud.
|
|
218
|
+
const hereyaDir = path.join(rootDir, '.hereya');
|
|
219
|
+
await fs.mkdir(hereyaDir, { recursive: true });
|
|
220
|
+
const deployEnvFile = path.join(hereyaDir, `deploy-env.${sanitizeWorkspaceForFilename(flags.workspace)}.json`);
|
|
221
|
+
const state = {
|
|
222
|
+
deployPackages: deployPackages.map((p) => ({ name: p.name, version: p.version })),
|
|
223
|
+
packages: packages.map((p) => ({ name: p.name, version: p.version })),
|
|
224
|
+
version: flags.version,
|
|
225
|
+
};
|
|
226
|
+
await fs.writeFile(deployEnvFile, JSON.stringify({ env: deployEnv, state }, null, 2), 'utf8');
|
|
227
|
+
myLogger.log(ListrLogLevels.COMPLETED, `App ${appName} deployed to ${flags.workspace}`);
|
|
228
|
+
this.log(`\nStatus: deployed`);
|
|
229
|
+
if (flags.version) {
|
|
230
|
+
this.log(`Version: ${flags.version}`);
|
|
231
|
+
}
|
|
232
|
+
if (Object.keys(deployEnv).length > 0) {
|
|
233
|
+
this.log('\nEnv outputs:');
|
|
234
|
+
for (const [key, value] of Object.entries(deployEnv)) {
|
|
235
|
+
this.log(` ${key}=${value}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
async runRemote(input) {
|
|
240
|
+
const { appName, flags } = input;
|
|
36
241
|
const backend = await getBackend();
|
|
37
242
|
if (!(backend instanceof CloudBackend)) {
|
|
38
243
|
this.error('App deployment requires the cloud backend. Run `hereya login` first.');
|
|
@@ -43,7 +248,7 @@ export default class AppDeploy extends Command {
|
|
|
43
248
|
const provided = arrayOfStringToObject(flags.parameter);
|
|
44
249
|
// Fetch the target version's declared parameters before submitting any cloud job.
|
|
45
250
|
const versionResult = await cloudBackend.getAppVersion({
|
|
46
|
-
name:
|
|
251
|
+
name: appName,
|
|
47
252
|
version: flags.version,
|
|
48
253
|
});
|
|
49
254
|
if (!versionResult.success) {
|
|
@@ -54,9 +259,9 @@ export default class AppDeploy extends Command {
|
|
|
54
259
|
if (errors.length > 0) {
|
|
55
260
|
this.error(['Cannot deploy app due to parameter errors:', ...errors.map((e) => ` - ${e}`)].join('\n'));
|
|
56
261
|
}
|
|
57
|
-
myLogger.log(ListrLogLevels.STARTED, `Deploying ${
|
|
262
|
+
myLogger.log(ListrLogLevels.STARTED, `Deploying ${appName}${flags.version ? `@${flags.version}` : ''} to workspace ${flags.workspace}...`);
|
|
58
263
|
const deployResult = await cloudBackend.deployApp({
|
|
59
|
-
name:
|
|
264
|
+
name: appName,
|
|
60
265
|
parameters: Object.keys(parameters).length > 0 ? parameters : undefined,
|
|
61
266
|
version: flags.version,
|
|
62
267
|
workspace: flags.workspace,
|
|
@@ -80,9 +285,9 @@ export default class AppDeploy extends Command {
|
|
|
80
285
|
if (!pollResult.success) {
|
|
81
286
|
this.error(pollResult.reason);
|
|
82
287
|
}
|
|
83
|
-
myLogger.log(ListrLogLevels.COMPLETED, `App ${
|
|
288
|
+
myLogger.log(ListrLogLevels.COMPLETED, `App ${appName} deployed to ${flags.workspace}`);
|
|
84
289
|
// Print final state
|
|
85
|
-
const status = await cloudBackend.getAppDeployment({ name:
|
|
290
|
+
const status = await cloudBackend.getAppDeployment({ name: appName, workspace: flags.workspace });
|
|
86
291
|
if (status.success) {
|
|
87
292
|
this.log(`\nStatus: ${status.deployment.status}`);
|
|
88
293
|
if (status.deployment.version) {
|
|
@@ -6,7 +6,12 @@ export default class AppDestroy extends Command {
|
|
|
6
6
|
static description: string;
|
|
7
7
|
static examples: string[];
|
|
8
8
|
static flags: {
|
|
9
|
+
chdir: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
local: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
parameter: import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
|
|
9
12
|
workspace: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
13
|
};
|
|
11
14
|
run(): Promise<void>;
|
|
15
|
+
private runLocal;
|
|
16
|
+
private runRemote;
|
|
12
17
|
}
|
|
@@ -1,8 +1,58 @@
|
|
|
1
1
|
import { Args, Command, Flags } from '@oclif/core';
|
|
2
2
|
import { ListrLogger, ListrLogLevels } from 'listr2';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import * as yaml from 'yaml';
|
|
3
6
|
import { CloudBackend } from '../../../backend/cloud/cloud-backend.js';
|
|
4
7
|
import { getBackend } from '../../../backend/index.js';
|
|
8
|
+
import { LocalExecutor } from '../../../executor/local.js';
|
|
9
|
+
import { resolveAndSubstituteAppParams } from '../../../lib/app-parameters.js';
|
|
10
|
+
import { getEnvManager } from '../../../lib/env/index.js';
|
|
11
|
+
import { arrayOfStringToObject } from '../../../lib/object-utils.js';
|
|
12
|
+
import { stripOrgPrefix } from '../../../lib/org-utils.js';
|
|
13
|
+
import { getProfileFromWorkspace } from '../../../lib/profile-utils.js';
|
|
5
14
|
import { pollExecutorJob } from '../../../lib/remote-job-utils.js';
|
|
15
|
+
import { shellUtils } from '../../../lib/shell.js';
|
|
16
|
+
function normalizePackageList(raw) {
|
|
17
|
+
if (!raw)
|
|
18
|
+
return [];
|
|
19
|
+
if (Array.isArray(raw)) {
|
|
20
|
+
return raw
|
|
21
|
+
.filter((entry) => typeof entry === 'string' && entry.length > 0)
|
|
22
|
+
.map((entry) => {
|
|
23
|
+
const [name, version] = entry.split('@');
|
|
24
|
+
return {
|
|
25
|
+
name,
|
|
26
|
+
spec: entry,
|
|
27
|
+
version,
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
if (typeof raw === 'object') {
|
|
32
|
+
return Object.entries(raw).map(([name, info]) => {
|
|
33
|
+
const version = info?.version;
|
|
34
|
+
return {
|
|
35
|
+
name,
|
|
36
|
+
spec: version ? `${name}@${version}` : name,
|
|
37
|
+
version,
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
async function getAnyExisting(...candidates) {
|
|
44
|
+
for (const candidate of candidates) {
|
|
45
|
+
try {
|
|
46
|
+
// eslint-disable-next-line no-await-in-loop
|
|
47
|
+
await fs.access(candidate);
|
|
48
|
+
return candidate;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// try next
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
6
56
|
export default class AppDestroy extends Command {
|
|
7
57
|
static args = {
|
|
8
58
|
name: Args.string({ description: 'app name in org/name format', required: true }),
|
|
@@ -10,6 +60,22 @@ export default class AppDestroy extends Command {
|
|
|
10
60
|
static description = 'Destroy a hereya-app deployment from a workspace.';
|
|
11
61
|
static examples = ['<%= config.bin %> <%= command.id %> my-org/my-app -w my-workspace'];
|
|
12
62
|
static flags = {
|
|
63
|
+
chdir: Flags.string({
|
|
64
|
+
description: `Directory where the command will be executed.
|
|
65
|
+
If not specified, it defaults to the current working directory.
|
|
66
|
+
Alternatively, you can define the project root by setting the HEREYA_PROJECT_ROOT_DIR environment variable.`,
|
|
67
|
+
required: false,
|
|
68
|
+
}),
|
|
69
|
+
local: Flags.boolean({
|
|
70
|
+
default: false,
|
|
71
|
+
description: 'force local execution (skip remote executor)',
|
|
72
|
+
}),
|
|
73
|
+
parameter: Flags.string({
|
|
74
|
+
char: 'p',
|
|
75
|
+
default: [],
|
|
76
|
+
description: 'parameter for the app deployment, in the form of key=value (repeatable)',
|
|
77
|
+
multiple: true,
|
|
78
|
+
}),
|
|
13
79
|
workspace: Flags.string({
|
|
14
80
|
char: 'w',
|
|
15
81
|
description: 'workspace where the app is currently deployed',
|
|
@@ -18,15 +84,100 @@ export default class AppDestroy extends Command {
|
|
|
18
84
|
};
|
|
19
85
|
async run() {
|
|
20
86
|
const { args, flags } = await this.parse(AppDestroy);
|
|
87
|
+
const forceLocal = flags.local || process.env.HEREYA_LOCAL_EXECUTION === 'true';
|
|
88
|
+
if (forceLocal) {
|
|
89
|
+
await this.runLocal({ appName: args.name, flags });
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
await this.runRemote({ appName: args.name, flags });
|
|
93
|
+
}
|
|
94
|
+
async runLocal(input) {
|
|
95
|
+
const { appName, flags } = input;
|
|
96
|
+
const rootDir = path.resolve(flags.chdir || process.env.HEREYA_PROJECT_ROOT_DIR || process.cwd());
|
|
97
|
+
const myLogger = new ListrLogger({ useIcons: false });
|
|
98
|
+
const logger = {
|
|
99
|
+
debug: (msg) => myLogger.log(ListrLogLevels.OUTPUT, msg),
|
|
100
|
+
error: (msg) => myLogger.log(ListrLogLevels.OUTPUT, msg),
|
|
101
|
+
info: (msg) => myLogger.log(ListrLogLevels.OUTPUT, msg),
|
|
102
|
+
};
|
|
103
|
+
// Read hereya.yaml
|
|
104
|
+
const hereyaYamlPath = await getAnyExisting(path.join(rootDir, 'hereya.yaml'), path.join(rootDir, 'hereya.yml'));
|
|
105
|
+
if (!hereyaYamlPath) {
|
|
106
|
+
this.error(`No hereya.yaml found in ${rootDir}`);
|
|
107
|
+
}
|
|
108
|
+
const parsedHereyaYaml = (yaml.parse(await fs.readFile(hereyaYamlPath, 'utf8')) ?? {});
|
|
109
|
+
const packages = normalizePackageList(parsedHereyaYaml.packages);
|
|
110
|
+
const deployPackages = normalizePackageList(parsedHereyaYaml.deployPackages ?? parsedHereyaYaml.deploy);
|
|
111
|
+
// Parse user-provided parameters
|
|
112
|
+
const provided = arrayOfStringToObject(flags.parameter);
|
|
113
|
+
// Resolve params + substitute hereyavars files
|
|
114
|
+
const paramsResult = await resolveAndSubstituteAppParams({
|
|
115
|
+
logger,
|
|
116
|
+
provided,
|
|
117
|
+
rootDir,
|
|
118
|
+
});
|
|
119
|
+
if (!paramsResult.success) {
|
|
120
|
+
this.error(paramsResult.reason);
|
|
121
|
+
}
|
|
122
|
+
const { parameters: resolvedParameters } = paramsResult;
|
|
123
|
+
// Run preDeployCommand if any (matches project undeploy + current executor behavior)
|
|
124
|
+
if (parsedHereyaYaml.preDeployCommand) {
|
|
125
|
+
logger.info('Running pre-deploy command...');
|
|
126
|
+
try {
|
|
127
|
+
await shellUtils.runShell(parsedHereyaYaml.preDeployCommand, [], { directory: rootDir, logger });
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
this.error(`pre-deploy command failed: ${error.message}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const backend = await getBackend();
|
|
134
|
+
const { profile } = await getProfileFromWorkspace(backend, flags.workspace, appName);
|
|
135
|
+
const executor = new LocalExecutor();
|
|
136
|
+
const envMgr = getEnvManager();
|
|
137
|
+
// Build projectEnv from saved per-package outputs.
|
|
138
|
+
const projectEnvResult = await envMgr.getProjectEnv({
|
|
139
|
+
markSecret: true,
|
|
140
|
+
profile: profile ?? stripOrgPrefix(flags.workspace),
|
|
141
|
+
project: appName,
|
|
142
|
+
projectRootDir: rootDir,
|
|
143
|
+
workspace: flags.workspace,
|
|
144
|
+
});
|
|
145
|
+
const projectEnv = projectEnvResult.success ? projectEnvResult.env : {};
|
|
146
|
+
// Iterate in declaration order: deploy packages first, then regular packages.
|
|
147
|
+
const allPackages = [...deployPackages, ...packages];
|
|
148
|
+
/* eslint-disable no-await-in-loop */
|
|
149
|
+
for (const pkg of allPackages) {
|
|
150
|
+
myLogger.log(ListrLogLevels.STARTED, `Destroying ${pkg.name}...`);
|
|
151
|
+
const result = await executor.destroy({
|
|
152
|
+
app: appName,
|
|
153
|
+
isDeploying: true,
|
|
154
|
+
logger,
|
|
155
|
+
package: pkg.spec,
|
|
156
|
+
parameters: resolvedParameters,
|
|
157
|
+
projectEnv,
|
|
158
|
+
projectRootDir: rootDir,
|
|
159
|
+
workspace: flags.workspace,
|
|
160
|
+
});
|
|
161
|
+
if (!result.success) {
|
|
162
|
+
this.error(`Failed to destroy package ${pkg.name}: ${result.reason}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/* eslint-enable no-await-in-loop */
|
|
166
|
+
myLogger.log(ListrLogLevels.COMPLETED, `App ${appName} destroyed from ${flags.workspace}`);
|
|
167
|
+
}
|
|
168
|
+
async runRemote(input) {
|
|
169
|
+
const { appName, flags } = input;
|
|
21
170
|
const backend = await getBackend();
|
|
22
171
|
if (!(backend instanceof CloudBackend)) {
|
|
23
172
|
this.error('App destroy requires the cloud backend. Run `hereya login` first.');
|
|
24
173
|
}
|
|
25
174
|
const cloudBackend = backend;
|
|
26
175
|
const myLogger = new ListrLogger({ useIcons: false });
|
|
27
|
-
myLogger.log(ListrLogLevels.STARTED, `Destroying ${
|
|
176
|
+
myLogger.log(ListrLogLevels.STARTED, `Destroying ${appName} on workspace ${flags.workspace}...`);
|
|
177
|
+
const providedParameters = arrayOfStringToObject(flags.parameter ?? []);
|
|
28
178
|
const destroyResult = await cloudBackend.destroyApp({
|
|
29
|
-
name:
|
|
179
|
+
name: appName,
|
|
180
|
+
parameters: providedParameters,
|
|
30
181
|
workspace: flags.workspace,
|
|
31
182
|
});
|
|
32
183
|
if (!destroyResult.success) {
|
|
@@ -47,6 +198,6 @@ export default class AppDestroy extends Command {
|
|
|
47
198
|
if (!pollResult.success) {
|
|
48
199
|
this.error(pollResult.reason);
|
|
49
200
|
}
|
|
50
|
-
myLogger.log(ListrLogLevels.COMPLETED, `App ${
|
|
201
|
+
myLogger.log(ListrLogLevels.COMPLETED, `App ${appName} destroyed from ${flags.workspace}`);
|
|
51
202
|
}
|
|
52
203
|
}
|
|
@@ -14,12 +14,10 @@ export default class ExecutorStart extends Command {
|
|
|
14
14
|
workspace: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
15
|
};
|
|
16
16
|
run(): Promise<void>;
|
|
17
|
-
private
|
|
18
|
-
private executeAppDestroyJob;
|
|
17
|
+
private executeAppJob;
|
|
19
18
|
private executeDeployJob;
|
|
20
19
|
private executeInitJob;
|
|
21
20
|
private executeJob;
|
|
22
21
|
private markAppDeploymentFailed;
|
|
23
|
-
private
|
|
24
|
-
private resolveAndSubstituteParams;
|
|
22
|
+
private patchAppFinalState;
|
|
25
23
|
}
|