@tolgamorf/env2op-cli 0.2.0 → 0.2.2
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/README.md +25 -5
- package/dist/cli.js +375 -69
- package/dist/index.d.ts +3 -1
- package/dist/index.js +48 -28
- package/dist/op2env-cli.js +365 -59
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# env2op
|
|
1
|
+
# env2op & op2env
|
|
2
2
|
|
|
3
3
|
Push `.env` files to 1Password and pull them back with two simple commands.
|
|
4
4
|
|
|
@@ -19,7 +19,9 @@ Or in a single command:
|
|
|
19
19
|
brew install tolgamorf/tap/env2op-cli
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
###
|
|
22
|
+
### Package Managers (macOS/Linux/Windows)
|
|
23
|
+
|
|
24
|
+
#### Global installation
|
|
23
25
|
|
|
24
26
|
```bash
|
|
25
27
|
# Using bun
|
|
@@ -28,10 +30,24 @@ bun add -g @tolgamorf/env2op-cli
|
|
|
28
30
|
# Using npm
|
|
29
31
|
npm install -g @tolgamorf/env2op-cli
|
|
30
32
|
|
|
31
|
-
#
|
|
33
|
+
# Using pnpm
|
|
34
|
+
pnpm add -g @tolgamorf/env2op-cli
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
#### Running directly
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Using bun
|
|
32
41
|
bunx @tolgamorf/env2op-cli .env Personal "MyApp"
|
|
42
|
+
|
|
43
|
+
# Using npm
|
|
44
|
+
npx @tolgamorf/env2op-cli .env Personal "MyApp"
|
|
45
|
+
|
|
46
|
+
# Using pnpm
|
|
47
|
+
pnpm dlx @tolgamorf/env2op-cli .env Personal "MyApp"
|
|
33
48
|
```
|
|
34
49
|
|
|
50
|
+
|
|
35
51
|
## Prerequisites
|
|
36
52
|
|
|
37
53
|
- [1Password CLI](https://1password.com/downloads/command-line/) installed and signed in
|
|
@@ -81,8 +97,10 @@ env2op .env.production Personal "MyApp" -f
|
|
|
81
97
|
| `-f, --force` | Skip confirmation prompts |
|
|
82
98
|
| `--dry-run` | Preview actions without executing |
|
|
83
99
|
| `--secret` | Store all fields as 'password' type (default: 'text') |
|
|
84
|
-
|
|
|
100
|
+
| `--verbose` | Show op CLI output |
|
|
101
|
+
| `--update` | Check for and install updates |
|
|
85
102
|
| `-v, --version` | Show version |
|
|
103
|
+
| `-h, --help` | Show help |
|
|
86
104
|
|
|
87
105
|
## op2env (Pull)
|
|
88
106
|
|
|
@@ -115,8 +133,10 @@ op2env .env.tpl -f
|
|
|
115
133
|
| `-o, --output` | Output .env path (default: template without `.tpl`) |
|
|
116
134
|
| `-f, --force` | Overwrite without prompting |
|
|
117
135
|
| `--dry-run` | Preview actions without executing |
|
|
118
|
-
|
|
|
136
|
+
| `--verbose` | Show op CLI output |
|
|
137
|
+
| `--update` | Check for and install updates |
|
|
119
138
|
| `-v, --version` | Show version |
|
|
139
|
+
| `-h, --help` | Show help |
|
|
120
140
|
|
|
121
141
|
## How It Works
|
|
122
142
|
|
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.2",
|
|
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/cli.ts
|
|
105
|
-
import
|
|
105
|
+
import pc5 from "picocolors";
|
|
106
106
|
|
|
107
107
|
// src/commands/convert.ts
|
|
108
108
|
import { basename, dirname, join } from "node:path";
|
|
109
|
-
import { setTimeout } from "node:timers/promises";
|
|
110
109
|
import * as p2 from "@clack/prompts";
|
|
111
110
|
|
|
112
111
|
// src/core/env-parser.ts
|
|
113
|
-
import {
|
|
112
|
+
import { readFile } from "node:fs/promises";
|
|
114
113
|
|
|
115
114
|
// src/utils/errors.ts
|
|
116
115
|
class Env2OpError extends Error {
|
|
@@ -133,6 +132,7 @@ var ErrorCodes = {
|
|
|
133
132
|
VAULT_CREATE_FAILED: "VAULT_CREATE_FAILED",
|
|
134
133
|
ITEM_EXISTS: "ITEM_EXISTS",
|
|
135
134
|
ITEM_CREATE_FAILED: "ITEM_CREATE_FAILED",
|
|
135
|
+
ITEM_EDIT_FAILED: "ITEM_EDIT_FAILED",
|
|
136
136
|
PARSE_ERROR: "PARSE_ERROR",
|
|
137
137
|
TEMPLATE_NOT_FOUND: "TEMPLATE_NOT_FOUND",
|
|
138
138
|
INJECT_FAILED: "INJECT_FAILED"
|
|
@@ -146,6 +146,7 @@ var errors = {
|
|
|
146
146
|
vaultCreateFailed: (message) => new Env2OpError(`Failed to create vault: ${message}`, ErrorCodes.VAULT_CREATE_FAILED),
|
|
147
147
|
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
148
|
itemCreateFailed: (message) => new Env2OpError(`Failed to create 1Password item: ${message}`, ErrorCodes.ITEM_CREATE_FAILED),
|
|
149
|
+
itemEditFailed: (message) => new Env2OpError(`Failed to edit 1Password item: ${message}`, ErrorCodes.ITEM_EDIT_FAILED),
|
|
149
150
|
parseError: (line, message) => new Env2OpError(`Parse error at line ${line}: ${message}`, ErrorCodes.PARSE_ERROR)
|
|
150
151
|
};
|
|
151
152
|
|
|
@@ -193,12 +194,20 @@ function parseValue(raw) {
|
|
|
193
194
|
const parts = trimmed.split(/\s+#/);
|
|
194
195
|
return (parts[0] ?? trimmed).trim();
|
|
195
196
|
}
|
|
196
|
-
function
|
|
197
|
-
if (
|
|
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 {
|
|
198
208
|
throw errors.envFileNotFound(filePath);
|
|
199
209
|
}
|
|
200
|
-
const
|
|
201
|
-
const content = stripHeaders(rawContent);
|
|
210
|
+
const content = stripHeaders(stripBom(rawContent));
|
|
202
211
|
const rawLines = content.split(`
|
|
203
212
|
`);
|
|
204
213
|
const variables = [];
|
|
@@ -263,33 +272,34 @@ async function exec(command, args = [], options = {}) {
|
|
|
263
272
|
const proc = spawn(command, args, {
|
|
264
273
|
stdio: ["ignore", "pipe", "pipe"]
|
|
265
274
|
});
|
|
266
|
-
|
|
267
|
-
|
|
275
|
+
const stdoutChunks = [];
|
|
276
|
+
const stderrChunks = [];
|
|
268
277
|
proc.stdout?.on("data", (data) => {
|
|
269
|
-
const text = data.toString();
|
|
270
|
-
|
|
278
|
+
const text = Buffer.isBuffer(data) ? data.toString() : String(data);
|
|
279
|
+
stdoutChunks.push(text);
|
|
271
280
|
if (verbose) {
|
|
272
281
|
process.stdout.write(text);
|
|
273
282
|
}
|
|
274
283
|
});
|
|
275
284
|
proc.stderr?.on("data", (data) => {
|
|
276
|
-
const text = data.toString();
|
|
277
|
-
|
|
285
|
+
const text = Buffer.isBuffer(data) ? data.toString() : String(data);
|
|
286
|
+
stderrChunks.push(text);
|
|
278
287
|
if (verbose) {
|
|
279
288
|
process.stderr.write(text);
|
|
280
289
|
}
|
|
281
290
|
});
|
|
282
291
|
proc.on("close", (code) => {
|
|
283
292
|
resolve({
|
|
284
|
-
stdout,
|
|
285
|
-
stderr,
|
|
286
|
-
exitCode: code ??
|
|
293
|
+
stdout: stdoutChunks.join(""),
|
|
294
|
+
stderr: stderrChunks.join(""),
|
|
295
|
+
exitCode: code ?? 1
|
|
287
296
|
});
|
|
288
297
|
});
|
|
289
|
-
proc.on("error", () => {
|
|
298
|
+
proc.on("error", (err) => {
|
|
299
|
+
stderrChunks.push(err.message);
|
|
290
300
|
resolve({
|
|
291
|
-
stdout,
|
|
292
|
-
stderr,
|
|
301
|
+
stdout: stdoutChunks.join(""),
|
|
302
|
+
stderr: stderrChunks.join(""),
|
|
293
303
|
exitCode: 1
|
|
294
304
|
});
|
|
295
305
|
});
|
|
@@ -305,27 +315,36 @@ async function execWithStdin(command, args = [], options) {
|
|
|
305
315
|
const proc = spawn(command, args, {
|
|
306
316
|
stdio: ["pipe", "pipe", "pipe"]
|
|
307
317
|
});
|
|
308
|
-
|
|
309
|
-
|
|
318
|
+
const stdoutChunks = [];
|
|
319
|
+
const stderrChunks = [];
|
|
310
320
|
proc.stdin?.write(stdinContent);
|
|
311
321
|
proc.stdin?.end();
|
|
312
322
|
proc.stdout?.on("data", (data) => {
|
|
313
|
-
const text = data.toString();
|
|
314
|
-
|
|
323
|
+
const text = Buffer.isBuffer(data) ? data.toString() : String(data);
|
|
324
|
+
stdoutChunks.push(text);
|
|
315
325
|
if (verbose)
|
|
316
326
|
process.stdout.write(text);
|
|
317
327
|
});
|
|
318
328
|
proc.stderr?.on("data", (data) => {
|
|
319
|
-
const text = data.toString();
|
|
320
|
-
|
|
329
|
+
const text = Buffer.isBuffer(data) ? data.toString() : String(data);
|
|
330
|
+
stderrChunks.push(text);
|
|
321
331
|
if (verbose)
|
|
322
332
|
process.stderr.write(text);
|
|
323
333
|
});
|
|
324
334
|
proc.on("close", (code) => {
|
|
325
|
-
resolve({
|
|
335
|
+
resolve({
|
|
336
|
+
stdout: stdoutChunks.join(""),
|
|
337
|
+
stderr: stderrChunks.join(""),
|
|
338
|
+
exitCode: code ?? 1
|
|
339
|
+
});
|
|
326
340
|
});
|
|
327
|
-
proc.on("error", () => {
|
|
328
|
-
|
|
341
|
+
proc.on("error", (err) => {
|
|
342
|
+
stderrChunks.push(err.message);
|
|
343
|
+
resolve({
|
|
344
|
+
stdout: stdoutChunks.join(""),
|
|
345
|
+
stderr: stderrChunks.join(""),
|
|
346
|
+
exitCode: 1
|
|
347
|
+
});
|
|
329
348
|
});
|
|
330
349
|
});
|
|
331
350
|
}
|
|
@@ -445,7 +464,7 @@ async function editSecureNote(options) {
|
|
|
445
464
|
};
|
|
446
465
|
} catch (error) {
|
|
447
466
|
const message = error instanceof Error ? error.message : String(error);
|
|
448
|
-
throw errors.
|
|
467
|
+
throw errors.itemEditFailed(message);
|
|
449
468
|
}
|
|
450
469
|
}
|
|
451
470
|
|
|
@@ -606,18 +625,21 @@ ${pc2.bold(pc2.underline(title))}`);
|
|
|
606
625
|
}
|
|
607
626
|
};
|
|
608
627
|
|
|
609
|
-
// src/
|
|
628
|
+
// src/utils/timing.ts
|
|
629
|
+
import { setTimeout } from "node:timers/promises";
|
|
610
630
|
var MIN_SPINNER_TIME = 500;
|
|
611
631
|
async function withMinTime(promise, minTime = MIN_SPINNER_TIME) {
|
|
612
632
|
const [result] = await Promise.all([promise, setTimeout(minTime)]);
|
|
613
633
|
return result;
|
|
614
634
|
}
|
|
635
|
+
|
|
636
|
+
// src/commands/convert.ts
|
|
615
637
|
async function runConvert(options) {
|
|
616
638
|
const { envFile, vault, itemName, output, dryRun, secret, force, verbose } = options;
|
|
617
639
|
const pkg2 = await Promise.resolve().then(() => __toESM(require_package(), 1));
|
|
618
640
|
logger.intro("env2op", pkg2.version, dryRun);
|
|
619
641
|
try {
|
|
620
|
-
const parseResult = parseEnvFile(envFile);
|
|
642
|
+
const parseResult = await parseEnvFile(envFile);
|
|
621
643
|
validateParseResult(parseResult, envFile);
|
|
622
644
|
const { variables, lines } = parseResult;
|
|
623
645
|
const varCount = variables.length;
|
|
@@ -728,7 +750,7 @@ async function runConvert(options) {
|
|
|
728
750
|
}
|
|
729
751
|
pushSpinner.stop(existingItemId ? `Updated "${itemResult.title}" in vault "${itemResult.vault}"` : `Created "${itemResult.title}" in vault "${itemResult.vault}"`);
|
|
730
752
|
} catch (error) {
|
|
731
|
-
pushSpinner.stop(
|
|
753
|
+
pushSpinner.stop();
|
|
732
754
|
throw error;
|
|
733
755
|
}
|
|
734
756
|
}
|
|
@@ -766,6 +788,274 @@ async function runConvert(options) {
|
|
|
766
788
|
}
|
|
767
789
|
}
|
|
768
790
|
|
|
791
|
+
// src/commands/update.ts
|
|
792
|
+
import * as p4 from "@clack/prompts";
|
|
793
|
+
import pc4 from "picocolors";
|
|
794
|
+
|
|
795
|
+
// src/lib/package-manager.ts
|
|
796
|
+
var UPDATE_COMMANDS = {
|
|
797
|
+
homebrew: "brew upgrade tolgamorf/tap/env2op-cli",
|
|
798
|
+
npm: "npm update -g @tolgamorf/env2op-cli",
|
|
799
|
+
bun: "bun update -g @tolgamorf/env2op-cli",
|
|
800
|
+
pnpm: "pnpm update -g @tolgamorf/env2op-cli",
|
|
801
|
+
unknown: "npm update -g @tolgamorf/env2op-cli"
|
|
802
|
+
};
|
|
803
|
+
var DISPLAY_NAMES = {
|
|
804
|
+
homebrew: "Homebrew",
|
|
805
|
+
npm: "npm",
|
|
806
|
+
bun: "Bun",
|
|
807
|
+
pnpm: "pnpm",
|
|
808
|
+
unknown: "npm (default)"
|
|
809
|
+
};
|
|
810
|
+
function detectFromPath() {
|
|
811
|
+
const binPath = process.argv[1] ?? "";
|
|
812
|
+
if (binPath.includes("/Cellar/") || binPath.includes("/homebrew/") || binPath.includes("/opt/homebrew/") || binPath.includes("/home/linuxbrew/")) {
|
|
813
|
+
return "homebrew";
|
|
814
|
+
}
|
|
815
|
+
if (binPath.includes("/.bun/")) {
|
|
816
|
+
return "bun";
|
|
817
|
+
}
|
|
818
|
+
if (binPath.includes("/pnpm/") || binPath.includes("/.pnpm/")) {
|
|
819
|
+
return "pnpm";
|
|
820
|
+
}
|
|
821
|
+
if (binPath.includes("/node_modules/")) {
|
|
822
|
+
return "npm";
|
|
823
|
+
}
|
|
824
|
+
return null;
|
|
825
|
+
}
|
|
826
|
+
async function detectFromCommands() {
|
|
827
|
+
const brewResult = await exec("brew", ["list", "env2op-cli"], { verbose: false });
|
|
828
|
+
if (brewResult.exitCode === 0) {
|
|
829
|
+
return "homebrew";
|
|
830
|
+
}
|
|
831
|
+
return "npm";
|
|
832
|
+
}
|
|
833
|
+
async function detectPackageManager() {
|
|
834
|
+
const fromPath = detectFromPath();
|
|
835
|
+
if (fromPath) {
|
|
836
|
+
return {
|
|
837
|
+
type: fromPath,
|
|
838
|
+
updateCommand: UPDATE_COMMANDS[fromPath],
|
|
839
|
+
displayName: DISPLAY_NAMES[fromPath]
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
const fromCommands = await detectFromCommands();
|
|
843
|
+
return {
|
|
844
|
+
type: fromCommands,
|
|
845
|
+
updateCommand: UPDATE_COMMANDS[fromCommands],
|
|
846
|
+
displayName: DISPLAY_NAMES[fromCommands]
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// src/lib/update.ts
|
|
851
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
852
|
+
import { homedir } from "node:os";
|
|
853
|
+
import { join as join2 } from "node:path";
|
|
854
|
+
var CACHE_DIR = join2(homedir(), ".env2op");
|
|
855
|
+
var CACHE_FILE = join2(CACHE_DIR, "update-check.json");
|
|
856
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
857
|
+
function getCliVersion() {
|
|
858
|
+
try {
|
|
859
|
+
const pkg2 = require_package();
|
|
860
|
+
return pkg2.version ?? "0.0.0";
|
|
861
|
+
} catch {
|
|
862
|
+
return "0.0.0";
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
function loadCache() {
|
|
866
|
+
try {
|
|
867
|
+
if (existsSync(CACHE_FILE)) {
|
|
868
|
+
const content = readFileSync(CACHE_FILE, "utf-8");
|
|
869
|
+
return JSON.parse(content);
|
|
870
|
+
}
|
|
871
|
+
} catch {}
|
|
872
|
+
return { lastCheck: 0, latestVersion: null };
|
|
873
|
+
}
|
|
874
|
+
function saveCache(cache) {
|
|
875
|
+
try {
|
|
876
|
+
if (!existsSync(CACHE_DIR)) {
|
|
877
|
+
mkdirSync(CACHE_DIR, { recursive: true });
|
|
878
|
+
}
|
|
879
|
+
writeFileSync2(CACHE_FILE, JSON.stringify(cache, null, 2));
|
|
880
|
+
} catch {}
|
|
881
|
+
}
|
|
882
|
+
function shouldCheckForUpdate(cache) {
|
|
883
|
+
const now = Date.now();
|
|
884
|
+
return now - cache.lastCheck > CHECK_INTERVAL_MS;
|
|
885
|
+
}
|
|
886
|
+
async function fetchLatestVersion() {
|
|
887
|
+
try {
|
|
888
|
+
const response = await fetch("https://registry.npmjs.org/@tolgamorf/env2op-cli/latest");
|
|
889
|
+
if (!response.ok)
|
|
890
|
+
return null;
|
|
891
|
+
const data = await response.json();
|
|
892
|
+
return data.version ?? null;
|
|
893
|
+
} catch {
|
|
894
|
+
return null;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
function compareVersions(v1, v2) {
|
|
898
|
+
const parts1 = v1.split(".").map(Number);
|
|
899
|
+
const parts2 = v2.split(".").map(Number);
|
|
900
|
+
for (let i = 0;i < 3; i++) {
|
|
901
|
+
const p1 = parts1[i] || 0;
|
|
902
|
+
const p22 = parts2[i] || 0;
|
|
903
|
+
if (p1 < p22)
|
|
904
|
+
return -1;
|
|
905
|
+
if (p1 > p22)
|
|
906
|
+
return 1;
|
|
907
|
+
}
|
|
908
|
+
return 0;
|
|
909
|
+
}
|
|
910
|
+
async function checkForUpdate(forceCheck = false) {
|
|
911
|
+
const currentVersion = getCliVersion();
|
|
912
|
+
const cache = loadCache();
|
|
913
|
+
if (!forceCheck && !shouldCheckForUpdate(cache) && cache.latestVersion) {
|
|
914
|
+
const updateAvailable2 = compareVersions(currentVersion, cache.latestVersion) < 0;
|
|
915
|
+
const isSkipped2 = cache.skipVersion === cache.latestVersion;
|
|
916
|
+
return {
|
|
917
|
+
currentVersion,
|
|
918
|
+
latestVersion: cache.latestVersion,
|
|
919
|
+
updateAvailable: updateAvailable2,
|
|
920
|
+
isSkipped: isSkipped2,
|
|
921
|
+
fromCache: true
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
const latestVersion = await fetchLatestVersion();
|
|
925
|
+
saveCache({
|
|
926
|
+
...cache,
|
|
927
|
+
lastCheck: Date.now(),
|
|
928
|
+
latestVersion
|
|
929
|
+
});
|
|
930
|
+
if (!latestVersion) {
|
|
931
|
+
return {
|
|
932
|
+
currentVersion,
|
|
933
|
+
latestVersion: null,
|
|
934
|
+
updateAvailable: false,
|
|
935
|
+
isSkipped: false,
|
|
936
|
+
fromCache: false
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
const updateAvailable = compareVersions(currentVersion, latestVersion) < 0;
|
|
940
|
+
const isSkipped = cache.skipVersion === latestVersion;
|
|
941
|
+
return {
|
|
942
|
+
currentVersion,
|
|
943
|
+
latestVersion,
|
|
944
|
+
updateAvailable,
|
|
945
|
+
isSkipped,
|
|
946
|
+
fromCache: false
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
async function performUpdate(pm) {
|
|
950
|
+
const packageManager = pm ?? await detectPackageManager();
|
|
951
|
+
try {
|
|
952
|
+
const [command, ...args] = packageManager.updateCommand.split(" ");
|
|
953
|
+
const result = await exec(command, args, { verbose: false });
|
|
954
|
+
if (result.exitCode !== 0) {
|
|
955
|
+
return {
|
|
956
|
+
success: false,
|
|
957
|
+
error: result.stderr || `Command exited with code ${result.exitCode}`
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
return { success: true };
|
|
961
|
+
} catch (error) {
|
|
962
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
963
|
+
return { success: false, error: message };
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
function skipVersion(version) {
|
|
967
|
+
const cache = loadCache();
|
|
968
|
+
cache.skipVersion = version;
|
|
969
|
+
saveCache(cache);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// src/lib/update-prompts.ts
|
|
973
|
+
import * as p3 from "@clack/prompts";
|
|
974
|
+
import pc3 from "picocolors";
|
|
975
|
+
var S_BAR_START = "┌";
|
|
976
|
+
var S_BAR_END = "└";
|
|
977
|
+
function showUpdateNotification(result, cliName = "env2op") {
|
|
978
|
+
console.log();
|
|
979
|
+
console.log(`${pc3.gray(S_BAR_START)}${pc3.gray("─")} ${pc3.yellow("Update available:")} ${pc3.dim(result.currentVersion)} ${pc3.dim("→")} ${pc3.green(result.latestVersion)}`);
|
|
980
|
+
console.log(`${pc3.gray(S_BAR_END)}${pc3.gray("─")} Run ${pc3.cyan(`'${cliName} update'`)} to update`);
|
|
981
|
+
}
|
|
982
|
+
async function askToUpdate(result) {
|
|
983
|
+
const response = await p3.select({
|
|
984
|
+
message: "Would you like to update?",
|
|
985
|
+
options: [
|
|
986
|
+
{ value: "update", label: "Update now", hint: "Download and install the latest version" },
|
|
987
|
+
{ value: "later", label: "Remind me later", hint: "Ask again next time" },
|
|
988
|
+
{ value: "skip", label: "Skip this version", hint: `Don't ask about ${result.latestVersion} again` }
|
|
989
|
+
]
|
|
990
|
+
});
|
|
991
|
+
if (p3.isCancel(response)) {
|
|
992
|
+
return "later";
|
|
993
|
+
}
|
|
994
|
+
return response;
|
|
995
|
+
}
|
|
996
|
+
function showUpdateAvailable(result) {
|
|
997
|
+
p3.log.success(`Update available: ${pc3.dim(result.currentVersion)} ${pc3.dim("→")} ${pc3.green(result.latestVersion)}`);
|
|
998
|
+
}
|
|
999
|
+
function showUpToDate(currentVersion) {
|
|
1000
|
+
p3.log.success(`You're on the latest version ${pc3.green(`(${currentVersion})`)}`);
|
|
1001
|
+
}
|
|
1002
|
+
function showPackageManagerInfo(pm) {
|
|
1003
|
+
console.log();
|
|
1004
|
+
console.log(` ${pc3.dim("Detected:")} ${pm.displayName} installation`);
|
|
1005
|
+
console.log(` ${pc3.dim("Command:")} ${pc3.cyan(pm.updateCommand)}`);
|
|
1006
|
+
console.log();
|
|
1007
|
+
}
|
|
1008
|
+
function showUpdateSuccess(newVersion) {
|
|
1009
|
+
p3.log.success(`Updated to version ${pc3.green(newVersion)}`);
|
|
1010
|
+
p3.log.info("Please restart to use the new version.");
|
|
1011
|
+
}
|
|
1012
|
+
function showUpdateError(error, pm) {
|
|
1013
|
+
if (error) {
|
|
1014
|
+
p3.log.error(pc3.dim(error));
|
|
1015
|
+
}
|
|
1016
|
+
p3.log.info(`Try running manually: ${pc3.cyan(pm.updateCommand)}`);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// src/commands/update.ts
|
|
1020
|
+
async function runUpdate(options) {
|
|
1021
|
+
const { force = false, cliName = "env2op" } = options;
|
|
1022
|
+
p4.intro(pc4.bgCyan(pc4.black(` ${cliName} update `)));
|
|
1023
|
+
const spinner4 = p4.spinner();
|
|
1024
|
+
spinner4.start("Checking for updates...");
|
|
1025
|
+
const result = await checkForUpdate(true);
|
|
1026
|
+
spinner4.stop("Checked for updates");
|
|
1027
|
+
if (!result.updateAvailable || !result.latestVersion) {
|
|
1028
|
+
showUpToDate(result.currentVersion);
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
showUpdateAvailable(result);
|
|
1032
|
+
const pm = await detectPackageManager();
|
|
1033
|
+
showPackageManagerInfo(pm);
|
|
1034
|
+
if (!force) {
|
|
1035
|
+
const choice = await askToUpdate(result);
|
|
1036
|
+
if (choice === "skip") {
|
|
1037
|
+
skipVersion(result.latestVersion);
|
|
1038
|
+
p4.log.info(`Skipped version ${result.latestVersion}`);
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
if (choice === "later") {
|
|
1042
|
+
p4.log.info("Update postponed");
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
const updateSpinner = p4.spinner();
|
|
1047
|
+
updateSpinner.start(`Updating to ${result.latestVersion}...`);
|
|
1048
|
+
const updateResult = await performUpdate(pm);
|
|
1049
|
+
if (updateResult.success) {
|
|
1050
|
+
updateSpinner.stop("Update completed");
|
|
1051
|
+
showUpdateSuccess(result.latestVersion);
|
|
1052
|
+
} else {
|
|
1053
|
+
updateSpinner.stop("Update failed");
|
|
1054
|
+
showUpdateError(updateResult.error, pm);
|
|
1055
|
+
process.exit(1);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
769
1059
|
// src/cli.ts
|
|
770
1060
|
var pkg2 = await Promise.resolve().then(() => __toESM(require_package(), 1));
|
|
771
1061
|
var args = process.argv.slice(2);
|
|
@@ -792,10 +1082,19 @@ for (let i = 0;i < args.length; i++) {
|
|
|
792
1082
|
}
|
|
793
1083
|
var hasHelp = flags.has("h") || flags.has("help");
|
|
794
1084
|
var hasVersion = flags.has("v") || flags.has("version");
|
|
1085
|
+
var hasUpdate = flags.has("update");
|
|
795
1086
|
if (hasVersion) {
|
|
796
1087
|
console.log(pkg2.version);
|
|
797
1088
|
process.exit(0);
|
|
798
1089
|
}
|
|
1090
|
+
if (hasUpdate) {
|
|
1091
|
+
await runUpdate({
|
|
1092
|
+
force: flags.has("f") || flags.has("force"),
|
|
1093
|
+
verbose: flags.has("verbose"),
|
|
1094
|
+
cliName: "env2op"
|
|
1095
|
+
});
|
|
1096
|
+
process.exit(0);
|
|
1097
|
+
}
|
|
799
1098
|
if (hasHelp || positional.length === 0) {
|
|
800
1099
|
showHelp();
|
|
801
1100
|
process.exit(0);
|
|
@@ -815,48 +1114,55 @@ await runConvert({
|
|
|
815
1114
|
force: flags.has("f") || flags.has("force"),
|
|
816
1115
|
verbose: flags.has("verbose")
|
|
817
1116
|
});
|
|
1117
|
+
try {
|
|
1118
|
+
const updateResult = await checkForUpdate();
|
|
1119
|
+
if (updateResult.updateAvailable && !updateResult.isSkipped) {
|
|
1120
|
+
showUpdateNotification(updateResult, "env2op");
|
|
1121
|
+
}
|
|
1122
|
+
} catch {}
|
|
818
1123
|
function showHelp() {
|
|
819
|
-
const name =
|
|
820
|
-
const version =
|
|
1124
|
+
const name = pc5.bold(pc5.cyan("env2op"));
|
|
1125
|
+
const version = pc5.dim(`v${pkg2.version}`);
|
|
821
1126
|
console.log(`
|
|
822
1127
|
${name} ${version}
|
|
823
1128
|
${pkg2.description}
|
|
824
1129
|
|
|
825
|
-
${
|
|
826
|
-
${
|
|
1130
|
+
${pc5.bold("USAGE")}
|
|
1131
|
+
${pc5.cyan("$")} env2op ${pc5.yellow("<env_file>")} ${pc5.yellow("<vault>")} ${pc5.yellow("<item_name>")} ${pc5.dim("[options]")}
|
|
827
1132
|
|
|
828
|
-
${
|
|
829
|
-
${
|
|
830
|
-
${
|
|
831
|
-
${
|
|
1133
|
+
${pc5.bold("ARGUMENTS")}
|
|
1134
|
+
${pc5.yellow("env_file")} Path to .env file
|
|
1135
|
+
${pc5.yellow("vault")} 1Password vault name
|
|
1136
|
+
${pc5.yellow("item_name")} Name for the Secure Note in 1Password
|
|
832
1137
|
|
|
833
|
-
${
|
|
834
|
-
${
|
|
835
|
-
${
|
|
836
|
-
${
|
|
837
|
-
${
|
|
838
|
-
${
|
|
839
|
-
${
|
|
840
|
-
${
|
|
1138
|
+
${pc5.bold("OPTIONS")}
|
|
1139
|
+
${pc5.cyan("-o, --output")} Output template path (default: <env_file>.tpl)
|
|
1140
|
+
${pc5.cyan("-f, --force")} Skip confirmation prompts
|
|
1141
|
+
${pc5.cyan(" --dry-run")} Preview actions without executing
|
|
1142
|
+
${pc5.cyan(" --secret")} Store all fields as password type (hidden)
|
|
1143
|
+
${pc5.cyan(" --verbose")} Show op CLI output
|
|
1144
|
+
${pc5.cyan(" --update")} Check for and install updates
|
|
1145
|
+
${pc5.cyan("-v, --version")} Show version
|
|
1146
|
+
${pc5.cyan("-h, --help")} Show this help message
|
|
841
1147
|
|
|
842
|
-
${
|
|
843
|
-
${
|
|
844
|
-
${
|
|
1148
|
+
${pc5.bold("EXAMPLES")}
|
|
1149
|
+
${pc5.dim("# Basic usage")}
|
|
1150
|
+
${pc5.cyan("$")} env2op .env.production Personal "MyApp - Production"
|
|
845
1151
|
|
|
846
|
-
${
|
|
847
|
-
${
|
|
1152
|
+
${pc5.dim("# Custom output path")}
|
|
1153
|
+
${pc5.cyan("$")} env2op .env Personal "MyApp" -o secrets.tpl
|
|
848
1154
|
|
|
849
|
-
${
|
|
850
|
-
${
|
|
1155
|
+
${pc5.dim("# Preview without making changes")}
|
|
1156
|
+
${pc5.cyan("$")} env2op .env Personal "MyApp" --dry-run
|
|
851
1157
|
|
|
852
|
-
${
|
|
853
|
-
${
|
|
1158
|
+
${pc5.dim("# Store as hidden password fields")}
|
|
1159
|
+
${pc5.cyan("$")} env2op .env Personal "MyApp" --secret
|
|
854
1160
|
|
|
855
|
-
${
|
|
856
|
-
${
|
|
1161
|
+
${pc5.dim("# Skip confirmation prompts (for CI/scripts)")}
|
|
1162
|
+
${pc5.cyan("$")} env2op .env Personal "MyApp" -f
|
|
857
1163
|
|
|
858
|
-
${
|
|
859
|
-
${
|
|
1164
|
+
${pc5.bold("DOCUMENTATION")}
|
|
1165
|
+
${pc5.dim("https://github.com/tolgamorf/env2op-cli")}
|
|
860
1166
|
`);
|
|
861
1167
|
}
|
|
862
1168
|
function showMissingArgsError(provided) {
|
|
@@ -868,17 +1174,17 @@ function showMissingArgsError(provided) {
|
|
|
868
1174
|
if (provided.length < 3)
|
|
869
1175
|
missing.push("item_name");
|
|
870
1176
|
console.log(`
|
|
871
|
-
${
|
|
1177
|
+
${pc5.red(pc5.bold("Error:"))} Missing required arguments
|
|
872
1178
|
|
|
873
|
-
${
|
|
1179
|
+
${pc5.bold("Usage:")} env2op ${pc5.yellow("<env_file>")} ${pc5.yellow("<vault>")} ${pc5.yellow("<item_name>")} ${pc5.dim("[options]")}
|
|
874
1180
|
|
|
875
|
-
${
|
|
876
|
-
${missing.map((arg) => ` ${
|
|
1181
|
+
${pc5.bold("Missing:")}
|
|
1182
|
+
${missing.map((arg) => ` ${pc5.red("•")} ${pc5.yellow(arg)}`).join(`
|
|
877
1183
|
`)}
|
|
878
1184
|
|
|
879
|
-
${
|
|
880
|
-
${
|
|
1185
|
+
${pc5.bold("Example:")}
|
|
1186
|
+
${pc5.cyan("$")} env2op .env.production Personal "MyApp - Production"
|
|
881
1187
|
|
|
882
|
-
Run ${
|
|
1188
|
+
Run ${pc5.cyan("env2op --help")} for more information.
|
|
883
1189
|
`);
|
|
884
1190
|
}
|