@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/op2env-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,15 +102,14 @@ var require_package = __commonJS((exports, module) => {
|
|
|
102
102
|
});
|
|
103
103
|
|
|
104
104
|
// src/op2env-cli.ts
|
|
105
|
-
import
|
|
105
|
+
import pc5 from "picocolors";
|
|
106
106
|
|
|
107
107
|
// src/commands/inject.ts
|
|
108
|
-
import { existsSync, readFileSync, writeFileSync as
|
|
108
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
109
109
|
import { basename } from "node:path";
|
|
110
|
-
import * as p2 from "@clack/prompts";
|
|
111
110
|
|
|
112
|
-
// src/core/
|
|
113
|
-
import
|
|
111
|
+
// src/core/auth.ts
|
|
112
|
+
import * as p from "@clack/prompts";
|
|
114
113
|
|
|
115
114
|
// src/utils/errors.ts
|
|
116
115
|
class Env2OpError extends Error {
|
|
@@ -143,117 +142,17 @@ var errors = {
|
|
|
143
142
|
envFileEmpty: (path) => new Env2OpError(`No valid environment variables found in ${path}`, ErrorCodes.ENV_FILE_EMPTY, "Ensure the file contains KEY=value pairs"),
|
|
144
143
|
opCliNotInstalled: () => new Env2OpError("1Password CLI (op) is not installed", ErrorCodes.OP_CLI_NOT_INSTALLED, "Install it from https://1password.com/downloads/command-line/"),
|
|
145
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'),
|
|
146
146
|
vaultNotFound: (vault) => new Env2OpError(`Vault not found: ${vault}`, ErrorCodes.VAULT_NOT_FOUND, 'Run "op vault list" to see available vaults'),
|
|
147
147
|
vaultCreateFailed: (message) => new Env2OpError(`Failed to create vault: ${message}`, ErrorCodes.VAULT_CREATE_FAILED),
|
|
148
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"),
|
|
149
149
|
itemCreateFailed: (message) => new Env2OpError(`Failed to create 1Password item: ${message}`, ErrorCodes.ITEM_CREATE_FAILED),
|
|
150
150
|
itemEditFailed: (message) => new Env2OpError(`Failed to edit 1Password item: ${message}`, ErrorCodes.ITEM_EDIT_FAILED),
|
|
151
|
-
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)
|
|
152
154
|
};
|
|
153
155
|
|
|
154
|
-
// src/core/env-parser.ts
|
|
155
|
-
var HEADER_SEPARATOR = "# ===========================================================================";
|
|
156
|
-
function stripHeaders(content) {
|
|
157
|
-
const lines = content.split(`
|
|
158
|
-
`);
|
|
159
|
-
const result = [];
|
|
160
|
-
let inHeader = false;
|
|
161
|
-
for (const line of lines) {
|
|
162
|
-
const trimmed = line.trim();
|
|
163
|
-
if (trimmed === HEADER_SEPARATOR) {
|
|
164
|
-
if (!inHeader) {
|
|
165
|
-
inHeader = true;
|
|
166
|
-
} else {
|
|
167
|
-
inHeader = false;
|
|
168
|
-
}
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
171
|
-
if (!inHeader) {
|
|
172
|
-
result.push(line);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
while (result.length > 0 && result[0]?.trim() === "") {
|
|
176
|
-
result.shift();
|
|
177
|
-
}
|
|
178
|
-
return result.join(`
|
|
179
|
-
`);
|
|
180
|
-
}
|
|
181
|
-
function parseValue(raw) {
|
|
182
|
-
const trimmed = raw.trim();
|
|
183
|
-
if (trimmed.startsWith('"')) {
|
|
184
|
-
const endQuote = trimmed.indexOf('"', 1);
|
|
185
|
-
if (endQuote !== -1) {
|
|
186
|
-
return trimmed.slice(1, endQuote);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
if (trimmed.startsWith("'")) {
|
|
190
|
-
const endQuote = trimmed.indexOf("'", 1);
|
|
191
|
-
if (endQuote !== -1) {
|
|
192
|
-
return trimmed.slice(1, endQuote);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
const parts = trimmed.split(/\s+#/);
|
|
196
|
-
return (parts[0] ?? trimmed).trim();
|
|
197
|
-
}
|
|
198
|
-
function stripBom(content) {
|
|
199
|
-
if (content.charCodeAt(0) === 65279) {
|
|
200
|
-
return content.slice(1);
|
|
201
|
-
}
|
|
202
|
-
return content;
|
|
203
|
-
}
|
|
204
|
-
async function parseEnvFile(filePath) {
|
|
205
|
-
let rawContent;
|
|
206
|
-
try {
|
|
207
|
-
rawContent = await readFile(filePath, "utf-8");
|
|
208
|
-
} catch {
|
|
209
|
-
throw errors.envFileNotFound(filePath);
|
|
210
|
-
}
|
|
211
|
-
const content = stripHeaders(stripBom(rawContent));
|
|
212
|
-
const rawLines = content.split(`
|
|
213
|
-
`);
|
|
214
|
-
const variables = [];
|
|
215
|
-
const lines = [];
|
|
216
|
-
const parseErrors = [];
|
|
217
|
-
let currentComment = "";
|
|
218
|
-
for (let i = 0;i < rawLines.length; i++) {
|
|
219
|
-
const line = rawLines[i] ?? "";
|
|
220
|
-
const trimmed = line.trim();
|
|
221
|
-
const lineNumber = i + 1;
|
|
222
|
-
if (!trimmed) {
|
|
223
|
-
lines.push({ type: "empty" });
|
|
224
|
-
currentComment = "";
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
if (trimmed.startsWith("#")) {
|
|
228
|
-
lines.push({ type: "comment", content: line });
|
|
229
|
-
currentComment = trimmed.slice(1).trim();
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
|
|
233
|
-
if (match?.[1]) {
|
|
234
|
-
const key = match[1];
|
|
235
|
-
const rawValue = match[2] ?? "";
|
|
236
|
-
const value = parseValue(rawValue);
|
|
237
|
-
variables.push({
|
|
238
|
-
key,
|
|
239
|
-
value,
|
|
240
|
-
comment: currentComment || undefined,
|
|
241
|
-
line: lineNumber
|
|
242
|
-
});
|
|
243
|
-
lines.push({ type: "variable", key, value });
|
|
244
|
-
currentComment = "";
|
|
245
|
-
} else if (trimmed.includes("=")) {
|
|
246
|
-
parseErrors.push(`Line ${lineNumber}: Invalid variable name`);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
return { variables, lines, errors: parseErrors };
|
|
250
|
-
}
|
|
251
|
-
function validateParseResult(result, filePath) {
|
|
252
|
-
if (result.variables.length === 0) {
|
|
253
|
-
throw errors.envFileEmpty(filePath);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
156
|
// src/utils/shell.ts
|
|
258
157
|
import { spawn } from "node:child_process";
|
|
259
158
|
import pc from "picocolors";
|
|
@@ -263,16 +162,8 @@ function quoteArg(arg) {
|
|
|
263
162
|
}
|
|
264
163
|
return arg;
|
|
265
164
|
}
|
|
266
|
-
|
|
267
|
-
const { verbose = false } = options;
|
|
268
|
-
const fullCommand = `${command} ${args.map(quoteArg).join(" ")}`;
|
|
269
|
-
if (verbose) {
|
|
270
|
-
console.log(pc.dim(`$ ${fullCommand}`));
|
|
271
|
-
}
|
|
165
|
+
function collectOutput(proc, verbose) {
|
|
272
166
|
return new Promise((resolve) => {
|
|
273
|
-
const proc = spawn(command, args, {
|
|
274
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
275
|
-
});
|
|
276
167
|
const stdoutChunks = [];
|
|
277
168
|
const stderrChunks = [];
|
|
278
169
|
proc.stdout?.on("data", (data) => {
|
|
@@ -306,48 +197,29 @@ async function exec(command, args = [], options = {}) {
|
|
|
306
197
|
});
|
|
307
198
|
});
|
|
308
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
|
+
}
|
|
309
211
|
async function execWithStdin(command, args = [], options) {
|
|
310
212
|
const { stdin: stdinContent, verbose = false } = options;
|
|
311
213
|
if (verbose) {
|
|
312
214
|
const fullCommand = `${command} ${args.map(quoteArg).join(" ")}`;
|
|
313
215
|
console.log(pc.dim(`$ echo '...' | ${fullCommand}`));
|
|
314
216
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
318
|
-
});
|
|
319
|
-
const stdoutChunks = [];
|
|
320
|
-
const stderrChunks = [];
|
|
321
|
-
proc.stdin?.write(stdinContent);
|
|
322
|
-
proc.stdin?.end();
|
|
323
|
-
proc.stdout?.on("data", (data) => {
|
|
324
|
-
const text = Buffer.isBuffer(data) ? data.toString() : String(data);
|
|
325
|
-
stdoutChunks.push(text);
|
|
326
|
-
if (verbose)
|
|
327
|
-
process.stdout.write(text);
|
|
328
|
-
});
|
|
329
|
-
proc.stderr?.on("data", (data) => {
|
|
330
|
-
const text = Buffer.isBuffer(data) ? data.toString() : String(data);
|
|
331
|
-
stderrChunks.push(text);
|
|
332
|
-
if (verbose)
|
|
333
|
-
process.stderr.write(text);
|
|
334
|
-
});
|
|
335
|
-
proc.on("close", (code) => {
|
|
336
|
-
resolve({
|
|
337
|
-
stdout: stdoutChunks.join(""),
|
|
338
|
-
stderr: stderrChunks.join(""),
|
|
339
|
-
exitCode: code ?? 1
|
|
340
|
-
});
|
|
341
|
-
});
|
|
342
|
-
proc.on("error", (err) => {
|
|
343
|
-
stderrChunks.push(err.message);
|
|
344
|
-
resolve({
|
|
345
|
-
stdout: stdoutChunks.join(""),
|
|
346
|
-
stderr: stderrChunks.join(""),
|
|
347
|
-
exitCode: 1
|
|
348
|
-
});
|
|
349
|
-
});
|
|
217
|
+
const proc = spawn(command, args, {
|
|
218
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
350
219
|
});
|
|
220
|
+
proc.stdin?.write(stdinContent);
|
|
221
|
+
proc.stdin?.end();
|
|
222
|
+
return collectOutput(proc, verbose);
|
|
351
223
|
}
|
|
352
224
|
|
|
353
225
|
// src/core/onepassword.ts
|
|
@@ -469,10 +341,332 @@ async function editSecureNote(options) {
|
|
|
469
341
|
}
|
|
470
342
|
}
|
|
471
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
|
+
|
|
472
669
|
// src/core/template-generator.ts
|
|
473
|
-
var import__package = __toESM(require_package(), 1);
|
|
474
|
-
import { writeFileSync } from "node:fs";
|
|
475
|
-
var SEPARATOR = "# ===========================================================================";
|
|
476
670
|
function deriveEnvFileName(templateFileName) {
|
|
477
671
|
if (templateFileName.endsWith(".tpl")) {
|
|
478
672
|
return templateFileName.slice(0, -4);
|
|
@@ -488,7 +682,7 @@ function formatTimestamp() {
|
|
|
488
682
|
function generateTemplateHeader(templateFileName) {
|
|
489
683
|
const envFileName = deriveEnvFileName(templateFileName);
|
|
490
684
|
return [
|
|
491
|
-
|
|
685
|
+
HEADER_SEPARATOR,
|
|
492
686
|
`# ${templateFileName} — 1Password Secret References`,
|
|
493
687
|
"#",
|
|
494
688
|
"# This template contains references to secrets stored in 1Password.",
|
|
@@ -501,27 +695,27 @@ function generateTemplateHeader(templateFileName) {
|
|
|
501
695
|
`# op run --env-file ${templateFileName} -- npm start`,
|
|
502
696
|
"#",
|
|
503
697
|
`# Pushed: ${formatTimestamp()}`,
|
|
504
|
-
`# Generated by env2op v${
|
|
698
|
+
`# Generated by env2op v${getCliVersion()}`,
|
|
505
699
|
"# https://github.com/tolgamorf/env2op-cli",
|
|
506
|
-
|
|
700
|
+
HEADER_SEPARATOR,
|
|
507
701
|
""
|
|
508
702
|
];
|
|
509
703
|
}
|
|
510
704
|
function generateEnvHeader(envFileName) {
|
|
511
705
|
const templateFileName = deriveTemplateFileName(envFileName);
|
|
512
706
|
return [
|
|
513
|
-
|
|
707
|
+
HEADER_SEPARATOR,
|
|
514
708
|
`# ${envFileName} — Environment Variables`,
|
|
515
709
|
"#",
|
|
516
710
|
"# WARNING: This file contains sensitive values. Do not commit to git!",
|
|
517
711
|
"#",
|
|
518
712
|
`# To push updates to 1Password and generate ${templateFileName}:`,
|
|
519
|
-
`# env2op ${envFileName} <vault> "<item_name>"`,
|
|
713
|
+
`# env2op ${envFileName} "<vault>" "<item_name>"`,
|
|
520
714
|
"#",
|
|
521
715
|
`# Pulled: ${formatTimestamp()}`,
|
|
522
|
-
`# Generated by op2env v${
|
|
716
|
+
`# Generated by op2env v${getCliVersion()}`,
|
|
523
717
|
"# https://github.com/tolgamorf/env2op-cli",
|
|
524
|
-
|
|
718
|
+
HEADER_SEPARATOR,
|
|
525
719
|
"",
|
|
526
720
|
""
|
|
527
721
|
];
|
|
@@ -549,7 +743,7 @@ function generateTemplateContent(options, templateFileName) {
|
|
|
549
743
|
`;
|
|
550
744
|
}
|
|
551
745
|
function writeTemplate(content, outputPath) {
|
|
552
|
-
|
|
746
|
+
writeFileSync2(outputPath, content, "utf-8");
|
|
553
747
|
}
|
|
554
748
|
function generateUsageInstructions(templatePath) {
|
|
555
749
|
return ["Usage:", ` op2env ${templatePath}`, ` op run --env-file ${templatePath} -- npm start`].join(`
|
|
@@ -557,7 +751,7 @@ function generateUsageInstructions(templatePath) {
|
|
|
557
751
|
}
|
|
558
752
|
|
|
559
753
|
// src/utils/logger.ts
|
|
560
|
-
import * as
|
|
754
|
+
import * as p2 from "@clack/prompts";
|
|
561
755
|
import pc2 from "picocolors";
|
|
562
756
|
var symbols = {
|
|
563
757
|
success: pc2.green("✓"),
|
|
@@ -570,29 +764,29 @@ var symbols = {
|
|
|
570
764
|
var logger = {
|
|
571
765
|
intro(name, version, dryRun = false) {
|
|
572
766
|
const label = dryRun ? pc2.bgYellow(pc2.black(` ${name} v${version} [DRY RUN] `)) : pc2.bgCyan(pc2.black(` ${name} v${version} `));
|
|
573
|
-
|
|
767
|
+
p2.intro(label);
|
|
574
768
|
},
|
|
575
769
|
section(title) {
|
|
576
770
|
console.log(`
|
|
577
771
|
${pc2.bold(pc2.underline(title))}`);
|
|
578
772
|
},
|
|
579
773
|
success(message) {
|
|
580
|
-
|
|
774
|
+
p2.log.success(message);
|
|
581
775
|
},
|
|
582
776
|
error(message) {
|
|
583
|
-
|
|
777
|
+
p2.log.error(message);
|
|
584
778
|
},
|
|
585
779
|
warn(message) {
|
|
586
|
-
|
|
780
|
+
p2.log.warn(message);
|
|
587
781
|
},
|
|
588
782
|
info(message) {
|
|
589
|
-
|
|
783
|
+
p2.log.info(message);
|
|
590
784
|
},
|
|
591
785
|
step(message) {
|
|
592
|
-
|
|
786
|
+
p2.log.step(message);
|
|
593
787
|
},
|
|
594
788
|
message(message) {
|
|
595
|
-
|
|
789
|
+
p2.log.message(message);
|
|
596
790
|
},
|
|
597
791
|
keyValue(key, value, indent = 2) {
|
|
598
792
|
console.log(`${" ".repeat(indent)}${pc2.dim(key)}: ${pc2.cyan(value)}`);
|
|
@@ -607,16 +801,16 @@ ${pc2.bold(pc2.underline(title))}`);
|
|
|
607
801
|
console.log(`${pc2.yellow("[DRY RUN]")} ${message}`);
|
|
608
802
|
},
|
|
609
803
|
spinner() {
|
|
610
|
-
return
|
|
804
|
+
return p2.spinner();
|
|
611
805
|
},
|
|
612
806
|
outro(message) {
|
|
613
|
-
|
|
807
|
+
p2.outro(pc2.green(message));
|
|
614
808
|
},
|
|
615
809
|
cancel(message) {
|
|
616
|
-
|
|
810
|
+
p2.cancel(message);
|
|
617
811
|
},
|
|
618
812
|
note(message, title) {
|
|
619
|
-
|
|
813
|
+
p2.note(message, title);
|
|
620
814
|
},
|
|
621
815
|
formatFields(fields, max = 3) {
|
|
622
816
|
if (fields.length <= max) {
|
|
@@ -626,6 +820,28 @@ ${pc2.bold(pc2.underline(title))}`);
|
|
|
626
820
|
}
|
|
627
821
|
};
|
|
628
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
|
+
|
|
629
845
|
// src/utils/timing.ts
|
|
630
846
|
import { setTimeout } from "node:timers/promises";
|
|
631
847
|
var MIN_SPINNER_TIME = 500;
|
|
@@ -644,38 +860,16 @@ function deriveOutputPath(templatePath) {
|
|
|
644
860
|
async function runInject(options) {
|
|
645
861
|
const { templateFile, output, dryRun, force, verbose } = options;
|
|
646
862
|
const outputPath = output ?? deriveOutputPath(templateFile);
|
|
647
|
-
|
|
648
|
-
logger.intro("op2env", pkg2.version, dryRun);
|
|
863
|
+
logger.intro("op2env", getCliVersion(), dryRun);
|
|
649
864
|
try {
|
|
650
|
-
if (!
|
|
651
|
-
throw
|
|
865
|
+
if (!existsSync2(templateFile)) {
|
|
866
|
+
throw errors.templateNotFound(templateFile);
|
|
652
867
|
}
|
|
653
868
|
logger.success(`Found template: ${basename(templateFile)}`);
|
|
654
869
|
if (!dryRun) {
|
|
655
|
-
|
|
656
|
-
authSpinner.start("Checking 1Password CLI...");
|
|
657
|
-
const opInstalled = await checkOpCli({ verbose });
|
|
658
|
-
if (!opInstalled) {
|
|
659
|
-
authSpinner.stop("1Password CLI not found");
|
|
660
|
-
throw new Env2OpError("1Password CLI (op) is not installed", "OP_CLI_NOT_INSTALLED", "Install from https://1password.com/downloads/command-line/");
|
|
661
|
-
}
|
|
662
|
-
let signedIn = await checkSignedIn({ verbose });
|
|
663
|
-
if (!signedIn) {
|
|
664
|
-
authSpinner.message("Signing in to 1Password...");
|
|
665
|
-
const signInSuccess = await signIn({ verbose });
|
|
666
|
-
if (!signInSuccess) {
|
|
667
|
-
authSpinner.stop();
|
|
668
|
-
throw new Env2OpError("Failed to sign in to 1Password CLI", "OP_SIGNIN_FAILED", 'Try running "op signin" manually');
|
|
669
|
-
}
|
|
670
|
-
signedIn = await checkSignedIn({ verbose });
|
|
671
|
-
if (!signedIn) {
|
|
672
|
-
authSpinner.stop();
|
|
673
|
-
throw new Env2OpError("Not signed in to 1Password CLI", "OP_NOT_SIGNED_IN", 'Run "op signin" to authenticate');
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
authSpinner.stop("1Password CLI ready");
|
|
870
|
+
await ensureOpAuthenticated({ verbose });
|
|
677
871
|
}
|
|
678
|
-
const outputExists =
|
|
872
|
+
const outputExists = existsSync2(outputPath);
|
|
679
873
|
if (dryRun) {
|
|
680
874
|
if (outputExists) {
|
|
681
875
|
logger.warn(`Would overwrite: ${outputPath}`);
|
|
@@ -686,13 +880,7 @@ async function runInject(options) {
|
|
|
686
880
|
return;
|
|
687
881
|
}
|
|
688
882
|
if (outputExists && !force) {
|
|
689
|
-
|
|
690
|
-
message: `File "${outputPath}" already exists. Overwrite?`
|
|
691
|
-
});
|
|
692
|
-
if (p2.isCancel(shouldOverwrite) || !shouldOverwrite) {
|
|
693
|
-
logger.cancel("Operation cancelled");
|
|
694
|
-
process.exit(0);
|
|
695
|
-
}
|
|
883
|
+
await confirmOrExit(`File "${outputPath}" already exists. Overwrite?`);
|
|
696
884
|
}
|
|
697
885
|
const spinner3 = verbose ? null : logger.spinner();
|
|
698
886
|
spinner3?.start("Pulling secrets from 1Password...");
|
|
@@ -701,11 +889,11 @@ async function runInject(options) {
|
|
|
701
889
|
if (result.exitCode !== 0) {
|
|
702
890
|
throw new Error(result.stderr);
|
|
703
891
|
}
|
|
704
|
-
const rawContent =
|
|
892
|
+
const rawContent = readFileSync2(outputPath, "utf-8");
|
|
705
893
|
const envContent = stripHeaders(rawContent);
|
|
706
894
|
const header = generateEnvHeader(basename(outputPath)).join(`
|
|
707
895
|
`);
|
|
708
|
-
|
|
896
|
+
writeFileSync3(outputPath, header + envContent, "utf-8");
|
|
709
897
|
const varCount = envContent.split(`
|
|
710
898
|
`).filter((line) => line.trim() && !line.trim().startsWith("#")).length;
|
|
711
899
|
const stopMessage = `Generated ${basename(outputPath)} — ${varCount} variable${varCount === 1 ? "" : "s"}`;
|
|
@@ -718,49 +906,146 @@ async function runInject(options) {
|
|
|
718
906
|
spinner3?.stop("Failed to pull secrets");
|
|
719
907
|
const stderr = error?.stderr;
|
|
720
908
|
const message = stderr || (error instanceof Error ? error.message : String(error));
|
|
721
|
-
throw
|
|
909
|
+
throw errors.injectFailed(message);
|
|
722
910
|
}
|
|
723
911
|
logger.outro("Done! Your .env file is ready");
|
|
724
912
|
} catch (error) {
|
|
725
|
-
|
|
726
|
-
logger.error(error.message);
|
|
727
|
-
if (error.suggestion) {
|
|
728
|
-
logger.info(`Suggestion: ${error.suggestion}`);
|
|
729
|
-
}
|
|
730
|
-
process.exit(1);
|
|
731
|
-
}
|
|
732
|
-
throw error;
|
|
913
|
+
handleCommandError(error);
|
|
733
914
|
}
|
|
734
915
|
}
|
|
735
916
|
|
|
736
|
-
// src/
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
917
|
+
// src/commands/update.ts
|
|
918
|
+
import * as p5 from "@clack/prompts";
|
|
919
|
+
import pc4 from "picocolors";
|
|
920
|
+
|
|
921
|
+
// src/lib/update-prompts.ts
|
|
922
|
+
import * as p4 from "@clack/prompts";
|
|
923
|
+
import pc3 from "picocolors";
|
|
924
|
+
var S_BAR_START = "┌";
|
|
925
|
+
var S_BAR_END = "└";
|
|
926
|
+
function showUpdateNotification(result, cliName = "env2op") {
|
|
927
|
+
console.log();
|
|
928
|
+
console.log(`${pc3.gray(S_BAR_START)}${pc3.gray("─")} ${pc3.yellow("Update available:")} ${pc3.dim(result.currentVersion)} ${pc3.dim("→")} ${pc3.green(result.latestVersion)}`);
|
|
929
|
+
console.log(`${pc3.gray(S_BAR_END)}${pc3.gray("─")} Run ${pc3.cyan(`'${cliName} update'`)} to update`);
|
|
930
|
+
}
|
|
931
|
+
async function askToUpdate(result) {
|
|
932
|
+
const response = await p4.select({
|
|
933
|
+
message: "Would you like to update?",
|
|
934
|
+
options: [
|
|
935
|
+
{ value: "update", label: "Update now", hint: "Download and install the latest version" },
|
|
936
|
+
{ value: "later", label: "Remind me later", hint: "Ask again next time" },
|
|
937
|
+
{ value: "skip", label: "Skip this version", hint: `Don't ask about ${result.latestVersion} again` }
|
|
938
|
+
]
|
|
939
|
+
});
|
|
940
|
+
if (p4.isCancel(response)) {
|
|
941
|
+
return "later";
|
|
942
|
+
}
|
|
943
|
+
return response;
|
|
944
|
+
}
|
|
945
|
+
function showUpdateAvailable(result) {
|
|
946
|
+
p4.log.success(`Update available: ${pc3.dim(result.currentVersion)} ${pc3.dim("→")} ${pc3.green(result.latestVersion)}`);
|
|
947
|
+
}
|
|
948
|
+
function showUpToDate(currentVersion) {
|
|
949
|
+
p4.log.success(`You're on the latest version ${pc3.green(`(${currentVersion})`)}`);
|
|
950
|
+
}
|
|
951
|
+
function showPackageManagerInfo(pm) {
|
|
952
|
+
console.log();
|
|
953
|
+
console.log(` ${pc3.dim("Detected:")} ${pm.displayName} installation`);
|
|
954
|
+
console.log(` ${pc3.dim("Command:")} ${pc3.cyan(pm.updateCommand)}`);
|
|
955
|
+
console.log();
|
|
956
|
+
}
|
|
957
|
+
function showUpdateSuccess(newVersion) {
|
|
958
|
+
p4.log.success(`Updated to version ${pc3.green(newVersion)}`);
|
|
959
|
+
p4.log.info("Please restart to use the new version.");
|
|
960
|
+
}
|
|
961
|
+
function showUpdateError(error, pm) {
|
|
962
|
+
if (error) {
|
|
963
|
+
p4.log.error(pc3.dim(error));
|
|
964
|
+
}
|
|
965
|
+
p4.log.info(`Try running manually: ${pc3.cyan(pm.updateCommand)}`);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// src/commands/update.ts
|
|
969
|
+
async function runUpdate(options) {
|
|
970
|
+
const { force = false, cliName = "env2op" } = options;
|
|
971
|
+
p5.intro(pc4.bgCyan(pc4.black(` ${cliName} update `)));
|
|
972
|
+
const spinner4 = p5.spinner();
|
|
973
|
+
spinner4.start("Checking for updates...");
|
|
974
|
+
const result = await checkForUpdate(true);
|
|
975
|
+
spinner4.stop("Checked for updates");
|
|
976
|
+
if (!result.updateAvailable || !result.latestVersion) {
|
|
977
|
+
showUpToDate(result.currentVersion);
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
showUpdateAvailable(result);
|
|
981
|
+
const pm = await detectPackageManager();
|
|
982
|
+
showPackageManagerInfo(pm);
|
|
983
|
+
if (!force) {
|
|
984
|
+
const choice = await askToUpdate(result);
|
|
985
|
+
if (choice === "skip") {
|
|
986
|
+
skipVersion(result.latestVersion);
|
|
987
|
+
p5.log.info(`Skipped version ${result.latestVersion}`);
|
|
988
|
+
return;
|
|
749
989
|
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
for (const char of arg.slice(1)) {
|
|
754
|
-
flags.add(char);
|
|
990
|
+
if (choice === "later") {
|
|
991
|
+
p5.log.info("Update postponed");
|
|
992
|
+
return;
|
|
755
993
|
}
|
|
994
|
+
}
|
|
995
|
+
const updateSpinner = p5.spinner();
|
|
996
|
+
updateSpinner.start(`Updating to ${result.latestVersion}...`);
|
|
997
|
+
const updateResult = await performUpdate(pm);
|
|
998
|
+
if (updateResult.success) {
|
|
999
|
+
updateSpinner.stop("Update completed");
|
|
1000
|
+
showUpdateSuccess(result.latestVersion);
|
|
756
1001
|
} else {
|
|
757
|
-
|
|
1002
|
+
updateSpinner.stop("Update failed");
|
|
1003
|
+
showUpdateError(updateResult.error, pm);
|
|
1004
|
+
process.exit(1);
|
|
758
1005
|
}
|
|
759
1006
|
}
|
|
1007
|
+
|
|
1008
|
+
// src/utils/args.ts
|
|
1009
|
+
function parseArgs(args) {
|
|
1010
|
+
const flags = new Set;
|
|
1011
|
+
const positional = [];
|
|
1012
|
+
const options = {};
|
|
1013
|
+
for (let i = 0;i < args.length; i++) {
|
|
1014
|
+
const arg = args[i];
|
|
1015
|
+
if (arg === "-o" || arg === "--output") {
|
|
1016
|
+
const next = args[i + 1];
|
|
1017
|
+
if (next && !next.startsWith("-")) {
|
|
1018
|
+
options.output = next;
|
|
1019
|
+
i++;
|
|
1020
|
+
}
|
|
1021
|
+
} else if (arg.startsWith("--")) {
|
|
1022
|
+
flags.add(arg.slice(2));
|
|
1023
|
+
} else if (arg.startsWith("-")) {
|
|
1024
|
+
for (const char of arg.slice(1)) {
|
|
1025
|
+
flags.add(char);
|
|
1026
|
+
}
|
|
1027
|
+
} else {
|
|
1028
|
+
positional.push(arg);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
return { flags, positional, options };
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// src/op2env-cli.ts
|
|
1035
|
+
var { flags, positional, options } = parseArgs(process.argv.slice(2));
|
|
760
1036
|
var hasHelp = flags.has("h") || flags.has("help");
|
|
761
1037
|
var hasVersion = flags.has("v") || flags.has("version");
|
|
1038
|
+
var hasUpdate = flags.has("update");
|
|
762
1039
|
if (hasVersion) {
|
|
763
|
-
console.log(
|
|
1040
|
+
console.log(getCliVersion());
|
|
1041
|
+
process.exit(0);
|
|
1042
|
+
}
|
|
1043
|
+
if (hasUpdate) {
|
|
1044
|
+
await runUpdate({
|
|
1045
|
+
force: flags.has("f") || flags.has("force"),
|
|
1046
|
+
verbose: flags.has("verbose"),
|
|
1047
|
+
cliName: "op2env"
|
|
1048
|
+
});
|
|
764
1049
|
process.exit(0);
|
|
765
1050
|
}
|
|
766
1051
|
if (hasHelp || positional.length === 0) {
|
|
@@ -775,41 +1060,43 @@ await runInject({
|
|
|
775
1060
|
force: flags.has("f") || flags.has("force"),
|
|
776
1061
|
verbose: flags.has("verbose")
|
|
777
1062
|
});
|
|
1063
|
+
await maybeShowUpdateNotification("op2env", showUpdateNotification);
|
|
778
1064
|
function showHelp() {
|
|
779
|
-
const name =
|
|
780
|
-
const version =
|
|
1065
|
+
const name = pc5.bold(pc5.cyan("op2env"));
|
|
1066
|
+
const version = pc5.dim(`v${getCliVersion()}`);
|
|
781
1067
|
console.log(`
|
|
782
1068
|
${name} ${version}
|
|
783
1069
|
Pull secrets from 1Password to generate .env files
|
|
784
1070
|
|
|
785
|
-
${
|
|
786
|
-
${
|
|
1071
|
+
${pc5.bold("USAGE")}
|
|
1072
|
+
${pc5.cyan("$")} op2env ${pc5.yellow("<template_file>")} ${pc5.dim("[options]")}
|
|
787
1073
|
|
|
788
|
-
${
|
|
789
|
-
${
|
|
1074
|
+
${pc5.bold("ARGUMENTS")}
|
|
1075
|
+
${pc5.yellow("template_file")} Path to .env.tpl template file
|
|
790
1076
|
|
|
791
|
-
${
|
|
792
|
-
${
|
|
793
|
-
${
|
|
794
|
-
${
|
|
795
|
-
${
|
|
796
|
-
${
|
|
797
|
-
${
|
|
1077
|
+
${pc5.bold("OPTIONS")}
|
|
1078
|
+
${pc5.cyan("-o, --output")} Output .env path (default: template without .tpl)
|
|
1079
|
+
${pc5.cyan("-f, --force")} Overwrite without prompting
|
|
1080
|
+
${pc5.cyan(" --dry-run")} Preview actions without executing
|
|
1081
|
+
${pc5.cyan(" --verbose")} Show op CLI output
|
|
1082
|
+
${pc5.cyan(" --update")} Check for and install updates
|
|
1083
|
+
${pc5.cyan("-v, --version")} Show version
|
|
1084
|
+
${pc5.cyan("-h, --help")} Show this help message
|
|
798
1085
|
|
|
799
|
-
${
|
|
800
|
-
${
|
|
801
|
-
${
|
|
1086
|
+
${pc5.bold("EXAMPLES")}
|
|
1087
|
+
${pc5.dim("# Basic usage - generates .env from .env.tpl")}
|
|
1088
|
+
${pc5.cyan("$")} op2env .env.tpl
|
|
802
1089
|
|
|
803
|
-
${
|
|
804
|
-
${
|
|
1090
|
+
${pc5.dim("# Custom output path")}
|
|
1091
|
+
${pc5.cyan("$")} op2env .env.tpl -o .env.local
|
|
805
1092
|
|
|
806
|
-
${
|
|
807
|
-
${
|
|
1093
|
+
${pc5.dim("# Preview without making changes")}
|
|
1094
|
+
${pc5.cyan("$")} op2env .env.tpl --dry-run
|
|
808
1095
|
|
|
809
|
-
${
|
|
810
|
-
${
|
|
1096
|
+
${pc5.dim("# Overwrite existing .env without prompting")}
|
|
1097
|
+
${pc5.cyan("$")} op2env .env.tpl -f
|
|
811
1098
|
|
|
812
|
-
${
|
|
813
|
-
${
|
|
1099
|
+
${pc5.bold("DOCUMENTATION")}
|
|
1100
|
+
${pc5.dim("https://github.com/tolgamorf/env2op-cli")}
|
|
814
1101
|
`);
|
|
815
1102
|
}
|