veylix-cli 0.0.0-stage → 1.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/package.json +14 -4
- package/veylix.mjs +353 -0
- package/README.md +0 -3
package/package.json
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "veylix-cli",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Pull Veylix Env Manager variables into apps, shells and CI pipelines",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"veylix": "veylix.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"veylix.mjs"
|
|
11
|
+
],
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=18"
|
|
14
|
+
},
|
|
15
|
+
"license": "MIT"
|
|
16
|
+
}
|
package/veylix.mjs
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Veylix CLI: pull Env Manager variables into apps, shells and CI pipelines.
|
|
3
|
+
// Zero dependencies; requires Node 18+ (global fetch).
|
|
4
|
+
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync, rmSync } from "node:fs";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { dirname, join, relative } from "node:path";
|
|
9
|
+
import { createInterface } from "node:readline/promises";
|
|
10
|
+
|
|
11
|
+
const VERSION = "1.1.0";
|
|
12
|
+
const CONFIG_PATH = join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "veylix", "config.json");
|
|
13
|
+
// Per-folder project/environment choice (no secrets, safe to commit).
|
|
14
|
+
const LINK_FILE = ".veylix.json";
|
|
15
|
+
|
|
16
|
+
const HELP = `veylix ${VERSION}: environment variables from Veylix
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
veylix login --token <vlx_…> [--url <https://your-veylix>]
|
|
20
|
+
veylix logout
|
|
21
|
+
veylix whoami Show your account, token access and linked project
|
|
22
|
+
veylix projects List projects and environments you can use
|
|
23
|
+
veylix link [--project <name>] [--env <name>]
|
|
24
|
+
Choose the project & environment for this folder
|
|
25
|
+
veylix unlink Remove this folder's link
|
|
26
|
+
veylix envs List environments of the project with variable counts
|
|
27
|
+
veylix pull [--format dotenv|json|shell] [--output <file>]
|
|
28
|
+
veylix run -- <command> [args…] Run a command with the variables injected
|
|
29
|
+
veylix push [--file .env] [--replace]
|
|
30
|
+
Upload a .env file (read-write token)
|
|
31
|
+
veylix set KEY=value [KEY2=value…]
|
|
32
|
+
veylix unset KEY
|
|
33
|
+
|
|
34
|
+
Every command accepts --project <name> and --env <name> to override the link.
|
|
35
|
+
|
|
36
|
+
Configuration (in order of priority):
|
|
37
|
+
Token / server: --token / --url › VEYLIX_TOKEN / VEYLIX_URL › ${CONFIG_PATH.replace(homedir(), "~")}
|
|
38
|
+
Project / env: --project / --env › VEYLIX_PROJECT / VEYLIX_ENV › ${LINK_FILE} (from \`veylix link\`)
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
veylix link # pick interactively
|
|
42
|
+
veylix pull > .env
|
|
43
|
+
veylix run --env staging -- npm start
|
|
44
|
+
veylix pull --project api --env production --format json
|
|
45
|
+
VEYLIX_TOKEN=vlx_… VEYLIX_PROJECT=api VEYLIX_ENV=production veylix run -- node server.js # CI`;
|
|
46
|
+
|
|
47
|
+
// ---------- args ----------
|
|
48
|
+
|
|
49
|
+
function parseArgs(argv) {
|
|
50
|
+
const flags = {};
|
|
51
|
+
const positional = [];
|
|
52
|
+
let rest = [];
|
|
53
|
+
for (let i = 0; i < argv.length; i++) {
|
|
54
|
+
const a = argv[i];
|
|
55
|
+
if (a === "--") {
|
|
56
|
+
rest = argv.slice(i + 1);
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
if (a.startsWith("--")) {
|
|
60
|
+
const [key, inline] = a.slice(2).split(/=(.*)/s);
|
|
61
|
+
if (inline !== undefined) flags[key] = inline;
|
|
62
|
+
else if (argv[i + 1] && !argv[i + 1].startsWith("--")) flags[key] = argv[++i];
|
|
63
|
+
else flags[key] = true;
|
|
64
|
+
} else if (a === "-h") flags.help = true;
|
|
65
|
+
else positional.push(a);
|
|
66
|
+
}
|
|
67
|
+
return { command: positional[0], args: positional.slice(1), flags, rest };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ---------- config ----------
|
|
71
|
+
|
|
72
|
+
function readConfig() {
|
|
73
|
+
try {
|
|
74
|
+
return JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
|
|
75
|
+
} catch {
|
|
76
|
+
return {};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function writeConfig(config) {
|
|
81
|
+
mkdirSync(dirname(CONFIG_PATH), { recursive: true });
|
|
82
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
83
|
+
chmodSync(CONFIG_PATH, 0o600); // the token is a secret
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function settings(flags) {
|
|
87
|
+
const config = readConfig();
|
|
88
|
+
const url = String(flags.url || process.env.VEYLIX_URL || config.url || "http://localhost:3000").replace(/\/$/, "");
|
|
89
|
+
const token = flags.token || process.env.VEYLIX_TOKEN || config.token;
|
|
90
|
+
if (!token) fail("Not logged in. Run `veylix login --token vlx_…` or set VEYLIX_TOKEN.");
|
|
91
|
+
return { url, token: String(token) };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Nearest .veylix.json walking up from the current folder. */
|
|
95
|
+
function findLink() {
|
|
96
|
+
for (let dir = process.cwd(); ; dir = dirname(dir)) {
|
|
97
|
+
const file = join(dir, LINK_FILE);
|
|
98
|
+
if (existsSync(file)) {
|
|
99
|
+
try {
|
|
100
|
+
return { file, ...JSON.parse(readFileSync(file, "utf8")) };
|
|
101
|
+
} catch {
|
|
102
|
+
fail(`Couldn't read ${file}. Run \`veylix link\` again.`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (dirname(dir) === dir) return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Project/environment for a command: flags › env vars › link file. Either may be undefined (the server picks when unambiguous). */
|
|
110
|
+
function target(flags) {
|
|
111
|
+
const link = findLink();
|
|
112
|
+
const chosen = flags.project || process.env.VEYLIX_PROJECT;
|
|
113
|
+
// The link's environment only applies to the linked project.
|
|
114
|
+
const sameProject = !chosen || chosen === link?.projectId || String(chosen).toLowerCase() === link?.project?.toLowerCase();
|
|
115
|
+
const project = chosen || link?.projectId || link?.project;
|
|
116
|
+
const env = flags.env || process.env.VEYLIX_ENV || (sameProject ? link?.env : undefined);
|
|
117
|
+
return { project: project ? String(project) : undefined, env: env ? String(env) : undefined };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ---------- http ----------
|
|
121
|
+
|
|
122
|
+
async function api(flags, path, init = {}) {
|
|
123
|
+
const { url, token } = settings(flags);
|
|
124
|
+
let res;
|
|
125
|
+
try {
|
|
126
|
+
res = await fetch(`${url}${path}`, {
|
|
127
|
+
...init,
|
|
128
|
+
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", "User-Agent": `veylix-cli/${VERSION}`, ...init.headers },
|
|
129
|
+
});
|
|
130
|
+
} catch (e) {
|
|
131
|
+
fail(`Can't reach Veylix at ${url} (${e.cause?.code || e.message})`);
|
|
132
|
+
}
|
|
133
|
+
const text = await res.text();
|
|
134
|
+
if (!res.ok) {
|
|
135
|
+
let message = text;
|
|
136
|
+
try {
|
|
137
|
+
message = JSON.parse(text).error || text;
|
|
138
|
+
} catch {}
|
|
139
|
+
fail(`${message} [HTTP ${res.status}]`);
|
|
140
|
+
}
|
|
141
|
+
return text;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function targetQuery(flags, extra = {}) {
|
|
145
|
+
const { project, env } = target(flags);
|
|
146
|
+
const q = new URLSearchParams(Object.entries({ ...extra, project, env }).filter(([, v]) => v));
|
|
147
|
+
const qs = q.toString();
|
|
148
|
+
return qs ? `?${qs}` : "";
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const fetchSecrets = async (flags) => JSON.parse(await api(flags, `/api/v1/secrets${targetQuery(flags)}`));
|
|
152
|
+
const fetchMe = async (flags) => JSON.parse(await api(flags, "/api/v1/me"));
|
|
153
|
+
|
|
154
|
+
/** Asks the user to pick one of `items` (numbered list on stderr) by number or name. */
|
|
155
|
+
async function choose(question, items, label) {
|
|
156
|
+
if (items.length === 1) return items[0];
|
|
157
|
+
if (!process.stdin.isTTY) fail(`${question} Pass it as a flag (one of: ${items.map((item) => item.name).join(", ")}).`);
|
|
158
|
+
info(c(1, question));
|
|
159
|
+
items.forEach((item, i) => info(` ${c(36, String(i + 1).padStart(2))} ${label(item)}`));
|
|
160
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
161
|
+
try {
|
|
162
|
+
for (;;) {
|
|
163
|
+
const answer = (await rl.question(`Choose 1-${items.length}: `)).trim();
|
|
164
|
+
const n = Number(answer);
|
|
165
|
+
if (Number.isInteger(n) && n >= 1 && n <= items.length) return items[n - 1];
|
|
166
|
+
const byName = items.find((item) => item.name.toLowerCase() === answer.toLowerCase());
|
|
167
|
+
if (byName) return byName;
|
|
168
|
+
}
|
|
169
|
+
} catch (e) {
|
|
170
|
+
if (e.name === "AbortError") fail("Cancelled");
|
|
171
|
+
throw e;
|
|
172
|
+
} finally {
|
|
173
|
+
rl.close();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const where = (res) => `${res.project} › ${res.environment}`;
|
|
178
|
+
|
|
179
|
+
// ---------- formats ----------
|
|
180
|
+
|
|
181
|
+
const toDotenv = (secrets) => Object.entries(secrets).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join("\n") + "\n";
|
|
182
|
+
const toShell = (secrets) => Object.entries(secrets).map(([k, v]) => `export ${k}='${v.replace(/'/g, `'\\''`)}'`).join("\n") + "\n";
|
|
183
|
+
|
|
184
|
+
function parseDotenv(content) {
|
|
185
|
+
const out = {};
|
|
186
|
+
for (const raw of content.split(/\r?\n/)) {
|
|
187
|
+
const line = raw.trim();
|
|
188
|
+
if (!line || line.startsWith("#")) continue;
|
|
189
|
+
const m = line.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
|
|
190
|
+
if (!m) continue;
|
|
191
|
+
let value = m[2];
|
|
192
|
+
const quoted = value.match(/^(['"])([\s\S]*)\1$/);
|
|
193
|
+
if (quoted) {
|
|
194
|
+
value = quoted[2];
|
|
195
|
+
if (quoted[1] === '"') value = value.replace(/\\n/g, "\n").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
196
|
+
} else value = value.replace(/\s+#.*$/, "");
|
|
197
|
+
out[m[1]] = value;
|
|
198
|
+
}
|
|
199
|
+
return out;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ---------- output ----------
|
|
203
|
+
|
|
204
|
+
const isTTY = process.stderr.isTTY;
|
|
205
|
+
const c = (code, s) => (isTTY ? `\x1b[${code}m${s}\x1b[0m` : s);
|
|
206
|
+
const info = (msg) => process.stderr.write(`${msg}\n`);
|
|
207
|
+
const success = (msg) => info(`${c(32, "✓")} ${msg}`);
|
|
208
|
+
function fail(msg) {
|
|
209
|
+
process.stderr.write(`${c(31, "✗")} ${msg}\n`);
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ---------- commands ----------
|
|
214
|
+
|
|
215
|
+
const commands = {
|
|
216
|
+
async login(_, flags) {
|
|
217
|
+
if (!flags.token || flags.token === true) fail("Usage: veylix login --token vlx_… [--url https://your-veylix]");
|
|
218
|
+
const config = { url: String(flags.url || readConfig().url || "http://localhost:3000").replace(/\/$/, ""), token: String(flags.token) };
|
|
219
|
+
const me = await fetchMe({ ...flags, ...config });
|
|
220
|
+
writeConfig(config);
|
|
221
|
+
const scope = me.scope === "all-projects" ? `all projects (${me.projects.length})` : `project ${c(1, me.projects[0]?.name)}`;
|
|
222
|
+
success(`Logged in to ${config.url} as ${me.user?.email ?? "you"}: ${scope}, ${me.access}`);
|
|
223
|
+
info(`Next: run ${c(1, "veylix link")} in your app's folder to choose a project and environment.`);
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
async logout() {
|
|
227
|
+
if (existsSync(CONFIG_PATH)) writeConfig({ url: readConfig().url });
|
|
228
|
+
success("Logged out");
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
async whoami(_, flags) {
|
|
232
|
+
const me = await fetchMe(flags);
|
|
233
|
+
const link = findLink();
|
|
234
|
+
const t = target(flags);
|
|
235
|
+
const project = me.projects.find((p) => p.id === t.project || p.name.toLowerCase() === t.project?.toLowerCase());
|
|
236
|
+
console.log(`Account: ${me.user ? `${me.user.name} <${me.user.email}>` : "-"}`);
|
|
237
|
+
console.log(`Server: ${settings(flags).url}`);
|
|
238
|
+
console.log(`Token: ${me.scope === "all-projects" ? "all projects" : me.projects[0]?.name}, ${me.access}`);
|
|
239
|
+
console.log(`Using: ${project ? `${project.name} › ${t.env ?? "(no environment)"}` : "nothing linked; run `veylix link`"}`);
|
|
240
|
+
if (link) console.log(`Linked: ${relative(process.cwd(), link.file) || LINK_FILE}`);
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
async projects(_, flags) {
|
|
244
|
+
const me = await fetchMe(flags);
|
|
245
|
+
if (!me.projects.length) return info("No projects yet. Create one in Veylix first.");
|
|
246
|
+
const t = target(flags);
|
|
247
|
+
let marked = false;
|
|
248
|
+
const width = Math.max(...me.projects.map((p) => p.name.length), 7);
|
|
249
|
+
console.log(`${"PROJECT".padEnd(width)} ENVIRONMENTS (variables)`);
|
|
250
|
+
for (const p of me.projects) {
|
|
251
|
+
const linked = p.id === t.project || p.name.toLowerCase() === t.project?.toLowerCase();
|
|
252
|
+
marked ||= linked;
|
|
253
|
+
const envs = p.environments.map((e) => `${linked && e.name === t.env ? c(32, `${e.name}*`) : e.name} (${e.variables})`).join(", ");
|
|
254
|
+
console.log(`${(linked ? c(32, p.name) : p.name) + " ".repeat(width - p.name.length)} ${envs || c(2, "no environments")}`);
|
|
255
|
+
}
|
|
256
|
+
if (marked && t.env) info(c(2, "\n* currently used in this folder"));
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
async link(_, flags) {
|
|
260
|
+
const me = await fetchMe(flags);
|
|
261
|
+
if (!me.projects.length) fail("No projects yet. Create one in Veylix first.");
|
|
262
|
+
let project;
|
|
263
|
+
if (flags.project) {
|
|
264
|
+
const ref = String(flags.project).toLowerCase();
|
|
265
|
+
project = me.projects.find((p) => p.id === flags.project || p.name.toLowerCase() === ref);
|
|
266
|
+
if (!project) fail(`Project "${flags.project}" not found. Available: ${me.projects.map((p) => p.name).join(", ")}`);
|
|
267
|
+
} else project = await choose("Which project?", me.projects, (p) => `${p.name} ${c(2, `(${p.environments.length} environment${p.environments.length === 1 ? "" : "s"})`)}`);
|
|
268
|
+
|
|
269
|
+
if (!project.environments.length) fail(`${project.name} has no environments yet.`);
|
|
270
|
+
let env;
|
|
271
|
+
if (flags.env) {
|
|
272
|
+
env = project.environments.find((e) => e.name === String(flags.env).toLowerCase());
|
|
273
|
+
if (!env) fail(`Environment "${flags.env}" not found in ${project.name}. Available: ${project.environments.map((e) => e.name).join(", ")}`);
|
|
274
|
+
} else env = await choose(`Which environment of ${project.name}?`, project.environments, (e) => `${e.name} ${c(2, `(${e.variables} variable${e.variables === 1 ? "" : "s"})`)}`);
|
|
275
|
+
|
|
276
|
+
writeFileSync(LINK_FILE, JSON.stringify({ projectId: project.id, project: project.name, env: env.name }, null, 2) + "\n");
|
|
277
|
+
success(`Linked this folder to ${c(1, `${project.name} › ${env.name}`)} (${LINK_FILE})`);
|
|
278
|
+
info(c(2, "Now try: veylix pull > .env or veylix run -- npm start"));
|
|
279
|
+
},
|
|
280
|
+
|
|
281
|
+
async unlink() {
|
|
282
|
+
const link = findLink();
|
|
283
|
+
if (!link) return info("This folder isn't linked.");
|
|
284
|
+
rmSync(link.file);
|
|
285
|
+
success(`Removed ${relative(process.cwd(), link.file) || LINK_FILE}`);
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
async envs(_, flags) {
|
|
289
|
+
const me = await fetchMe(flags);
|
|
290
|
+
const t = target(flags);
|
|
291
|
+
const project = t.project
|
|
292
|
+
? me.projects.find((p) => p.id === t.project || p.name.toLowerCase() === t.project.toLowerCase())
|
|
293
|
+
: me.projects.length === 1 ? me.projects[0] : null;
|
|
294
|
+
if (!project) fail(t.project ? `Project "${t.project}" not found.` : "Which project? Run `veylix link` or pass --project <name>.");
|
|
295
|
+
const width = Math.max(...project.environments.map((e) => e.name.length), 11);
|
|
296
|
+
info(c(2, project.name));
|
|
297
|
+
console.log(`${"ENVIRONMENT".padEnd(width)} VARIABLES`);
|
|
298
|
+
for (const e of project.environments) console.log(`${e.name.padEnd(width)} ${e.variables}`);
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
async pull(_, flags) {
|
|
302
|
+
const res = await fetchSecrets(flags);
|
|
303
|
+
const { secrets } = res;
|
|
304
|
+
const format = flags.format || "dotenv";
|
|
305
|
+
const body = format === "json" ? JSON.stringify(secrets, null, 2) + "\n" : format === "shell" ? toShell(secrets) : toDotenv(secrets);
|
|
306
|
+
if (flags.output) {
|
|
307
|
+
writeFileSync(String(flags.output), body, { mode: 0o600 });
|
|
308
|
+
success(`Wrote ${Object.keys(secrets).length} variables from ${where(res)} to ${flags.output}`);
|
|
309
|
+
} else process.stdout.write(body);
|
|
310
|
+
},
|
|
311
|
+
|
|
312
|
+
async run(_, flags, rest) {
|
|
313
|
+
if (!rest.length) fail("Usage: veylix run [--project <name>] [--env <name>] -- <command> [args…]");
|
|
314
|
+
const res = await fetchSecrets(flags);
|
|
315
|
+
const { secrets } = res;
|
|
316
|
+
info(c(2, `veylix: injecting ${Object.keys(secrets).length} variables from ${where(res)}`));
|
|
317
|
+
const child = spawn(rest[0], rest.slice(1), { stdio: "inherit", env: { ...process.env, ...secrets }, shell: process.platform === "win32" });
|
|
318
|
+
for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) process.on(sig, () => child.kill(sig));
|
|
319
|
+
child.on("error", (e) => fail(`Couldn't start ${rest[0]}: ${e.message}`));
|
|
320
|
+
child.on("exit", (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
321
|
+
},
|
|
322
|
+
|
|
323
|
+
async push(_, flags) {
|
|
324
|
+
const file = String(flags.file || ".env");
|
|
325
|
+
if (!existsSync(file)) fail(`File not found: ${file}`);
|
|
326
|
+
const secrets = parseDotenv(readFileSync(file, "utf8"));
|
|
327
|
+
if (!Object.keys(secrets).length) fail(`No KEY=value lines found in ${file}`);
|
|
328
|
+
const res = JSON.parse(await api(flags, "/api/v1/secrets", { method: "PUT", body: JSON.stringify({ ...target(flags), secrets, replace: !!flags.replace }) }));
|
|
329
|
+
success(`${where(res)}: ${res.created} created, ${res.updated} updated${res.deleted ? `, ${res.deleted} deleted` : ""}`);
|
|
330
|
+
},
|
|
331
|
+
|
|
332
|
+
async set(args, flags) {
|
|
333
|
+
const pairs = args.map((a) => a.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/s));
|
|
334
|
+
if (!pairs.length || pairs.some((p) => !p)) fail("Usage: veylix set KEY=value [KEY2=value…]");
|
|
335
|
+
const secrets = Object.fromEntries(pairs.map((p) => [p[1], p[2]]));
|
|
336
|
+
const res = JSON.parse(await api(flags, "/api/v1/secrets", { method: "PUT", body: JSON.stringify({ ...target(flags), secrets }) }));
|
|
337
|
+
success(`${where(res)}: ${res.created} created, ${res.updated} updated`);
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
async unset(args, flags) {
|
|
341
|
+
if (!args[0]) fail("Usage: veylix unset KEY");
|
|
342
|
+
const res = JSON.parse(await api(flags, `/api/v1/secrets${targetQuery(flags, { key: args[0] })}`, { method: "DELETE" }));
|
|
343
|
+
success(`Deleted ${res.deleted} from ${where(res)}`);
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
// ---------- main ----------
|
|
348
|
+
|
|
349
|
+
const { command, args, flags, rest } = parseArgs(process.argv.slice(2));
|
|
350
|
+
if (flags.version || command === "version") console.log(VERSION);
|
|
351
|
+
else if (!command || flags.help || command === "help") console.log(HELP);
|
|
352
|
+
else if (!commands[command]) fail(`Unknown command "${command}". Run \`veylix help\`.`);
|
|
353
|
+
else await commands[command](args, flags, rest);
|
package/README.md
DELETED