hereya-cli 0.69.3 → 0.71.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 +162 -47
- package/dist/commands/devenv/config/index.d.ts +9 -0
- package/dist/commands/devenv/config/index.js +59 -0
- package/dist/commands/devenv/install/index.d.ts +13 -0
- package/dist/commands/devenv/install/index.js +170 -0
- package/dist/commands/devenv/project/init/index.d.ts +14 -0
- package/dist/commands/devenv/project/init/index.js +94 -0
- package/dist/commands/devenv/ssh/index.d.ts +10 -0
- package/dist/commands/devenv/ssh/index.js +79 -0
- package/dist/commands/devenv/uninstall/index.d.ts +10 -0
- package/dist/commands/devenv/uninstall/index.js +115 -0
- package/dist/lib/package/cloud.js +1 -1
- package/dist/lib/ssh-utils.d.ts +2 -0
- package/dist/lib/ssh-utils.js +18 -0
- package/oclif.manifest.json +227 -1
- package/package.json +1 -1
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { Listr, ListrLogger, ListrLogLevels } from 'listr2';
|
|
3
|
+
import { getCloudCredentials, loadBackendConfig } from '../../../backend/config.js';
|
|
4
|
+
import { getBackend } from '../../../backend/index.js';
|
|
5
|
+
import { getExecutor } from '../../../executor/index.js';
|
|
6
|
+
import { getLogger, getLogPath, isDebug, setDebug } from '../../../lib/log.js';
|
|
7
|
+
import { arrayOfStringToObject } from '../../../lib/object-utils.js';
|
|
8
|
+
import { delay } from '../../../lib/shell.js';
|
|
9
|
+
import { load } from '../../../lib/yaml-utils.js';
|
|
10
|
+
export default class DevenvInstall extends Command {
|
|
11
|
+
static description = 'Provision a remote dev environment on a workspace.';
|
|
12
|
+
static examples = ['<%= config.bin %> <%= command.id %> -w my-workspace -p instanceType=t3.large'];
|
|
13
|
+
static flags = {
|
|
14
|
+
debug: Flags.boolean({
|
|
15
|
+
default: false,
|
|
16
|
+
description: 'enable debug mode',
|
|
17
|
+
}),
|
|
18
|
+
parameter: Flags.string({
|
|
19
|
+
char: 'p',
|
|
20
|
+
default: [],
|
|
21
|
+
description: "parameter for the package, in the form of 'key=value'. Can be specified multiple times.",
|
|
22
|
+
multiple: true,
|
|
23
|
+
}),
|
|
24
|
+
'parameter-file': Flags.string({
|
|
25
|
+
char: 'f',
|
|
26
|
+
description: 'path to a file containing parameters for the package',
|
|
27
|
+
}),
|
|
28
|
+
version: Flags.string({
|
|
29
|
+
char: 'v',
|
|
30
|
+
description: 'version of the dev environment package',
|
|
31
|
+
}),
|
|
32
|
+
workspace: Flags.string({
|
|
33
|
+
char: 'w',
|
|
34
|
+
description: 'name of the workspace to provision the dev environment on',
|
|
35
|
+
required: true,
|
|
36
|
+
}),
|
|
37
|
+
};
|
|
38
|
+
async run() {
|
|
39
|
+
const { flags } = await this.parse(DevenvInstall);
|
|
40
|
+
setDebug(flags.debug);
|
|
41
|
+
const pkg = flags.version ? `hereya/dev-env-aws@${flags.version}` : 'hereya/dev-env-aws';
|
|
42
|
+
const myLogger = new ListrLogger({ useIcons: false });
|
|
43
|
+
const task = new Listr([
|
|
44
|
+
{
|
|
45
|
+
async task(ctx, task) {
|
|
46
|
+
return task.newListr([
|
|
47
|
+
{
|
|
48
|
+
async task(ctx) {
|
|
49
|
+
const backend = await getBackend();
|
|
50
|
+
const loadWorkspaceOutput = await backend.getWorkspace(flags.workspace);
|
|
51
|
+
if (!loadWorkspaceOutput.found || loadWorkspaceOutput.hasError) {
|
|
52
|
+
throw new Error(`Workspace ${flags.workspace} not found`);
|
|
53
|
+
}
|
|
54
|
+
if (loadWorkspaceOutput.workspace.mirrorOf) {
|
|
55
|
+
throw new Error(`Workspace ${flags.workspace} is a mirror of ${loadWorkspaceOutput.workspace.mirrorOf}`);
|
|
56
|
+
}
|
|
57
|
+
ctx.workspace = loadWorkspaceOutput;
|
|
58
|
+
await delay(500);
|
|
59
|
+
},
|
|
60
|
+
title: `Loading workspace ${flags.workspace}`,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
async task(ctx) {
|
|
64
|
+
const parametersInCmdline = arrayOfStringToObject(flags.parameter);
|
|
65
|
+
let parametersFromFile = {};
|
|
66
|
+
if (flags['parameter-file']) {
|
|
67
|
+
const { data, found } = await load(flags['parameter-file']);
|
|
68
|
+
if (!found) {
|
|
69
|
+
throw new Error(`Parameter file ${flags['parameter-file']} not found`);
|
|
70
|
+
}
|
|
71
|
+
parametersFromFile = data;
|
|
72
|
+
}
|
|
73
|
+
const parameters = { ...parametersFromFile, ...parametersInCmdline };
|
|
74
|
+
ctx.parameters = parameters;
|
|
75
|
+
await delay(500);
|
|
76
|
+
},
|
|
77
|
+
title: 'Resolving parameters',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
async task(ctx) {
|
|
81
|
+
const token = await generateHereyaToken(flags.workspace);
|
|
82
|
+
if (token) {
|
|
83
|
+
ctx.parameters = { ...ctx.parameters, hereyaCloudUrl: token.cloudUrl, hereyaToken: token.token };
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
title: 'Generating Hereya token for dev environment',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
rendererOptions: {
|
|
90
|
+
persistentOutput: isDebug(),
|
|
91
|
+
},
|
|
92
|
+
async task(ctx, task) {
|
|
93
|
+
const executor$ = getExecutor();
|
|
94
|
+
if (!executor$.success) {
|
|
95
|
+
throw new Error(executor$.reason);
|
|
96
|
+
}
|
|
97
|
+
const { executor } = executor$;
|
|
98
|
+
const provisionOutput = await executor.provision({
|
|
99
|
+
logger: getLogger(task),
|
|
100
|
+
package: pkg,
|
|
101
|
+
parameters: ctx.parameters,
|
|
102
|
+
workspace: flags.workspace,
|
|
103
|
+
});
|
|
104
|
+
if (!provisionOutput.success) {
|
|
105
|
+
throw new Error(provisionOutput.reason);
|
|
106
|
+
}
|
|
107
|
+
ctx.provisionOutput = provisionOutput;
|
|
108
|
+
},
|
|
109
|
+
title: 'Provisioning dev environment',
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
async task(ctx) {
|
|
113
|
+
const { env, metadata } = ctx.provisionOutput;
|
|
114
|
+
const backend = await getBackend();
|
|
115
|
+
const output = await backend.addPackageToWorkspace({
|
|
116
|
+
env,
|
|
117
|
+
infra: metadata.infra,
|
|
118
|
+
package: ctx.provisionOutput.pkgName,
|
|
119
|
+
parameters: ctx.parameters,
|
|
120
|
+
version: ctx.provisionOutput.version,
|
|
121
|
+
workspace: flags.workspace,
|
|
122
|
+
});
|
|
123
|
+
if (!output.success) {
|
|
124
|
+
throw new Error(output.reason);
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
title: 'Saving exported environment variables to workspace',
|
|
128
|
+
},
|
|
129
|
+
]);
|
|
130
|
+
},
|
|
131
|
+
title: `Installing ${pkg} into workspace ${flags.workspace}`,
|
|
132
|
+
},
|
|
133
|
+
], { concurrent: false, rendererOptions: { collapseSubtasks: !isDebug() } });
|
|
134
|
+
try {
|
|
135
|
+
await task.run();
|
|
136
|
+
myLogger.log(ListrLogLevels.COMPLETED, `Dev environment provisioned successfully into workspace ${flags.workspace}`);
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
this.error(`${error.message}
|
|
140
|
+
|
|
141
|
+
See ${getLogPath()} for more details`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async function generateHereyaToken(workspace) {
|
|
146
|
+
const backendConfig = await loadBackendConfig();
|
|
147
|
+
if (!backendConfig.cloud) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
const { clientId, url } = backendConfig.cloud;
|
|
151
|
+
const credentials = await getCloudCredentials(clientId);
|
|
152
|
+
if (!credentials) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
const formData = new FormData();
|
|
156
|
+
formData.append('description', `Dev environment: ${workspace}`);
|
|
157
|
+
formData.append('expiresInDays', '365');
|
|
158
|
+
const response = await fetch(`${url}/api/personal-tokens`, {
|
|
159
|
+
body: formData,
|
|
160
|
+
headers: {
|
|
161
|
+
Authorization: `Bearer ${credentials.accessToken}`,
|
|
162
|
+
},
|
|
163
|
+
method: 'POST',
|
|
164
|
+
});
|
|
165
|
+
if (!response.ok) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
const result = await response.json();
|
|
169
|
+
return { cloudUrl: url, token: result.data.token };
|
|
170
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class DevenvProjectInit extends Command {
|
|
3
|
+
static args: {
|
|
4
|
+
project: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<void>;
|
|
13
|
+
protected spawnSsh(args: string[]): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import fs from 'node:fs/promises';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { getBackend } from '../../../../backend/index.js';
|
|
8
|
+
import { getExecutorForWorkspace } from '../../../../executor/context.js';
|
|
9
|
+
import { setKeyFilePermissions } from '../../../../lib/ssh-utils.js';
|
|
10
|
+
export default class DevenvProjectInit extends Command {
|
|
11
|
+
static args = {
|
|
12
|
+
project: Args.string({
|
|
13
|
+
description: 'project name',
|
|
14
|
+
required: true,
|
|
15
|
+
}),
|
|
16
|
+
};
|
|
17
|
+
static description = 'Initialize a project on a remote dev environment.';
|
|
18
|
+
static examples = [
|
|
19
|
+
'<%= config.bin %> <%= command.id %> my-app -w my-workspace',
|
|
20
|
+
];
|
|
21
|
+
static flags = {
|
|
22
|
+
force: Flags.boolean({
|
|
23
|
+
char: 'f',
|
|
24
|
+
default: false,
|
|
25
|
+
description: 'continue even if folder already exists',
|
|
26
|
+
}),
|
|
27
|
+
workspace: Flags.string({
|
|
28
|
+
char: 'w',
|
|
29
|
+
description: 'name of the workspace',
|
|
30
|
+
required: true,
|
|
31
|
+
}),
|
|
32
|
+
};
|
|
33
|
+
async run() {
|
|
34
|
+
const { args, flags } = await this.parse(DevenvProjectInit);
|
|
35
|
+
const { project } = args;
|
|
36
|
+
const { force, workspace } = flags;
|
|
37
|
+
const backend = await getBackend();
|
|
38
|
+
const getWorkspaceEnvOutput = await backend.getWorkspaceEnv({ workspace });
|
|
39
|
+
if (!getWorkspaceEnvOutput.success) {
|
|
40
|
+
this.error(getWorkspaceEnvOutput.reason);
|
|
41
|
+
}
|
|
42
|
+
let { env } = getWorkspaceEnvOutput;
|
|
43
|
+
const executor$ = await getExecutorForWorkspace(workspace);
|
|
44
|
+
if (!executor$.success) {
|
|
45
|
+
this.error(executor$.reason);
|
|
46
|
+
}
|
|
47
|
+
const { executor } = executor$;
|
|
48
|
+
env = await executor.resolveEnvValues({ env });
|
|
49
|
+
const sshHost = env.devEnvSshHost;
|
|
50
|
+
const sshPrivateKey = env.devEnvSshPrivateKey;
|
|
51
|
+
const sshUser = env.devEnvSshUser;
|
|
52
|
+
const sshHostDns = env.devEnvSshHostDns;
|
|
53
|
+
if (!sshHost || !sshPrivateKey || !sshUser) {
|
|
54
|
+
this.error('devEnvSshHost, devEnvSshPrivateKey, and devEnvSshUser must be set in the workspace environment');
|
|
55
|
+
}
|
|
56
|
+
const host = sshHostDns || sshHost;
|
|
57
|
+
const tempKeyPath = path.join(os.tmpdir(), `hereya-ssh-${randomUUID()}`);
|
|
58
|
+
try {
|
|
59
|
+
await fs.writeFile(tempKeyPath, sshPrivateKey);
|
|
60
|
+
await setKeyFilePermissions(tempKeyPath);
|
|
61
|
+
const remoteScript = force
|
|
62
|
+
? `mkdir -p ~/${project} && cd ~/${project} && hereya init ${project} -w ${workspace}`
|
|
63
|
+
: `if [ -d ~/${project} ]; then echo "ERROR: Folder ~/${project} already exists. Use --force to continue." && exit 1; fi && mkdir -p ~/${project} && cd ~/${project} && hereya init ${project} -w ${workspace}`;
|
|
64
|
+
const sshArgs = ['-i', tempKeyPath, '-o', 'StrictHostKeyChecking=no', `${sshUser}@${host}`, remoteScript];
|
|
65
|
+
await this.spawnSsh(sshArgs);
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
try {
|
|
69
|
+
await fs.unlink(tempKeyPath);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// Ignore cleanup errors
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
spawnSsh(args) {
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
const child = spawn('ssh', args, {
|
|
79
|
+
stdio: 'inherit',
|
|
80
|
+
});
|
|
81
|
+
child.on('close', (code) => {
|
|
82
|
+
if (code === 0) {
|
|
83
|
+
resolve();
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
reject(new Error(`SSH exited with code ${code}`));
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
child.on('error', (err) => {
|
|
90
|
+
reject(err);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class DevenvSsh extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
};
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
protected spawnSsh(args: string[]): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import fs from 'node:fs/promises';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { getBackend } from '../../../backend/index.js';
|
|
8
|
+
import { getExecutorForWorkspace } from '../../../executor/context.js';
|
|
9
|
+
import { setKeyFilePermissions } from '../../../lib/ssh-utils.js';
|
|
10
|
+
export default class DevenvSsh extends Command {
|
|
11
|
+
static description = 'SSH into a dev environment instance.';
|
|
12
|
+
static examples = [
|
|
13
|
+
'<%= config.bin %> <%= command.id %> -w my-workspace',
|
|
14
|
+
];
|
|
15
|
+
static flags = {
|
|
16
|
+
workspace: Flags.string({
|
|
17
|
+
char: 'w',
|
|
18
|
+
description: 'name of the workspace to SSH into',
|
|
19
|
+
required: true,
|
|
20
|
+
}),
|
|
21
|
+
};
|
|
22
|
+
async run() {
|
|
23
|
+
const { flags } = await this.parse(DevenvSsh);
|
|
24
|
+
const { workspace } = flags;
|
|
25
|
+
const backend = await getBackend();
|
|
26
|
+
const getWorkspaceEnvOutput = await backend.getWorkspaceEnv({ workspace });
|
|
27
|
+
if (!getWorkspaceEnvOutput.success) {
|
|
28
|
+
this.error(getWorkspaceEnvOutput.reason);
|
|
29
|
+
}
|
|
30
|
+
let { env } = getWorkspaceEnvOutput;
|
|
31
|
+
const executor$ = await getExecutorForWorkspace(workspace);
|
|
32
|
+
if (!executor$.success) {
|
|
33
|
+
this.error(executor$.reason);
|
|
34
|
+
}
|
|
35
|
+
const { executor } = executor$;
|
|
36
|
+
env = await executor.resolveEnvValues({ env });
|
|
37
|
+
const sshHost = env.devEnvSshHost;
|
|
38
|
+
const sshPrivateKey = env.devEnvSshPrivateKey;
|
|
39
|
+
const sshUser = env.devEnvSshUser;
|
|
40
|
+
const sshHostDns = env.devEnvSshHostDns;
|
|
41
|
+
if (!sshHost || !sshPrivateKey || !sshUser) {
|
|
42
|
+
this.error('devEnvSshHost, devEnvSshPrivateKey, and devEnvSshUser must be set in the workspace environment');
|
|
43
|
+
}
|
|
44
|
+
const host = sshHostDns || sshHost;
|
|
45
|
+
const tempKeyPath = path.join(os.tmpdir(), `hereya-ssh-${randomUUID()}`);
|
|
46
|
+
try {
|
|
47
|
+
await fs.writeFile(tempKeyPath, sshPrivateKey);
|
|
48
|
+
await setKeyFilePermissions(tempKeyPath);
|
|
49
|
+
const sshArgs = ['-i', tempKeyPath, '-o', 'StrictHostKeyChecking=no', `${sshUser}@${host}`];
|
|
50
|
+
await this.spawnSsh(sshArgs);
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
try {
|
|
54
|
+
await fs.unlink(tempKeyPath);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Ignore cleanup errors
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
spawnSsh(args) {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
const child = spawn('ssh', args, {
|
|
64
|
+
stdio: 'inherit',
|
|
65
|
+
});
|
|
66
|
+
child.on('close', (code) => {
|
|
67
|
+
if (code === 0) {
|
|
68
|
+
resolve();
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
reject(new Error(`SSH exited with code ${code}`));
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
child.on('error', (err) => {
|
|
75
|
+
reject(err);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class DevenvUninstall extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
debug: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
};
|
|
9
|
+
run(): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { Listr, ListrLogger, ListrLogLevels } from 'listr2';
|
|
3
|
+
import { getBackend } from '../../../backend/index.js';
|
|
4
|
+
import { getExecutor } from '../../../executor/index.js';
|
|
5
|
+
import { getLogger, getLogPath, isDebug, setDebug } from '../../../lib/log.js';
|
|
6
|
+
import { delay } from '../../../lib/shell.js';
|
|
7
|
+
export default class DevenvUninstall extends Command {
|
|
8
|
+
static description = 'Destroy and remove the dev environment from a workspace.';
|
|
9
|
+
static examples = ['<%= config.bin %> <%= command.id %> -w my-workspace'];
|
|
10
|
+
static flags = {
|
|
11
|
+
debug: Flags.boolean({
|
|
12
|
+
default: false,
|
|
13
|
+
description: 'enable debug mode',
|
|
14
|
+
}),
|
|
15
|
+
workspace: Flags.string({
|
|
16
|
+
char: 'w',
|
|
17
|
+
description: 'name of the workspace to remove the dev environment from',
|
|
18
|
+
required: true,
|
|
19
|
+
}),
|
|
20
|
+
};
|
|
21
|
+
async run() {
|
|
22
|
+
const { flags } = await this.parse(DevenvUninstall);
|
|
23
|
+
setDebug(flags.debug);
|
|
24
|
+
const pkg = 'hereya/dev-env-aws';
|
|
25
|
+
const myLogger = new ListrLogger({ useIcons: false });
|
|
26
|
+
const task = new Listr([
|
|
27
|
+
{
|
|
28
|
+
async task(ctx, task) {
|
|
29
|
+
return task.newListr([
|
|
30
|
+
{
|
|
31
|
+
async task(ctx) {
|
|
32
|
+
const backend = await getBackend();
|
|
33
|
+
const loadWorkspaceOutput = await backend.getWorkspace(flags.workspace);
|
|
34
|
+
if (!loadWorkspaceOutput.found || loadWorkspaceOutput.hasError) {
|
|
35
|
+
throw new Error(`Workspace ${flags.workspace} not found`);
|
|
36
|
+
}
|
|
37
|
+
if (loadWorkspaceOutput.workspace.mirrorOf) {
|
|
38
|
+
throw new Error(`Workspace ${flags.workspace} is a mirror of ${loadWorkspaceOutput.workspace.mirrorOf}`);
|
|
39
|
+
}
|
|
40
|
+
const packageInfo = loadWorkspaceOutput.workspace.packages?.[pkg];
|
|
41
|
+
if (!packageInfo) {
|
|
42
|
+
throw new Error(`Package ${pkg} not found in workspace ${flags.workspace}`);
|
|
43
|
+
}
|
|
44
|
+
const installedVersion = packageInfo.version || '';
|
|
45
|
+
ctx.packageWithInstalledVersion = installedVersion ? `${pkg}@${installedVersion}` : pkg;
|
|
46
|
+
ctx.workspace = loadWorkspaceOutput;
|
|
47
|
+
await delay(500);
|
|
48
|
+
},
|
|
49
|
+
title: `Loading workspace ${flags.workspace}`,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
async task(ctx) {
|
|
53
|
+
const parameters = {
|
|
54
|
+
...ctx.workspace.workspace.packages?.[pkg].parameters,
|
|
55
|
+
};
|
|
56
|
+
ctx.parameters = parameters;
|
|
57
|
+
await delay(500);
|
|
58
|
+
},
|
|
59
|
+
title: 'Resolving parameters',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
rendererOptions: {
|
|
63
|
+
persistentOutput: isDebug(),
|
|
64
|
+
},
|
|
65
|
+
async task(ctx, task) {
|
|
66
|
+
const executor$ = getExecutor();
|
|
67
|
+
if (!executor$.success) {
|
|
68
|
+
throw new Error(executor$.reason);
|
|
69
|
+
}
|
|
70
|
+
const { executor } = executor$;
|
|
71
|
+
const destroyOutput = await executor.destroy({
|
|
72
|
+
logger: getLogger(task),
|
|
73
|
+
package: ctx.packageWithInstalledVersion,
|
|
74
|
+
parameters: ctx.parameters,
|
|
75
|
+
workspace: flags.workspace,
|
|
76
|
+
});
|
|
77
|
+
if (!destroyOutput.success) {
|
|
78
|
+
throw new Error(destroyOutput.reason);
|
|
79
|
+
}
|
|
80
|
+
ctx.destroyOutput = destroyOutput;
|
|
81
|
+
},
|
|
82
|
+
title: 'Destroying dev environment',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
async task(ctx) {
|
|
86
|
+
const { env, metadata } = ctx.destroyOutput;
|
|
87
|
+
const backend = await getBackend();
|
|
88
|
+
const output = await backend.removePackageFromWorkspace({
|
|
89
|
+
env,
|
|
90
|
+
infra: metadata.infra,
|
|
91
|
+
package: ctx.destroyOutput.pkgName,
|
|
92
|
+
workspace: flags.workspace,
|
|
93
|
+
});
|
|
94
|
+
if (!output.success) {
|
|
95
|
+
throw new Error(output.reason);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
title: 'Removing exported environment variables from workspace',
|
|
99
|
+
},
|
|
100
|
+
]);
|
|
101
|
+
},
|
|
102
|
+
title: `Uninstalling dev environment from workspace ${flags.workspace}`,
|
|
103
|
+
},
|
|
104
|
+
], { concurrent: false, rendererOptions: { collapseSubtasks: !isDebug() } });
|
|
105
|
+
try {
|
|
106
|
+
await task.run();
|
|
107
|
+
myLogger.log(ListrLogLevels.COMPLETED, `Dev environment uninstalled successfully from workspace ${flags.workspace}`);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
this.error(`${error.message}
|
|
111
|
+
|
|
112
|
+
See ${getLogPath()} for more details`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -192,7 +192,7 @@ export class CloudPackageManager {
|
|
|
192
192
|
try {
|
|
193
193
|
// Try to get package from registry
|
|
194
194
|
// Registry API accepts both 'name' and 'org/name' formats
|
|
195
|
-
const result = version
|
|
195
|
+
const result = (version && version !== 'latest')
|
|
196
196
|
? await this.backend.getPackageByVersion(packageName, version)
|
|
197
197
|
: await this.backend.getPackageLatest(packageName);
|
|
198
198
|
if (result.success && result.package) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { exec } from 'node:child_process';
|
|
2
|
+
import { chmod } from 'node:fs/promises';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
4
|
+
const execAsync = promisify(exec);
|
|
5
|
+
export async function setKeyFilePermissions(filePath) {
|
|
6
|
+
// On Windows, use icacls to restrict the key file to the current user only.
|
|
7
|
+
// On Unix, chmod 600 restricts the file to owner read/write.
|
|
8
|
+
await (process.platform === 'win32'
|
|
9
|
+
? execAsync(`icacls "${filePath}" /inheritance:r /grant:r "%USERNAME%:R"`)
|
|
10
|
+
: chmod(filePath, 0o600));
|
|
11
|
+
}
|
|
12
|
+
export async function setSshDirPermissions(dirPath) {
|
|
13
|
+
// chmod is a no-op on Windows; permissions are inherited from the parent directory
|
|
14
|
+
if (process.platform === 'win32') {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
await chmod(dirPath, 0o700);
|
|
18
|
+
}
|