@elding/cli 0.1.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/init.d.ts +1 -0
- package/dist/commands/init.js +49 -0
- package/dist/commands/login.d.ts +1 -0
- package/dist/commands/login.js +65 -0
- package/dist/commands/run.d.ts +1 -0
- package/dist/commands/run.js +42 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +42 -0
- package/dist/lib/api.d.ts +7 -0
- package/dist/lib/api.js +52 -0
- package/dist/lib/config.d.ts +13 -0
- package/dist/lib/config.js +48 -0
- package/package.json +36 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function init(): Promise<void>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.init = init;
|
|
4
|
+
const config_js_1 = require("../lib/config.js");
|
|
5
|
+
const api_js_1 = require("../lib/api.js");
|
|
6
|
+
async function init() {
|
|
7
|
+
const { default: chalk } = await import("chalk");
|
|
8
|
+
const { default: ora } = await import("ora");
|
|
9
|
+
const { default: inquirer } = await import("inquirer");
|
|
10
|
+
const config = (0, config_js_1.readConfig)();
|
|
11
|
+
if (!config) {
|
|
12
|
+
console.error(chalk.red("Non connecté. Lancez `elding login` d'abord."));
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
const spinner = ora("Récupération des sets...").start();
|
|
16
|
+
let accessToken;
|
|
17
|
+
try {
|
|
18
|
+
accessToken = await (0, api_js_1.exchangeToken)(config.refreshToken);
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
spinner.fail(chalk.red("Impossible d'obtenir un access token. Relancez `elding login`."));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
let sets;
|
|
25
|
+
try {
|
|
26
|
+
sets = await (0, api_js_1.listSets)(accessToken);
|
|
27
|
+
spinner.stop();
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
spinner.fail(chalk.red("Impossible de récupérer les sets."));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
if (sets.length === 0) {
|
|
34
|
+
console.log(chalk.yellow("Aucun set trouvé. Créez-en un sur app.elding.io."));
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
const { setId } = await inquirer.prompt([
|
|
38
|
+
{
|
|
39
|
+
type: "list",
|
|
40
|
+
name: "setId",
|
|
41
|
+
message: "Quel set utiliser pour ce projet ?",
|
|
42
|
+
choices: sets.map((s) => ({ name: s.name, value: s.id })),
|
|
43
|
+
},
|
|
44
|
+
]);
|
|
45
|
+
const selected = sets.find((s) => s.id === setId);
|
|
46
|
+
(0, config_js_1.writeProject)({ setId: selected.id, setName: selected.name });
|
|
47
|
+
console.log(chalk.green(`✓ Set "${selected.name}" configuré dans .elding.json`));
|
|
48
|
+
console.log(chalk.dim("Ajoutez .elding.json à votre .gitignore si besoin."));
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function login(): Promise<void>;
|
|
@@ -0,0 +1,65 @@
|
|
|
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.login = login;
|
|
7
|
+
const http_1 = __importDefault(require("http"));
|
|
8
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
9
|
+
const config_js_1 = require("../lib/config.js");
|
|
10
|
+
const BASE_URL = process.env.ELDING_API_URL ?? "https://app.elding.io";
|
|
11
|
+
const TIMEOUT_MS = 5 * 60 * 1000;
|
|
12
|
+
function randomPort() {
|
|
13
|
+
return Math.floor(Math.random() * (65535 - 49152 + 1)) + 49152;
|
|
14
|
+
}
|
|
15
|
+
async function login() {
|
|
16
|
+
const { default: chalk } = await import("chalk");
|
|
17
|
+
const { default: open } = await import("open");
|
|
18
|
+
const { default: ora } = await import("ora");
|
|
19
|
+
const state = crypto_1.default.randomBytes(16).toString("hex");
|
|
20
|
+
const port = randomPort();
|
|
21
|
+
const callbackUrl = `http://localhost:${port}`;
|
|
22
|
+
const authUrl = `${BASE_URL}/cli` +
|
|
23
|
+
`?state=${encodeURIComponent(state)}` +
|
|
24
|
+
`&callback=${encodeURIComponent(callbackUrl)}`;
|
|
25
|
+
const spinner = ora("En attente d'autorisation dans le navigateur...").start();
|
|
26
|
+
const token = await new Promise((resolve, reject) => {
|
|
27
|
+
const timeout = setTimeout(() => {
|
|
28
|
+
server.close();
|
|
29
|
+
reject(new Error("Timeout — aucune réponse après 5 minutes"));
|
|
30
|
+
}, TIMEOUT_MS);
|
|
31
|
+
const server = http_1.default.createServer((req, res) => {
|
|
32
|
+
const url = new URL(req.url ?? "/", `http://localhost:${port}`);
|
|
33
|
+
const receivedToken = url.searchParams.get("token");
|
|
34
|
+
const receivedState = url.searchParams.get("state");
|
|
35
|
+
const tokenValid = typeof receivedToken === "string" &&
|
|
36
|
+
receivedToken.startsWith("eld_rt_") &&
|
|
37
|
+
receivedToken.length === 7 + 64; // "eld_rt_" + 32 bytes hex
|
|
38
|
+
if (receivedState !== state || !tokenValid) {
|
|
39
|
+
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
|
|
40
|
+
res.end("<p>Paramètres invalides. Fermez cet onglet.</p>");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
44
|
+
res.end(`<!DOCTYPE html><html><head><meta charset="utf-8"></head><body style="font-family:sans-serif;padding:2rem">
|
|
45
|
+
<p>✅ <strong>Authentification réussie.</strong> Vous pouvez fermer cet onglet.</p>
|
|
46
|
+
</body></html>`);
|
|
47
|
+
clearTimeout(timeout);
|
|
48
|
+
server.close();
|
|
49
|
+
resolve(receivedToken);
|
|
50
|
+
});
|
|
51
|
+
server.on("error", (err) => {
|
|
52
|
+
clearTimeout(timeout);
|
|
53
|
+
reject(err);
|
|
54
|
+
});
|
|
55
|
+
server.listen(port, "127.0.0.1", () => {
|
|
56
|
+
open(authUrl).catch((err) => {
|
|
57
|
+
spinner.warn(`Impossible d'ouvrir le navigateur automatiquement.`);
|
|
58
|
+
console.log(chalk.cyan(`Ouvrez manuellement : ${authUrl}`));
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
(0, config_js_1.writeConfig)({ refreshToken: token });
|
|
63
|
+
spinner.succeed(chalk.green("Connecté avec succès."));
|
|
64
|
+
console.log(chalk.dim("Token sauvegardé dans ~/.elding/config.json"));
|
|
65
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function run(cmd: string, args: string[]): Promise<void>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.run = run;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const config_js_1 = require("../lib/config.js");
|
|
6
|
+
const api_js_1 = require("../lib/api.js");
|
|
7
|
+
async function run(cmd, args) {
|
|
8
|
+
const { default: chalk } = await import("chalk");
|
|
9
|
+
const { default: ora } = await import("ora");
|
|
10
|
+
const config = (0, config_js_1.readConfig)();
|
|
11
|
+
if (!config) {
|
|
12
|
+
console.error(chalk.red("Non connecté. Lancez `elding login` d'abord."));
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
const project = (0, config_js_1.readProject)();
|
|
16
|
+
if (!project) {
|
|
17
|
+
console.error(chalk.red("Projet non initialisé. Lancez `elding init` d'abord."));
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
const spinner = ora("Récupération des secrets...").start();
|
|
21
|
+
let secrets;
|
|
22
|
+
try {
|
|
23
|
+
const accessToken = await (0, api_js_1.exchangeToken)(config.refreshToken);
|
|
24
|
+
secrets = await (0, api_js_1.fetchSecrets)(accessToken, project.setId);
|
|
25
|
+
spinner.succeed(chalk.green(`${Object.keys(secrets).length} secret(s) chargé(s).`));
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
spinner.fail(chalk.red(err instanceof Error ? err.message : "Erreur inconnue"));
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
const env = { ...process.env, ...secrets };
|
|
32
|
+
const result = (0, child_process_1.spawnSync)(cmd, args, {
|
|
33
|
+
env,
|
|
34
|
+
stdio: "inherit",
|
|
35
|
+
shell: true,
|
|
36
|
+
});
|
|
37
|
+
if (result.error) {
|
|
38
|
+
console.error(chalk.red(`Impossible de lancer la commande : ${result.error.message}`));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
process.exit(result.status ?? 0);
|
|
42
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const login_js_1 = require("./commands/login.js");
|
|
6
|
+
const init_js_1 = require("./commands/init.js");
|
|
7
|
+
const run_js_1 = require("./commands/run.js");
|
|
8
|
+
const program = new commander_1.Command();
|
|
9
|
+
program
|
|
10
|
+
.name("elding")
|
|
11
|
+
.description("Elding CLI — secrets depuis le vault, zéro .env")
|
|
12
|
+
.version("0.1.0");
|
|
13
|
+
program
|
|
14
|
+
.command("login")
|
|
15
|
+
.description("Authentifier le CLI via le navigateur")
|
|
16
|
+
.action(async () => {
|
|
17
|
+
await (0, login_js_1.login)().catch((err) => {
|
|
18
|
+
console.error(err.message);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
program
|
|
23
|
+
.command("init")
|
|
24
|
+
.description("Associer ce projet à un set de secrets")
|
|
25
|
+
.action(async () => {
|
|
26
|
+
await (0, init_js_1.init)().catch((err) => {
|
|
27
|
+
console.error(err.message);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
program
|
|
32
|
+
.command("run <cmd>")
|
|
33
|
+
.description("Lancer une commande avec les secrets injectés en variables d'environnement")
|
|
34
|
+
.allowUnknownOption()
|
|
35
|
+
.action(async (cmd, options, command) => {
|
|
36
|
+
const args = command.args.slice(1);
|
|
37
|
+
await (0, run_js_1.run)(cmd, args).catch((err) => {
|
|
38
|
+
console.error(err.message);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
program.parse();
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type ApiKeySetItem = {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
};
|
|
5
|
+
export declare function exchangeToken(refreshToken: string): Promise<string>;
|
|
6
|
+
export declare function listSets(accessToken: string): Promise<ApiKeySetItem[]>;
|
|
7
|
+
export declare function fetchSecrets(accessToken: string, setId: string): Promise<Record<string, string>>;
|
package/dist/lib/api.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.exchangeToken = exchangeToken;
|
|
4
|
+
exports.listSets = listSets;
|
|
5
|
+
exports.fetchSecrets = fetchSecrets;
|
|
6
|
+
const BASE_URL = process.env.ELDING_API_URL ?? "https://app.elding.io";
|
|
7
|
+
async function exchangeToken(refreshToken) {
|
|
8
|
+
const res = await fetch(`${BASE_URL}/api/cli/auth/token`, {
|
|
9
|
+
method: "POST",
|
|
10
|
+
headers: { "Content-Type": "application/json" },
|
|
11
|
+
body: JSON.stringify({ refreshToken }),
|
|
12
|
+
});
|
|
13
|
+
const body = (await res.json());
|
|
14
|
+
if (!body.success || !body.accessToken)
|
|
15
|
+
throw new Error(body.error ?? "Impossible d'obtenir un access token");
|
|
16
|
+
return body.accessToken;
|
|
17
|
+
}
|
|
18
|
+
async function listSets(accessToken) {
|
|
19
|
+
const res = await fetch(`${BASE_URL}/api/cli/sets`, {
|
|
20
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
21
|
+
});
|
|
22
|
+
if (!res.ok)
|
|
23
|
+
throw new Error("Impossible de récupérer les sets");
|
|
24
|
+
const body = (await res.json());
|
|
25
|
+
return Array.isArray(body.sets) ? body.sets : [];
|
|
26
|
+
}
|
|
27
|
+
async function fetchSecrets(accessToken, setId) {
|
|
28
|
+
const res = await fetch(`${BASE_URL}/api/cli/secrets?setId=${encodeURIComponent(setId)}`, {
|
|
29
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
30
|
+
});
|
|
31
|
+
if (!res.ok) {
|
|
32
|
+
const body = (await res.json().catch(() => ({})));
|
|
33
|
+
throw new Error(body.error ?? `Erreur ${res.status}`);
|
|
34
|
+
}
|
|
35
|
+
const body = (await res.json());
|
|
36
|
+
if (!body.success || !body.secrets || typeof body.secrets !== "object")
|
|
37
|
+
throw new Error("Réponse invalide");
|
|
38
|
+
return sanitizeSecrets(body.secrets);
|
|
39
|
+
}
|
|
40
|
+
const DANGEROUS_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
41
|
+
// Garde seulement les paires clé/valeur string sûres — évite injection d'objets ou pollution de prototype dans process.env
|
|
42
|
+
function sanitizeSecrets(raw) {
|
|
43
|
+
const out = Object.create(null);
|
|
44
|
+
for (const [name, value] of Object.entries(raw)) {
|
|
45
|
+
if (DANGEROUS_KEYS.has(name))
|
|
46
|
+
continue;
|
|
47
|
+
if (typeof value !== "string")
|
|
48
|
+
continue;
|
|
49
|
+
out[name] = value;
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type EldingConfig = {
|
|
2
|
+
refreshToken: string;
|
|
3
|
+
workspaceId?: string;
|
|
4
|
+
};
|
|
5
|
+
export declare function readConfig(): EldingConfig | null;
|
|
6
|
+
export declare function writeConfig(config: EldingConfig): void;
|
|
7
|
+
export declare function clearConfig(): void;
|
|
8
|
+
export type ProjectConfig = {
|
|
9
|
+
setId: string;
|
|
10
|
+
setName: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function readProject(): ProjectConfig | null;
|
|
13
|
+
export declare function writeProject(cfg: ProjectConfig): void;
|
|
@@ -0,0 +1,48 @@
|
|
|
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.readConfig = readConfig;
|
|
7
|
+
exports.writeConfig = writeConfig;
|
|
8
|
+
exports.clearConfig = clearConfig;
|
|
9
|
+
exports.readProject = readProject;
|
|
10
|
+
exports.writeProject = writeProject;
|
|
11
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
|
+
const os_1 = __importDefault(require("os"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), ".elding");
|
|
15
|
+
const CONFIG_FILE = path_1.default.join(CONFIG_DIR, "config.json");
|
|
16
|
+
function readConfig() {
|
|
17
|
+
try {
|
|
18
|
+
const raw = fs_1.default.readFileSync(CONFIG_FILE, "utf-8");
|
|
19
|
+
return JSON.parse(raw);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function writeConfig(config) {
|
|
26
|
+
fs_1.default.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
27
|
+
fs_1.default.chmodSync(CONFIG_DIR, 0o700);
|
|
28
|
+
fs_1.default.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
29
|
+
}
|
|
30
|
+
function clearConfig() {
|
|
31
|
+
try {
|
|
32
|
+
fs_1.default.unlinkSync(CONFIG_FILE);
|
|
33
|
+
}
|
|
34
|
+
catch { /* already gone */ }
|
|
35
|
+
}
|
|
36
|
+
const PROJECT_FILE = ".elding.json";
|
|
37
|
+
function readProject() {
|
|
38
|
+
try {
|
|
39
|
+
const raw = fs_1.default.readFileSync(PROJECT_FILE, "utf-8");
|
|
40
|
+
return JSON.parse(raw);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function writeProject(cfg) {
|
|
47
|
+
fs_1.default.writeFileSync(PROJECT_FILE, JSON.stringify(cfg, null, 2));
|
|
48
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@elding/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Elding CLI — zero .env, secrets from vault",
|
|
5
|
+
"bin": {
|
|
6
|
+
"elding": "./dist/index.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"dev": "tsc --watch",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"commander": "^12.1.0",
|
|
22
|
+
"open": "^10.1.0",
|
|
23
|
+
"ora": "^8.1.1",
|
|
24
|
+
"chalk": "^5.3.0",
|
|
25
|
+
"inquirer": "^10.2.2"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^22.0.0",
|
|
29
|
+
"@types/inquirer": "^9.0.7",
|
|
30
|
+
"typescript": "^5.5.0"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
},
|
|
35
|
+
"license": "MIT"
|
|
36
|
+
}
|