@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/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.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,12 +105,11 @@ var require_package = __commonJS((exports, module) => {
|
|
|
105
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
|
|
@@ -432,47 +304,369 @@ async function createSecureNote(options) {
|
|
|
432
304
|
vaultId: item.vault?.id ?? "",
|
|
433
305
|
fieldIds
|
|
434
306
|
};
|
|
435
|
-
} catch (error) {
|
|
436
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
437
|
-
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
|
+
};
|
|
438
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
|
+
};
|
|
439
637
|
}
|
|
440
|
-
async function
|
|
441
|
-
const
|
|
442
|
-
const template = buildItemTemplate(title, vault, fields, secret);
|
|
443
|
-
const json = JSON.stringify(template);
|
|
638
|
+
async function performUpdate(pm) {
|
|
639
|
+
const packageManager = pm ?? await detectPackageManager();
|
|
444
640
|
try {
|
|
445
|
-
const
|
|
446
|
-
|
|
447
|
-
verbose
|
|
448
|
-
});
|
|
641
|
+
const [command, ...args] = packageManager.updateCommand.split(" ");
|
|
642
|
+
const result = await exec(command, args, { verbose: false });
|
|
449
643
|
if (result.exitCode !== 0) {
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
for (const field of item.fields ?? []) {
|
|
455
|
-
if (field.label && field.id) {
|
|
456
|
-
fieldIds[field.label] = field.id;
|
|
457
|
-
}
|
|
644
|
+
return {
|
|
645
|
+
success: false,
|
|
646
|
+
error: result.stderr || `Command exited with code ${result.exitCode}`
|
|
647
|
+
};
|
|
458
648
|
}
|
|
459
|
-
return {
|
|
460
|
-
id: item.id,
|
|
461
|
-
title: item.title,
|
|
462
|
-
vault: item.vault?.name ?? vault,
|
|
463
|
-
vaultId: item.vault?.id ?? "",
|
|
464
|
-
fieldIds
|
|
465
|
-
};
|
|
649
|
+
return { success: true };
|
|
466
650
|
} catch (error) {
|
|
467
651
|
const message = error instanceof Error ? error.message : String(error);
|
|
468
|
-
|
|
652
|
+
return { success: false, error: message };
|
|
469
653
|
}
|
|
470
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
|
+
}
|
|
471
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,214 +906,31 @@ 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
917
|
// src/commands/update.ts
|
|
737
|
-
import * as
|
|
918
|
+
import * as p5 from "@clack/prompts";
|
|
738
919
|
import pc4 from "picocolors";
|
|
739
920
|
|
|
740
|
-
// src/lib/package-manager.ts
|
|
741
|
-
var UPDATE_COMMANDS = {
|
|
742
|
-
homebrew: "brew upgrade tolgamorf/tap/env2op-cli",
|
|
743
|
-
npm: "npm update -g @tolgamorf/env2op-cli",
|
|
744
|
-
bun: "bun update -g @tolgamorf/env2op-cli",
|
|
745
|
-
pnpm: "pnpm update -g @tolgamorf/env2op-cli",
|
|
746
|
-
unknown: "npm update -g @tolgamorf/env2op-cli"
|
|
747
|
-
};
|
|
748
|
-
var DISPLAY_NAMES = {
|
|
749
|
-
homebrew: "Homebrew",
|
|
750
|
-
npm: "npm",
|
|
751
|
-
bun: "Bun",
|
|
752
|
-
pnpm: "pnpm",
|
|
753
|
-
unknown: "npm (default)"
|
|
754
|
-
};
|
|
755
|
-
function detectFromPath() {
|
|
756
|
-
const binPath = process.argv[1] ?? "";
|
|
757
|
-
if (binPath.includes("/Cellar/") || binPath.includes("/homebrew/") || binPath.includes("/opt/homebrew/") || binPath.includes("/home/linuxbrew/")) {
|
|
758
|
-
return "homebrew";
|
|
759
|
-
}
|
|
760
|
-
if (binPath.includes("/.bun/")) {
|
|
761
|
-
return "bun";
|
|
762
|
-
}
|
|
763
|
-
if (binPath.includes("/pnpm/") || binPath.includes("/.pnpm/")) {
|
|
764
|
-
return "pnpm";
|
|
765
|
-
}
|
|
766
|
-
if (binPath.includes("/node_modules/")) {
|
|
767
|
-
return "npm";
|
|
768
|
-
}
|
|
769
|
-
return null;
|
|
770
|
-
}
|
|
771
|
-
async function detectFromCommands() {
|
|
772
|
-
const brewResult = await exec("brew", ["list", "env2op-cli"], { verbose: false });
|
|
773
|
-
if (brewResult.exitCode === 0) {
|
|
774
|
-
return "homebrew";
|
|
775
|
-
}
|
|
776
|
-
return "npm";
|
|
777
|
-
}
|
|
778
|
-
async function detectPackageManager() {
|
|
779
|
-
const fromPath = detectFromPath();
|
|
780
|
-
if (fromPath) {
|
|
781
|
-
return {
|
|
782
|
-
type: fromPath,
|
|
783
|
-
updateCommand: UPDATE_COMMANDS[fromPath],
|
|
784
|
-
displayName: DISPLAY_NAMES[fromPath]
|
|
785
|
-
};
|
|
786
|
-
}
|
|
787
|
-
const fromCommands = await detectFromCommands();
|
|
788
|
-
return {
|
|
789
|
-
type: fromCommands,
|
|
790
|
-
updateCommand: UPDATE_COMMANDS[fromCommands],
|
|
791
|
-
displayName: DISPLAY_NAMES[fromCommands]
|
|
792
|
-
};
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
// src/lib/update.ts
|
|
796
|
-
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
797
|
-
import { homedir } from "node:os";
|
|
798
|
-
import { join } from "node:path";
|
|
799
|
-
var CACHE_DIR = join(homedir(), ".env2op");
|
|
800
|
-
var CACHE_FILE = join(CACHE_DIR, "update-check.json");
|
|
801
|
-
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
802
|
-
function getCliVersion() {
|
|
803
|
-
try {
|
|
804
|
-
const pkg2 = require_package();
|
|
805
|
-
return pkg2.version ?? "0.0.0";
|
|
806
|
-
} catch {
|
|
807
|
-
return "0.0.0";
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
function loadCache() {
|
|
811
|
-
try {
|
|
812
|
-
if (existsSync2(CACHE_FILE)) {
|
|
813
|
-
const content = readFileSync2(CACHE_FILE, "utf-8");
|
|
814
|
-
return JSON.parse(content);
|
|
815
|
-
}
|
|
816
|
-
} catch {}
|
|
817
|
-
return { lastCheck: 0, latestVersion: null };
|
|
818
|
-
}
|
|
819
|
-
function saveCache(cache) {
|
|
820
|
-
try {
|
|
821
|
-
if (!existsSync2(CACHE_DIR)) {
|
|
822
|
-
mkdirSync(CACHE_DIR, { recursive: true });
|
|
823
|
-
}
|
|
824
|
-
writeFileSync3(CACHE_FILE, JSON.stringify(cache, null, 2));
|
|
825
|
-
} catch {}
|
|
826
|
-
}
|
|
827
|
-
function shouldCheckForUpdate(cache) {
|
|
828
|
-
const now = Date.now();
|
|
829
|
-
return now - cache.lastCheck > CHECK_INTERVAL_MS;
|
|
830
|
-
}
|
|
831
|
-
async function fetchLatestVersion() {
|
|
832
|
-
try {
|
|
833
|
-
const response = await fetch("https://registry.npmjs.org/@tolgamorf/env2op-cli/latest");
|
|
834
|
-
if (!response.ok)
|
|
835
|
-
return null;
|
|
836
|
-
const data = await response.json();
|
|
837
|
-
return data.version ?? null;
|
|
838
|
-
} catch {
|
|
839
|
-
return null;
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
function compareVersions(v1, v2) {
|
|
843
|
-
const parts1 = v1.split(".").map(Number);
|
|
844
|
-
const parts2 = v2.split(".").map(Number);
|
|
845
|
-
for (let i = 0;i < 3; i++) {
|
|
846
|
-
const p1 = parts1[i] || 0;
|
|
847
|
-
const p22 = parts2[i] || 0;
|
|
848
|
-
if (p1 < p22)
|
|
849
|
-
return -1;
|
|
850
|
-
if (p1 > p22)
|
|
851
|
-
return 1;
|
|
852
|
-
}
|
|
853
|
-
return 0;
|
|
854
|
-
}
|
|
855
|
-
async function checkForUpdate(forceCheck = false) {
|
|
856
|
-
const currentVersion = getCliVersion();
|
|
857
|
-
const cache = loadCache();
|
|
858
|
-
if (!forceCheck && !shouldCheckForUpdate(cache) && cache.latestVersion) {
|
|
859
|
-
const updateAvailable2 = compareVersions(currentVersion, cache.latestVersion) < 0;
|
|
860
|
-
const isSkipped2 = cache.skipVersion === cache.latestVersion;
|
|
861
|
-
return {
|
|
862
|
-
currentVersion,
|
|
863
|
-
latestVersion: cache.latestVersion,
|
|
864
|
-
updateAvailable: updateAvailable2,
|
|
865
|
-
isSkipped: isSkipped2,
|
|
866
|
-
fromCache: true
|
|
867
|
-
};
|
|
868
|
-
}
|
|
869
|
-
const latestVersion = await fetchLatestVersion();
|
|
870
|
-
saveCache({
|
|
871
|
-
...cache,
|
|
872
|
-
lastCheck: Date.now(),
|
|
873
|
-
latestVersion
|
|
874
|
-
});
|
|
875
|
-
if (!latestVersion) {
|
|
876
|
-
return {
|
|
877
|
-
currentVersion,
|
|
878
|
-
latestVersion: null,
|
|
879
|
-
updateAvailable: false,
|
|
880
|
-
isSkipped: false,
|
|
881
|
-
fromCache: false
|
|
882
|
-
};
|
|
883
|
-
}
|
|
884
|
-
const updateAvailable = compareVersions(currentVersion, latestVersion) < 0;
|
|
885
|
-
const isSkipped = cache.skipVersion === latestVersion;
|
|
886
|
-
return {
|
|
887
|
-
currentVersion,
|
|
888
|
-
latestVersion,
|
|
889
|
-
updateAvailable,
|
|
890
|
-
isSkipped,
|
|
891
|
-
fromCache: false
|
|
892
|
-
};
|
|
893
|
-
}
|
|
894
|
-
async function performUpdate(pm) {
|
|
895
|
-
const packageManager = pm ?? await detectPackageManager();
|
|
896
|
-
try {
|
|
897
|
-
const [command, ...args] = packageManager.updateCommand.split(" ");
|
|
898
|
-
const result = await exec(command, args, { verbose: false });
|
|
899
|
-
if (result.exitCode !== 0) {
|
|
900
|
-
return {
|
|
901
|
-
success: false,
|
|
902
|
-
error: result.stderr || `Command exited with code ${result.exitCode}`
|
|
903
|
-
};
|
|
904
|
-
}
|
|
905
|
-
return { success: true };
|
|
906
|
-
} catch (error) {
|
|
907
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
908
|
-
return { success: false, error: message };
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
function skipVersion(version) {
|
|
912
|
-
const cache = loadCache();
|
|
913
|
-
cache.skipVersion = version;
|
|
914
|
-
saveCache(cache);
|
|
915
|
-
}
|
|
916
|
-
|
|
917
921
|
// src/lib/update-prompts.ts
|
|
918
|
-
import * as
|
|
922
|
+
import * as p4 from "@clack/prompts";
|
|
919
923
|
import pc3 from "picocolors";
|
|
924
|
+
var S_BAR = "│";
|
|
920
925
|
var S_BAR_START = "┌";
|
|
921
926
|
var S_BAR_END = "└";
|
|
922
927
|
function showUpdateNotification(result, cliName = "env2op") {
|
|
923
928
|
console.log();
|
|
924
929
|
console.log(`${pc3.gray(S_BAR_START)}${pc3.gray("─")} ${pc3.yellow("Update available:")} ${pc3.dim(result.currentVersion)} ${pc3.dim("→")} ${pc3.green(result.latestVersion)}`);
|
|
925
|
-
console.log(`${pc3.gray(S_BAR_END)}${pc3.gray("─")} Run ${pc3.cyan(`'${cliName} update'`)} to update`);
|
|
930
|
+
console.log(`${pc3.gray(S_BAR_END)}${pc3.gray("─")} Run ${pc3.cyan(`'${cliName} --update'`)} to update`);
|
|
926
931
|
}
|
|
927
932
|
async function askToUpdate(result) {
|
|
928
|
-
const response = await
|
|
933
|
+
const response = await p4.select({
|
|
929
934
|
message: "Would you like to update?",
|
|
930
935
|
options: [
|
|
931
936
|
{ value: "update", label: "Update now", hint: "Download and install the latest version" },
|
|
@@ -933,39 +938,38 @@ async function askToUpdate(result) {
|
|
|
933
938
|
{ value: "skip", label: "Skip this version", hint: `Don't ask about ${result.latestVersion} again` }
|
|
934
939
|
]
|
|
935
940
|
});
|
|
936
|
-
if (
|
|
941
|
+
if (p4.isCancel(response)) {
|
|
937
942
|
return "later";
|
|
938
943
|
}
|
|
939
944
|
return response;
|
|
940
945
|
}
|
|
941
946
|
function showUpdateAvailable(result) {
|
|
942
|
-
|
|
947
|
+
p4.log.success(`Update available: ${pc3.dim(result.currentVersion)} ${pc3.dim("→")} ${pc3.green(result.latestVersion)}`);
|
|
943
948
|
}
|
|
944
949
|
function showUpToDate(currentVersion) {
|
|
945
|
-
|
|
950
|
+
p4.log.success(`You're on the latest version ${pc3.green(`(${currentVersion})`)}`);
|
|
946
951
|
}
|
|
947
952
|
function showPackageManagerInfo(pm) {
|
|
948
|
-
console.log();
|
|
949
|
-
console.log(
|
|
950
|
-
console.log(
|
|
951
|
-
console.log();
|
|
953
|
+
console.log(pc3.gray(S_BAR));
|
|
954
|
+
console.log(`${pc3.gray(S_BAR)} ${pc3.dim("Detected:")} ${pm.displayName} installation`);
|
|
955
|
+
console.log(`${pc3.gray(S_BAR)} ${pc3.dim("Command:")} ${pc3.cyan(pm.updateCommand)}`);
|
|
952
956
|
}
|
|
953
957
|
function showUpdateSuccess(newVersion) {
|
|
954
|
-
|
|
955
|
-
|
|
958
|
+
p4.log.success(`Updated to version ${pc3.green(newVersion)}`);
|
|
959
|
+
p4.log.info("Please restart to use the new version.");
|
|
956
960
|
}
|
|
957
961
|
function showUpdateError(error, pm) {
|
|
958
962
|
if (error) {
|
|
959
|
-
|
|
963
|
+
p4.log.error(pc3.dim(error));
|
|
960
964
|
}
|
|
961
|
-
|
|
965
|
+
p4.log.info(`Try running manually: ${pc3.cyan(pm.updateCommand)}`);
|
|
962
966
|
}
|
|
963
967
|
|
|
964
968
|
// src/commands/update.ts
|
|
965
969
|
async function runUpdate(options) {
|
|
966
970
|
const { force = false, cliName = "env2op" } = options;
|
|
967
|
-
|
|
968
|
-
const spinner4 =
|
|
971
|
+
p5.intro(pc4.bgCyan(pc4.black(` ${cliName} update `)));
|
|
972
|
+
const spinner4 = p5.spinner();
|
|
969
973
|
spinner4.start("Checking for updates...");
|
|
970
974
|
const result = await checkForUpdate(true);
|
|
971
975
|
spinner4.stop("Checked for updates");
|
|
@@ -980,15 +984,15 @@ async function runUpdate(options) {
|
|
|
980
984
|
const choice = await askToUpdate(result);
|
|
981
985
|
if (choice === "skip") {
|
|
982
986
|
skipVersion(result.latestVersion);
|
|
983
|
-
|
|
987
|
+
p5.log.info(`Skipped version ${result.latestVersion}`);
|
|
984
988
|
return;
|
|
985
989
|
}
|
|
986
990
|
if (choice === "later") {
|
|
987
|
-
|
|
991
|
+
p5.log.info("Update postponed");
|
|
988
992
|
return;
|
|
989
993
|
}
|
|
990
994
|
}
|
|
991
|
-
const updateSpinner =
|
|
995
|
+
const updateSpinner = p5.spinner();
|
|
992
996
|
updateSpinner.start(`Updating to ${result.latestVersion}...`);
|
|
993
997
|
const updateResult = await performUpdate(pm);
|
|
994
998
|
if (updateResult.success) {
|
|
@@ -1001,35 +1005,39 @@ async function runUpdate(options) {
|
|
|
1001
1005
|
}
|
|
1002
1006
|
}
|
|
1003
1007
|
|
|
1004
|
-
// src/
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
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);
|
|
1023
1029
|
}
|
|
1024
|
-
} else {
|
|
1025
|
-
positional.push(arg);
|
|
1026
1030
|
}
|
|
1031
|
+
return { flags, positional, options };
|
|
1027
1032
|
}
|
|
1033
|
+
|
|
1034
|
+
// src/op2env-cli.ts
|
|
1035
|
+
var { flags, positional, options } = parseArgs(process.argv.slice(2));
|
|
1028
1036
|
var hasHelp = flags.has("h") || flags.has("help");
|
|
1029
1037
|
var hasVersion = flags.has("v") || flags.has("version");
|
|
1030
1038
|
var hasUpdate = flags.has("update");
|
|
1031
1039
|
if (hasVersion) {
|
|
1032
|
-
console.log(
|
|
1040
|
+
console.log(getCliVersion());
|
|
1033
1041
|
process.exit(0);
|
|
1034
1042
|
}
|
|
1035
1043
|
if (hasUpdate) {
|
|
@@ -1042,6 +1050,7 @@ if (hasUpdate) {
|
|
|
1042
1050
|
}
|
|
1043
1051
|
if (hasHelp || positional.length === 0) {
|
|
1044
1052
|
showHelp();
|
|
1053
|
+
await maybeShowUpdateNotification("op2env", showUpdateNotification);
|
|
1045
1054
|
process.exit(0);
|
|
1046
1055
|
}
|
|
1047
1056
|
var [templateFile] = positional;
|
|
@@ -1052,15 +1061,10 @@ await runInject({
|
|
|
1052
1061
|
force: flags.has("f") || flags.has("force"),
|
|
1053
1062
|
verbose: flags.has("verbose")
|
|
1054
1063
|
});
|
|
1055
|
-
|
|
1056
|
-
const updateResult = await checkForUpdate();
|
|
1057
|
-
if (updateResult.updateAvailable && !updateResult.isSkipped) {
|
|
1058
|
-
showUpdateNotification(updateResult, "op2env");
|
|
1059
|
-
}
|
|
1060
|
-
} catch {}
|
|
1064
|
+
await maybeShowUpdateNotification("op2env", showUpdateNotification);
|
|
1061
1065
|
function showHelp() {
|
|
1062
1066
|
const name = pc5.bold(pc5.cyan("op2env"));
|
|
1063
|
-
const version = pc5.dim(`v${
|
|
1067
|
+
const version = pc5.dim(`v${getCliVersion()}`);
|
|
1064
1068
|
console.log(`
|
|
1065
1069
|
${name} ${version}
|
|
1066
1070
|
Pull secrets from 1Password to generate .env files
|