@tolgamorf/env2op-cli 0.2.1 → 0.2.3
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 +39 -9
- package/dist/cli.js +569 -280
- package/dist/index.d.ts +3 -0
- package/dist/index.js +130 -131
- package/dist/op2env-cli.js +553 -266
- 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.3",
|
|
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",
|
|
@@ -102,14 +102,14 @@ var require_package = __commonJS((exports, module) => {
|
|
|
102
102
|
});
|
|
103
103
|
|
|
104
104
|
// src/cli.ts
|
|
105
|
-
import
|
|
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
|
|
@@ -468,10 +341,332 @@ async function editSecureNote(options) {
|
|
|
468
341
|
}
|
|
469
342
|
}
|
|
470
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
|
+
};
|
|
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
|
+
};
|
|
637
|
+
}
|
|
638
|
+
async function performUpdate(pm) {
|
|
639
|
+
const packageManager = pm ?? await detectPackageManager();
|
|
640
|
+
try {
|
|
641
|
+
const [command, ...args] = packageManager.updateCommand.split(" ");
|
|
642
|
+
const result = await exec(command, args, { verbose: false });
|
|
643
|
+
if (result.exitCode !== 0) {
|
|
644
|
+
return {
|
|
645
|
+
success: false,
|
|
646
|
+
error: result.stderr || `Command exited with code ${result.exitCode}`
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
return { success: true };
|
|
650
|
+
} catch (error) {
|
|
651
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
652
|
+
return { success: false, error: message };
|
|
653
|
+
}
|
|
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
|
+
}
|
|
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,45 +966,143 @@ 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
|
-
// src/
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
973
|
+
// src/commands/update.ts
|
|
974
|
+
import * as p6 from "@clack/prompts";
|
|
975
|
+
import pc4 from "picocolors";
|
|
976
|
+
|
|
977
|
+
// src/lib/update-prompts.ts
|
|
978
|
+
import * as p5 from "@clack/prompts";
|
|
979
|
+
import pc3 from "picocolors";
|
|
980
|
+
var S_BAR_START = "┌";
|
|
981
|
+
var S_BAR_END = "└";
|
|
982
|
+
function showUpdateNotification(result, cliName = "env2op") {
|
|
983
|
+
console.log();
|
|
984
|
+
console.log(`${pc3.gray(S_BAR_START)}${pc3.gray("─")} ${pc3.yellow("Update available:")} ${pc3.dim(result.currentVersion)} ${pc3.dim("→")} ${pc3.green(result.latestVersion)}`);
|
|
985
|
+
console.log(`${pc3.gray(S_BAR_END)}${pc3.gray("─")} Run ${pc3.cyan(`'${cliName} update'`)} to update`);
|
|
986
|
+
}
|
|
987
|
+
async function askToUpdate(result) {
|
|
988
|
+
const response = await p5.select({
|
|
989
|
+
message: "Would you like to update?",
|
|
990
|
+
options: [
|
|
991
|
+
{ value: "update", label: "Update now", hint: "Download and install the latest version" },
|
|
992
|
+
{ value: "later", label: "Remind me later", hint: "Ask again next time" },
|
|
993
|
+
{ value: "skip", label: "Skip this version", hint: `Don't ask about ${result.latestVersion} again` }
|
|
994
|
+
]
|
|
995
|
+
});
|
|
996
|
+
if (p5.isCancel(response)) {
|
|
997
|
+
return "later";
|
|
998
|
+
}
|
|
999
|
+
return response;
|
|
1000
|
+
}
|
|
1001
|
+
function showUpdateAvailable(result) {
|
|
1002
|
+
p5.log.success(`Update available: ${pc3.dim(result.currentVersion)} ${pc3.dim("→")} ${pc3.green(result.latestVersion)}`);
|
|
1003
|
+
}
|
|
1004
|
+
function showUpToDate(currentVersion) {
|
|
1005
|
+
p5.log.success(`You're on the latest version ${pc3.green(`(${currentVersion})`)}`);
|
|
1006
|
+
}
|
|
1007
|
+
function showPackageManagerInfo(pm) {
|
|
1008
|
+
console.log();
|
|
1009
|
+
console.log(` ${pc3.dim("Detected:")} ${pm.displayName} installation`);
|
|
1010
|
+
console.log(` ${pc3.dim("Command:")} ${pc3.cyan(pm.updateCommand)}`);
|
|
1011
|
+
console.log();
|
|
1012
|
+
}
|
|
1013
|
+
function showUpdateSuccess(newVersion) {
|
|
1014
|
+
p5.log.success(`Updated to version ${pc3.green(newVersion)}`);
|
|
1015
|
+
p5.log.info("Please restart to use the new version.");
|
|
1016
|
+
}
|
|
1017
|
+
function showUpdateError(error, pm) {
|
|
1018
|
+
if (error) {
|
|
1019
|
+
p5.log.error(pc3.dim(error));
|
|
1020
|
+
}
|
|
1021
|
+
p5.log.info(`Try running manually: ${pc3.cyan(pm.updateCommand)}`);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// src/commands/update.ts
|
|
1025
|
+
async function runUpdate(options) {
|
|
1026
|
+
const { force = false, cliName = "env2op" } = options;
|
|
1027
|
+
p6.intro(pc4.bgCyan(pc4.black(` ${cliName} update `)));
|
|
1028
|
+
const spinner5 = p6.spinner();
|
|
1029
|
+
spinner5.start("Checking for updates...");
|
|
1030
|
+
const result = await checkForUpdate(true);
|
|
1031
|
+
spinner5.stop("Checked for updates");
|
|
1032
|
+
if (!result.updateAvailable || !result.latestVersion) {
|
|
1033
|
+
showUpToDate(result.currentVersion);
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
showUpdateAvailable(result);
|
|
1037
|
+
const pm = await detectPackageManager();
|
|
1038
|
+
showPackageManagerInfo(pm);
|
|
1039
|
+
if (!force) {
|
|
1040
|
+
const choice = await askToUpdate(result);
|
|
1041
|
+
if (choice === "skip") {
|
|
1042
|
+
skipVersion(result.latestVersion);
|
|
1043
|
+
p6.log.info(`Skipped version ${result.latestVersion}`);
|
|
1044
|
+
return;
|
|
804
1045
|
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
for (const char of arg.slice(1)) {
|
|
809
|
-
flags.add(char);
|
|
1046
|
+
if (choice === "later") {
|
|
1047
|
+
p6.log.info("Update postponed");
|
|
1048
|
+
return;
|
|
810
1049
|
}
|
|
1050
|
+
}
|
|
1051
|
+
const updateSpinner = p6.spinner();
|
|
1052
|
+
updateSpinner.start(`Updating to ${result.latestVersion}...`);
|
|
1053
|
+
const updateResult = await performUpdate(pm);
|
|
1054
|
+
if (updateResult.success) {
|
|
1055
|
+
updateSpinner.stop("Update completed");
|
|
1056
|
+
showUpdateSuccess(result.latestVersion);
|
|
811
1057
|
} else {
|
|
812
|
-
|
|
1058
|
+
updateSpinner.stop("Update failed");
|
|
1059
|
+
showUpdateError(updateResult.error, pm);
|
|
1060
|
+
process.exit(1);
|
|
813
1061
|
}
|
|
814
1062
|
}
|
|
1063
|
+
|
|
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);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
return { flags, positional, options };
|
|
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));
|
|
815
1093
|
var hasHelp = flags.has("h") || flags.has("help");
|
|
816
1094
|
var hasVersion = flags.has("v") || flags.has("version");
|
|
1095
|
+
var hasUpdate = flags.has("update");
|
|
817
1096
|
if (hasVersion) {
|
|
818
|
-
console.log(
|
|
1097
|
+
console.log(getCliVersion());
|
|
1098
|
+
process.exit(0);
|
|
1099
|
+
}
|
|
1100
|
+
if (hasUpdate) {
|
|
1101
|
+
await runUpdate({
|
|
1102
|
+
force: flags.has("f") || flags.has("force"),
|
|
1103
|
+
verbose: flags.has("verbose"),
|
|
1104
|
+
cliName: "env2op"
|
|
1105
|
+
});
|
|
819
1106
|
process.exit(0);
|
|
820
1107
|
}
|
|
821
1108
|
if (hasHelp || positional.length === 0) {
|
|
@@ -837,48 +1124,50 @@ await runConvert({
|
|
|
837
1124
|
force: flags.has("f") || flags.has("force"),
|
|
838
1125
|
verbose: flags.has("verbose")
|
|
839
1126
|
});
|
|
1127
|
+
await maybeShowUpdateNotification("env2op", showUpdateNotification);
|
|
840
1128
|
function showHelp() {
|
|
841
|
-
const name =
|
|
842
|
-
const version =
|
|
1129
|
+
const name = pc5.bold(pc5.cyan("env2op"));
|
|
1130
|
+
const version = pc5.dim(`v${getCliVersion()}`);
|
|
843
1131
|
console.log(`
|
|
844
1132
|
${name} ${version}
|
|
845
|
-
${
|
|
1133
|
+
${pkg.description}
|
|
846
1134
|
|
|
847
|
-
${
|
|
848
|
-
${
|
|
1135
|
+
${pc5.bold("USAGE")}
|
|
1136
|
+
${pc5.cyan("$")} env2op ${pc5.yellow("<env_file>")} ${pc5.yellow("<vault>")} ${pc5.yellow("<item_name>")} ${pc5.dim("[options]")}
|
|
849
1137
|
|
|
850
|
-
${
|
|
851
|
-
${
|
|
852
|
-
${
|
|
853
|
-
${
|
|
1138
|
+
${pc5.bold("ARGUMENTS")}
|
|
1139
|
+
${pc5.yellow("env_file")} Path to .env file
|
|
1140
|
+
${pc5.yellow("vault")} 1Password vault name
|
|
1141
|
+
${pc5.yellow("item_name")} Name for the Secure Note in 1Password
|
|
854
1142
|
|
|
855
|
-
${
|
|
856
|
-
${
|
|
857
|
-
${
|
|
858
|
-
${
|
|
859
|
-
${
|
|
860
|
-
${
|
|
861
|
-
${
|
|
862
|
-
${
|
|
1143
|
+
${pc5.bold("OPTIONS")}
|
|
1144
|
+
${pc5.cyan("-o, --output")} Output template path (default: <env_file>.tpl)
|
|
1145
|
+
${pc5.cyan("-f, --force")} Skip confirmation prompts
|
|
1146
|
+
${pc5.cyan(" --dry-run")} Preview actions without executing
|
|
1147
|
+
${pc5.cyan(" --secret")} Store all fields as password type (hidden)
|
|
1148
|
+
${pc5.cyan(" --verbose")} Show op CLI output
|
|
1149
|
+
${pc5.cyan(" --update")} Check for and install updates
|
|
1150
|
+
${pc5.cyan("-v, --version")} Show version
|
|
1151
|
+
${pc5.cyan("-h, --help")} Show this help message
|
|
863
1152
|
|
|
864
|
-
${
|
|
865
|
-
${
|
|
866
|
-
${
|
|
1153
|
+
${pc5.bold("EXAMPLES")}
|
|
1154
|
+
${pc5.dim("# Basic usage")}
|
|
1155
|
+
${pc5.cyan("$")} env2op .env.production Personal "MyApp - Production"
|
|
867
1156
|
|
|
868
|
-
${
|
|
869
|
-
${
|
|
1157
|
+
${pc5.dim("# Custom output path")}
|
|
1158
|
+
${pc5.cyan("$")} env2op .env Personal "MyApp" -o secrets.tpl
|
|
870
1159
|
|
|
871
|
-
${
|
|
872
|
-
${
|
|
1160
|
+
${pc5.dim("# Preview without making changes")}
|
|
1161
|
+
${pc5.cyan("$")} env2op .env Personal "MyApp" --dry-run
|
|
873
1162
|
|
|
874
|
-
${
|
|
875
|
-
${
|
|
1163
|
+
${pc5.dim("# Store as hidden password fields")}
|
|
1164
|
+
${pc5.cyan("$")} env2op .env Personal "MyApp" --secret
|
|
876
1165
|
|
|
877
|
-
${
|
|
878
|
-
${
|
|
1166
|
+
${pc5.dim("# Skip confirmation prompts (for CI/scripts)")}
|
|
1167
|
+
${pc5.cyan("$")} env2op .env Personal "MyApp" -f
|
|
879
1168
|
|
|
880
|
-
${
|
|
881
|
-
${
|
|
1169
|
+
${pc5.bold("DOCUMENTATION")}
|
|
1170
|
+
${pc5.dim("https://github.com/tolgamorf/env2op-cli")}
|
|
882
1171
|
`);
|
|
883
1172
|
}
|
|
884
1173
|
function showMissingArgsError(provided) {
|
|
@@ -890,17 +1179,17 @@ function showMissingArgsError(provided) {
|
|
|
890
1179
|
if (provided.length < 3)
|
|
891
1180
|
missing.push("item_name");
|
|
892
1181
|
console.log(`
|
|
893
|
-
${
|
|
1182
|
+
${pc5.red(pc5.bold("Error:"))} Missing required arguments
|
|
894
1183
|
|
|
895
|
-
${
|
|
1184
|
+
${pc5.bold("Usage:")} env2op ${pc5.yellow("<env_file>")} ${pc5.yellow("<vault>")} ${pc5.yellow("<item_name>")} ${pc5.dim("[options]")}
|
|
896
1185
|
|
|
897
|
-
${
|
|
898
|
-
${missing.map((arg) => ` ${
|
|
1186
|
+
${pc5.bold("Missing:")}
|
|
1187
|
+
${missing.map((arg) => ` ${pc5.red("•")} ${pc5.yellow(arg)}`).join(`
|
|
899
1188
|
`)}
|
|
900
1189
|
|
|
901
|
-
${
|
|
902
|
-
${
|
|
1190
|
+
${pc5.bold("Example:")}
|
|
1191
|
+
${pc5.cyan("$")} env2op .env.production Personal "MyApp - Production"
|
|
903
1192
|
|
|
904
|
-
Run ${
|
|
1193
|
+
Run ${pc5.cyan("env2op --help")} for more information.
|
|
905
1194
|
`);
|
|
906
1195
|
}
|