@elding/cli 0.3.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.js +4 -3
- package/dist/commands/init.js +29 -5
- package/dist/commands/keys.js +4 -3
- package/dist/commands/login.js +40 -25
- 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 +16 -5
- package/dist/commands/sets.js +4 -2
- package/dist/commands/status.js +3 -2
- package/dist/commands/use.js +13 -5
- package/dist/commands/whoami.js +2 -1
- package/dist/index.js +14 -9
- package/dist/lib/api.d.ts +4 -0
- package/dist/lib/api.js +58 -22
- 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 +208 -58
- 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,51 +5,149 @@ 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 = {}, verbose = false) {
|
|
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
|
|
33
|
-
const log = (method, host, path, status, ms, note = "") => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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 });
|
|
38
128
|
};
|
|
39
129
|
const placeholdersIn = (headers) => {
|
|
40
130
|
const names = new Set();
|
|
41
|
-
for (const v of Object.values(headers))
|
|
42
|
-
|
|
43
|
-
|
|
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
|
+
}
|
|
44
137
|
return names.size ? `{{${[...names].join(",")}}}` : "";
|
|
45
138
|
};
|
|
46
|
-
// Vérifie que chaque placeholder utilisé est autorisé pour ce host. Retourne
|
|
139
|
+
// Vérifie que chaque placeholder utilisé est autorisé pour ce host. Retourne une raison de blocage, ou null.
|
|
47
140
|
const findHostViolation = (val, targetHost) => {
|
|
48
141
|
for (const m of val.matchAll(PLACEHOLDER)) {
|
|
49
142
|
const name = m[1];
|
|
143
|
+
if (!Object.prototype.hasOwnProperty.call(secrets, name))
|
|
144
|
+
return `${name} inconnu`;
|
|
50
145
|
const allowed = hosts[name];
|
|
51
|
-
if (allowed
|
|
52
|
-
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`;
|
|
53
151
|
}
|
|
54
152
|
return null;
|
|
55
153
|
};
|
|
@@ -57,7 +155,7 @@ function startProxy(secrets, hosts = {}, verbose = false) {
|
|
|
57
155
|
const server = http_1.default.createServer(async (req, res) => {
|
|
58
156
|
const started = Date.now();
|
|
59
157
|
try {
|
|
60
|
-
if (req.headers["x-elding-token"]
|
|
158
|
+
if (!tokenMatches(req.headers["x-elding-token"], token)) {
|
|
61
159
|
res.writeHead(401);
|
|
62
160
|
res.end("unauthorized");
|
|
63
161
|
return;
|
|
@@ -82,24 +180,33 @@ function startProxy(secrets, hosts = {}, verbose = false) {
|
|
|
82
180
|
res.end("https only");
|
|
83
181
|
return;
|
|
84
182
|
}
|
|
85
|
-
if (
|
|
86
|
-
|
|
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é");
|
|
87
192
|
res.writeHead(403);
|
|
88
193
|
res.end("blocked host");
|
|
89
194
|
return;
|
|
90
195
|
}
|
|
196
|
+
const pinned = resolved[0];
|
|
91
197
|
const upstream = new URL(req.url ?? "/", targetUrl);
|
|
92
198
|
upstream.protocol = "https:";
|
|
93
199
|
upstream.host = targetUrl.host;
|
|
94
200
|
// Enforce host binding : un secret lié à un domaine ne peut partir que vers celui-ci
|
|
95
201
|
for (const v of Object.values(req.headers)) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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);
|
|
101
208
|
res.writeHead(403);
|
|
102
|
-
res.end(`secret
|
|
209
|
+
res.end(`secret bloque: ${bad}`);
|
|
103
210
|
return;
|
|
104
211
|
}
|
|
105
212
|
}
|
|
@@ -109,34 +216,22 @@ function startProxy(secrets, hosts = {}, verbose = false) {
|
|
|
109
216
|
const lk = k.toLowerCase();
|
|
110
217
|
if (lk.startsWith("x-elding-"))
|
|
111
218
|
continue;
|
|
112
|
-
if (lk
|
|
219
|
+
if (HOP_BY_HOP_HEADERS.has(lk))
|
|
113
220
|
continue;
|
|
114
221
|
if (typeof v === "string") {
|
|
115
222
|
rawHeaders[k] = v;
|
|
116
223
|
headers[k] = substitute(v);
|
|
117
224
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
chunks.push(c);
|
|
122
|
-
const hasBody = chunks.length > 0 && req.method !== "GET" && req.method !== "HEAD";
|
|
123
|
-
const upstreamRes = await fetch(upstream.toString(), {
|
|
124
|
-
method: req.method,
|
|
125
|
-
headers,
|
|
126
|
-
body: hasBody ? Buffer.concat(chunks) : undefined,
|
|
127
|
-
});
|
|
128
|
-
log(req.method ?? "?", targetUrl.hostname, upstream.pathname, upstreamRes.status, Date.now() - started, placeholdersIn(rawHeaders));
|
|
129
|
-
res.writeHead(upstreamRes.status, Object.fromEntries(upstreamRes.headers));
|
|
130
|
-
if (upstreamRes.body) {
|
|
131
|
-
const reader = upstreamRes.body.getReader();
|
|
132
|
-
for (;;) {
|
|
133
|
-
const { done, value } = await reader.read();
|
|
134
|
-
if (done)
|
|
135
|
-
break;
|
|
136
|
-
res.write(value);
|
|
225
|
+
else if (Array.isArray(v)) {
|
|
226
|
+
rawHeaders[k] = v;
|
|
227
|
+
headers[k] = v.map(substitute);
|
|
137
228
|
}
|
|
138
229
|
}
|
|
139
|
-
|
|
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);
|
|
140
235
|
}
|
|
141
236
|
catch {
|
|
142
237
|
if (!res.headersSent)
|
|
@@ -152,3 +247,58 @@ function startProxy(secrets, hosts = {}, verbose = false) {
|
|
|
152
247
|
});
|
|
153
248
|
});
|
|
154
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,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"
|