hereya-cli 0.33.0 → 0.35.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 +64 -23
- package/dist/backend/cloud/cloud-backend.d.ts +27 -0
- package/dist/backend/cloud/cloud-backend.js +334 -0
- package/dist/backend/cloud/login.d.ts +16 -0
- package/dist/backend/cloud/login.js +145 -0
- package/dist/backend/cloud/logout.d.ts +9 -0
- package/dist/backend/cloud/logout.js +12 -0
- package/dist/backend/cloud/utils.d.ts +3 -0
- package/dist/backend/cloud/utils.js +6 -0
- package/dist/backend/common.d.ts +3 -0
- package/dist/backend/config.d.ts +30 -3
- package/dist/backend/config.js +36 -2
- package/dist/backend/file.js +2 -2
- package/dist/backend/index.d.ts +3 -1
- package/dist/backend/index.js +26 -3
- package/dist/backend/secrets.d.ts +5 -0
- package/dist/backend/secrets.js +66 -0
- package/dist/commands/bootstrap/index.js +15 -7
- package/dist/commands/init/index.js +1 -1
- package/dist/commands/login/index.d.ts +9 -0
- package/dist/commands/login/index.js +27 -0
- package/dist/commands/logout/index.d.ts +6 -0
- package/dist/commands/logout/index.js +23 -0
- package/dist/commands/unbootstrap/index.js +12 -1
- package/dist/executor/local.js +3 -1
- package/dist/infrastructure/aws-config.js +16 -5
- package/dist/infrastructure/aws.js +8 -23
- package/oclif.manifest.json +56 -1
- package/package.json +5 -2
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/* eslint-disable n/no-unsupported-features/node-builtins */
|
|
2
|
+
import machineId from 'node-machine-id';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import http from 'node:http';
|
|
5
|
+
import net from 'node:net';
|
|
6
|
+
import { browserUtils } from './utils.js';
|
|
7
|
+
export async function loginToCloudBackend(url) {
|
|
8
|
+
const registerResult = await registerDevice(url);
|
|
9
|
+
if (!registerResult.success) {
|
|
10
|
+
return registerResult;
|
|
11
|
+
}
|
|
12
|
+
const { clientId } = registerResult;
|
|
13
|
+
const codeVerifier = crypto.randomBytes(32).toString('hex');
|
|
14
|
+
const codeChallengeRaw = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(codeVerifier));
|
|
15
|
+
const codeChallenge = Buffer.from(codeChallengeRaw).toString('base64');
|
|
16
|
+
const availablePort = await getAvailablePort(3000);
|
|
17
|
+
const myRedirectUri = `http://localhost:${availablePort}/auth/cli/callback`;
|
|
18
|
+
const redirectUrl = `${url}/auth/cli/login?client_id=${clientId}&code_challenge=${codeChallenge}&redirect_uri=${myRedirectUri}`;
|
|
19
|
+
console.log('Opening browser to', redirectUrl);
|
|
20
|
+
browserUtils.open(redirectUrl);
|
|
21
|
+
const codeResult = await waitForAuthorizationCode(availablePort);
|
|
22
|
+
if (!codeResult.success) {
|
|
23
|
+
return {
|
|
24
|
+
error: codeResult.error,
|
|
25
|
+
success: false,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const { code } = codeResult;
|
|
29
|
+
const formData = new FormData();
|
|
30
|
+
formData.append('clientId', clientId);
|
|
31
|
+
formData.append('code', code);
|
|
32
|
+
formData.append('codeVerifier', codeVerifier);
|
|
33
|
+
const tokenResult = await fetch(`${url}/auth/cli/code`, {
|
|
34
|
+
body: formData,
|
|
35
|
+
method: 'POST',
|
|
36
|
+
});
|
|
37
|
+
if (!tokenResult.ok) {
|
|
38
|
+
return {
|
|
39
|
+
error: `Failed to get token: ${JSON.stringify(await tokenResult.json())}`,
|
|
40
|
+
success: false,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const token = (await tokenResult.json());
|
|
44
|
+
return {
|
|
45
|
+
accessToken: token.data.accessToken,
|
|
46
|
+
clientId,
|
|
47
|
+
refreshToken: token.data.refreshToken,
|
|
48
|
+
success: true,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export async function registerDevice(url) {
|
|
52
|
+
const registerUrl = `${url}/auth/cli/register`;
|
|
53
|
+
const deviceId = await machineId.machineId();
|
|
54
|
+
const formData = new FormData();
|
|
55
|
+
formData.append('deviceId', deviceId);
|
|
56
|
+
const response = await fetch(registerUrl, {
|
|
57
|
+
body: formData,
|
|
58
|
+
method: 'POST',
|
|
59
|
+
});
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
return {
|
|
62
|
+
error: `Failed to register device: ${JSON.stringify(await response.json())}`,
|
|
63
|
+
success: false,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const result = (await response.json());
|
|
67
|
+
return {
|
|
68
|
+
clientId: result.data.clientId,
|
|
69
|
+
success: true,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
async function getAvailablePort(startPort) {
|
|
73
|
+
return new Promise((resolve) => {
|
|
74
|
+
const server = net.createServer();
|
|
75
|
+
server.unref();
|
|
76
|
+
server.on('error', () => {
|
|
77
|
+
server.listen(++startPort);
|
|
78
|
+
});
|
|
79
|
+
server.listen(startPort, () => {
|
|
80
|
+
server.close(() => resolve(startPort));
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
async function waitForAuthorizationCode(port) {
|
|
85
|
+
return new Promise((resolve) => {
|
|
86
|
+
const server = http.createServer((req, res) => {
|
|
87
|
+
const searchParams = new URLSearchParams(req.url.split('?')[1]);
|
|
88
|
+
const code = searchParams.get('code');
|
|
89
|
+
if (code) {
|
|
90
|
+
resolve({ code, success: true });
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
resolve({ error: 'No authorization code received', success: false });
|
|
94
|
+
}
|
|
95
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
96
|
+
res.end(`
|
|
97
|
+
<!DOCTYPE html>
|
|
98
|
+
<html>
|
|
99
|
+
<head>
|
|
100
|
+
<style>
|
|
101
|
+
body {
|
|
102
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
103
|
+
display: flex;
|
|
104
|
+
justify-content: center;
|
|
105
|
+
align-items: center;
|
|
106
|
+
height: 100vh;
|
|
107
|
+
margin: 0;
|
|
108
|
+
background: #f5f5f5;
|
|
109
|
+
}
|
|
110
|
+
.card {
|
|
111
|
+
background: white;
|
|
112
|
+
padding: 2rem;
|
|
113
|
+
border-radius: 8px;
|
|
114
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
115
|
+
text-align: center;
|
|
116
|
+
}
|
|
117
|
+
.success-icon {
|
|
118
|
+
color: #10B981;
|
|
119
|
+
font-size: 3rem;
|
|
120
|
+
margin-bottom: 1rem;
|
|
121
|
+
}
|
|
122
|
+
.message {
|
|
123
|
+
color: #666;
|
|
124
|
+
font-size: 0.9rem;
|
|
125
|
+
margin-top: 1rem;
|
|
126
|
+
}
|
|
127
|
+
</style>
|
|
128
|
+
</head>
|
|
129
|
+
<body>
|
|
130
|
+
<div class="card">
|
|
131
|
+
<div class="success-icon">✓</div>
|
|
132
|
+
<h2>Authorization Successful!</h2>
|
|
133
|
+
<p class="message">You can close this tab now (Ctrl+W or Cmd+W)</p>
|
|
134
|
+
</div>
|
|
135
|
+
</body>
|
|
136
|
+
</html>
|
|
137
|
+
`);
|
|
138
|
+
server.closeAllConnections();
|
|
139
|
+
server.close();
|
|
140
|
+
});
|
|
141
|
+
server.listen(port, () => {
|
|
142
|
+
console.log(`Waiting for authorization code on port ${port}...`);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/* eslint-disable n/no-unsupported-features/node-builtins */
|
|
2
|
+
export async function logoutFromCloudBackend({ secret, url }) {
|
|
3
|
+
if (!secret) {
|
|
4
|
+
return { didDelete: false, success: true };
|
|
5
|
+
}
|
|
6
|
+
return fetch(`${url}/auth/cli/logout`, {
|
|
7
|
+
headers: {
|
|
8
|
+
Authorization: `Bearer ${secret.refreshToken}`,
|
|
9
|
+
},
|
|
10
|
+
method: 'POST',
|
|
11
|
+
});
|
|
12
|
+
}
|
package/dist/backend/common.d.ts
CHANGED
|
@@ -133,6 +133,7 @@ export type GetStateOutput = {
|
|
|
133
133
|
found: true;
|
|
134
134
|
} | {
|
|
135
135
|
found: false;
|
|
136
|
+
reason?: string;
|
|
136
137
|
};
|
|
137
138
|
export type DeleteWorkspaceInput = {
|
|
138
139
|
name: string;
|
|
@@ -158,6 +159,7 @@ export type GetProvisioningIdOutput = {
|
|
|
158
159
|
success: false;
|
|
159
160
|
};
|
|
160
161
|
export type SetEnvVarInput = {
|
|
162
|
+
infrastructure: InfrastructureType;
|
|
161
163
|
name: string;
|
|
162
164
|
value: string;
|
|
163
165
|
workspace: string;
|
|
@@ -169,6 +171,7 @@ export type SetEnvVarOutput = {
|
|
|
169
171
|
success: true;
|
|
170
172
|
};
|
|
171
173
|
export type UnsetEnvVarInput = {
|
|
174
|
+
infrastructure: InfrastructureType;
|
|
172
175
|
name: string;
|
|
173
176
|
workspace: string;
|
|
174
177
|
};
|
package/dist/backend/config.d.ts
CHANGED
|
@@ -1,10 +1,37 @@
|
|
|
1
|
-
import { BackendType } from
|
|
1
|
+
import { BackendType } from './index.js';
|
|
2
2
|
export declare function getCurrentBackendType(): Promise<BackendType>;
|
|
3
3
|
export declare function setBackendType(type: BackendType): Promise<void>;
|
|
4
|
-
export declare function loadBackendConfig(): Promise<
|
|
5
|
-
|
|
4
|
+
export declare function loadBackendConfig(): Promise<BackendConfig>;
|
|
5
|
+
export declare function saveCloudCredentials(credentials: {
|
|
6
|
+
accessToken: string;
|
|
7
|
+
clientId: string;
|
|
8
|
+
refreshToken: string;
|
|
9
|
+
url: string;
|
|
10
|
+
}): Promise<void>;
|
|
11
|
+
export declare function deleteCloudCredentials(): Promise<{
|
|
12
|
+
didDelete: boolean;
|
|
13
|
+
originalConfig: {
|
|
14
|
+
cloud?: {
|
|
15
|
+
clientId: string;
|
|
16
|
+
url: string;
|
|
17
|
+
};
|
|
18
|
+
current: BackendType;
|
|
19
|
+
};
|
|
20
|
+
secret: {
|
|
21
|
+
accessToken: string;
|
|
22
|
+
refreshToken: string;
|
|
23
|
+
} | null;
|
|
24
|
+
success: boolean;
|
|
6
25
|
}>;
|
|
26
|
+
export declare function getCloudCredentials(clientId: string): Promise<{
|
|
27
|
+
accessToken: string;
|
|
28
|
+
refreshToken: string;
|
|
29
|
+
} | null>;
|
|
7
30
|
export declare function getBackendConfigPath(): string;
|
|
8
31
|
export type BackendConfig = {
|
|
32
|
+
cloud?: {
|
|
33
|
+
clientId: string;
|
|
34
|
+
url: string;
|
|
35
|
+
};
|
|
9
36
|
current: BackendType;
|
|
10
37
|
};
|
package/dist/backend/config.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { load, save } from
|
|
4
|
-
import { BackendType } from
|
|
3
|
+
import { load, save } from '../lib/yaml-utils.js';
|
|
4
|
+
import { BackendType } from './index.js';
|
|
5
|
+
import { secretManager } from './secrets.js';
|
|
5
6
|
export async function getCurrentBackendType() {
|
|
6
7
|
const config = await loadBackendConfig();
|
|
7
8
|
return config.current;
|
|
@@ -18,6 +19,39 @@ export async function loadBackendConfig() {
|
|
|
18
19
|
}
|
|
19
20
|
return data;
|
|
20
21
|
}
|
|
22
|
+
export async function saveCloudCredentials(credentials) {
|
|
23
|
+
await secretManager.saveSecret(credentials.clientId, {
|
|
24
|
+
accessToken: credentials.accessToken,
|
|
25
|
+
refreshToken: credentials.refreshToken,
|
|
26
|
+
});
|
|
27
|
+
const config = await loadBackendConfig();
|
|
28
|
+
config.cloud = {
|
|
29
|
+
clientId: credentials.clientId,
|
|
30
|
+
url: credentials.url,
|
|
31
|
+
};
|
|
32
|
+
config.current = BackendType.Cloud;
|
|
33
|
+
await save(config, getBackendConfigPath());
|
|
34
|
+
}
|
|
35
|
+
export async function deleteCloudCredentials() {
|
|
36
|
+
const config = await loadBackendConfig();
|
|
37
|
+
const originalConfig = { ...config };
|
|
38
|
+
if (!config.cloud) {
|
|
39
|
+
return { didDelete: false, originalConfig, secret: null, success: true };
|
|
40
|
+
}
|
|
41
|
+
const secret = await secretManager.getSecret(config.cloud.clientId);
|
|
42
|
+
if (secret) {
|
|
43
|
+
await secretManager.deleteSecret(config.cloud.clientId);
|
|
44
|
+
}
|
|
45
|
+
config.cloud = undefined;
|
|
46
|
+
if (config.current === BackendType.Cloud) {
|
|
47
|
+
config.current = BackendType.Local;
|
|
48
|
+
}
|
|
49
|
+
await save(config, getBackendConfigPath());
|
|
50
|
+
return { didDelete: true, originalConfig, secret, success: true };
|
|
51
|
+
}
|
|
52
|
+
export async function getCloudCredentials(clientId) {
|
|
53
|
+
return secretManager.getSecret(clientId);
|
|
54
|
+
}
|
|
21
55
|
export function getBackendConfigPath() {
|
|
22
56
|
return path.join(os.homedir(), '.hereya', 'backend.yaml');
|
|
23
57
|
}
|
package/dist/backend/file.js
CHANGED
|
@@ -24,7 +24,7 @@ export class FileBackend {
|
|
|
24
24
|
const { workspace } = workspace$;
|
|
25
25
|
if (workspace.mirrorOf) {
|
|
26
26
|
return {
|
|
27
|
-
reason: `Cannot add package to
|
|
27
|
+
reason: `Cannot add package to mirroring workspace ${input.workspace}`,
|
|
28
28
|
success: false,
|
|
29
29
|
};
|
|
30
30
|
}
|
|
@@ -372,7 +372,7 @@ export class FileBackend {
|
|
|
372
372
|
const { workspace } = workspace$;
|
|
373
373
|
workspace.env = {
|
|
374
374
|
...workspace.env,
|
|
375
|
-
[input.name]: input.value
|
|
375
|
+
[input.name]: `${input.infrastructure}:${input.value}`,
|
|
376
376
|
};
|
|
377
377
|
await this.saveWorkspace(workspace, input.workspace);
|
|
378
378
|
return {
|
package/dist/backend/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Backend } from './common.js';
|
|
2
|
-
export declare function getBackend(): Promise<Backend>;
|
|
2
|
+
export declare function getBackend(type?: BackendType): Promise<Backend>;
|
|
3
|
+
export declare function setBackendType(type: BackendType): void;
|
|
3
4
|
export declare enum BackendType {
|
|
5
|
+
Cloud = "cloud",
|
|
4
6
|
Local = "local",
|
|
5
7
|
S3 = "s3"
|
|
6
8
|
}
|
package/dist/backend/index.js
CHANGED
|
@@ -1,14 +1,33 @@
|
|
|
1
1
|
import { getAwsConfig } from '../infrastructure/aws-config.js';
|
|
2
|
-
import {
|
|
2
|
+
import { CloudBackend } from './cloud/cloud-backend.js';
|
|
3
|
+
import { getCloudCredentials, loadBackendConfig } from './config.js';
|
|
3
4
|
import { LocalFileBackend } from './local.js';
|
|
4
5
|
import { S3FileBackend } from './s3.js';
|
|
5
6
|
let backend;
|
|
6
|
-
|
|
7
|
+
let currentBackendType;
|
|
8
|
+
export async function getBackend(type) {
|
|
7
9
|
if (backend) {
|
|
8
10
|
return backend;
|
|
9
11
|
}
|
|
10
|
-
const
|
|
12
|
+
const backendConfig = await loadBackendConfig();
|
|
13
|
+
const backendType = type ?? currentBackendType ?? backendConfig.current;
|
|
11
14
|
switch (backendType) {
|
|
15
|
+
case BackendType.Cloud: {
|
|
16
|
+
if (!backendConfig.cloud) {
|
|
17
|
+
throw new Error('Cloud credentials not found. Please run `hereya login` first.');
|
|
18
|
+
}
|
|
19
|
+
const credentials = await getCloudCredentials(backendConfig.cloud.clientId);
|
|
20
|
+
if (!credentials) {
|
|
21
|
+
throw new Error('Cloud credentials not found. Please run `hereya login` first.');
|
|
22
|
+
}
|
|
23
|
+
backend = new CloudBackend({
|
|
24
|
+
accessToken: credentials.accessToken,
|
|
25
|
+
clientId: backendConfig.cloud.clientId,
|
|
26
|
+
refreshToken: credentials.refreshToken,
|
|
27
|
+
url: backendConfig.cloud.url,
|
|
28
|
+
});
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
12
31
|
case BackendType.Local: {
|
|
13
32
|
backend = new LocalFileBackend();
|
|
14
33
|
break;
|
|
@@ -27,8 +46,12 @@ export async function getBackend() {
|
|
|
27
46
|
}
|
|
28
47
|
return backend;
|
|
29
48
|
}
|
|
49
|
+
export function setBackendType(type) {
|
|
50
|
+
currentBackendType = type;
|
|
51
|
+
}
|
|
30
52
|
export var BackendType;
|
|
31
53
|
(function (BackendType) {
|
|
54
|
+
BackendType["Cloud"] = "cloud";
|
|
32
55
|
BackendType["Local"] = "local";
|
|
33
56
|
BackendType["S3"] = "s3";
|
|
34
57
|
})(BackendType || (BackendType = {}));
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
const SECRETS_DIR = path.join(os.homedir(), '.hereya', 'secrets');
|
|
5
|
+
async function ensureSecretsDir() {
|
|
6
|
+
try {
|
|
7
|
+
await fs.mkdir(SECRETS_DIR, { recursive: true });
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
// Ignore if directory already exists
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
async function getSecretPath(name) {
|
|
14
|
+
await ensureSecretsDir();
|
|
15
|
+
return path.join(SECRETS_DIR, `${name}.json`);
|
|
16
|
+
}
|
|
17
|
+
export const secretManager = {
|
|
18
|
+
async deleteSecret(name) {
|
|
19
|
+
try {
|
|
20
|
+
const keytar = await import('keytar').then((m) => m.default);
|
|
21
|
+
await keytar.deletePassword('hereya', name);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// Fallback to file-based storage
|
|
25
|
+
const secretPath = await getSecretPath(name);
|
|
26
|
+
try {
|
|
27
|
+
await fs.unlink(secretPath);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Ignore if file doesn't exist
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
async getSecret(name) {
|
|
35
|
+
try {
|
|
36
|
+
const keytar = await import('keytar').then((m) => m.default);
|
|
37
|
+
const value = await keytar.getPassword('hereya', name);
|
|
38
|
+
if (!value) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return JSON.parse(value);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Fallback to file-based storage
|
|
45
|
+
const secretPath = await getSecretPath(name);
|
|
46
|
+
try {
|
|
47
|
+
const value = await fs.readFile(secretPath, 'utf8');
|
|
48
|
+
return JSON.parse(value);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
async saveSecret(name, value) {
|
|
56
|
+
try {
|
|
57
|
+
const keytar = await import('keytar').then((m) => m.default);
|
|
58
|
+
await keytar.setPassword('hereya', name, JSON.stringify(value));
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Fallback to file-based storage
|
|
62
|
+
const secretPath = await getSecretPath(name);
|
|
63
|
+
await fs.writeFile(secretPath, JSON.stringify(value), 'utf8');
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
};
|
|
@@ -1,28 +1,36 @@
|
|
|
1
1
|
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { BackendType, setBackendType } from '../../backend/index.js';
|
|
2
3
|
import { getInfrastructure } from '../../infrastructure/index.js';
|
|
4
|
+
import { getLogPath, setDebug } from '../../lib/log.js';
|
|
3
5
|
export default class Bootstrap extends Command {
|
|
4
6
|
static args = {
|
|
5
7
|
infrastructureType: Args.string({
|
|
6
8
|
description: 'infrastructure to bootstrap. Options are local, aws',
|
|
7
|
-
required: true
|
|
8
|
-
})
|
|
9
|
+
required: true,
|
|
10
|
+
}),
|
|
9
11
|
};
|
|
10
12
|
static description = 'Install necessary resources for hereya operations in an infrastructure.';
|
|
11
|
-
static examples = [
|
|
12
|
-
'<%= config.bin %> <%= command.id %> aws',
|
|
13
|
-
'<%= config.bin %> <%= command.id %> local'
|
|
14
|
-
];
|
|
13
|
+
static examples = ['<%= config.bin %> <%= command.id %> aws', '<%= config.bin %> <%= command.id %> local'];
|
|
15
14
|
static flags = {
|
|
16
15
|
force: Flags.boolean({ char: 'f', description: 'redeploy hereya resources if already deployed' }),
|
|
17
16
|
};
|
|
18
17
|
async run() {
|
|
19
18
|
const { args, flags } = await this.parse(Bootstrap);
|
|
19
|
+
setBackendType(BackendType.Local);
|
|
20
20
|
const infrastructure$ = getInfrastructure({ type: args.infrastructureType });
|
|
21
21
|
if (!infrastructure$.supported) {
|
|
22
22
|
this.warn(infrastructure$.reason);
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
25
25
|
const { infrastructure } = infrastructure$;
|
|
26
|
-
|
|
26
|
+
setDebug(true);
|
|
27
|
+
try {
|
|
28
|
+
await infrastructure.bootstrap({ force: flags.force });
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
this.error(`${error.message}
|
|
32
|
+
|
|
33
|
+
See ${getLogPath()} for more details`);
|
|
34
|
+
}
|
|
27
35
|
}
|
|
28
36
|
}
|
|
@@ -36,7 +36,7 @@ export default class Init extends Command {
|
|
|
36
36
|
workspace: flags.workspace,
|
|
37
37
|
});
|
|
38
38
|
const content = {
|
|
39
|
-
project: initProjectOutput.project.
|
|
39
|
+
project: initProjectOutput.project.name,
|
|
40
40
|
workspace: initProjectOutput.workspace.name,
|
|
41
41
|
};
|
|
42
42
|
await configManager.saveConfig({ config: content, projectRootDir });
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Login extends Command {
|
|
3
|
+
static args: {
|
|
4
|
+
url: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Args, Command } from '@oclif/core';
|
|
2
|
+
import { loginToCloudBackend } from '../../backend/cloud/login.js';
|
|
3
|
+
import { saveCloudCredentials } from '../../backend/config.js';
|
|
4
|
+
export default class Login extends Command {
|
|
5
|
+
static args = {
|
|
6
|
+
url: Args.string({ description: 'URL of the Hereya Cloud backend', required: true }),
|
|
7
|
+
};
|
|
8
|
+
static description = 'Login to the Hereya Cloud backend';
|
|
9
|
+
static examples = [
|
|
10
|
+
'<%= config.bin %> <%= command.id %> https://cloud.hereya.dev',
|
|
11
|
+
'<%= config.bin %> <%= command.id %> http://localhost:5173',
|
|
12
|
+
];
|
|
13
|
+
async run() {
|
|
14
|
+
const { args } = await this.parse(Login);
|
|
15
|
+
const result = await loginToCloudBackend(args.url);
|
|
16
|
+
if (!result.success) {
|
|
17
|
+
this.error(result.error);
|
|
18
|
+
}
|
|
19
|
+
await saveCloudCredentials({
|
|
20
|
+
accessToken: result.accessToken,
|
|
21
|
+
clientId: result.clientId,
|
|
22
|
+
refreshToken: result.refreshToken,
|
|
23
|
+
url: args.url,
|
|
24
|
+
});
|
|
25
|
+
this.log(`Logged in to ${args.url}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { logoutFromCloudBackend } from '../../backend/cloud/logout.js';
|
|
3
|
+
import { deleteCloudCredentials } from '../../backend/config.js';
|
|
4
|
+
export default class Logout extends Command {
|
|
5
|
+
static description = 'Logout from Hereya Cloud';
|
|
6
|
+
static examples = ['<%= config.bin %> <%= command.id %>'];
|
|
7
|
+
async run() {
|
|
8
|
+
await this.parse(Logout);
|
|
9
|
+
const result = await deleteCloudCredentials();
|
|
10
|
+
if (result.originalConfig.cloud) {
|
|
11
|
+
await logoutFromCloudBackend({
|
|
12
|
+
secret: result.secret,
|
|
13
|
+
url: result.originalConfig.cloud.url,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
if (result.didDelete) {
|
|
17
|
+
this.log('Logged out from Hereya Cloud');
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
this.log('Not logged in to Hereya Cloud');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { BackendType, setBackendType } from '../../backend/index.js';
|
|
2
3
|
import { getInfrastructure } from '../../infrastructure/index.js';
|
|
4
|
+
import { getLogPath, setDebug } from '../../lib/log.js';
|
|
3
5
|
export default class Unbootstrap extends Command {
|
|
4
6
|
static args = {
|
|
5
7
|
infrastructureType: Args.string({
|
|
@@ -17,12 +19,21 @@ export default class Unbootstrap extends Command {
|
|
|
17
19
|
};
|
|
18
20
|
async run() {
|
|
19
21
|
const { args, flags } = await this.parse(Unbootstrap);
|
|
22
|
+
setBackendType(BackendType.Local);
|
|
20
23
|
const infrastructure$ = getInfrastructure({ type: args.infrastructureType });
|
|
21
24
|
if (!infrastructure$.supported) {
|
|
22
25
|
this.warn(infrastructure$.reason);
|
|
23
26
|
return;
|
|
24
27
|
}
|
|
25
28
|
const { infrastructure } = infrastructure$;
|
|
26
|
-
|
|
29
|
+
setDebug(true);
|
|
30
|
+
try {
|
|
31
|
+
await infrastructure.unbootstrap({ force: flags.force });
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
this.error(`${error.message}
|
|
35
|
+
|
|
36
|
+
See ${getLogPath()} for more details`);
|
|
37
|
+
}
|
|
27
38
|
}
|
|
28
39
|
}
|
package/dist/executor/local.js
CHANGED
|
@@ -76,8 +76,9 @@ export class LocalExecutor {
|
|
|
76
76
|
return { reason: setEnvVarOutput.reason, success: false };
|
|
77
77
|
}
|
|
78
78
|
return backend.setEnvVar({
|
|
79
|
+
infrastructure: input.infra,
|
|
79
80
|
name: input.name,
|
|
80
|
-
value:
|
|
81
|
+
value: setEnvVarOutput.value,
|
|
81
82
|
workspace: input.workspace,
|
|
82
83
|
});
|
|
83
84
|
}
|
|
@@ -116,6 +117,7 @@ export class LocalExecutor {
|
|
|
116
117
|
};
|
|
117
118
|
}
|
|
118
119
|
return backend.unsetEnvVar({
|
|
120
|
+
infrastructure: infra,
|
|
119
121
|
name: input.name,
|
|
120
122
|
workspace: input.workspace,
|
|
121
123
|
});
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
import { GetParameterCommand, SSMClient } from '@aws-sdk/client-ssm';
|
|
2
|
+
import { getDefaultLogger } from '../lib/log.js';
|
|
2
3
|
const configKey = '/hereya-bootstrap/config';
|
|
3
4
|
export function getAwsConfigKey() {
|
|
4
5
|
return configKey;
|
|
5
6
|
}
|
|
6
7
|
export async function getAwsConfig() {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
try {
|
|
9
|
+
const ssmClient = new SSMClient({});
|
|
10
|
+
const ssmParameter = await ssmClient.send(new GetParameterCommand({
|
|
11
|
+
Name: configKey,
|
|
12
|
+
}));
|
|
13
|
+
return JSON.parse(ssmParameter.Parameter?.Value ?? '{}');
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
getDefaultLogger().error(`Could not get AWS config: ${error.message}`);
|
|
17
|
+
return {
|
|
18
|
+
backendBucket: '',
|
|
19
|
+
terraformStateBucketName: '',
|
|
20
|
+
terraformStateLockTableName: '',
|
|
21
|
+
};
|
|
22
|
+
}
|
|
12
23
|
}
|