@elding/cli 0.2.0 → 0.8.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/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +58 -0
- package/dist/commands/init.js +29 -5
- package/dist/commands/keys.d.ts +1 -0
- package/dist/commands/keys.js +31 -0
- package/dist/commands/login.js +40 -25
- package/dist/commands/open.d.ts +1 -0
- package/dist/commands/open.js +13 -0
- package/dist/commands/proxy.d.ts +6 -1
- package/dist/commands/proxy.js +23 -7
- package/dist/commands/run.d.ts +4 -1
- package/dist/commands/run.js +17 -6
- package/dist/commands/sets.d.ts +1 -0
- package/dist/commands/sets.js +26 -0
- package/dist/commands/status.js +3 -2
- package/dist/commands/use.d.ts +1 -0
- package/dist/commands/use.js +37 -0
- package/dist/commands/whoami.d.ts +1 -0
- package/dist/commands/whoami.js +12 -0
- package/dist/index.js +75 -9
- package/dist/lib/api.d.ts +11 -1
- package/dist/lib/api.js +65 -20
- package/dist/lib/apiUrl.d.ts +1 -0
- package/dist/lib/apiUrl.js +44 -0
- package/dist/lib/config.d.ts +11 -0
- package/dist/lib/config.js +100 -7
- package/dist/lib/env.d.ts +5 -0
- package/dist/lib/env.js +49 -0
- package/dist/lib/keychain.d.ts +3 -0
- package/dist/lib/keychain.js +39 -0
- package/dist/lib/logBatcher.d.ts +5 -0
- package/dist/lib/logBatcher.js +42 -0
- package/dist/lib/proxyServer.d.ts +10 -1
- package/dist/lib/proxyServer.js +219 -48
- package/dist/lib/session.d.ts +1 -0
- package/dist/lib/session.js +17 -0
- package/dist/lib/terminal.d.ts +2 -0
- package/dist/lib/terminal.js +15 -0
- package/dist/lib/trust.d.ts +2 -0
- package/dist/lib/trust.js +33 -0
- package/package.json +11 -10
package/dist/lib/proxyServer.js
CHANGED
|
@@ -5,44 +5,157 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.startProxy = startProxy;
|
|
7
7
|
const http_1 = __importDefault(require("http"));
|
|
8
|
+
const https_1 = __importDefault(require("https"));
|
|
9
|
+
const net_1 = __importDefault(require("net"));
|
|
10
|
+
const dns_1 = require("dns");
|
|
8
11
|
const crypto_1 = require("crypto");
|
|
9
12
|
const PLACEHOLDER = /\{\{([A-Z0-9_]+)\}\}/g;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
13
|
+
const PROXY_TIMEOUT_MS = 60_000;
|
|
14
|
+
const MAX_HOST_LENGTH = 253;
|
|
15
|
+
const HOP_BY_HOP_HEADERS = new Set([
|
|
16
|
+
"connection",
|
|
17
|
+
"content-length",
|
|
18
|
+
"keep-alive",
|
|
19
|
+
"proxy-authenticate",
|
|
20
|
+
"proxy-authorization",
|
|
21
|
+
"proxy-connection",
|
|
22
|
+
"te",
|
|
23
|
+
"trailer",
|
|
24
|
+
"transfer-encoding",
|
|
25
|
+
"upgrade",
|
|
26
|
+
]);
|
|
27
|
+
// Valide une IP résolue : bloque loopback + plages privées + link-local + ULA + métadata cloud.
|
|
28
|
+
function isBlockedIp(ip) {
|
|
29
|
+
const normalized = stripBrackets(ip).toLowerCase();
|
|
30
|
+
const v4 = ipv4FromMappedIpv6(normalized) ?? normalized;
|
|
31
|
+
if (net_1.default.isIPv4(v4)) {
|
|
32
|
+
const [a, b, c] = v4.split(".").map(Number);
|
|
33
|
+
if (a === 0 || a === 10 || a === 127)
|
|
34
|
+
return true;
|
|
35
|
+
if (a === 100 && b >= 64 && b <= 127)
|
|
36
|
+
return true; // carrier-grade NAT
|
|
37
|
+
if (a === 192 && b === 168)
|
|
38
|
+
return true;
|
|
39
|
+
if (a === 192 && b === 0)
|
|
40
|
+
return true;
|
|
41
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
42
|
+
return true;
|
|
43
|
+
if (a === 169 && b === 254)
|
|
44
|
+
return true; // link-local + metadata 169.254.169.254
|
|
45
|
+
if (a === 198 && (b === 18 || b === 19))
|
|
46
|
+
return true; // benchmarking
|
|
47
|
+
if (a === 192 && b === 0 && c === 2)
|
|
48
|
+
return true;
|
|
49
|
+
if (a === 198 && b === 51 && c === 100)
|
|
50
|
+
return true;
|
|
51
|
+
if (a === 203 && b === 0 && c === 113)
|
|
52
|
+
return true;
|
|
53
|
+
if (a >= 224)
|
|
54
|
+
return true; // multicast + reserved
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const l = normalized;
|
|
58
|
+
if (l === "::1" || l === "::")
|
|
26
59
|
return true;
|
|
60
|
+
if (l.startsWith("fe8") || l.startsWith("fe9") || l.startsWith("fea") || l.startsWith("feb"))
|
|
61
|
+
return true; // fe80::/10
|
|
62
|
+
if (l.startsWith("fc") || l.startsWith("fd"))
|
|
63
|
+
return true; // ULA
|
|
64
|
+
if (l.startsWith("ff"))
|
|
65
|
+
return true; // multicast
|
|
66
|
+
if (l.startsWith("2001:db8"))
|
|
67
|
+
return true; // documentation
|
|
27
68
|
return false;
|
|
28
69
|
}
|
|
70
|
+
function ipv4FromMappedIpv6(ip) {
|
|
71
|
+
if (!ip.startsWith("::ffff:"))
|
|
72
|
+
return null;
|
|
73
|
+
const tail = ip.slice("::ffff:".length);
|
|
74
|
+
if (net_1.default.isIPv4(tail))
|
|
75
|
+
return tail;
|
|
76
|
+
const parts = tail.split(":");
|
|
77
|
+
if (parts.length !== 2)
|
|
78
|
+
return null;
|
|
79
|
+
const high = Number.parseInt(parts[0], 16);
|
|
80
|
+
const low = Number.parseInt(parts[1], 16);
|
|
81
|
+
if (!Number.isInteger(high) || !Number.isInteger(low) || high < 0 || high > 0xffff || low < 0 || low > 0xffff) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
return `${(high >> 8) & 255}.${high & 255}.${(low >> 8) & 255}.${low & 255}`;
|
|
85
|
+
}
|
|
86
|
+
function stripBrackets(hostname) {
|
|
87
|
+
return hostname.startsWith("[") && hostname.endsWith("]")
|
|
88
|
+
? hostname.slice(1, -1)
|
|
89
|
+
: hostname;
|
|
90
|
+
}
|
|
91
|
+
function normalizeHostname(hostname) {
|
|
92
|
+
return stripBrackets(hostname).toLowerCase().replace(/\.$/, "");
|
|
93
|
+
}
|
|
94
|
+
// Résout le hostname et exige que TOUTES les adresses soient publiques.
|
|
95
|
+
async function resolvePublicAddresses(hostname) {
|
|
96
|
+
const normalized = normalizeHostname(hostname);
|
|
97
|
+
if (!normalized || normalized.length > MAX_HOST_LENGTH || normalized === "localhost")
|
|
98
|
+
return [];
|
|
99
|
+
const directIp = net_1.default.isIP(normalized);
|
|
100
|
+
if (directIp)
|
|
101
|
+
return isBlockedIp(normalized) ? [] : [{ address: normalized, family: directIp }];
|
|
102
|
+
try {
|
|
103
|
+
const addrs = await dns_1.promises.lookup(normalized, { all: true, verbatim: true });
|
|
104
|
+
if (addrs.length === 0 || addrs.some((a) => isBlockedIp(a.address)))
|
|
105
|
+
return [];
|
|
106
|
+
return addrs.map((a) => ({ address: a.address, family: a.family }));
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Comparaison constant-time du token de session.
|
|
113
|
+
function tokenMatches(provided, expected) {
|
|
114
|
+
if (typeof provided !== "string" || provided.length !== expected.length)
|
|
115
|
+
return false;
|
|
116
|
+
return (0, crypto_1.timingSafeEqual)(Buffer.from(provided), Buffer.from(expected));
|
|
117
|
+
}
|
|
29
118
|
// hosts: map nom_secret -> domaine autorisé. Si défini, le secret ne peut être envoyé qu'à ce host.
|
|
30
|
-
function startProxy(secrets, hosts = {}) {
|
|
119
|
+
function startProxy(secrets, hosts = {}, verbose = false, onLog) {
|
|
31
120
|
const token = (0, crypto_1.randomBytes)(24).toString("hex");
|
|
32
|
-
//
|
|
121
|
+
// Log une ligne par requête — jamais les valeurs, seulement les placeholders référencés
|
|
122
|
+
const log = (method, host, path, status, ms, note = "", secretNames = "") => {
|
|
123
|
+
const blocked = status === "BLOCKED";
|
|
124
|
+
const code = blocked ? "BLOCKED" : String(status);
|
|
125
|
+
if (verbose)
|
|
126
|
+
console.error(`[elding] ${method} ${host}${path} → ${code} ${ms}ms${note ? " " + note : ""}`);
|
|
127
|
+
onLog?.({ method, host, path, status: blocked ? 403 : status, latencyMs: ms, blocked, secretNames });
|
|
128
|
+
};
|
|
129
|
+
const placeholdersIn = (headers) => {
|
|
130
|
+
const names = new Set();
|
|
131
|
+
for (const v of Object.values(headers)) {
|
|
132
|
+
const values = Array.isArray(v) ? v : [v];
|
|
133
|
+
for (const item of values)
|
|
134
|
+
for (const m of item.matchAll(PLACEHOLDER))
|
|
135
|
+
names.add(m[1]);
|
|
136
|
+
}
|
|
137
|
+
return names.size ? `{{${[...names].join(",")}}}` : "";
|
|
138
|
+
};
|
|
139
|
+
// Vérifie que chaque placeholder utilisé est autorisé pour ce host. Retourne une raison de blocage, ou null.
|
|
33
140
|
const findHostViolation = (val, targetHost) => {
|
|
34
141
|
for (const m of val.matchAll(PLACEHOLDER)) {
|
|
35
142
|
const name = m[1];
|
|
143
|
+
if (!Object.prototype.hasOwnProperty.call(secrets, name))
|
|
144
|
+
return `${name} inconnu`;
|
|
36
145
|
const allowed = hosts[name];
|
|
37
|
-
if (allowed
|
|
38
|
-
return name
|
|
146
|
+
if (!allowed)
|
|
147
|
+
return `${name} sans domaine autorise`;
|
|
148
|
+
const allowedHost = normalizeAllowedHost(allowed);
|
|
149
|
+
if (!allowedHost || allowedHost !== targetHost)
|
|
150
|
+
return `${name} non autorise`;
|
|
39
151
|
}
|
|
40
152
|
return null;
|
|
41
153
|
};
|
|
42
154
|
const substitute = (val) => val.replace(PLACEHOLDER, (_, name) => secrets[name] ?? "");
|
|
43
155
|
const server = http_1.default.createServer(async (req, res) => {
|
|
156
|
+
const started = Date.now();
|
|
44
157
|
try {
|
|
45
|
-
if (req.headers["x-elding-token"]
|
|
158
|
+
if (!tokenMatches(req.headers["x-elding-token"], token)) {
|
|
46
159
|
res.writeHead(401);
|
|
47
160
|
res.end("unauthorized");
|
|
48
161
|
return;
|
|
@@ -67,55 +180,58 @@ function startProxy(secrets, hosts = {}) {
|
|
|
67
180
|
res.end("https only");
|
|
68
181
|
return;
|
|
69
182
|
}
|
|
70
|
-
if (
|
|
183
|
+
if (targetUrl.username || targetUrl.password) {
|
|
184
|
+
res.writeHead(400);
|
|
185
|
+
res.end("credentials not allowed");
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const targetHost = normalizeHostname(targetUrl.hostname);
|
|
189
|
+
const resolved = await resolvePublicAddresses(targetHost);
|
|
190
|
+
if (resolved.length === 0) {
|
|
191
|
+
log(req.method ?? "?", targetHost, req.url ?? "/", "BLOCKED", Date.now() - started, "host bloqué");
|
|
71
192
|
res.writeHead(403);
|
|
72
193
|
res.end("blocked host");
|
|
73
194
|
return;
|
|
74
195
|
}
|
|
196
|
+
const pinned = resolved[0];
|
|
75
197
|
const upstream = new URL(req.url ?? "/", targetUrl);
|
|
76
198
|
upstream.protocol = "https:";
|
|
77
199
|
upstream.host = targetUrl.host;
|
|
78
200
|
// Enforce host binding : un secret lié à un domaine ne peut partir que vers celui-ci
|
|
79
201
|
for (const v of Object.values(req.headers)) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
202
|
+
const values = Array.isArray(v) ? v : typeof v === "string" ? [v] : [];
|
|
203
|
+
for (const item of values) {
|
|
204
|
+
const bad = findHostViolation(item, targetHost);
|
|
205
|
+
if (!bad)
|
|
206
|
+
continue;
|
|
207
|
+
log(req.method ?? "?", targetHost, upstream.pathname, "BLOCKED", Date.now() - started, bad);
|
|
84
208
|
res.writeHead(403);
|
|
85
|
-
res.end(`secret
|
|
209
|
+
res.end(`secret bloque: ${bad}`);
|
|
86
210
|
return;
|
|
87
211
|
}
|
|
88
212
|
}
|
|
89
213
|
const headers = {};
|
|
214
|
+
const rawHeaders = {};
|
|
90
215
|
for (const [k, v] of Object.entries(req.headers)) {
|
|
91
216
|
const lk = k.toLowerCase();
|
|
92
217
|
if (lk.startsWith("x-elding-"))
|
|
93
218
|
continue;
|
|
94
|
-
if (lk
|
|
219
|
+
if (HOP_BY_HOP_HEADERS.has(lk))
|
|
95
220
|
continue;
|
|
96
|
-
if (typeof v === "string")
|
|
221
|
+
if (typeof v === "string") {
|
|
222
|
+
rawHeaders[k] = v;
|
|
97
223
|
headers[k] = substitute(v);
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const hasBody = chunks.length > 0 && req.method !== "GET" && req.method !== "HEAD";
|
|
103
|
-
const upstreamRes = await fetch(upstream.toString(), {
|
|
104
|
-
method: req.method,
|
|
105
|
-
headers,
|
|
106
|
-
body: hasBody ? Buffer.concat(chunks) : undefined,
|
|
107
|
-
});
|
|
108
|
-
res.writeHead(upstreamRes.status, Object.fromEntries(upstreamRes.headers));
|
|
109
|
-
if (upstreamRes.body) {
|
|
110
|
-
const reader = upstreamRes.body.getReader();
|
|
111
|
-
for (;;) {
|
|
112
|
-
const { done, value } = await reader.read();
|
|
113
|
-
if (done)
|
|
114
|
-
break;
|
|
115
|
-
res.write(value);
|
|
224
|
+
}
|
|
225
|
+
else if (Array.isArray(v)) {
|
|
226
|
+
rawHeaders[k] = v;
|
|
227
|
+
headers[k] = v.map(substitute);
|
|
116
228
|
}
|
|
117
229
|
}
|
|
118
|
-
|
|
230
|
+
// Streame le corps vers l'upstream sans le bufferiser en mémoire (évite un DoS sur gros upload)
|
|
231
|
+
const hasBody = req.method !== "GET" && req.method !== "HEAD";
|
|
232
|
+
const used = placeholdersIn(rawHeaders);
|
|
233
|
+
const status = await forwardHttps(req, res, upstream, headers, pinned, hasBody);
|
|
234
|
+
log(req.method ?? "?", targetHost, upstream.pathname, status, Date.now() - started, used, used);
|
|
119
235
|
}
|
|
120
236
|
catch {
|
|
121
237
|
if (!res.headersSent)
|
|
@@ -131,3 +247,58 @@ function startProxy(secrets, hosts = {}) {
|
|
|
131
247
|
});
|
|
132
248
|
});
|
|
133
249
|
}
|
|
250
|
+
function normalizeAllowedHost(value) {
|
|
251
|
+
const raw = value.trim();
|
|
252
|
+
if (!raw || raw.length > MAX_HOST_LENGTH + 8)
|
|
253
|
+
return null;
|
|
254
|
+
try {
|
|
255
|
+
const url = new URL(raw.includes("://") ? raw : `https://${raw}`);
|
|
256
|
+
if (url.username || url.password)
|
|
257
|
+
return null;
|
|
258
|
+
return normalizeHostname(url.hostname);
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function filterResponseHeaders(headers) {
|
|
265
|
+
const out = {};
|
|
266
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
267
|
+
if (HOP_BY_HOP_HEADERS.has(name.toLowerCase()))
|
|
268
|
+
continue;
|
|
269
|
+
out[name] = value;
|
|
270
|
+
}
|
|
271
|
+
return out;
|
|
272
|
+
}
|
|
273
|
+
function forwardHttps(req, res, upstream, headers, pinned, hasBody) {
|
|
274
|
+
return new Promise((resolve, reject) => {
|
|
275
|
+
const upstreamHostname = normalizeHostname(upstream.hostname);
|
|
276
|
+
const upstreamReq = https_1.default.request({
|
|
277
|
+
protocol: "https:",
|
|
278
|
+
hostname: upstreamHostname,
|
|
279
|
+
port: upstream.port ? Number(upstream.port) : 443,
|
|
280
|
+
path: `${upstream.pathname}${upstream.search}`,
|
|
281
|
+
method: req.method,
|
|
282
|
+
headers: { ...headers, host: upstream.host },
|
|
283
|
+
servername: net_1.default.isIP(upstreamHostname) ? undefined : upstreamHostname,
|
|
284
|
+
timeout: PROXY_TIMEOUT_MS,
|
|
285
|
+
lookup: (_hostname, _options, callback) => {
|
|
286
|
+
callback(null, pinned.address, pinned.family);
|
|
287
|
+
},
|
|
288
|
+
}, (upstreamRes) => {
|
|
289
|
+
const status = upstreamRes.statusCode ?? 502;
|
|
290
|
+
res.writeHead(status, filterResponseHeaders(upstreamRes.headers));
|
|
291
|
+
upstreamRes.pipe(res);
|
|
292
|
+
upstreamRes.on("end", () => resolve(status));
|
|
293
|
+
upstreamRes.on("error", reject);
|
|
294
|
+
});
|
|
295
|
+
upstreamReq.on("timeout", () => upstreamReq.destroy(new Error("upstream timeout")));
|
|
296
|
+
upstreamReq.on("error", reject);
|
|
297
|
+
req.on("error", reject);
|
|
298
|
+
res.on("close", () => upstreamReq.destroy());
|
|
299
|
+
if (hasBody)
|
|
300
|
+
req.pipe(upstreamReq);
|
|
301
|
+
else
|
|
302
|
+
upstreamReq.end();
|
|
303
|
+
});
|
|
304
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function requireAccessToken(): Promise<string>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.requireAccessToken = requireAccessToken;
|
|
4
|
+
const config_js_1 = require("./config.js");
|
|
5
|
+
const api_js_1 = require("./api.js");
|
|
6
|
+
// Lit le token local, l'échange contre un access token. Lève une erreur claire sinon.
|
|
7
|
+
async function requireAccessToken() {
|
|
8
|
+
const config = (0, config_js_1.readConfig)();
|
|
9
|
+
if (!config)
|
|
10
|
+
throw new Error("Non connecté. Lancez `elding login` d'abord.");
|
|
11
|
+
try {
|
|
12
|
+
return await (0, api_js_1.exchangeToken)(config.refreshToken);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
throw new Error("Token expiré ou révoqué. Relancez `elding login`.");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.safeText = safeText;
|
|
4
|
+
exports.safeError = safeError;
|
|
5
|
+
const ANSI_PATTERN = /[\u001b\u009b][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[a-zA-Z\d]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))/g;
|
|
6
|
+
const CONTROL_PATTERN = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g;
|
|
7
|
+
function safeText(value, maxLength = 500) {
|
|
8
|
+
return String(value ?? "")
|
|
9
|
+
.replace(ANSI_PATTERN, "")
|
|
10
|
+
.replace(CONTROL_PATTERN, "")
|
|
11
|
+
.slice(0, maxLength);
|
|
12
|
+
}
|
|
13
|
+
function safeError(value) {
|
|
14
|
+
return safeText(value instanceof Error ? value.message : value, 300) || "Erreur inconnue";
|
|
15
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ensureProjectTrusted = ensureProjectTrusted;
|
|
7
|
+
const promises_1 = __importDefault(require("readline/promises"));
|
|
8
|
+
const process_1 = require("process");
|
|
9
|
+
const config_js_1 = require("./config.js");
|
|
10
|
+
const terminal_js_1 = require("./terminal.js");
|
|
11
|
+
async function ensureProjectTrusted(project) {
|
|
12
|
+
if ((0, config_js_1.isProjectTrusted)(project))
|
|
13
|
+
return;
|
|
14
|
+
const location = (0, config_js_1.projectTrustKey)();
|
|
15
|
+
const target = `${(0, terminal_js_1.safeText)(project.setName)} (${(0, terminal_js_1.safeText)(project.setId)})`;
|
|
16
|
+
const workspace = project.workspaceName ? ` / ${(0, terminal_js_1.safeText)(project.workspaceName)}` : "";
|
|
17
|
+
if (!process_1.stdin.isTTY || !process_1.stdout.isTTY) {
|
|
18
|
+
throw new Error(`Projet non approuve pour le set ${target}${workspace}. Lancez \`elding init\` ou \`elding use <set>\` interactivement.`);
|
|
19
|
+
}
|
|
20
|
+
const rl = promises_1.default.createInterface({ input: process_1.stdin, output: process_1.stdout });
|
|
21
|
+
try {
|
|
22
|
+
console.log(`Projet : ${(0, terminal_js_1.safeText)(location)}`);
|
|
23
|
+
console.log(`Set Elding : ${target}${workspace}`);
|
|
24
|
+
const answer = await rl.question("Approuver ce projet pour ce set ? [y/N] ");
|
|
25
|
+
if (!/^y(es)?$/i.test(answer.trim())) {
|
|
26
|
+
throw new Error("Projet non approuve.");
|
|
27
|
+
}
|
|
28
|
+
(0, config_js_1.trustProject)(project);
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
rl.close();
|
|
32
|
+
}
|
|
33
|
+
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elding/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Elding CLI — zero .env, secrets from vault",
|
|
5
5
|
"bin": {
|
|
6
|
-
"elding": "
|
|
6
|
+
"elding": "dist/index.js"
|
|
7
7
|
},
|
|
8
8
|
"main": "./dist/index.js",
|
|
9
9
|
"files": [
|
|
@@ -15,19 +15,20 @@
|
|
|
15
15
|
"scripts": {
|
|
16
16
|
"build": "tsc",
|
|
17
17
|
"dev": "tsc --watch",
|
|
18
|
+
"test": "node --test test/*.test.mjs",
|
|
18
19
|
"prepublishOnly": "npm run build"
|
|
19
20
|
},
|
|
20
21
|
"dependencies": {
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
22
|
+
"@napi-rs/keyring": "1.1.7",
|
|
23
|
+
"chalk": "5.6.2",
|
|
24
|
+
"commander": "12.1.0",
|
|
25
|
+
"inquirer": "14.0.2",
|
|
26
|
+
"open": "10.2.0",
|
|
27
|
+
"ora": "8.2.0"
|
|
26
28
|
},
|
|
27
29
|
"devDependencies": {
|
|
28
|
-
"@types/node": "
|
|
29
|
-
"
|
|
30
|
-
"typescript": "^5.5.0"
|
|
30
|
+
"@types/node": "22.19.21",
|
|
31
|
+
"typescript": "5.9.3"
|
|
31
32
|
},
|
|
32
33
|
"engines": {
|
|
33
34
|
"node": ">=18"
|