@tolgamorf/env2op-cli 0.1.5 → 0.2.1
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 +19 -3
- package/dist/cli.js +489 -393
- package/dist/index.d.ts +222 -0
- package/dist/index.js +521 -0
- package/dist/op2env-cli.js +551 -258
- package/package.json +3 -3
package/dist/op2env-cli.js
CHANGED
|
@@ -1,140 +1,161 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
2
3
|
var __create = Object.create;
|
|
4
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
3
5
|
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var
|
|
9
|
-
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
9
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
|
+
for (let key of __getOwnPropNames(mod))
|
|
12
|
+
if (!__hasOwnProp.call(to, key))
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: () => mod[key],
|
|
15
|
+
enumerable: true
|
|
16
|
+
});
|
|
17
17
|
return to;
|
|
18
18
|
};
|
|
19
|
-
var
|
|
20
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
-
mod
|
|
26
|
-
));
|
|
19
|
+
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
27
20
|
|
|
28
21
|
// package.json
|
|
29
|
-
var require_package = __commonJS({
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"."
|
|
41
|
-
|
|
42
|
-
types: "./dist/index.d.ts"
|
|
43
|
-
}
|
|
44
|
-
},
|
|
45
|
-
bin: {
|
|
46
|
-
env2op: "dist/cli.js",
|
|
47
|
-
op2env: "dist/op2env-cli.js"
|
|
48
|
-
},
|
|
49
|
-
files: [
|
|
50
|
-
"dist",
|
|
51
|
-
"LICENSE",
|
|
52
|
-
"README.md"
|
|
53
|
-
],
|
|
54
|
-
scripts: {
|
|
55
|
-
dev: "bun run src/cli.ts",
|
|
56
|
-
build: "tsup",
|
|
57
|
-
test: "bun test",
|
|
58
|
-
"test:watch": "bun test --watch",
|
|
59
|
-
"test:coverage": "bun test --coverage",
|
|
60
|
-
typecheck: "tsc --noEmit",
|
|
61
|
-
lint: "bunx biome check .",
|
|
62
|
-
"lint:fix": "bunx biome check . --write",
|
|
63
|
-
format: "bunx biome format --write .",
|
|
64
|
-
"format:check": "bunx biome format .",
|
|
65
|
-
prepublishOnly: "bun run build",
|
|
66
|
-
release: "bun run scripts/release.ts"
|
|
67
|
-
},
|
|
68
|
-
keywords: [
|
|
69
|
-
"env",
|
|
70
|
-
"1password",
|
|
71
|
-
"op",
|
|
72
|
-
"cli",
|
|
73
|
-
"secrets",
|
|
74
|
-
"environment-variables",
|
|
75
|
-
"dotenv",
|
|
76
|
-
"secure-notes",
|
|
77
|
-
"bun",
|
|
78
|
-
"op-inject",
|
|
79
|
-
"op-run",
|
|
80
|
-
"template"
|
|
81
|
-
],
|
|
82
|
-
author: {
|
|
83
|
-
name: "Tolga O.",
|
|
84
|
-
url: "https://github.com/tolgamorf"
|
|
85
|
-
},
|
|
86
|
-
license: "MIT",
|
|
87
|
-
repository: {
|
|
88
|
-
type: "git",
|
|
89
|
-
url: "git+https://github.com/tolgamorf/env2op-cli.git"
|
|
90
|
-
},
|
|
91
|
-
bugs: {
|
|
92
|
-
url: "https://github.com/tolgamorf/env2op-cli/issues"
|
|
93
|
-
},
|
|
94
|
-
homepage: "https://github.com/tolgamorf/env2op-cli#readme",
|
|
95
|
-
engines: {
|
|
96
|
-
node: ">=18.0.0"
|
|
97
|
-
},
|
|
98
|
-
dependencies: {
|
|
99
|
-
"@clack/prompts": "^0.11.0",
|
|
100
|
-
picocolors: "^1.1.1"
|
|
101
|
-
},
|
|
102
|
-
devDependencies: {
|
|
103
|
-
"@biomejs/biome": "^2.3.10",
|
|
104
|
-
"@tsconfig/bun": "^1.0.10",
|
|
105
|
-
"@types/bun": "^1.3.5",
|
|
106
|
-
tsup: "^8.5.1",
|
|
107
|
-
typescript: "^5.9.3"
|
|
22
|
+
var require_package = __commonJS((exports, module) => {
|
|
23
|
+
module.exports = {
|
|
24
|
+
name: "@tolgamorf/env2op-cli",
|
|
25
|
+
version: "0.2.1",
|
|
26
|
+
description: "Convert .env files to 1Password Secure Notes and generate templates for op inject/run",
|
|
27
|
+
type: "module",
|
|
28
|
+
main: "dist/index.js",
|
|
29
|
+
module: "dist/index.js",
|
|
30
|
+
types: "dist/index.d.ts",
|
|
31
|
+
exports: {
|
|
32
|
+
".": {
|
|
33
|
+
import: "./dist/index.js",
|
|
34
|
+
types: "./dist/index.d.ts"
|
|
108
35
|
}
|
|
109
|
-
}
|
|
110
|
-
|
|
36
|
+
},
|
|
37
|
+
bin: {
|
|
38
|
+
env2op: "dist/cli.js",
|
|
39
|
+
op2env: "dist/op2env-cli.js"
|
|
40
|
+
},
|
|
41
|
+
files: [
|
|
42
|
+
"dist",
|
|
43
|
+
"LICENSE",
|
|
44
|
+
"README.md"
|
|
45
|
+
],
|
|
46
|
+
scripts: {
|
|
47
|
+
dev: "bun run src/cli.ts",
|
|
48
|
+
build: "bunup",
|
|
49
|
+
test: "bun test",
|
|
50
|
+
"test:watch": "bun test --watch",
|
|
51
|
+
"test:coverage": "bun test --coverage",
|
|
52
|
+
typecheck: "tsc --noEmit",
|
|
53
|
+
lint: "bunx biome check .",
|
|
54
|
+
"lint:fix": "bunx biome check . --write",
|
|
55
|
+
format: "bunx biome format --write .",
|
|
56
|
+
"format:check": "bunx biome format .",
|
|
57
|
+
prepublishOnly: "bun run build",
|
|
58
|
+
release: "bun run scripts/release.ts"
|
|
59
|
+
},
|
|
60
|
+
keywords: [
|
|
61
|
+
"env",
|
|
62
|
+
"1password",
|
|
63
|
+
"op",
|
|
64
|
+
"cli",
|
|
65
|
+
"secrets",
|
|
66
|
+
"environment-variables",
|
|
67
|
+
"dotenv",
|
|
68
|
+
"secure-notes",
|
|
69
|
+
"bun",
|
|
70
|
+
"op-inject",
|
|
71
|
+
"op-run",
|
|
72
|
+
"template"
|
|
73
|
+
],
|
|
74
|
+
author: {
|
|
75
|
+
name: "Tolga O.",
|
|
76
|
+
url: "https://github.com/tolgamorf"
|
|
77
|
+
},
|
|
78
|
+
license: "MIT",
|
|
79
|
+
repository: {
|
|
80
|
+
type: "git",
|
|
81
|
+
url: "git+https://github.com/tolgamorf/env2op-cli.git"
|
|
82
|
+
},
|
|
83
|
+
bugs: {
|
|
84
|
+
url: "https://github.com/tolgamorf/env2op-cli/issues"
|
|
85
|
+
},
|
|
86
|
+
homepage: "https://github.com/tolgamorf/env2op-cli#readme",
|
|
87
|
+
engines: {
|
|
88
|
+
node: ">=18.0.0"
|
|
89
|
+
},
|
|
90
|
+
dependencies: {
|
|
91
|
+
"@clack/prompts": "^0.11.0",
|
|
92
|
+
picocolors: "^1.1.1"
|
|
93
|
+
},
|
|
94
|
+
devDependencies: {
|
|
95
|
+
"@biomejs/biome": "^2.3.10",
|
|
96
|
+
"@tsconfig/bun": "^1.0.10",
|
|
97
|
+
"@types/bun": "^1.3.5",
|
|
98
|
+
bunup: "^0.16.17",
|
|
99
|
+
typescript: "^5.9.3"
|
|
100
|
+
}
|
|
101
|
+
};
|
|
111
102
|
});
|
|
112
103
|
|
|
113
104
|
// src/op2env-cli.ts
|
|
114
|
-
import
|
|
105
|
+
import pc3 from "picocolors";
|
|
115
106
|
|
|
116
107
|
// src/commands/inject.ts
|
|
117
|
-
import { existsSync
|
|
118
|
-
import { basename } from "path";
|
|
108
|
+
import { existsSync, readFileSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
109
|
+
import { basename } from "node:path";
|
|
119
110
|
import * as p2 from "@clack/prompts";
|
|
120
111
|
|
|
121
112
|
// src/core/env-parser.ts
|
|
122
|
-
import {
|
|
113
|
+
import { readFile } from "node:fs/promises";
|
|
123
114
|
|
|
124
115
|
// src/utils/errors.ts
|
|
125
|
-
|
|
116
|
+
class Env2OpError extends Error {
|
|
117
|
+
code;
|
|
118
|
+
suggestion;
|
|
126
119
|
constructor(message, code, suggestion) {
|
|
127
120
|
super(message);
|
|
128
121
|
this.code = code;
|
|
129
122
|
this.suggestion = suggestion;
|
|
130
123
|
this.name = "Env2OpError";
|
|
131
124
|
}
|
|
125
|
+
}
|
|
126
|
+
var ErrorCodes = {
|
|
127
|
+
ENV_FILE_NOT_FOUND: "ENV_FILE_NOT_FOUND",
|
|
128
|
+
ENV_FILE_EMPTY: "ENV_FILE_EMPTY",
|
|
129
|
+
OP_CLI_NOT_INSTALLED: "OP_CLI_NOT_INSTALLED",
|
|
130
|
+
OP_NOT_SIGNED_IN: "OP_NOT_SIGNED_IN",
|
|
131
|
+
OP_SIGNIN_FAILED: "OP_SIGNIN_FAILED",
|
|
132
|
+
VAULT_NOT_FOUND: "VAULT_NOT_FOUND",
|
|
133
|
+
VAULT_CREATE_FAILED: "VAULT_CREATE_FAILED",
|
|
134
|
+
ITEM_EXISTS: "ITEM_EXISTS",
|
|
135
|
+
ITEM_CREATE_FAILED: "ITEM_CREATE_FAILED",
|
|
136
|
+
ITEM_EDIT_FAILED: "ITEM_EDIT_FAILED",
|
|
137
|
+
PARSE_ERROR: "PARSE_ERROR",
|
|
138
|
+
TEMPLATE_NOT_FOUND: "TEMPLATE_NOT_FOUND",
|
|
139
|
+
INJECT_FAILED: "INJECT_FAILED"
|
|
140
|
+
};
|
|
141
|
+
var errors = {
|
|
142
|
+
envFileNotFound: (path) => new Env2OpError(`File not found: ${path}`, ErrorCodes.ENV_FILE_NOT_FOUND, "Check that the file path is correct"),
|
|
143
|
+
envFileEmpty: (path) => new Env2OpError(`No valid environment variables found in ${path}`, ErrorCodes.ENV_FILE_EMPTY, "Ensure the file contains KEY=value pairs"),
|
|
144
|
+
opCliNotInstalled: () => new Env2OpError("1Password CLI (op) is not installed", ErrorCodes.OP_CLI_NOT_INSTALLED, "Install it from https://1password.com/downloads/command-line/"),
|
|
145
|
+
opNotSignedIn: () => new Env2OpError("Not signed in to 1Password CLI", ErrorCodes.OP_NOT_SIGNED_IN, 'Run "op signin" to authenticate'),
|
|
146
|
+
vaultNotFound: (vault) => new Env2OpError(`Vault not found: ${vault}`, ErrorCodes.VAULT_NOT_FOUND, 'Run "op vault list" to see available vaults'),
|
|
147
|
+
vaultCreateFailed: (message) => new Env2OpError(`Failed to create vault: ${message}`, ErrorCodes.VAULT_CREATE_FAILED),
|
|
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
|
+
itemCreateFailed: (message) => new Env2OpError(`Failed to create 1Password item: ${message}`, ErrorCodes.ITEM_CREATE_FAILED),
|
|
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)
|
|
132
152
|
};
|
|
133
153
|
|
|
134
154
|
// src/core/env-parser.ts
|
|
135
155
|
var HEADER_SEPARATOR = "# ===========================================================================";
|
|
136
156
|
function stripHeaders(content) {
|
|
137
|
-
const lines = content.split(
|
|
157
|
+
const lines = content.split(`
|
|
158
|
+
`);
|
|
138
159
|
const result = [];
|
|
139
160
|
let inHeader = false;
|
|
140
161
|
for (const line of lines) {
|
|
@@ -154,69 +175,343 @@ function stripHeaders(content) {
|
|
|
154
175
|
while (result.length > 0 && result[0]?.trim() === "") {
|
|
155
176
|
result.shift();
|
|
156
177
|
}
|
|
157
|
-
return result.join(
|
|
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
|
+
}
|
|
158
255
|
}
|
|
159
256
|
|
|
160
257
|
// src/utils/shell.ts
|
|
161
|
-
import { spawn } from "child_process";
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
258
|
+
import { spawn } from "node:child_process";
|
|
259
|
+
import pc from "picocolors";
|
|
260
|
+
function quoteArg(arg) {
|
|
261
|
+
if (/[ [\]'"\\=]/.test(arg)) {
|
|
262
|
+
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
263
|
+
}
|
|
264
|
+
return arg;
|
|
265
|
+
}
|
|
266
|
+
async function exec(command, args = [], options = {}) {
|
|
267
|
+
const { verbose = false } = options;
|
|
268
|
+
const fullCommand = `${command} ${args.map(quoteArg).join(" ")}`;
|
|
269
|
+
if (verbose) {
|
|
270
|
+
console.log(pc.dim(`$ ${fullCommand}`));
|
|
271
|
+
}
|
|
272
|
+
return new Promise((resolve) => {
|
|
273
|
+
const proc = spawn(command, args, {
|
|
274
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
167
275
|
});
|
|
168
|
-
|
|
169
|
-
|
|
276
|
+
const stdoutChunks = [];
|
|
277
|
+
const stderrChunks = [];
|
|
170
278
|
proc.stdout?.on("data", (data) => {
|
|
171
|
-
|
|
279
|
+
const text = Buffer.isBuffer(data) ? data.toString() : String(data);
|
|
280
|
+
stdoutChunks.push(text);
|
|
281
|
+
if (verbose) {
|
|
282
|
+
process.stdout.write(text);
|
|
283
|
+
}
|
|
172
284
|
});
|
|
173
285
|
proc.stderr?.on("data", (data) => {
|
|
174
|
-
|
|
286
|
+
const text = Buffer.isBuffer(data) ? data.toString() : String(data);
|
|
287
|
+
stderrChunks.push(text);
|
|
288
|
+
if (verbose) {
|
|
289
|
+
process.stderr.write(text);
|
|
290
|
+
}
|
|
175
291
|
});
|
|
176
|
-
proc.on("
|
|
177
|
-
|
|
292
|
+
proc.on("close", (code) => {
|
|
293
|
+
resolve({
|
|
294
|
+
stdout: stdoutChunks.join(""),
|
|
295
|
+
stderr: stderrChunks.join(""),
|
|
296
|
+
exitCode: code ?? 1
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
proc.on("error", (err) => {
|
|
300
|
+
stderrChunks.push(err.message);
|
|
301
|
+
resolve({
|
|
302
|
+
stdout: stdoutChunks.join(""),
|
|
303
|
+
stderr: stderrChunks.join(""),
|
|
304
|
+
exitCode: 1
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
async function execWithStdin(command, args = [], options) {
|
|
310
|
+
const { stdin: stdinContent, verbose = false } = options;
|
|
311
|
+
if (verbose) {
|
|
312
|
+
const fullCommand = `${command} ${args.map(quoteArg).join(" ")}`;
|
|
313
|
+
console.log(pc.dim(`$ echo '...' | ${fullCommand}`));
|
|
314
|
+
}
|
|
315
|
+
return new Promise((resolve) => {
|
|
316
|
+
const proc = spawn(command, args, {
|
|
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);
|
|
178
334
|
});
|
|
179
335
|
proc.on("close", (code) => {
|
|
180
336
|
resolve({
|
|
181
|
-
stdout,
|
|
182
|
-
stderr,
|
|
183
|
-
exitCode: code ??
|
|
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
|
|
184
348
|
});
|
|
185
349
|
});
|
|
186
350
|
});
|
|
187
351
|
}
|
|
188
|
-
|
|
352
|
+
|
|
353
|
+
// src/core/onepassword.ts
|
|
354
|
+
async function checkOpCli(options = {}) {
|
|
355
|
+
const result = await exec("op", ["--version"], options);
|
|
356
|
+
return result.exitCode === 0;
|
|
357
|
+
}
|
|
358
|
+
async function checkSignedIn(options = {}) {
|
|
359
|
+
const result = await exec("op", ["whoami", "--format", "json"], options);
|
|
360
|
+
return result.exitCode === 0;
|
|
361
|
+
}
|
|
362
|
+
async function signIn(options = {}) {
|
|
363
|
+
const result = await exec("op", ["signin"], options);
|
|
364
|
+
return result.exitCode === 0;
|
|
365
|
+
}
|
|
366
|
+
async function itemExists(vault, title, options = {}) {
|
|
367
|
+
const result = await exec("op", ["item", "list", "--vault", vault, "--format", "json"], options);
|
|
368
|
+
if (result.exitCode !== 0) {
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
try {
|
|
372
|
+
const items = JSON.parse(result.stdout);
|
|
373
|
+
const item = items.find((item2) => item2.title === title);
|
|
374
|
+
return item?.id ?? null;
|
|
375
|
+
} catch {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
async function vaultExists(vault, options = {}) {
|
|
380
|
+
const result = await exec("op", ["vault", "list", "--format", "json"], options);
|
|
381
|
+
if (result.exitCode !== 0) {
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
189
384
|
try {
|
|
190
|
-
const
|
|
191
|
-
return
|
|
385
|
+
const vaults = JSON.parse(result.stdout);
|
|
386
|
+
return vaults.some((v) => v.name === vault);
|
|
192
387
|
} catch {
|
|
193
388
|
return false;
|
|
194
389
|
}
|
|
195
390
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
391
|
+
async function createVault(name, options = {}) {
|
|
392
|
+
try {
|
|
393
|
+
await exec("op", ["vault", "create", name], options);
|
|
394
|
+
} catch (error) {
|
|
395
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
396
|
+
throw errors.vaultCreateFailed(message);
|
|
397
|
+
}
|
|
200
398
|
}
|
|
201
|
-
|
|
202
|
-
|
|
399
|
+
function buildItemTemplate(title, vault, fields, secret) {
|
|
400
|
+
const fieldType = secret ? "CONCEALED" : "STRING";
|
|
401
|
+
return {
|
|
402
|
+
title,
|
|
403
|
+
vault: { name: vault },
|
|
404
|
+
category: "SECURE_NOTE",
|
|
405
|
+
fields: fields.map(({ key, value }) => ({
|
|
406
|
+
type: fieldType,
|
|
407
|
+
label: key,
|
|
408
|
+
value
|
|
409
|
+
}))
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
async function createSecureNote(options) {
|
|
413
|
+
const { vault, title, fields, secret, verbose } = options;
|
|
414
|
+
const template = buildItemTemplate(title, vault, fields, secret);
|
|
415
|
+
const json = JSON.stringify(template);
|
|
416
|
+
try {
|
|
417
|
+
const result = await execWithStdin("op", ["item", "create", "--format", "json"], { stdin: json, verbose });
|
|
418
|
+
if (result.exitCode !== 0) {
|
|
419
|
+
throw new Error(result.stderr || "Failed to create item");
|
|
420
|
+
}
|
|
421
|
+
const item = JSON.parse(result.stdout);
|
|
422
|
+
const fieldIds = {};
|
|
423
|
+
for (const field of item.fields ?? []) {
|
|
424
|
+
if (field.label && field.id) {
|
|
425
|
+
fieldIds[field.label] = field.id;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return {
|
|
429
|
+
id: item.id,
|
|
430
|
+
title: item.title,
|
|
431
|
+
vault: item.vault?.name ?? vault,
|
|
432
|
+
vaultId: item.vault?.id ?? "",
|
|
433
|
+
fieldIds
|
|
434
|
+
};
|
|
435
|
+
} catch (error) {
|
|
436
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
437
|
+
throw errors.itemCreateFailed(message);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
async function editSecureNote(options) {
|
|
441
|
+
const { vault, title, fields, secret, verbose, itemId } = options;
|
|
442
|
+
const template = buildItemTemplate(title, vault, fields, secret);
|
|
443
|
+
const json = JSON.stringify(template);
|
|
444
|
+
try {
|
|
445
|
+
const result = await execWithStdin("op", ["item", "edit", itemId, "--format", "json"], {
|
|
446
|
+
stdin: json,
|
|
447
|
+
verbose
|
|
448
|
+
});
|
|
449
|
+
if (result.exitCode !== 0) {
|
|
450
|
+
throw new Error(result.stderr || "Failed to edit item");
|
|
451
|
+
}
|
|
452
|
+
const item = JSON.parse(result.stdout);
|
|
453
|
+
const fieldIds = {};
|
|
454
|
+
for (const field of item.fields ?? []) {
|
|
455
|
+
if (field.label && field.id) {
|
|
456
|
+
fieldIds[field.label] = field.id;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return {
|
|
460
|
+
id: item.id,
|
|
461
|
+
title: item.title,
|
|
462
|
+
vault: item.vault?.name ?? vault,
|
|
463
|
+
vaultId: item.vault?.id ?? "",
|
|
464
|
+
fieldIds
|
|
465
|
+
};
|
|
466
|
+
} catch (error) {
|
|
467
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
468
|
+
throw errors.itemEditFailed(message);
|
|
469
|
+
}
|
|
203
470
|
}
|
|
204
471
|
|
|
205
472
|
// src/core/template-generator.ts
|
|
206
|
-
var
|
|
207
|
-
import { writeFileSync } from "fs";
|
|
473
|
+
var import__package = __toESM(require_package(), 1);
|
|
474
|
+
import { writeFileSync } from "node:fs";
|
|
208
475
|
var SEPARATOR = "# ===========================================================================";
|
|
476
|
+
function deriveEnvFileName(templateFileName) {
|
|
477
|
+
if (templateFileName.endsWith(".tpl")) {
|
|
478
|
+
return templateFileName.slice(0, -4);
|
|
479
|
+
}
|
|
480
|
+
return templateFileName;
|
|
481
|
+
}
|
|
209
482
|
function deriveTemplateFileName(envFileName) {
|
|
210
483
|
return `${envFileName}.tpl`;
|
|
211
484
|
}
|
|
212
485
|
function formatTimestamp() {
|
|
213
|
-
return
|
|
486
|
+
return new Date().toISOString().replace("T", " ").replace(/\.\d{3}Z$/, " UTC");
|
|
487
|
+
}
|
|
488
|
+
function generateTemplateHeader(templateFileName) {
|
|
489
|
+
const envFileName = deriveEnvFileName(templateFileName);
|
|
490
|
+
return [
|
|
491
|
+
SEPARATOR,
|
|
492
|
+
`# ${templateFileName} — 1Password Secret References`,
|
|
493
|
+
"#",
|
|
494
|
+
"# This template contains references to secrets stored in 1Password.",
|
|
495
|
+
"# The actual values are not stored here — only secret references.",
|
|
496
|
+
"#",
|
|
497
|
+
`# To generate ${envFileName} with real values:`,
|
|
498
|
+
`# op2env ${templateFileName}`,
|
|
499
|
+
"#",
|
|
500
|
+
"# To run a command with secrets injected:",
|
|
501
|
+
`# op run --env-file ${templateFileName} -- npm start`,
|
|
502
|
+
"#",
|
|
503
|
+
`# Pushed: ${formatTimestamp()}`,
|
|
504
|
+
`# Generated by env2op v${import__package.default.version}`,
|
|
505
|
+
"# https://github.com/tolgamorf/env2op-cli",
|
|
506
|
+
SEPARATOR,
|
|
507
|
+
""
|
|
508
|
+
];
|
|
214
509
|
}
|
|
215
510
|
function generateEnvHeader(envFileName) {
|
|
216
511
|
const templateFileName = deriveTemplateFileName(envFileName);
|
|
217
512
|
return [
|
|
218
513
|
SEPARATOR,
|
|
219
|
-
`# ${envFileName}
|
|
514
|
+
`# ${envFileName} — Environment Variables`,
|
|
220
515
|
"#",
|
|
221
516
|
"# WARNING: This file contains sensitive values. Do not commit to git!",
|
|
222
517
|
"#",
|
|
@@ -224,127 +519,105 @@ function generateEnvHeader(envFileName) {
|
|
|
224
519
|
`# env2op ${envFileName} <vault> "<item_name>"`,
|
|
225
520
|
"#",
|
|
226
521
|
`# Pulled: ${formatTimestamp()}`,
|
|
227
|
-
`# Generated by op2env v${
|
|
522
|
+
`# Generated by op2env v${import__package.default.version}`,
|
|
228
523
|
"# https://github.com/tolgamorf/env2op-cli",
|
|
229
524
|
SEPARATOR,
|
|
230
525
|
"",
|
|
231
526
|
""
|
|
232
527
|
];
|
|
233
528
|
}
|
|
529
|
+
function generateTemplateContent(options, templateFileName) {
|
|
530
|
+
const { vaultId, itemId, lines: envLines, fieldIds } = options;
|
|
531
|
+
const outputLines = generateTemplateHeader(templateFileName);
|
|
532
|
+
for (const line of envLines) {
|
|
533
|
+
switch (line.type) {
|
|
534
|
+
case "empty":
|
|
535
|
+
outputLines.push("");
|
|
536
|
+
break;
|
|
537
|
+
case "comment":
|
|
538
|
+
outputLines.push(line.content);
|
|
539
|
+
break;
|
|
540
|
+
case "variable": {
|
|
541
|
+
const fieldId = fieldIds[line.key] ?? line.key;
|
|
542
|
+
outputLines.push(`${line.key}=op://${vaultId}/${itemId}/${fieldId}`);
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return `${outputLines.join(`
|
|
548
|
+
`)}
|
|
549
|
+
`;
|
|
550
|
+
}
|
|
551
|
+
function writeTemplate(content, outputPath) {
|
|
552
|
+
writeFileSync(outputPath, content, "utf-8");
|
|
553
|
+
}
|
|
554
|
+
function generateUsageInstructions(templatePath) {
|
|
555
|
+
return ["Usage:", ` op2env ${templatePath}`, ` op run --env-file ${templatePath} -- npm start`].join(`
|
|
556
|
+
`);
|
|
557
|
+
}
|
|
234
558
|
|
|
235
559
|
// src/utils/logger.ts
|
|
236
560
|
import * as p from "@clack/prompts";
|
|
237
|
-
import
|
|
561
|
+
import pc2 from "picocolors";
|
|
238
562
|
var symbols = {
|
|
239
|
-
success:
|
|
240
|
-
error:
|
|
241
|
-
warning:
|
|
242
|
-
info:
|
|
243
|
-
arrow:
|
|
244
|
-
bullet:
|
|
563
|
+
success: pc2.green("✓"),
|
|
564
|
+
error: pc2.red("✗"),
|
|
565
|
+
warning: pc2.yellow("⚠"),
|
|
566
|
+
info: pc2.blue("ℹ"),
|
|
567
|
+
arrow: pc2.cyan("→"),
|
|
568
|
+
bullet: pc2.dim("•")
|
|
245
569
|
};
|
|
246
570
|
var logger = {
|
|
247
|
-
/**
|
|
248
|
-
* Display CLI intro banner
|
|
249
|
-
*/
|
|
250
571
|
intro(name, version, dryRun = false) {
|
|
251
|
-
const label = dryRun ?
|
|
572
|
+
const label = dryRun ? pc2.bgYellow(pc2.black(` ${name} v${version} [DRY RUN] `)) : pc2.bgCyan(pc2.black(` ${name} v${version} `));
|
|
252
573
|
p.intro(label);
|
|
253
574
|
},
|
|
254
|
-
/**
|
|
255
|
-
* Display section header
|
|
256
|
-
*/
|
|
257
575
|
section(title) {
|
|
258
576
|
console.log(`
|
|
259
|
-
${
|
|
577
|
+
${pc2.bold(pc2.underline(title))}`);
|
|
260
578
|
},
|
|
261
|
-
/**
|
|
262
|
-
* Success message
|
|
263
|
-
*/
|
|
264
579
|
success(message) {
|
|
265
580
|
p.log.success(message);
|
|
266
581
|
},
|
|
267
|
-
/**
|
|
268
|
-
* Error message
|
|
269
|
-
*/
|
|
270
582
|
error(message) {
|
|
271
583
|
p.log.error(message);
|
|
272
584
|
},
|
|
273
|
-
/**
|
|
274
|
-
* Warning message
|
|
275
|
-
*/
|
|
276
585
|
warn(message) {
|
|
277
586
|
p.log.warn(message);
|
|
278
587
|
},
|
|
279
|
-
/**
|
|
280
|
-
* Info message
|
|
281
|
-
*/
|
|
282
588
|
info(message) {
|
|
283
589
|
p.log.info(message);
|
|
284
590
|
},
|
|
285
|
-
/**
|
|
286
|
-
* Step in a process
|
|
287
|
-
*/
|
|
288
591
|
step(message) {
|
|
289
592
|
p.log.step(message);
|
|
290
593
|
},
|
|
291
|
-
/**
|
|
292
|
-
* Message (neutral)
|
|
293
|
-
*/
|
|
294
594
|
message(message) {
|
|
295
595
|
p.log.message(message);
|
|
296
596
|
},
|
|
297
|
-
/**
|
|
298
|
-
* Display key-value pair
|
|
299
|
-
*/
|
|
300
597
|
keyValue(key, value, indent = 2) {
|
|
301
|
-
console.log(`${" ".repeat(indent)}${
|
|
598
|
+
console.log(`${" ".repeat(indent)}${pc2.dim(key)}: ${pc2.cyan(value)}`);
|
|
302
599
|
},
|
|
303
|
-
/**
|
|
304
|
-
* Display list item
|
|
305
|
-
*/
|
|
306
600
|
listItem(item, indent = 2) {
|
|
307
601
|
console.log(`${" ".repeat(indent)}${symbols.bullet} ${item}`);
|
|
308
602
|
},
|
|
309
|
-
/**
|
|
310
|
-
* Display arrow item
|
|
311
|
-
*/
|
|
312
603
|
arrowItem(item, indent = 2) {
|
|
313
604
|
console.log(`${" ".repeat(indent)}${symbols.arrow} ${item}`);
|
|
314
605
|
},
|
|
315
|
-
/**
|
|
316
|
-
* Display dry run indicator
|
|
317
|
-
*/
|
|
318
606
|
dryRun(message) {
|
|
319
|
-
console.log(`${
|
|
607
|
+
console.log(`${pc2.yellow("[DRY RUN]")} ${message}`);
|
|
320
608
|
},
|
|
321
|
-
/**
|
|
322
|
-
* Create a spinner for async operations
|
|
323
|
-
*/
|
|
324
609
|
spinner() {
|
|
325
610
|
return p.spinner();
|
|
326
611
|
},
|
|
327
|
-
/**
|
|
328
|
-
* Display outro message
|
|
329
|
-
*/
|
|
330
612
|
outro(message) {
|
|
331
|
-
p.outro(
|
|
613
|
+
p.outro(pc2.green(message));
|
|
332
614
|
},
|
|
333
|
-
/**
|
|
334
|
-
* Display cancellation message
|
|
335
|
-
*/
|
|
336
615
|
cancel(message) {
|
|
337
616
|
p.cancel(message);
|
|
338
617
|
},
|
|
339
|
-
/**
|
|
340
|
-
* Display a note block
|
|
341
|
-
*/
|
|
342
618
|
note(message, title) {
|
|
343
619
|
p.note(message, title);
|
|
344
620
|
},
|
|
345
|
-
/**
|
|
346
|
-
* Format a field list for display
|
|
347
|
-
*/
|
|
348
621
|
formatFields(fields, max = 3) {
|
|
349
622
|
if (fields.length <= max) {
|
|
350
623
|
return fields.join(", ");
|
|
@@ -353,6 +626,14 @@ ${pc.bold(pc.underline(title))}`);
|
|
|
353
626
|
}
|
|
354
627
|
};
|
|
355
628
|
|
|
629
|
+
// src/utils/timing.ts
|
|
630
|
+
import { setTimeout } from "node:timers/promises";
|
|
631
|
+
var MIN_SPINNER_TIME = 500;
|
|
632
|
+
async function withMinTime(promise, minTime = MIN_SPINNER_TIME) {
|
|
633
|
+
const [result] = await Promise.all([promise, setTimeout(minTime)]);
|
|
634
|
+
return result;
|
|
635
|
+
}
|
|
636
|
+
|
|
356
637
|
// src/commands/inject.ts
|
|
357
638
|
function deriveOutputPath(templatePath) {
|
|
358
639
|
if (templatePath.endsWith(".tpl")) {
|
|
@@ -360,39 +641,41 @@ function deriveOutputPath(templatePath) {
|
|
|
360
641
|
}
|
|
361
642
|
return `${templatePath}.env`;
|
|
362
643
|
}
|
|
363
|
-
async function runInject(
|
|
364
|
-
const { templateFile
|
|
365
|
-
const outputPath = output ?? deriveOutputPath(
|
|
366
|
-
const
|
|
367
|
-
logger.intro("op2env",
|
|
644
|
+
async function runInject(options) {
|
|
645
|
+
const { templateFile, output, dryRun, force, verbose } = options;
|
|
646
|
+
const outputPath = output ?? deriveOutputPath(templateFile);
|
|
647
|
+
const pkg2 = await Promise.resolve().then(() => __toESM(require_package(), 1));
|
|
648
|
+
logger.intro("op2env", pkg2.version, dryRun);
|
|
368
649
|
try {
|
|
369
|
-
if (!
|
|
370
|
-
throw new Env2OpError(
|
|
371
|
-
`Template file not found: ${templateFile2}`,
|
|
372
|
-
"TEMPLATE_NOT_FOUND",
|
|
373
|
-
"Ensure the file exists and the path is correct"
|
|
374
|
-
);
|
|
650
|
+
if (!existsSync(templateFile)) {
|
|
651
|
+
throw new Env2OpError(`Template file not found: ${templateFile}`, "TEMPLATE_NOT_FOUND", "Ensure the file exists and the path is correct");
|
|
375
652
|
}
|
|
376
|
-
logger.success(`Found template: ${basename(
|
|
653
|
+
logger.success(`Found template: ${basename(templateFile)}`);
|
|
377
654
|
if (!dryRun) {
|
|
378
|
-
const
|
|
655
|
+
const authSpinner = p2.spinner();
|
|
656
|
+
authSpinner.start("Checking 1Password CLI...");
|
|
657
|
+
const opInstalled = await checkOpCli({ verbose });
|
|
379
658
|
if (!opInstalled) {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
"OP_CLI_NOT_INSTALLED",
|
|
383
|
-
"Install from https://1password.com/downloads/command-line/"
|
|
384
|
-
);
|
|
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/");
|
|
385
661
|
}
|
|
386
|
-
|
|
662
|
+
let signedIn = await checkSignedIn({ verbose });
|
|
387
663
|
if (!signedIn) {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
+
}
|
|
393
675
|
}
|
|
676
|
+
authSpinner.stop("1Password CLI ready");
|
|
394
677
|
}
|
|
395
|
-
const outputExists =
|
|
678
|
+
const outputExists = existsSync(outputPath);
|
|
396
679
|
if (dryRun) {
|
|
397
680
|
if (outputExists) {
|
|
398
681
|
logger.warn(`Would overwrite: ${outputPath}`);
|
|
@@ -411,23 +694,31 @@ async function runInject(options2) {
|
|
|
411
694
|
process.exit(0);
|
|
412
695
|
}
|
|
413
696
|
}
|
|
414
|
-
const
|
|
415
|
-
|
|
697
|
+
const spinner3 = verbose ? null : logger.spinner();
|
|
698
|
+
spinner3?.start("Pulling secrets from 1Password...");
|
|
416
699
|
try {
|
|
417
|
-
const result = await exec("op", ["inject", "-i",
|
|
700
|
+
const result = await withMinTime(exec("op", ["inject", "-i", templateFile, "-o", outputPath, "-f"], { verbose }));
|
|
418
701
|
if (result.exitCode !== 0) {
|
|
419
702
|
throw new Error(result.stderr);
|
|
420
703
|
}
|
|
421
|
-
const rawContent =
|
|
704
|
+
const rawContent = readFileSync(outputPath, "utf-8");
|
|
422
705
|
const envContent = stripHeaders(rawContent);
|
|
423
|
-
const header = generateEnvHeader(basename(outputPath)).join(
|
|
706
|
+
const header = generateEnvHeader(basename(outputPath)).join(`
|
|
707
|
+
`);
|
|
424
708
|
writeFileSync2(outputPath, header + envContent, "utf-8");
|
|
425
|
-
|
|
709
|
+
const varCount = envContent.split(`
|
|
710
|
+
`).filter((line) => line.trim() && !line.trim().startsWith("#")).length;
|
|
711
|
+
const stopMessage = `Generated ${basename(outputPath)} — ${varCount} variable${varCount === 1 ? "" : "s"}`;
|
|
712
|
+
if (spinner3) {
|
|
713
|
+
spinner3.stop(stopMessage);
|
|
714
|
+
} else {
|
|
715
|
+
logger.success(stopMessage);
|
|
716
|
+
}
|
|
426
717
|
} catch (error) {
|
|
427
|
-
|
|
718
|
+
spinner3?.stop("Failed to pull secrets");
|
|
428
719
|
const stderr = error?.stderr;
|
|
429
720
|
const message = stderr || (error instanceof Error ? error.message : String(error));
|
|
430
|
-
throw new Env2OpError("Failed to
|
|
721
|
+
throw new Env2OpError("Failed to pull secrets from 1Password", "INJECT_FAILED", message);
|
|
431
722
|
}
|
|
432
723
|
logger.outro("Done! Your .env file is ready");
|
|
433
724
|
} catch (error) {
|
|
@@ -445,10 +736,10 @@ async function runInject(options2) {
|
|
|
445
736
|
// src/op2env-cli.ts
|
|
446
737
|
var pkg2 = await Promise.resolve().then(() => __toESM(require_package(), 1));
|
|
447
738
|
var args = process.argv.slice(2);
|
|
448
|
-
var flags =
|
|
739
|
+
var flags = new Set;
|
|
449
740
|
var positional = [];
|
|
450
741
|
var options = {};
|
|
451
|
-
for (let i = 0;
|
|
742
|
+
for (let i = 0;i < args.length; i++) {
|
|
452
743
|
const arg = args[i];
|
|
453
744
|
if (arg === "-o" || arg === "--output") {
|
|
454
745
|
const next = args[i + 1];
|
|
@@ -481,42 +772,44 @@ await runInject({
|
|
|
481
772
|
templateFile,
|
|
482
773
|
output: options.output,
|
|
483
774
|
dryRun: flags.has("dry-run"),
|
|
484
|
-
force: flags.has("f") || flags.has("force")
|
|
775
|
+
force: flags.has("f") || flags.has("force"),
|
|
776
|
+
verbose: flags.has("verbose")
|
|
485
777
|
});
|
|
486
778
|
function showHelp() {
|
|
487
|
-
const name =
|
|
488
|
-
const version =
|
|
779
|
+
const name = pc3.bold(pc3.cyan("op2env"));
|
|
780
|
+
const version = pc3.dim(`v${pkg2.version}`);
|
|
489
781
|
console.log(`
|
|
490
782
|
${name} ${version}
|
|
491
783
|
Pull secrets from 1Password to generate .env files
|
|
492
784
|
|
|
493
|
-
${
|
|
494
|
-
${
|
|
785
|
+
${pc3.bold("USAGE")}
|
|
786
|
+
${pc3.cyan("$")} op2env ${pc3.yellow("<template_file>")} ${pc3.dim("[options]")}
|
|
495
787
|
|
|
496
|
-
${
|
|
497
|
-
${
|
|
788
|
+
${pc3.bold("ARGUMENTS")}
|
|
789
|
+
${pc3.yellow("template_file")} Path to .env.tpl template file
|
|
498
790
|
|
|
499
|
-
${
|
|
500
|
-
${
|
|
501
|
-
${
|
|
502
|
-
${
|
|
503
|
-
${
|
|
504
|
-
${
|
|
791
|
+
${pc3.bold("OPTIONS")}
|
|
792
|
+
${pc3.cyan("-o, --output")} Output .env path (default: template without .tpl)
|
|
793
|
+
${pc3.cyan("-f, --force")} Overwrite without prompting
|
|
794
|
+
${pc3.cyan("--dry-run")} Preview actions without executing
|
|
795
|
+
${pc3.cyan("--verbose")} Show op CLI output
|
|
796
|
+
${pc3.cyan("-h, --help")} Show this help message
|
|
797
|
+
${pc3.cyan("-v, --version")} Show version
|
|
505
798
|
|
|
506
|
-
${
|
|
507
|
-
${
|
|
508
|
-
${
|
|
799
|
+
${pc3.bold("EXAMPLES")}
|
|
800
|
+
${pc3.dim("# Basic usage - generates .env from .env.tpl")}
|
|
801
|
+
${pc3.cyan("$")} op2env .env.tpl
|
|
509
802
|
|
|
510
|
-
${
|
|
511
|
-
${
|
|
803
|
+
${pc3.dim("# Custom output path")}
|
|
804
|
+
${pc3.cyan("$")} op2env .env.tpl -o .env.local
|
|
512
805
|
|
|
513
|
-
${
|
|
514
|
-
${
|
|
806
|
+
${pc3.dim("# Preview without making changes")}
|
|
807
|
+
${pc3.cyan("$")} op2env .env.tpl --dry-run
|
|
515
808
|
|
|
516
|
-
${
|
|
517
|
-
${
|
|
809
|
+
${pc3.dim("# Overwrite existing .env without prompting")}
|
|
810
|
+
${pc3.cyan("$")} op2env .env.tpl -f
|
|
518
811
|
|
|
519
|
-
${
|
|
520
|
-
${
|
|
812
|
+
${pc3.bold("DOCUMENTATION")}
|
|
813
|
+
${pc3.dim("https://github.com/tolgamorf/env2op-cli")}
|
|
521
814
|
`);
|
|
522
815
|
}
|