@tolgamorf/env2op-cli 0.2.2 → 0.2.4
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/LICENSE +1 -1
- package/README.md +31 -6
- package/dist/cli.js +485 -478
- package/dist/index.d.ts +3 -0
- package/dist/index.js +130 -131
- package/dist/op2env-cli.js +478 -474
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -22,7 +22,7 @@ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports,
|
|
|
22
22
|
var require_package = __commonJS((exports, module) => {
|
|
23
23
|
module.exports = {
|
|
24
24
|
name: "@tolgamorf/env2op-cli",
|
|
25
|
-
version: "0.2.
|
|
25
|
+
version: "0.2.4",
|
|
26
26
|
description: "Convert .env files to 1Password Secure Notes and generate templates for op inject/run",
|
|
27
27
|
type: "module",
|
|
28
28
|
main: "dist/index.js",
|
|
@@ -105,11 +105,11 @@ var require_package = __commonJS((exports, module) => {
|
|
|
105
105
|
import pc5 from "picocolors";
|
|
106
106
|
|
|
107
107
|
// src/commands/convert.ts
|
|
108
|
-
import { basename, dirname, join } from "node:path";
|
|
109
|
-
import * as
|
|
108
|
+
import { basename, dirname, join as join2 } from "node:path";
|
|
109
|
+
import * as p4 from "@clack/prompts";
|
|
110
110
|
|
|
111
|
-
// src/core/
|
|
112
|
-
import
|
|
111
|
+
// src/core/auth.ts
|
|
112
|
+
import * as p from "@clack/prompts";
|
|
113
113
|
|
|
114
114
|
// src/utils/errors.ts
|
|
115
115
|
class Env2OpError extends Error {
|
|
@@ -142,117 +142,17 @@ var errors = {
|
|
|
142
142
|
envFileEmpty: (path) => new Env2OpError(`No valid environment variables found in ${path}`, ErrorCodes.ENV_FILE_EMPTY, "Ensure the file contains KEY=value pairs"),
|
|
143
143
|
opCliNotInstalled: () => new Env2OpError("1Password CLI (op) is not installed", ErrorCodes.OP_CLI_NOT_INSTALLED, "Install it from https://1password.com/downloads/command-line/"),
|
|
144
144
|
opNotSignedIn: () => new Env2OpError("Not signed in to 1Password CLI", ErrorCodes.OP_NOT_SIGNED_IN, 'Run "op signin" to authenticate'),
|
|
145
|
+
opSigninFailed: () => new Env2OpError("Failed to sign in to 1Password CLI", ErrorCodes.OP_SIGNIN_FAILED, 'Try running "op signin" manually'),
|
|
145
146
|
vaultNotFound: (vault) => new Env2OpError(`Vault not found: ${vault}`, ErrorCodes.VAULT_NOT_FOUND, 'Run "op vault list" to see available vaults'),
|
|
146
147
|
vaultCreateFailed: (message) => new Env2OpError(`Failed to create vault: ${message}`, ErrorCodes.VAULT_CREATE_FAILED),
|
|
147
148
|
itemExists: (title, vault) => new Env2OpError(`Item "${title}" already exists in vault "${vault}"`, ErrorCodes.ITEM_EXISTS, "Use default behavior (overwrites) or choose a different item name"),
|
|
148
149
|
itemCreateFailed: (message) => new Env2OpError(`Failed to create 1Password item: ${message}`, ErrorCodes.ITEM_CREATE_FAILED),
|
|
149
150
|
itemEditFailed: (message) => new Env2OpError(`Failed to edit 1Password item: ${message}`, ErrorCodes.ITEM_EDIT_FAILED),
|
|
150
|
-
parseError: (line, message) => new Env2OpError(`Parse error at line ${line}: ${message}`, ErrorCodes.PARSE_ERROR)
|
|
151
|
+
parseError: (line, message) => new Env2OpError(`Parse error at line ${line}: ${message}`, ErrorCodes.PARSE_ERROR),
|
|
152
|
+
templateNotFound: (path) => new Env2OpError(`Template file not found: ${path}`, ErrorCodes.TEMPLATE_NOT_FOUND, "Ensure the file exists and the path is correct"),
|
|
153
|
+
injectFailed: (message) => new Env2OpError("Failed to pull secrets from 1Password", ErrorCodes.INJECT_FAILED, message)
|
|
151
154
|
};
|
|
152
155
|
|
|
153
|
-
// src/core/env-parser.ts
|
|
154
|
-
var HEADER_SEPARATOR = "# ===========================================================================";
|
|
155
|
-
function stripHeaders(content) {
|
|
156
|
-
const lines = content.split(`
|
|
157
|
-
`);
|
|
158
|
-
const result = [];
|
|
159
|
-
let inHeader = false;
|
|
160
|
-
for (const line of lines) {
|
|
161
|
-
const trimmed = line.trim();
|
|
162
|
-
if (trimmed === HEADER_SEPARATOR) {
|
|
163
|
-
if (!inHeader) {
|
|
164
|
-
inHeader = true;
|
|
165
|
-
} else {
|
|
166
|
-
inHeader = false;
|
|
167
|
-
}
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
if (!inHeader) {
|
|
171
|
-
result.push(line);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
while (result.length > 0 && result[0]?.trim() === "") {
|
|
175
|
-
result.shift();
|
|
176
|
-
}
|
|
177
|
-
return result.join(`
|
|
178
|
-
`);
|
|
179
|
-
}
|
|
180
|
-
function parseValue(raw) {
|
|
181
|
-
const trimmed = raw.trim();
|
|
182
|
-
if (trimmed.startsWith('"')) {
|
|
183
|
-
const endQuote = trimmed.indexOf('"', 1);
|
|
184
|
-
if (endQuote !== -1) {
|
|
185
|
-
return trimmed.slice(1, endQuote);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
if (trimmed.startsWith("'")) {
|
|
189
|
-
const endQuote = trimmed.indexOf("'", 1);
|
|
190
|
-
if (endQuote !== -1) {
|
|
191
|
-
return trimmed.slice(1, endQuote);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
const parts = trimmed.split(/\s+#/);
|
|
195
|
-
return (parts[0] ?? trimmed).trim();
|
|
196
|
-
}
|
|
197
|
-
function stripBom(content) {
|
|
198
|
-
if (content.charCodeAt(0) === 65279) {
|
|
199
|
-
return content.slice(1);
|
|
200
|
-
}
|
|
201
|
-
return content;
|
|
202
|
-
}
|
|
203
|
-
async function parseEnvFile(filePath) {
|
|
204
|
-
let rawContent;
|
|
205
|
-
try {
|
|
206
|
-
rawContent = await readFile(filePath, "utf-8");
|
|
207
|
-
} catch {
|
|
208
|
-
throw errors.envFileNotFound(filePath);
|
|
209
|
-
}
|
|
210
|
-
const content = stripHeaders(stripBom(rawContent));
|
|
211
|
-
const rawLines = content.split(`
|
|
212
|
-
`);
|
|
213
|
-
const variables = [];
|
|
214
|
-
const lines = [];
|
|
215
|
-
const parseErrors = [];
|
|
216
|
-
let currentComment = "";
|
|
217
|
-
for (let i = 0;i < rawLines.length; i++) {
|
|
218
|
-
const line = rawLines[i] ?? "";
|
|
219
|
-
const trimmed = line.trim();
|
|
220
|
-
const lineNumber = i + 1;
|
|
221
|
-
if (!trimmed) {
|
|
222
|
-
lines.push({ type: "empty" });
|
|
223
|
-
currentComment = "";
|
|
224
|
-
continue;
|
|
225
|
-
}
|
|
226
|
-
if (trimmed.startsWith("#")) {
|
|
227
|
-
lines.push({ type: "comment", content: line });
|
|
228
|
-
currentComment = trimmed.slice(1).trim();
|
|
229
|
-
continue;
|
|
230
|
-
}
|
|
231
|
-
const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
|
|
232
|
-
if (match?.[1]) {
|
|
233
|
-
const key = match[1];
|
|
234
|
-
const rawValue = match[2] ?? "";
|
|
235
|
-
const value = parseValue(rawValue);
|
|
236
|
-
variables.push({
|
|
237
|
-
key,
|
|
238
|
-
value,
|
|
239
|
-
comment: currentComment || undefined,
|
|
240
|
-
line: lineNumber
|
|
241
|
-
});
|
|
242
|
-
lines.push({ type: "variable", key, value });
|
|
243
|
-
currentComment = "";
|
|
244
|
-
} else if (trimmed.includes("=")) {
|
|
245
|
-
parseErrors.push(`Line ${lineNumber}: Invalid variable name`);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
return { variables, lines, errors: parseErrors };
|
|
249
|
-
}
|
|
250
|
-
function validateParseResult(result, filePath) {
|
|
251
|
-
if (result.variables.length === 0) {
|
|
252
|
-
throw errors.envFileEmpty(filePath);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
156
|
// src/utils/shell.ts
|
|
257
157
|
import { spawn } from "node:child_process";
|
|
258
158
|
import pc from "picocolors";
|
|
@@ -262,16 +162,8 @@ function quoteArg(arg) {
|
|
|
262
162
|
}
|
|
263
163
|
return arg;
|
|
264
164
|
}
|
|
265
|
-
|
|
266
|
-
const { verbose = false } = options;
|
|
267
|
-
const fullCommand = `${command} ${args.map(quoteArg).join(" ")}`;
|
|
268
|
-
if (verbose) {
|
|
269
|
-
console.log(pc.dim(`$ ${fullCommand}`));
|
|
270
|
-
}
|
|
165
|
+
function collectOutput(proc, verbose) {
|
|
271
166
|
return new Promise((resolve) => {
|
|
272
|
-
const proc = spawn(command, args, {
|
|
273
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
274
|
-
});
|
|
275
167
|
const stdoutChunks = [];
|
|
276
168
|
const stderrChunks = [];
|
|
277
169
|
proc.stdout?.on("data", (data) => {
|
|
@@ -305,48 +197,29 @@ async function exec(command, args = [], options = {}) {
|
|
|
305
197
|
});
|
|
306
198
|
});
|
|
307
199
|
}
|
|
200
|
+
async function exec(command, args = [], options = {}) {
|
|
201
|
+
const { verbose = false } = options;
|
|
202
|
+
const fullCommand = `${command} ${args.map(quoteArg).join(" ")}`;
|
|
203
|
+
if (verbose) {
|
|
204
|
+
console.log(pc.dim(`$ ${fullCommand}`));
|
|
205
|
+
}
|
|
206
|
+
const proc = spawn(command, args, {
|
|
207
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
208
|
+
});
|
|
209
|
+
return collectOutput(proc, verbose);
|
|
210
|
+
}
|
|
308
211
|
async function execWithStdin(command, args = [], options) {
|
|
309
212
|
const { stdin: stdinContent, verbose = false } = options;
|
|
310
213
|
if (verbose) {
|
|
311
214
|
const fullCommand = `${command} ${args.map(quoteArg).join(" ")}`;
|
|
312
215
|
console.log(pc.dim(`$ echo '...' | ${fullCommand}`));
|
|
313
216
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
317
|
-
});
|
|
318
|
-
const stdoutChunks = [];
|
|
319
|
-
const stderrChunks = [];
|
|
320
|
-
proc.stdin?.write(stdinContent);
|
|
321
|
-
proc.stdin?.end();
|
|
322
|
-
proc.stdout?.on("data", (data) => {
|
|
323
|
-
const text = Buffer.isBuffer(data) ? data.toString() : String(data);
|
|
324
|
-
stdoutChunks.push(text);
|
|
325
|
-
if (verbose)
|
|
326
|
-
process.stdout.write(text);
|
|
327
|
-
});
|
|
328
|
-
proc.stderr?.on("data", (data) => {
|
|
329
|
-
const text = Buffer.isBuffer(data) ? data.toString() : String(data);
|
|
330
|
-
stderrChunks.push(text);
|
|
331
|
-
if (verbose)
|
|
332
|
-
process.stderr.write(text);
|
|
333
|
-
});
|
|
334
|
-
proc.on("close", (code) => {
|
|
335
|
-
resolve({
|
|
336
|
-
stdout: stdoutChunks.join(""),
|
|
337
|
-
stderr: stderrChunks.join(""),
|
|
338
|
-
exitCode: code ?? 1
|
|
339
|
-
});
|
|
340
|
-
});
|
|
341
|
-
proc.on("error", (err) => {
|
|
342
|
-
stderrChunks.push(err.message);
|
|
343
|
-
resolve({
|
|
344
|
-
stdout: stdoutChunks.join(""),
|
|
345
|
-
stderr: stderrChunks.join(""),
|
|
346
|
-
exitCode: 1
|
|
347
|
-
});
|
|
348
|
-
});
|
|
217
|
+
const proc = spawn(command, args, {
|
|
218
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
349
219
|
});
|
|
220
|
+
proc.stdin?.write(stdinContent);
|
|
221
|
+
proc.stdin?.end();
|
|
222
|
+
return collectOutput(proc, verbose);
|
|
350
223
|
}
|
|
351
224
|
|
|
352
225
|
// src/core/onepassword.ts
|
|
@@ -431,47 +304,369 @@ async function createSecureNote(options) {
|
|
|
431
304
|
vaultId: item.vault?.id ?? "",
|
|
432
305
|
fieldIds
|
|
433
306
|
};
|
|
434
|
-
} catch (error) {
|
|
435
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
436
|
-
throw errors.itemCreateFailed(message);
|
|
307
|
+
} catch (error) {
|
|
308
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
309
|
+
throw errors.itemCreateFailed(message);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
async function editSecureNote(options) {
|
|
313
|
+
const { vault, title, fields, secret, verbose, itemId } = options;
|
|
314
|
+
const template = buildItemTemplate(title, vault, fields, secret);
|
|
315
|
+
const json = JSON.stringify(template);
|
|
316
|
+
try {
|
|
317
|
+
const result = await execWithStdin("op", ["item", "edit", itemId, "--format", "json"], {
|
|
318
|
+
stdin: json,
|
|
319
|
+
verbose
|
|
320
|
+
});
|
|
321
|
+
if (result.exitCode !== 0) {
|
|
322
|
+
throw new Error(result.stderr || "Failed to edit item");
|
|
323
|
+
}
|
|
324
|
+
const item = JSON.parse(result.stdout);
|
|
325
|
+
const fieldIds = {};
|
|
326
|
+
for (const field of item.fields ?? []) {
|
|
327
|
+
if (field.label && field.id) {
|
|
328
|
+
fieldIds[field.label] = field.id;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return {
|
|
332
|
+
id: item.id,
|
|
333
|
+
title: item.title,
|
|
334
|
+
vault: item.vault?.name ?? vault,
|
|
335
|
+
vaultId: item.vault?.id ?? "",
|
|
336
|
+
fieldIds
|
|
337
|
+
};
|
|
338
|
+
} catch (error) {
|
|
339
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
340
|
+
throw errors.itemEditFailed(message);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// src/core/auth.ts
|
|
345
|
+
async function ensureOpAuthenticated(options) {
|
|
346
|
+
const { verbose } = options;
|
|
347
|
+
const authSpinner = p.spinner();
|
|
348
|
+
authSpinner.start("Checking 1Password CLI...");
|
|
349
|
+
const opInstalled = await checkOpCli({ verbose });
|
|
350
|
+
if (!opInstalled) {
|
|
351
|
+
authSpinner.stop("1Password CLI not found");
|
|
352
|
+
throw errors.opCliNotInstalled();
|
|
353
|
+
}
|
|
354
|
+
let signedIn = await checkSignedIn({ verbose });
|
|
355
|
+
if (!signedIn) {
|
|
356
|
+
authSpinner.message("Signing in to 1Password...");
|
|
357
|
+
const signInSuccess = await signIn({ verbose });
|
|
358
|
+
if (!signInSuccess) {
|
|
359
|
+
authSpinner.stop();
|
|
360
|
+
throw errors.opSigninFailed();
|
|
361
|
+
}
|
|
362
|
+
signedIn = await checkSignedIn({ verbose });
|
|
363
|
+
if (!signedIn) {
|
|
364
|
+
authSpinner.stop();
|
|
365
|
+
throw errors.opNotSignedIn();
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
authSpinner.stop("1Password CLI ready");
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// src/core/env-parser.ts
|
|
372
|
+
import { readFile } from "node:fs/promises";
|
|
373
|
+
|
|
374
|
+
// src/core/constants.ts
|
|
375
|
+
var HEADER_SEPARATOR = `# ${"=".repeat(75)}`;
|
|
376
|
+
|
|
377
|
+
// src/core/env-parser.ts
|
|
378
|
+
function stripHeaders(content) {
|
|
379
|
+
const lines = content.split(`
|
|
380
|
+
`);
|
|
381
|
+
const result = [];
|
|
382
|
+
let inHeader = false;
|
|
383
|
+
for (const line of lines) {
|
|
384
|
+
const trimmed = line.trim();
|
|
385
|
+
if (trimmed === HEADER_SEPARATOR) {
|
|
386
|
+
if (!inHeader) {
|
|
387
|
+
inHeader = true;
|
|
388
|
+
} else {
|
|
389
|
+
inHeader = false;
|
|
390
|
+
}
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
if (!inHeader) {
|
|
394
|
+
result.push(line);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
while (result.length > 0 && result[0]?.trim() === "") {
|
|
398
|
+
result.shift();
|
|
399
|
+
}
|
|
400
|
+
return result.join(`
|
|
401
|
+
`);
|
|
402
|
+
}
|
|
403
|
+
function parseValue(raw) {
|
|
404
|
+
const trimmed = raw.trim();
|
|
405
|
+
if (trimmed.startsWith('"')) {
|
|
406
|
+
const endQuote = trimmed.indexOf('"', 1);
|
|
407
|
+
if (endQuote !== -1) {
|
|
408
|
+
return trimmed.slice(1, endQuote);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
if (trimmed.startsWith("'")) {
|
|
412
|
+
const endQuote = trimmed.indexOf("'", 1);
|
|
413
|
+
if (endQuote !== -1) {
|
|
414
|
+
return trimmed.slice(1, endQuote);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
const parts = trimmed.split(/\s+#/);
|
|
418
|
+
return (parts[0] ?? trimmed).trim();
|
|
419
|
+
}
|
|
420
|
+
function stripBom(content) {
|
|
421
|
+
if (content.charCodeAt(0) === 65279) {
|
|
422
|
+
return content.slice(1);
|
|
423
|
+
}
|
|
424
|
+
return content;
|
|
425
|
+
}
|
|
426
|
+
async function parseEnvFile(filePath) {
|
|
427
|
+
let rawContent;
|
|
428
|
+
try {
|
|
429
|
+
rawContent = await readFile(filePath, "utf-8");
|
|
430
|
+
} catch {
|
|
431
|
+
throw errors.envFileNotFound(filePath);
|
|
432
|
+
}
|
|
433
|
+
const content = stripHeaders(stripBom(rawContent));
|
|
434
|
+
const rawLines = content.split(`
|
|
435
|
+
`);
|
|
436
|
+
const variables = [];
|
|
437
|
+
const lines = [];
|
|
438
|
+
const parseErrors = [];
|
|
439
|
+
let currentComment = "";
|
|
440
|
+
for (let i = 0;i < rawLines.length; i++) {
|
|
441
|
+
const line = rawLines[i] ?? "";
|
|
442
|
+
const trimmed = line.trim();
|
|
443
|
+
const lineNumber = i + 1;
|
|
444
|
+
if (!trimmed) {
|
|
445
|
+
lines.push({ type: "empty" });
|
|
446
|
+
currentComment = "";
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
if (trimmed.startsWith("#")) {
|
|
450
|
+
lines.push({ type: "comment", content: line });
|
|
451
|
+
currentComment = trimmed.slice(1).trim();
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
|
|
455
|
+
if (match?.[1]) {
|
|
456
|
+
const key = match[1];
|
|
457
|
+
const rawValue = match[2] ?? "";
|
|
458
|
+
const value = parseValue(rawValue);
|
|
459
|
+
variables.push({
|
|
460
|
+
key,
|
|
461
|
+
value,
|
|
462
|
+
comment: currentComment || undefined,
|
|
463
|
+
line: lineNumber
|
|
464
|
+
});
|
|
465
|
+
lines.push({ type: "variable", key, value });
|
|
466
|
+
currentComment = "";
|
|
467
|
+
} else if (trimmed.includes("=")) {
|
|
468
|
+
parseErrors.push(`Line ${lineNumber}: Invalid variable name`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return { variables, lines, errors: parseErrors };
|
|
472
|
+
}
|
|
473
|
+
function validateParseResult(result, filePath) {
|
|
474
|
+
if (result.variables.length === 0) {
|
|
475
|
+
throw errors.envFileEmpty(filePath);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// src/core/template-generator.ts
|
|
480
|
+
import { writeFileSync as writeFileSync2 } from "node:fs";
|
|
481
|
+
|
|
482
|
+
// src/lib/update.ts
|
|
483
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
484
|
+
import { homedir } from "node:os";
|
|
485
|
+
import { join } from "node:path";
|
|
486
|
+
|
|
487
|
+
// src/lib/package-manager.ts
|
|
488
|
+
var UPDATE_COMMANDS = {
|
|
489
|
+
homebrew: "brew upgrade tolgamorf/tap/env2op-cli",
|
|
490
|
+
npm: "npm update -g @tolgamorf/env2op-cli",
|
|
491
|
+
bun: "bun update -g @tolgamorf/env2op-cli",
|
|
492
|
+
pnpm: "pnpm update -g @tolgamorf/env2op-cli",
|
|
493
|
+
unknown: "npm update -g @tolgamorf/env2op-cli"
|
|
494
|
+
};
|
|
495
|
+
var DISPLAY_NAMES = {
|
|
496
|
+
homebrew: "Homebrew",
|
|
497
|
+
npm: "npm",
|
|
498
|
+
bun: "Bun",
|
|
499
|
+
pnpm: "pnpm",
|
|
500
|
+
unknown: "npm (default)"
|
|
501
|
+
};
|
|
502
|
+
function detectFromPath() {
|
|
503
|
+
const binPath = process.argv[1] ?? "";
|
|
504
|
+
if (binPath.includes("/Cellar/") || binPath.includes("/homebrew/") || binPath.includes("/opt/homebrew/") || binPath.includes("/home/linuxbrew/")) {
|
|
505
|
+
return "homebrew";
|
|
506
|
+
}
|
|
507
|
+
if (binPath.includes("/.bun/")) {
|
|
508
|
+
return "bun";
|
|
509
|
+
}
|
|
510
|
+
if (binPath.includes("/pnpm/") || binPath.includes("/.pnpm/")) {
|
|
511
|
+
return "pnpm";
|
|
512
|
+
}
|
|
513
|
+
if (binPath.includes("/node_modules/")) {
|
|
514
|
+
return "npm";
|
|
515
|
+
}
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
async function detectFromCommands() {
|
|
519
|
+
const brewResult = await exec("brew", ["list", "env2op-cli"], { verbose: false });
|
|
520
|
+
if (brewResult.exitCode === 0) {
|
|
521
|
+
return "homebrew";
|
|
522
|
+
}
|
|
523
|
+
return "npm";
|
|
524
|
+
}
|
|
525
|
+
async function detectPackageManager() {
|
|
526
|
+
const fromPath = detectFromPath();
|
|
527
|
+
if (fromPath) {
|
|
528
|
+
return {
|
|
529
|
+
type: fromPath,
|
|
530
|
+
updateCommand: UPDATE_COMMANDS[fromPath],
|
|
531
|
+
displayName: DISPLAY_NAMES[fromPath]
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
const fromCommands = await detectFromCommands();
|
|
535
|
+
return {
|
|
536
|
+
type: fromCommands,
|
|
537
|
+
updateCommand: UPDATE_COMMANDS[fromCommands],
|
|
538
|
+
displayName: DISPLAY_NAMES[fromCommands]
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// src/lib/update.ts
|
|
543
|
+
var CACHE_DIR = join(homedir(), ".env2op");
|
|
544
|
+
var CACHE_FILE = join(CACHE_DIR, "update-check.json");
|
|
545
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
546
|
+
function getCliVersion() {
|
|
547
|
+
try {
|
|
548
|
+
const pkg = require_package();
|
|
549
|
+
return pkg.version ?? "0.0.0";
|
|
550
|
+
} catch {
|
|
551
|
+
return "0.0.0";
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
function loadCache() {
|
|
555
|
+
try {
|
|
556
|
+
if (existsSync(CACHE_FILE)) {
|
|
557
|
+
const content = readFileSync(CACHE_FILE, "utf-8");
|
|
558
|
+
return JSON.parse(content);
|
|
559
|
+
}
|
|
560
|
+
} catch {}
|
|
561
|
+
return { lastCheck: 0, latestVersion: null };
|
|
562
|
+
}
|
|
563
|
+
function saveCache(cache) {
|
|
564
|
+
try {
|
|
565
|
+
if (!existsSync(CACHE_DIR)) {
|
|
566
|
+
mkdirSync(CACHE_DIR, { recursive: true });
|
|
567
|
+
}
|
|
568
|
+
writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2));
|
|
569
|
+
} catch {}
|
|
570
|
+
}
|
|
571
|
+
function shouldCheckForUpdate(cache) {
|
|
572
|
+
const now = Date.now();
|
|
573
|
+
return now - cache.lastCheck > CHECK_INTERVAL_MS;
|
|
574
|
+
}
|
|
575
|
+
async function fetchLatestVersion() {
|
|
576
|
+
try {
|
|
577
|
+
const response = await fetch("https://registry.npmjs.org/@tolgamorf/env2op-cli/latest");
|
|
578
|
+
if (!response.ok)
|
|
579
|
+
return null;
|
|
580
|
+
const data = await response.json();
|
|
581
|
+
return data.version ?? null;
|
|
582
|
+
} catch {
|
|
583
|
+
return null;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
function compareVersions(v1, v2) {
|
|
587
|
+
const parts1 = v1.split(".").map(Number);
|
|
588
|
+
const parts2 = v2.split(".").map(Number);
|
|
589
|
+
for (let i = 0;i < 3; i++) {
|
|
590
|
+
const p1 = parts1[i] || 0;
|
|
591
|
+
const p2 = parts2[i] || 0;
|
|
592
|
+
if (p1 < p2)
|
|
593
|
+
return -1;
|
|
594
|
+
if (p1 > p2)
|
|
595
|
+
return 1;
|
|
596
|
+
}
|
|
597
|
+
return 0;
|
|
598
|
+
}
|
|
599
|
+
async function checkForUpdate(forceCheck = false) {
|
|
600
|
+
const currentVersion = getCliVersion();
|
|
601
|
+
const cache = loadCache();
|
|
602
|
+
if (!forceCheck && !shouldCheckForUpdate(cache) && cache.latestVersion) {
|
|
603
|
+
const updateAvailable2 = compareVersions(currentVersion, cache.latestVersion) < 0;
|
|
604
|
+
const isSkipped2 = cache.skipVersion === cache.latestVersion;
|
|
605
|
+
return {
|
|
606
|
+
currentVersion,
|
|
607
|
+
latestVersion: cache.latestVersion,
|
|
608
|
+
updateAvailable: updateAvailable2,
|
|
609
|
+
isSkipped: isSkipped2,
|
|
610
|
+
fromCache: true
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
const latestVersion = await fetchLatestVersion();
|
|
614
|
+
saveCache({
|
|
615
|
+
...cache,
|
|
616
|
+
lastCheck: Date.now(),
|
|
617
|
+
latestVersion
|
|
618
|
+
});
|
|
619
|
+
if (!latestVersion) {
|
|
620
|
+
return {
|
|
621
|
+
currentVersion,
|
|
622
|
+
latestVersion: null,
|
|
623
|
+
updateAvailable: false,
|
|
624
|
+
isSkipped: false,
|
|
625
|
+
fromCache: false
|
|
626
|
+
};
|
|
437
627
|
}
|
|
628
|
+
const updateAvailable = compareVersions(currentVersion, latestVersion) < 0;
|
|
629
|
+
const isSkipped = cache.skipVersion === latestVersion;
|
|
630
|
+
return {
|
|
631
|
+
currentVersion,
|
|
632
|
+
latestVersion,
|
|
633
|
+
updateAvailable,
|
|
634
|
+
isSkipped,
|
|
635
|
+
fromCache: false
|
|
636
|
+
};
|
|
438
637
|
}
|
|
439
|
-
async function
|
|
440
|
-
const
|
|
441
|
-
const template = buildItemTemplate(title, vault, fields, secret);
|
|
442
|
-
const json = JSON.stringify(template);
|
|
638
|
+
async function performUpdate(pm) {
|
|
639
|
+
const packageManager = pm ?? await detectPackageManager();
|
|
443
640
|
try {
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
verbose
|
|
447
|
-
});
|
|
641
|
+
const [command, ...args] = packageManager.updateCommand.split(" ");
|
|
642
|
+
const result = await exec(command, args, { verbose: false });
|
|
448
643
|
if (result.exitCode !== 0) {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
for (const field of item.fields ?? []) {
|
|
454
|
-
if (field.label && field.id) {
|
|
455
|
-
fieldIds[field.label] = field.id;
|
|
456
|
-
}
|
|
644
|
+
return {
|
|
645
|
+
success: false,
|
|
646
|
+
error: result.stderr || `Command exited with code ${result.exitCode}`
|
|
647
|
+
};
|
|
457
648
|
}
|
|
458
|
-
return {
|
|
459
|
-
id: item.id,
|
|
460
|
-
title: item.title,
|
|
461
|
-
vault: item.vault?.name ?? vault,
|
|
462
|
-
vaultId: item.vault?.id ?? "",
|
|
463
|
-
fieldIds
|
|
464
|
-
};
|
|
649
|
+
return { success: true };
|
|
465
650
|
} catch (error) {
|
|
466
651
|
const message = error instanceof Error ? error.message : String(error);
|
|
467
|
-
|
|
652
|
+
return { success: false, error: message };
|
|
468
653
|
}
|
|
469
654
|
}
|
|
655
|
+
function skipVersion(version) {
|
|
656
|
+
const cache = loadCache();
|
|
657
|
+
cache.skipVersion = version;
|
|
658
|
+
saveCache(cache);
|
|
659
|
+
}
|
|
660
|
+
async function maybeShowUpdateNotification(cliName, showNotification) {
|
|
661
|
+
try {
|
|
662
|
+
const result = await checkForUpdate();
|
|
663
|
+
if (result.updateAvailable && !result.isSkipped) {
|
|
664
|
+
showNotification(result, cliName);
|
|
665
|
+
}
|
|
666
|
+
} catch {}
|
|
667
|
+
}
|
|
470
668
|
|
|
471
669
|
// src/core/template-generator.ts
|
|
472
|
-
var import__package = __toESM(require_package(), 1);
|
|
473
|
-
import { writeFileSync } from "node:fs";
|
|
474
|
-
var SEPARATOR = "# ===========================================================================";
|
|
475
670
|
function deriveEnvFileName(templateFileName) {
|
|
476
671
|
if (templateFileName.endsWith(".tpl")) {
|
|
477
672
|
return templateFileName.slice(0, -4);
|
|
@@ -487,7 +682,7 @@ function formatTimestamp() {
|
|
|
487
682
|
function generateTemplateHeader(templateFileName) {
|
|
488
683
|
const envFileName = deriveEnvFileName(templateFileName);
|
|
489
684
|
return [
|
|
490
|
-
|
|
685
|
+
HEADER_SEPARATOR,
|
|
491
686
|
`# ${templateFileName} — 1Password Secret References`,
|
|
492
687
|
"#",
|
|
493
688
|
"# This template contains references to secrets stored in 1Password.",
|
|
@@ -500,27 +695,27 @@ function generateTemplateHeader(templateFileName) {
|
|
|
500
695
|
`# op run --env-file ${templateFileName} -- npm start`,
|
|
501
696
|
"#",
|
|
502
697
|
`# Pushed: ${formatTimestamp()}`,
|
|
503
|
-
`# Generated by env2op v${
|
|
698
|
+
`# Generated by env2op v${getCliVersion()}`,
|
|
504
699
|
"# https://github.com/tolgamorf/env2op-cli",
|
|
505
|
-
|
|
700
|
+
HEADER_SEPARATOR,
|
|
506
701
|
""
|
|
507
702
|
];
|
|
508
703
|
}
|
|
509
704
|
function generateEnvHeader(envFileName) {
|
|
510
705
|
const templateFileName = deriveTemplateFileName(envFileName);
|
|
511
706
|
return [
|
|
512
|
-
|
|
707
|
+
HEADER_SEPARATOR,
|
|
513
708
|
`# ${envFileName} — Environment Variables`,
|
|
514
709
|
"#",
|
|
515
710
|
"# WARNING: This file contains sensitive values. Do not commit to git!",
|
|
516
711
|
"#",
|
|
517
712
|
`# To push updates to 1Password and generate ${templateFileName}:`,
|
|
518
|
-
`# env2op ${envFileName} <vault> "<item_name>"`,
|
|
713
|
+
`# env2op ${envFileName} "<vault>" "<item_name>"`,
|
|
519
714
|
"#",
|
|
520
715
|
`# Pulled: ${formatTimestamp()}`,
|
|
521
|
-
`# Generated by op2env v${
|
|
716
|
+
`# Generated by op2env v${getCliVersion()}`,
|
|
522
717
|
"# https://github.com/tolgamorf/env2op-cli",
|
|
523
|
-
|
|
718
|
+
HEADER_SEPARATOR,
|
|
524
719
|
"",
|
|
525
720
|
""
|
|
526
721
|
];
|
|
@@ -548,7 +743,7 @@ function generateTemplateContent(options, templateFileName) {
|
|
|
548
743
|
`;
|
|
549
744
|
}
|
|
550
745
|
function writeTemplate(content, outputPath) {
|
|
551
|
-
|
|
746
|
+
writeFileSync2(outputPath, content, "utf-8");
|
|
552
747
|
}
|
|
553
748
|
function generateUsageInstructions(templatePath) {
|
|
554
749
|
return ["Usage:", ` op2env ${templatePath}`, ` op run --env-file ${templatePath} -- npm start`].join(`
|
|
@@ -556,7 +751,7 @@ function generateUsageInstructions(templatePath) {
|
|
|
556
751
|
}
|
|
557
752
|
|
|
558
753
|
// src/utils/logger.ts
|
|
559
|
-
import * as
|
|
754
|
+
import * as p2 from "@clack/prompts";
|
|
560
755
|
import pc2 from "picocolors";
|
|
561
756
|
var symbols = {
|
|
562
757
|
success: pc2.green("✓"),
|
|
@@ -569,29 +764,29 @@ var symbols = {
|
|
|
569
764
|
var logger = {
|
|
570
765
|
intro(name, version, dryRun = false) {
|
|
571
766
|
const label = dryRun ? pc2.bgYellow(pc2.black(` ${name} v${version} [DRY RUN] `)) : pc2.bgCyan(pc2.black(` ${name} v${version} `));
|
|
572
|
-
|
|
767
|
+
p2.intro(label);
|
|
573
768
|
},
|
|
574
769
|
section(title) {
|
|
575
770
|
console.log(`
|
|
576
771
|
${pc2.bold(pc2.underline(title))}`);
|
|
577
772
|
},
|
|
578
773
|
success(message) {
|
|
579
|
-
|
|
774
|
+
p2.log.success(message);
|
|
580
775
|
},
|
|
581
776
|
error(message) {
|
|
582
|
-
|
|
777
|
+
p2.log.error(message);
|
|
583
778
|
},
|
|
584
779
|
warn(message) {
|
|
585
|
-
|
|
780
|
+
p2.log.warn(message);
|
|
586
781
|
},
|
|
587
782
|
info(message) {
|
|
588
|
-
|
|
783
|
+
p2.log.info(message);
|
|
589
784
|
},
|
|
590
785
|
step(message) {
|
|
591
|
-
|
|
786
|
+
p2.log.step(message);
|
|
592
787
|
},
|
|
593
788
|
message(message) {
|
|
594
|
-
|
|
789
|
+
p2.log.message(message);
|
|
595
790
|
},
|
|
596
791
|
keyValue(key, value, indent = 2) {
|
|
597
792
|
console.log(`${" ".repeat(indent)}${pc2.dim(key)}: ${pc2.cyan(value)}`);
|
|
@@ -606,16 +801,16 @@ ${pc2.bold(pc2.underline(title))}`);
|
|
|
606
801
|
console.log(`${pc2.yellow("[DRY RUN]")} ${message}`);
|
|
607
802
|
},
|
|
608
803
|
spinner() {
|
|
609
|
-
return
|
|
804
|
+
return p2.spinner();
|
|
610
805
|
},
|
|
611
806
|
outro(message) {
|
|
612
|
-
|
|
807
|
+
p2.outro(pc2.green(message));
|
|
613
808
|
},
|
|
614
809
|
cancel(message) {
|
|
615
|
-
|
|
810
|
+
p2.cancel(message);
|
|
616
811
|
},
|
|
617
812
|
note(message, title) {
|
|
618
|
-
|
|
813
|
+
p2.note(message, title);
|
|
619
814
|
},
|
|
620
815
|
formatFields(fields, max = 3) {
|
|
621
816
|
if (fields.length <= max) {
|
|
@@ -625,6 +820,28 @@ ${pc2.bold(pc2.underline(title))}`);
|
|
|
625
820
|
}
|
|
626
821
|
};
|
|
627
822
|
|
|
823
|
+
// src/utils/error-handler.ts
|
|
824
|
+
function handleCommandError(error) {
|
|
825
|
+
if (error instanceof Env2OpError) {
|
|
826
|
+
logger.error(error.message);
|
|
827
|
+
if (error.suggestion) {
|
|
828
|
+
logger.info(`Suggestion: ${error.suggestion}`);
|
|
829
|
+
}
|
|
830
|
+
process.exit(1);
|
|
831
|
+
}
|
|
832
|
+
throw error;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// src/utils/prompts.ts
|
|
836
|
+
import * as p3 from "@clack/prompts";
|
|
837
|
+
async function confirmOrExit(message) {
|
|
838
|
+
const confirmed = await p3.confirm({ message });
|
|
839
|
+
if (p3.isCancel(confirmed) || !confirmed) {
|
|
840
|
+
logger.cancel("Operation cancelled");
|
|
841
|
+
process.exit(0);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
628
845
|
// src/utils/timing.ts
|
|
629
846
|
import { setTimeout } from "node:timers/promises";
|
|
630
847
|
var MIN_SPINNER_TIME = 500;
|
|
@@ -636,8 +853,7 @@ async function withMinTime(promise, minTime = MIN_SPINNER_TIME) {
|
|
|
636
853
|
// src/commands/convert.ts
|
|
637
854
|
async function runConvert(options) {
|
|
638
855
|
const { envFile, vault, itemName, output, dryRun, secret, force, verbose } = options;
|
|
639
|
-
|
|
640
|
-
logger.intro("env2op", pkg2.version, dryRun);
|
|
856
|
+
logger.intro("env2op", getCliVersion(), dryRun);
|
|
641
857
|
try {
|
|
642
858
|
const parseResult = await parseEnvFile(envFile);
|
|
643
859
|
validateParseResult(parseResult, envFile);
|
|
@@ -662,29 +878,8 @@ async function runConvert(options) {
|
|
|
662
878
|
logger.keyValue("Type", secret ? "password (hidden)" : "text (visible)");
|
|
663
879
|
logger.keyValue("Fields", logger.formatFields(variables.map((v) => v.key)));
|
|
664
880
|
} else {
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
const opInstalled = await checkOpCli({ verbose });
|
|
668
|
-
if (!opInstalled) {
|
|
669
|
-
authSpinner.stop("1Password CLI not found");
|
|
670
|
-
throw new Env2OpError("1Password CLI (op) is not installed", "OP_CLI_NOT_INSTALLED", "Install from https://1password.com/downloads/command-line/");
|
|
671
|
-
}
|
|
672
|
-
let signedIn = await checkSignedIn({ verbose });
|
|
673
|
-
if (!signedIn) {
|
|
674
|
-
authSpinner.message("Signing in to 1Password...");
|
|
675
|
-
const signInSuccess = await signIn({ verbose });
|
|
676
|
-
if (!signInSuccess) {
|
|
677
|
-
authSpinner.stop();
|
|
678
|
-
throw new Env2OpError("Failed to sign in to 1Password CLI", "OP_SIGNIN_FAILED", 'Try running "op signin" manually');
|
|
679
|
-
}
|
|
680
|
-
signedIn = await checkSignedIn({ verbose });
|
|
681
|
-
if (!signedIn) {
|
|
682
|
-
authSpinner.stop();
|
|
683
|
-
throw new Env2OpError("Not signed in to 1Password CLI", "OP_NOT_SIGNED_IN", 'Run "op signin" to authenticate');
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
authSpinner.stop("1Password CLI ready");
|
|
687
|
-
const vaultSpinner = p2.spinner();
|
|
881
|
+
await ensureOpAuthenticated({ verbose });
|
|
882
|
+
const vaultSpinner = p4.spinner();
|
|
688
883
|
vaultSpinner.start(`Checking for vault "${vault}"...`);
|
|
689
884
|
const vaultFound = await withMinTime(vaultExists(vault, { verbose }));
|
|
690
885
|
if (vaultFound) {
|
|
@@ -696,38 +891,32 @@ async function runConvert(options) {
|
|
|
696
891
|
vaultSpinner.stop(`Created vault "${vault}"`);
|
|
697
892
|
} else {
|
|
698
893
|
vaultSpinner.stop(`Vault "${vault}" not found`);
|
|
699
|
-
const shouldCreate = await
|
|
894
|
+
const shouldCreate = await p4.confirm({
|
|
700
895
|
message: `Vault "${vault}" does not exist. Create it?`
|
|
701
896
|
});
|
|
702
|
-
if (
|
|
897
|
+
if (p4.isCancel(shouldCreate) || !shouldCreate) {
|
|
703
898
|
logger.cancel("Operation cancelled");
|
|
704
899
|
logger.info('Run "op vault list" to see available vaults');
|
|
705
900
|
process.exit(0);
|
|
706
901
|
}
|
|
707
|
-
const createSpinner =
|
|
902
|
+
const createSpinner = p4.spinner();
|
|
708
903
|
createSpinner.start(`Creating vault "${vault}"...`);
|
|
709
904
|
await createVault(vault, { verbose });
|
|
710
905
|
createSpinner.stop(`Created vault "${vault}"`);
|
|
711
906
|
}
|
|
712
907
|
}
|
|
713
|
-
const itemSpinner =
|
|
908
|
+
const itemSpinner = p4.spinner();
|
|
714
909
|
itemSpinner.start(`Checking for item "${itemName}"...`);
|
|
715
910
|
const existingItemId = await withMinTime(itemExists(vault, itemName, { verbose }));
|
|
716
911
|
if (existingItemId) {
|
|
717
912
|
itemSpinner.stop(`Item "${itemName}" found`);
|
|
718
913
|
if (!force) {
|
|
719
|
-
|
|
720
|
-
message: `Item "${itemName}" already exists in vault "${vault}". Update it?`
|
|
721
|
-
});
|
|
722
|
-
if (p2.isCancel(shouldOverwrite) || !shouldOverwrite) {
|
|
723
|
-
logger.cancel("Operation cancelled");
|
|
724
|
-
process.exit(0);
|
|
725
|
-
}
|
|
914
|
+
await confirmOrExit(`Item "${itemName}" already exists in vault "${vault}". Update it?`);
|
|
726
915
|
}
|
|
727
916
|
} else {
|
|
728
917
|
itemSpinner.stop(`Item "${itemName}" not found`);
|
|
729
918
|
}
|
|
730
|
-
const pushSpinner =
|
|
919
|
+
const pushSpinner = p4.spinner();
|
|
731
920
|
pushSpinner.start("Pushing environment variables...");
|
|
732
921
|
try {
|
|
733
922
|
if (existingItemId) {
|
|
@@ -754,7 +943,7 @@ async function runConvert(options) {
|
|
|
754
943
|
throw error;
|
|
755
944
|
}
|
|
756
945
|
}
|
|
757
|
-
const templatePath = output ??
|
|
946
|
+
const templatePath = output ?? join2(dirname(envFile), `${basename(envFile)}.tpl`);
|
|
758
947
|
const templateFileName = basename(templatePath);
|
|
759
948
|
if (dryRun) {
|
|
760
949
|
logger.warn(`Would generate template: ${templatePath}`);
|
|
@@ -777,210 +966,27 @@ async function runConvert(options) {
|
|
|
777
966
|
logger.outro("Done! Your secrets are now in 1Password");
|
|
778
967
|
}
|
|
779
968
|
} catch (error) {
|
|
780
|
-
|
|
781
|
-
logger.error(error.message);
|
|
782
|
-
if (error.suggestion) {
|
|
783
|
-
logger.info(`Suggestion: ${error.suggestion}`);
|
|
784
|
-
}
|
|
785
|
-
process.exit(1);
|
|
786
|
-
}
|
|
787
|
-
throw error;
|
|
969
|
+
handleCommandError(error);
|
|
788
970
|
}
|
|
789
971
|
}
|
|
790
972
|
|
|
791
973
|
// src/commands/update.ts
|
|
792
|
-
import * as
|
|
974
|
+
import * as p6 from "@clack/prompts";
|
|
793
975
|
import pc4 from "picocolors";
|
|
794
976
|
|
|
795
|
-
// src/lib/package-manager.ts
|
|
796
|
-
var UPDATE_COMMANDS = {
|
|
797
|
-
homebrew: "brew upgrade tolgamorf/tap/env2op-cli",
|
|
798
|
-
npm: "npm update -g @tolgamorf/env2op-cli",
|
|
799
|
-
bun: "bun update -g @tolgamorf/env2op-cli",
|
|
800
|
-
pnpm: "pnpm update -g @tolgamorf/env2op-cli",
|
|
801
|
-
unknown: "npm update -g @tolgamorf/env2op-cli"
|
|
802
|
-
};
|
|
803
|
-
var DISPLAY_NAMES = {
|
|
804
|
-
homebrew: "Homebrew",
|
|
805
|
-
npm: "npm",
|
|
806
|
-
bun: "Bun",
|
|
807
|
-
pnpm: "pnpm",
|
|
808
|
-
unknown: "npm (default)"
|
|
809
|
-
};
|
|
810
|
-
function detectFromPath() {
|
|
811
|
-
const binPath = process.argv[1] ?? "";
|
|
812
|
-
if (binPath.includes("/Cellar/") || binPath.includes("/homebrew/") || binPath.includes("/opt/homebrew/") || binPath.includes("/home/linuxbrew/")) {
|
|
813
|
-
return "homebrew";
|
|
814
|
-
}
|
|
815
|
-
if (binPath.includes("/.bun/")) {
|
|
816
|
-
return "bun";
|
|
817
|
-
}
|
|
818
|
-
if (binPath.includes("/pnpm/") || binPath.includes("/.pnpm/")) {
|
|
819
|
-
return "pnpm";
|
|
820
|
-
}
|
|
821
|
-
if (binPath.includes("/node_modules/")) {
|
|
822
|
-
return "npm";
|
|
823
|
-
}
|
|
824
|
-
return null;
|
|
825
|
-
}
|
|
826
|
-
async function detectFromCommands() {
|
|
827
|
-
const brewResult = await exec("brew", ["list", "env2op-cli"], { verbose: false });
|
|
828
|
-
if (brewResult.exitCode === 0) {
|
|
829
|
-
return "homebrew";
|
|
830
|
-
}
|
|
831
|
-
return "npm";
|
|
832
|
-
}
|
|
833
|
-
async function detectPackageManager() {
|
|
834
|
-
const fromPath = detectFromPath();
|
|
835
|
-
if (fromPath) {
|
|
836
|
-
return {
|
|
837
|
-
type: fromPath,
|
|
838
|
-
updateCommand: UPDATE_COMMANDS[fromPath],
|
|
839
|
-
displayName: DISPLAY_NAMES[fromPath]
|
|
840
|
-
};
|
|
841
|
-
}
|
|
842
|
-
const fromCommands = await detectFromCommands();
|
|
843
|
-
return {
|
|
844
|
-
type: fromCommands,
|
|
845
|
-
updateCommand: UPDATE_COMMANDS[fromCommands],
|
|
846
|
-
displayName: DISPLAY_NAMES[fromCommands]
|
|
847
|
-
};
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
// src/lib/update.ts
|
|
851
|
-
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
852
|
-
import { homedir } from "node:os";
|
|
853
|
-
import { join as join2 } from "node:path";
|
|
854
|
-
var CACHE_DIR = join2(homedir(), ".env2op");
|
|
855
|
-
var CACHE_FILE = join2(CACHE_DIR, "update-check.json");
|
|
856
|
-
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
857
|
-
function getCliVersion() {
|
|
858
|
-
try {
|
|
859
|
-
const pkg2 = require_package();
|
|
860
|
-
return pkg2.version ?? "0.0.0";
|
|
861
|
-
} catch {
|
|
862
|
-
return "0.0.0";
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
function loadCache() {
|
|
866
|
-
try {
|
|
867
|
-
if (existsSync(CACHE_FILE)) {
|
|
868
|
-
const content = readFileSync(CACHE_FILE, "utf-8");
|
|
869
|
-
return JSON.parse(content);
|
|
870
|
-
}
|
|
871
|
-
} catch {}
|
|
872
|
-
return { lastCheck: 0, latestVersion: null };
|
|
873
|
-
}
|
|
874
|
-
function saveCache(cache) {
|
|
875
|
-
try {
|
|
876
|
-
if (!existsSync(CACHE_DIR)) {
|
|
877
|
-
mkdirSync(CACHE_DIR, { recursive: true });
|
|
878
|
-
}
|
|
879
|
-
writeFileSync2(CACHE_FILE, JSON.stringify(cache, null, 2));
|
|
880
|
-
} catch {}
|
|
881
|
-
}
|
|
882
|
-
function shouldCheckForUpdate(cache) {
|
|
883
|
-
const now = Date.now();
|
|
884
|
-
return now - cache.lastCheck > CHECK_INTERVAL_MS;
|
|
885
|
-
}
|
|
886
|
-
async function fetchLatestVersion() {
|
|
887
|
-
try {
|
|
888
|
-
const response = await fetch("https://registry.npmjs.org/@tolgamorf/env2op-cli/latest");
|
|
889
|
-
if (!response.ok)
|
|
890
|
-
return null;
|
|
891
|
-
const data = await response.json();
|
|
892
|
-
return data.version ?? null;
|
|
893
|
-
} catch {
|
|
894
|
-
return null;
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
function compareVersions(v1, v2) {
|
|
898
|
-
const parts1 = v1.split(".").map(Number);
|
|
899
|
-
const parts2 = v2.split(".").map(Number);
|
|
900
|
-
for (let i = 0;i < 3; i++) {
|
|
901
|
-
const p1 = parts1[i] || 0;
|
|
902
|
-
const p22 = parts2[i] || 0;
|
|
903
|
-
if (p1 < p22)
|
|
904
|
-
return -1;
|
|
905
|
-
if (p1 > p22)
|
|
906
|
-
return 1;
|
|
907
|
-
}
|
|
908
|
-
return 0;
|
|
909
|
-
}
|
|
910
|
-
async function checkForUpdate(forceCheck = false) {
|
|
911
|
-
const currentVersion = getCliVersion();
|
|
912
|
-
const cache = loadCache();
|
|
913
|
-
if (!forceCheck && !shouldCheckForUpdate(cache) && cache.latestVersion) {
|
|
914
|
-
const updateAvailable2 = compareVersions(currentVersion, cache.latestVersion) < 0;
|
|
915
|
-
const isSkipped2 = cache.skipVersion === cache.latestVersion;
|
|
916
|
-
return {
|
|
917
|
-
currentVersion,
|
|
918
|
-
latestVersion: cache.latestVersion,
|
|
919
|
-
updateAvailable: updateAvailable2,
|
|
920
|
-
isSkipped: isSkipped2,
|
|
921
|
-
fromCache: true
|
|
922
|
-
};
|
|
923
|
-
}
|
|
924
|
-
const latestVersion = await fetchLatestVersion();
|
|
925
|
-
saveCache({
|
|
926
|
-
...cache,
|
|
927
|
-
lastCheck: Date.now(),
|
|
928
|
-
latestVersion
|
|
929
|
-
});
|
|
930
|
-
if (!latestVersion) {
|
|
931
|
-
return {
|
|
932
|
-
currentVersion,
|
|
933
|
-
latestVersion: null,
|
|
934
|
-
updateAvailable: false,
|
|
935
|
-
isSkipped: false,
|
|
936
|
-
fromCache: false
|
|
937
|
-
};
|
|
938
|
-
}
|
|
939
|
-
const updateAvailable = compareVersions(currentVersion, latestVersion) < 0;
|
|
940
|
-
const isSkipped = cache.skipVersion === latestVersion;
|
|
941
|
-
return {
|
|
942
|
-
currentVersion,
|
|
943
|
-
latestVersion,
|
|
944
|
-
updateAvailable,
|
|
945
|
-
isSkipped,
|
|
946
|
-
fromCache: false
|
|
947
|
-
};
|
|
948
|
-
}
|
|
949
|
-
async function performUpdate(pm) {
|
|
950
|
-
const packageManager = pm ?? await detectPackageManager();
|
|
951
|
-
try {
|
|
952
|
-
const [command, ...args] = packageManager.updateCommand.split(" ");
|
|
953
|
-
const result = await exec(command, args, { verbose: false });
|
|
954
|
-
if (result.exitCode !== 0) {
|
|
955
|
-
return {
|
|
956
|
-
success: false,
|
|
957
|
-
error: result.stderr || `Command exited with code ${result.exitCode}`
|
|
958
|
-
};
|
|
959
|
-
}
|
|
960
|
-
return { success: true };
|
|
961
|
-
} catch (error) {
|
|
962
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
963
|
-
return { success: false, error: message };
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
function skipVersion(version) {
|
|
967
|
-
const cache = loadCache();
|
|
968
|
-
cache.skipVersion = version;
|
|
969
|
-
saveCache(cache);
|
|
970
|
-
}
|
|
971
|
-
|
|
972
977
|
// src/lib/update-prompts.ts
|
|
973
|
-
import * as
|
|
978
|
+
import * as p5 from "@clack/prompts";
|
|
974
979
|
import pc3 from "picocolors";
|
|
980
|
+
var S_BAR = "│";
|
|
975
981
|
var S_BAR_START = "┌";
|
|
976
982
|
var S_BAR_END = "└";
|
|
977
983
|
function showUpdateNotification(result, cliName = "env2op") {
|
|
978
984
|
console.log();
|
|
979
985
|
console.log(`${pc3.gray(S_BAR_START)}${pc3.gray("─")} ${pc3.yellow("Update available:")} ${pc3.dim(result.currentVersion)} ${pc3.dim("→")} ${pc3.green(result.latestVersion)}`);
|
|
980
|
-
console.log(`${pc3.gray(S_BAR_END)}${pc3.gray("─")} Run ${pc3.cyan(`'${cliName} update'`)} to update`);
|
|
986
|
+
console.log(`${pc3.gray(S_BAR_END)}${pc3.gray("─")} Run ${pc3.cyan(`'${cliName} --update'`)} to update`);
|
|
981
987
|
}
|
|
982
988
|
async function askToUpdate(result) {
|
|
983
|
-
const response = await
|
|
989
|
+
const response = await p5.select({
|
|
984
990
|
message: "Would you like to update?",
|
|
985
991
|
options: [
|
|
986
992
|
{ value: "update", label: "Update now", hint: "Download and install the latest version" },
|
|
@@ -988,42 +994,41 @@ async function askToUpdate(result) {
|
|
|
988
994
|
{ value: "skip", label: "Skip this version", hint: `Don't ask about ${result.latestVersion} again` }
|
|
989
995
|
]
|
|
990
996
|
});
|
|
991
|
-
if (
|
|
997
|
+
if (p5.isCancel(response)) {
|
|
992
998
|
return "later";
|
|
993
999
|
}
|
|
994
1000
|
return response;
|
|
995
1001
|
}
|
|
996
1002
|
function showUpdateAvailable(result) {
|
|
997
|
-
|
|
1003
|
+
p5.log.success(`Update available: ${pc3.dim(result.currentVersion)} ${pc3.dim("→")} ${pc3.green(result.latestVersion)}`);
|
|
998
1004
|
}
|
|
999
1005
|
function showUpToDate(currentVersion) {
|
|
1000
|
-
|
|
1006
|
+
p5.log.success(`You're on the latest version ${pc3.green(`(${currentVersion})`)}`);
|
|
1001
1007
|
}
|
|
1002
1008
|
function showPackageManagerInfo(pm) {
|
|
1003
|
-
console.log();
|
|
1004
|
-
console.log(
|
|
1005
|
-
console.log(
|
|
1006
|
-
console.log();
|
|
1009
|
+
console.log(pc3.gray(S_BAR));
|
|
1010
|
+
console.log(`${pc3.gray(S_BAR)} ${pc3.dim("Detected:")} ${pm.displayName} installation`);
|
|
1011
|
+
console.log(`${pc3.gray(S_BAR)} ${pc3.dim("Command:")} ${pc3.cyan(pm.updateCommand)}`);
|
|
1007
1012
|
}
|
|
1008
1013
|
function showUpdateSuccess(newVersion) {
|
|
1009
|
-
|
|
1010
|
-
|
|
1014
|
+
p5.log.success(`Updated to version ${pc3.green(newVersion)}`);
|
|
1015
|
+
p5.log.info("Please restart to use the new version.");
|
|
1011
1016
|
}
|
|
1012
1017
|
function showUpdateError(error, pm) {
|
|
1013
1018
|
if (error) {
|
|
1014
|
-
|
|
1019
|
+
p5.log.error(pc3.dim(error));
|
|
1015
1020
|
}
|
|
1016
|
-
|
|
1021
|
+
p5.log.info(`Try running manually: ${pc3.cyan(pm.updateCommand)}`);
|
|
1017
1022
|
}
|
|
1018
1023
|
|
|
1019
1024
|
// src/commands/update.ts
|
|
1020
1025
|
async function runUpdate(options) {
|
|
1021
1026
|
const { force = false, cliName = "env2op" } = options;
|
|
1022
|
-
|
|
1023
|
-
const
|
|
1024
|
-
|
|
1027
|
+
p6.intro(pc4.bgCyan(pc4.black(` ${cliName} update `)));
|
|
1028
|
+
const spinner5 = p6.spinner();
|
|
1029
|
+
spinner5.start("Checking for updates...");
|
|
1025
1030
|
const result = await checkForUpdate(true);
|
|
1026
|
-
|
|
1031
|
+
spinner5.stop("Checked for updates");
|
|
1027
1032
|
if (!result.updateAvailable || !result.latestVersion) {
|
|
1028
1033
|
showUpToDate(result.currentVersion);
|
|
1029
1034
|
return;
|
|
@@ -1035,15 +1040,15 @@ async function runUpdate(options) {
|
|
|
1035
1040
|
const choice = await askToUpdate(result);
|
|
1036
1041
|
if (choice === "skip") {
|
|
1037
1042
|
skipVersion(result.latestVersion);
|
|
1038
|
-
|
|
1043
|
+
p6.log.info(`Skipped version ${result.latestVersion}`);
|
|
1039
1044
|
return;
|
|
1040
1045
|
}
|
|
1041
1046
|
if (choice === "later") {
|
|
1042
|
-
|
|
1047
|
+
p6.log.info("Update postponed");
|
|
1043
1048
|
return;
|
|
1044
1049
|
}
|
|
1045
1050
|
}
|
|
1046
|
-
const updateSpinner =
|
|
1051
|
+
const updateSpinner = p6.spinner();
|
|
1047
1052
|
updateSpinner.start(`Updating to ${result.latestVersion}...`);
|
|
1048
1053
|
const updateResult = await performUpdate(pm);
|
|
1049
1054
|
if (updateResult.success) {
|
|
@@ -1056,35 +1061,40 @@ async function runUpdate(options) {
|
|
|
1056
1061
|
}
|
|
1057
1062
|
}
|
|
1058
1063
|
|
|
1059
|
-
// src/
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1064
|
+
// src/utils/args.ts
|
|
1065
|
+
function parseArgs(args) {
|
|
1066
|
+
const flags = new Set;
|
|
1067
|
+
const positional = [];
|
|
1068
|
+
const options = {};
|
|
1069
|
+
for (let i = 0;i < args.length; i++) {
|
|
1070
|
+
const arg = args[i];
|
|
1071
|
+
if (arg === "-o" || arg === "--output") {
|
|
1072
|
+
const next = args[i + 1];
|
|
1073
|
+
if (next && !next.startsWith("-")) {
|
|
1074
|
+
options.output = next;
|
|
1075
|
+
i++;
|
|
1076
|
+
}
|
|
1077
|
+
} else if (arg.startsWith("--")) {
|
|
1078
|
+
flags.add(arg.slice(2));
|
|
1079
|
+
} else if (arg.startsWith("-")) {
|
|
1080
|
+
for (const char of arg.slice(1)) {
|
|
1081
|
+
flags.add(char);
|
|
1082
|
+
}
|
|
1083
|
+
} else {
|
|
1084
|
+
positional.push(arg);
|
|
1078
1085
|
}
|
|
1079
|
-
} else {
|
|
1080
|
-
positional.push(arg);
|
|
1081
1086
|
}
|
|
1087
|
+
return { flags, positional, options };
|
|
1082
1088
|
}
|
|
1089
|
+
|
|
1090
|
+
// src/cli.ts
|
|
1091
|
+
var pkg = await Promise.resolve().then(() => __toESM(require_package(), 1));
|
|
1092
|
+
var { flags, positional, options } = parseArgs(process.argv.slice(2));
|
|
1083
1093
|
var hasHelp = flags.has("h") || flags.has("help");
|
|
1084
1094
|
var hasVersion = flags.has("v") || flags.has("version");
|
|
1085
1095
|
var hasUpdate = flags.has("update");
|
|
1086
1096
|
if (hasVersion) {
|
|
1087
|
-
console.log(
|
|
1097
|
+
console.log(getCliVersion());
|
|
1088
1098
|
process.exit(0);
|
|
1089
1099
|
}
|
|
1090
1100
|
if (hasUpdate) {
|
|
@@ -1097,10 +1107,12 @@ if (hasUpdate) {
|
|
|
1097
1107
|
}
|
|
1098
1108
|
if (hasHelp || positional.length === 0) {
|
|
1099
1109
|
showHelp();
|
|
1110
|
+
await maybeShowUpdateNotification("env2op", showUpdateNotification);
|
|
1100
1111
|
process.exit(0);
|
|
1101
1112
|
}
|
|
1102
1113
|
if (positional.length < 3) {
|
|
1103
1114
|
showMissingArgsError(positional);
|
|
1115
|
+
await maybeShowUpdateNotification("env2op", showUpdateNotification);
|
|
1104
1116
|
process.exit(1);
|
|
1105
1117
|
}
|
|
1106
1118
|
var [envFile, vault, itemName] = positional;
|
|
@@ -1114,18 +1126,13 @@ await runConvert({
|
|
|
1114
1126
|
force: flags.has("f") || flags.has("force"),
|
|
1115
1127
|
verbose: flags.has("verbose")
|
|
1116
1128
|
});
|
|
1117
|
-
|
|
1118
|
-
const updateResult = await checkForUpdate();
|
|
1119
|
-
if (updateResult.updateAvailable && !updateResult.isSkipped) {
|
|
1120
|
-
showUpdateNotification(updateResult, "env2op");
|
|
1121
|
-
}
|
|
1122
|
-
} catch {}
|
|
1129
|
+
await maybeShowUpdateNotification("env2op", showUpdateNotification);
|
|
1123
1130
|
function showHelp() {
|
|
1124
1131
|
const name = pc5.bold(pc5.cyan("env2op"));
|
|
1125
|
-
const version = pc5.dim(`v${
|
|
1132
|
+
const version = pc5.dim(`v${getCliVersion()}`);
|
|
1126
1133
|
console.log(`
|
|
1127
1134
|
${name} ${version}
|
|
1128
|
-
${
|
|
1135
|
+
${pkg.description}
|
|
1129
1136
|
|
|
1130
1137
|
${pc5.bold("USAGE")}
|
|
1131
1138
|
${pc5.cyan("$")} env2op ${pc5.yellow("<env_file>")} ${pc5.yellow("<vault>")} ${pc5.yellow("<item_name>")} ${pc5.dim("[options]")}
|