@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/cli.js
CHANGED
|
@@ -1,187 +1,160 @@
|
|
|
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/cli.ts
|
|
114
|
-
import
|
|
105
|
+
import pc3 from "picocolors";
|
|
115
106
|
|
|
116
107
|
// src/commands/convert.ts
|
|
117
|
-
import { basename, dirname, join } from "path";
|
|
108
|
+
import { basename, dirname, join } from "node:path";
|
|
118
109
|
import * as p2 from "@clack/prompts";
|
|
119
110
|
|
|
120
111
|
// src/core/env-parser.ts
|
|
121
|
-
import {
|
|
112
|
+
import { readFile } from "node:fs/promises";
|
|
122
113
|
|
|
123
114
|
// src/utils/errors.ts
|
|
124
|
-
|
|
115
|
+
class Env2OpError extends Error {
|
|
116
|
+
code;
|
|
117
|
+
suggestion;
|
|
125
118
|
constructor(message, code, suggestion) {
|
|
126
119
|
super(message);
|
|
127
120
|
this.code = code;
|
|
128
121
|
this.suggestion = suggestion;
|
|
129
122
|
this.name = "Env2OpError";
|
|
130
123
|
}
|
|
131
|
-
}
|
|
124
|
+
}
|
|
132
125
|
var ErrorCodes = {
|
|
133
126
|
ENV_FILE_NOT_FOUND: "ENV_FILE_NOT_FOUND",
|
|
134
127
|
ENV_FILE_EMPTY: "ENV_FILE_EMPTY",
|
|
135
128
|
OP_CLI_NOT_INSTALLED: "OP_CLI_NOT_INSTALLED",
|
|
136
129
|
OP_NOT_SIGNED_IN: "OP_NOT_SIGNED_IN",
|
|
130
|
+
OP_SIGNIN_FAILED: "OP_SIGNIN_FAILED",
|
|
137
131
|
VAULT_NOT_FOUND: "VAULT_NOT_FOUND",
|
|
138
132
|
VAULT_CREATE_FAILED: "VAULT_CREATE_FAILED",
|
|
139
133
|
ITEM_EXISTS: "ITEM_EXISTS",
|
|
140
134
|
ITEM_CREATE_FAILED: "ITEM_CREATE_FAILED",
|
|
135
|
+
ITEM_EDIT_FAILED: "ITEM_EDIT_FAILED",
|
|
141
136
|
PARSE_ERROR: "PARSE_ERROR",
|
|
142
137
|
TEMPLATE_NOT_FOUND: "TEMPLATE_NOT_FOUND",
|
|
143
138
|
INJECT_FAILED: "INJECT_FAILED"
|
|
144
139
|
};
|
|
145
140
|
var errors = {
|
|
146
|
-
envFileNotFound: (path) => new Env2OpError(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
),
|
|
151
|
-
envFileEmpty: (path) => new Env2OpError(
|
|
152
|
-
`No valid environment variables found in ${path}`,
|
|
153
|
-
ErrorCodes.ENV_FILE_EMPTY,
|
|
154
|
-
"Ensure the file contains KEY=value pairs"
|
|
155
|
-
),
|
|
156
|
-
opCliNotInstalled: () => new Env2OpError(
|
|
157
|
-
"1Password CLI (op) is not installed",
|
|
158
|
-
ErrorCodes.OP_CLI_NOT_INSTALLED,
|
|
159
|
-
"Install it from https://1password.com/downloads/command-line/"
|
|
160
|
-
),
|
|
161
|
-
opNotSignedIn: () => new Env2OpError(
|
|
162
|
-
"Not signed in to 1Password CLI",
|
|
163
|
-
ErrorCodes.OP_NOT_SIGNED_IN,
|
|
164
|
-
'Run "op signin" to authenticate'
|
|
165
|
-
),
|
|
166
|
-
vaultNotFound: (vault2) => new Env2OpError(
|
|
167
|
-
`Vault not found: ${vault2}`,
|
|
168
|
-
ErrorCodes.VAULT_NOT_FOUND,
|
|
169
|
-
'Run "op vault list" to see available vaults'
|
|
170
|
-
),
|
|
141
|
+
envFileNotFound: (path) => new Env2OpError(`File not found: ${path}`, ErrorCodes.ENV_FILE_NOT_FOUND, "Check that the file path is correct"),
|
|
142
|
+
envFileEmpty: (path) => new Env2OpError(`No valid environment variables found in ${path}`, ErrorCodes.ENV_FILE_EMPTY, "Ensure the file contains KEY=value pairs"),
|
|
143
|
+
opCliNotInstalled: () => new Env2OpError("1Password CLI (op) is not installed", ErrorCodes.OP_CLI_NOT_INSTALLED, "Install it from https://1password.com/downloads/command-line/"),
|
|
144
|
+
opNotSignedIn: () => new Env2OpError("Not signed in to 1Password CLI", ErrorCodes.OP_NOT_SIGNED_IN, 'Run "op signin" to authenticate'),
|
|
145
|
+
vaultNotFound: (vault) => new Env2OpError(`Vault not found: ${vault}`, ErrorCodes.VAULT_NOT_FOUND, 'Run "op vault list" to see available vaults'),
|
|
171
146
|
vaultCreateFailed: (message) => new Env2OpError(`Failed to create vault: ${message}`, ErrorCodes.VAULT_CREATE_FAILED),
|
|
172
|
-
itemExists: (title,
|
|
173
|
-
`Item "${title}" already exists in vault "${vault2}"`,
|
|
174
|
-
ErrorCodes.ITEM_EXISTS,
|
|
175
|
-
"Use default behavior (overwrites) or choose a different item name"
|
|
176
|
-
),
|
|
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"),
|
|
177
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),
|
|
178
150
|
parseError: (line, message) => new Env2OpError(`Parse error at line ${line}: ${message}`, ErrorCodes.PARSE_ERROR)
|
|
179
151
|
};
|
|
180
152
|
|
|
181
153
|
// src/core/env-parser.ts
|
|
182
154
|
var HEADER_SEPARATOR = "# ===========================================================================";
|
|
183
155
|
function stripHeaders(content) {
|
|
184
|
-
const lines = content.split(
|
|
156
|
+
const lines = content.split(`
|
|
157
|
+
`);
|
|
185
158
|
const result = [];
|
|
186
159
|
let inHeader = false;
|
|
187
160
|
for (const line of lines) {
|
|
@@ -201,7 +174,8 @@ function stripHeaders(content) {
|
|
|
201
174
|
while (result.length > 0 && result[0]?.trim() === "") {
|
|
202
175
|
result.shift();
|
|
203
176
|
}
|
|
204
|
-
return result.join(
|
|
177
|
+
return result.join(`
|
|
178
|
+
`);
|
|
205
179
|
}
|
|
206
180
|
function parseValue(raw) {
|
|
207
181
|
const trimmed = raw.trim();
|
|
@@ -220,18 +194,27 @@ function parseValue(raw) {
|
|
|
220
194
|
const parts = trimmed.split(/\s+#/);
|
|
221
195
|
return (parts[0] ?? trimmed).trim();
|
|
222
196
|
}
|
|
223
|
-
function
|
|
224
|
-
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 {
|
|
225
208
|
throw errors.envFileNotFound(filePath);
|
|
226
209
|
}
|
|
227
|
-
const
|
|
228
|
-
const
|
|
229
|
-
|
|
210
|
+
const content = stripHeaders(stripBom(rawContent));
|
|
211
|
+
const rawLines = content.split(`
|
|
212
|
+
`);
|
|
230
213
|
const variables = [];
|
|
231
214
|
const lines = [];
|
|
232
215
|
const parseErrors = [];
|
|
233
216
|
let currentComment = "";
|
|
234
|
-
for (let i = 0;
|
|
217
|
+
for (let i = 0;i < rawLines.length; i++) {
|
|
235
218
|
const line = rawLines[i] ?? "";
|
|
236
219
|
const trimmed = line.trim();
|
|
237
220
|
const lineNumber = i + 1;
|
|
@@ -253,7 +236,7 @@ function parseEnvFile(filePath) {
|
|
|
253
236
|
variables.push({
|
|
254
237
|
key,
|
|
255
238
|
value,
|
|
256
|
-
comment: currentComment ||
|
|
239
|
+
comment: currentComment || undefined,
|
|
257
240
|
line: lineNumber
|
|
258
241
|
});
|
|
259
242
|
lines.push({ type: "variable", key, value });
|
|
@@ -271,101 +254,181 @@ function validateParseResult(result, filePath) {
|
|
|
271
254
|
}
|
|
272
255
|
|
|
273
256
|
// src/utils/shell.ts
|
|
274
|
-
import { spawn } from "child_process";
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
257
|
+
import { spawn } from "node:child_process";
|
|
258
|
+
import pc from "picocolors";
|
|
259
|
+
function quoteArg(arg) {
|
|
260
|
+
if (/[ [\]'"\\=]/.test(arg)) {
|
|
261
|
+
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
262
|
+
}
|
|
263
|
+
return arg;
|
|
264
|
+
}
|
|
265
|
+
async function exec(command, args = [], options = {}) {
|
|
266
|
+
const { verbose = false } = options;
|
|
267
|
+
const fullCommand = `${command} ${args.map(quoteArg).join(" ")}`;
|
|
268
|
+
if (verbose) {
|
|
269
|
+
console.log(pc.dim(`$ ${fullCommand}`));
|
|
270
|
+
}
|
|
271
|
+
return new Promise((resolve) => {
|
|
272
|
+
const proc = spawn(command, args, {
|
|
273
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
280
274
|
});
|
|
281
|
-
|
|
282
|
-
|
|
275
|
+
const stdoutChunks = [];
|
|
276
|
+
const stderrChunks = [];
|
|
283
277
|
proc.stdout?.on("data", (data) => {
|
|
284
|
-
|
|
278
|
+
const text = Buffer.isBuffer(data) ? data.toString() : String(data);
|
|
279
|
+
stdoutChunks.push(text);
|
|
280
|
+
if (verbose) {
|
|
281
|
+
process.stdout.write(text);
|
|
282
|
+
}
|
|
285
283
|
});
|
|
286
284
|
proc.stderr?.on("data", (data) => {
|
|
287
|
-
|
|
285
|
+
const text = Buffer.isBuffer(data) ? data.toString() : String(data);
|
|
286
|
+
stderrChunks.push(text);
|
|
287
|
+
if (verbose) {
|
|
288
|
+
process.stderr.write(text);
|
|
289
|
+
}
|
|
288
290
|
});
|
|
289
|
-
proc.on("
|
|
290
|
-
|
|
291
|
+
proc.on("close", (code) => {
|
|
292
|
+
resolve({
|
|
293
|
+
stdout: stdoutChunks.join(""),
|
|
294
|
+
stderr: stderrChunks.join(""),
|
|
295
|
+
exitCode: code ?? 1
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
proc.on("error", (err) => {
|
|
299
|
+
stderrChunks.push(err.message);
|
|
300
|
+
resolve({
|
|
301
|
+
stdout: stdoutChunks.join(""),
|
|
302
|
+
stderr: stderrChunks.join(""),
|
|
303
|
+
exitCode: 1
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
async function execWithStdin(command, args = [], options) {
|
|
309
|
+
const { stdin: stdinContent, verbose = false } = options;
|
|
310
|
+
if (verbose) {
|
|
311
|
+
const fullCommand = `${command} ${args.map(quoteArg).join(" ")}`;
|
|
312
|
+
console.log(pc.dim(`$ echo '...' | ${fullCommand}`));
|
|
313
|
+
}
|
|
314
|
+
return new Promise((resolve) => {
|
|
315
|
+
const proc = spawn(command, args, {
|
|
316
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
317
|
+
});
|
|
318
|
+
const stdoutChunks = [];
|
|
319
|
+
const stderrChunks = [];
|
|
320
|
+
proc.stdin?.write(stdinContent);
|
|
321
|
+
proc.stdin?.end();
|
|
322
|
+
proc.stdout?.on("data", (data) => {
|
|
323
|
+
const text = Buffer.isBuffer(data) ? data.toString() : String(data);
|
|
324
|
+
stdoutChunks.push(text);
|
|
325
|
+
if (verbose)
|
|
326
|
+
process.stdout.write(text);
|
|
327
|
+
});
|
|
328
|
+
proc.stderr?.on("data", (data) => {
|
|
329
|
+
const text = Buffer.isBuffer(data) ? data.toString() : String(data);
|
|
330
|
+
stderrChunks.push(text);
|
|
331
|
+
if (verbose)
|
|
332
|
+
process.stderr.write(text);
|
|
291
333
|
});
|
|
292
334
|
proc.on("close", (code) => {
|
|
293
335
|
resolve({
|
|
294
|
-
stdout,
|
|
295
|
-
stderr,
|
|
296
|
-
exitCode: code ??
|
|
336
|
+
stdout: stdoutChunks.join(""),
|
|
337
|
+
stderr: stderrChunks.join(""),
|
|
338
|
+
exitCode: code ?? 1
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
proc.on("error", (err) => {
|
|
342
|
+
stderrChunks.push(err.message);
|
|
343
|
+
resolve({
|
|
344
|
+
stdout: stdoutChunks.join(""),
|
|
345
|
+
stderr: stderrChunks.join(""),
|
|
346
|
+
exitCode: 1
|
|
297
347
|
});
|
|
298
348
|
});
|
|
299
349
|
});
|
|
300
350
|
}
|
|
301
|
-
|
|
302
|
-
|
|
351
|
+
|
|
352
|
+
// src/core/onepassword.ts
|
|
353
|
+
async function checkOpCli(options = {}) {
|
|
354
|
+
const result = await exec("op", ["--version"], options);
|
|
355
|
+
return result.exitCode === 0;
|
|
356
|
+
}
|
|
357
|
+
async function checkSignedIn(options = {}) {
|
|
358
|
+
const result = await exec("op", ["whoami", "--format", "json"], options);
|
|
359
|
+
return result.exitCode === 0;
|
|
360
|
+
}
|
|
361
|
+
async function signIn(options = {}) {
|
|
362
|
+
const result = await exec("op", ["signin"], options);
|
|
363
|
+
return result.exitCode === 0;
|
|
364
|
+
}
|
|
365
|
+
async function itemExists(vault, title, options = {}) {
|
|
366
|
+
const result = await exec("op", ["item", "list", "--vault", vault, "--format", "json"], options);
|
|
303
367
|
if (result.exitCode !== 0) {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
try {
|
|
371
|
+
const items = JSON.parse(result.stdout);
|
|
372
|
+
const item = items.find((item2) => item2.title === title);
|
|
373
|
+
return item?.id ?? null;
|
|
374
|
+
} catch {
|
|
375
|
+
return null;
|
|
309
376
|
}
|
|
310
|
-
return result;
|
|
311
|
-
}
|
|
312
|
-
async function execJson(command, args2 = []) {
|
|
313
|
-
const result = await execOrThrow(command, args2);
|
|
314
|
-
return JSON.parse(result.stdout);
|
|
315
377
|
}
|
|
316
|
-
async function
|
|
378
|
+
async function vaultExists(vault, options = {}) {
|
|
379
|
+
const result = await exec("op", ["vault", "list", "--format", "json"], options);
|
|
380
|
+
if (result.exitCode !== 0) {
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
317
383
|
try {
|
|
318
|
-
const
|
|
319
|
-
return
|
|
384
|
+
const vaults = JSON.parse(result.stdout);
|
|
385
|
+
return vaults.some((v) => v.name === vault);
|
|
320
386
|
} catch {
|
|
321
387
|
return false;
|
|
322
388
|
}
|
|
323
389
|
}
|
|
324
|
-
|
|
325
|
-
// src/core/onepassword.ts
|
|
326
|
-
async function checkOpCli() {
|
|
327
|
-
return execQuiet("op", ["--version"]);
|
|
328
|
-
}
|
|
329
|
-
async function checkSignedIn() {
|
|
330
|
-
return execQuiet("op", ["account", "get"]);
|
|
331
|
-
}
|
|
332
|
-
async function itemExists(vault2, title) {
|
|
333
|
-
return execQuiet("op", ["item", "get", title, "--vault", vault2]);
|
|
334
|
-
}
|
|
335
|
-
async function deleteItem(vault2, title) {
|
|
390
|
+
async function createVault(name, options = {}) {
|
|
336
391
|
try {
|
|
337
|
-
await exec("op", ["
|
|
338
|
-
} catch (
|
|
392
|
+
await exec("op", ["vault", "create", name], options);
|
|
393
|
+
} catch (error) {
|
|
394
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
395
|
+
throw errors.vaultCreateFailed(message);
|
|
339
396
|
}
|
|
340
397
|
}
|
|
341
|
-
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
398
|
+
function buildItemTemplate(title, vault, fields, secret) {
|
|
399
|
+
const fieldType = secret ? "CONCEALED" : "STRING";
|
|
400
|
+
return {
|
|
401
|
+
title,
|
|
402
|
+
vault: { name: vault },
|
|
403
|
+
category: "SECURE_NOTE",
|
|
404
|
+
fields: fields.map(({ key, value }) => ({
|
|
405
|
+
type: fieldType,
|
|
406
|
+
label: key,
|
|
407
|
+
value
|
|
408
|
+
}))
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
async function createSecureNote(options) {
|
|
412
|
+
const { vault, title, fields, secret, verbose } = options;
|
|
413
|
+
const template = buildItemTemplate(title, vault, fields, secret);
|
|
414
|
+
const json = JSON.stringify(template);
|
|
345
415
|
try {
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
"create"
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
`--title=${title}`,
|
|
352
|
-
"--format=json",
|
|
353
|
-
...fieldArgs
|
|
354
|
-
];
|
|
355
|
-
const result = await execJson("op", args2);
|
|
416
|
+
const result = await execWithStdin("op", ["item", "create", "--format", "json"], { stdin: json, verbose });
|
|
417
|
+
if (result.exitCode !== 0) {
|
|
418
|
+
throw new Error(result.stderr || "Failed to create item");
|
|
419
|
+
}
|
|
420
|
+
const item = JSON.parse(result.stdout);
|
|
356
421
|
const fieldIds = {};
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
fieldIds[field.label] = field.id;
|
|
361
|
-
}
|
|
422
|
+
for (const field of item.fields ?? []) {
|
|
423
|
+
if (field.label && field.id) {
|
|
424
|
+
fieldIds[field.label] = field.id;
|
|
362
425
|
}
|
|
363
426
|
}
|
|
364
427
|
return {
|
|
365
|
-
id:
|
|
366
|
-
title:
|
|
367
|
-
vault:
|
|
368
|
-
vaultId:
|
|
428
|
+
id: item.id,
|
|
429
|
+
title: item.title,
|
|
430
|
+
vault: item.vault?.name ?? vault,
|
|
431
|
+
vaultId: item.vault?.id ?? "",
|
|
369
432
|
fieldIds
|
|
370
433
|
};
|
|
371
434
|
} catch (error) {
|
|
@@ -373,21 +436,41 @@ async function createSecureNote(options2) {
|
|
|
373
436
|
throw errors.itemCreateFailed(message);
|
|
374
437
|
}
|
|
375
438
|
}
|
|
376
|
-
async function
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
439
|
+
async function editSecureNote(options) {
|
|
440
|
+
const { vault, title, fields, secret, verbose, itemId } = options;
|
|
441
|
+
const template = buildItemTemplate(title, vault, fields, secret);
|
|
442
|
+
const json = JSON.stringify(template);
|
|
380
443
|
try {
|
|
381
|
-
await
|
|
444
|
+
const result = await execWithStdin("op", ["item", "edit", itemId, "--format", "json"], {
|
|
445
|
+
stdin: json,
|
|
446
|
+
verbose
|
|
447
|
+
});
|
|
448
|
+
if (result.exitCode !== 0) {
|
|
449
|
+
throw new Error(result.stderr || "Failed to edit item");
|
|
450
|
+
}
|
|
451
|
+
const item = JSON.parse(result.stdout);
|
|
452
|
+
const fieldIds = {};
|
|
453
|
+
for (const field of item.fields ?? []) {
|
|
454
|
+
if (field.label && field.id) {
|
|
455
|
+
fieldIds[field.label] = field.id;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return {
|
|
459
|
+
id: item.id,
|
|
460
|
+
title: item.title,
|
|
461
|
+
vault: item.vault?.name ?? vault,
|
|
462
|
+
vaultId: item.vault?.id ?? "",
|
|
463
|
+
fieldIds
|
|
464
|
+
};
|
|
382
465
|
} catch (error) {
|
|
383
466
|
const message = error instanceof Error ? error.message : String(error);
|
|
384
|
-
throw errors.
|
|
467
|
+
throw errors.itemEditFailed(message);
|
|
385
468
|
}
|
|
386
469
|
}
|
|
387
470
|
|
|
388
471
|
// src/core/template-generator.ts
|
|
389
|
-
var
|
|
390
|
-
import { writeFileSync } from "fs";
|
|
472
|
+
var import__package = __toESM(require_package(), 1);
|
|
473
|
+
import { writeFileSync } from "node:fs";
|
|
391
474
|
var SEPARATOR = "# ===========================================================================";
|
|
392
475
|
function deriveEnvFileName(templateFileName) {
|
|
393
476
|
if (templateFileName.endsWith(".tpl")) {
|
|
@@ -395,17 +478,20 @@ function deriveEnvFileName(templateFileName) {
|
|
|
395
478
|
}
|
|
396
479
|
return templateFileName;
|
|
397
480
|
}
|
|
481
|
+
function deriveTemplateFileName(envFileName) {
|
|
482
|
+
return `${envFileName}.tpl`;
|
|
483
|
+
}
|
|
398
484
|
function formatTimestamp() {
|
|
399
|
-
return
|
|
485
|
+
return new Date().toISOString().replace("T", " ").replace(/\.\d{3}Z$/, " UTC");
|
|
400
486
|
}
|
|
401
487
|
function generateTemplateHeader(templateFileName) {
|
|
402
488
|
const envFileName = deriveEnvFileName(templateFileName);
|
|
403
489
|
return [
|
|
404
490
|
SEPARATOR,
|
|
405
|
-
`# ${templateFileName}
|
|
491
|
+
`# ${templateFileName} — 1Password Secret References`,
|
|
406
492
|
"#",
|
|
407
493
|
"# This template contains references to secrets stored in 1Password.",
|
|
408
|
-
"# The actual values are not stored here
|
|
494
|
+
"# The actual values are not stored here — only secret references.",
|
|
409
495
|
"#",
|
|
410
496
|
`# To generate ${envFileName} with real values:`,
|
|
411
497
|
`# op2env ${templateFileName}`,
|
|
@@ -414,14 +500,33 @@ function generateTemplateHeader(templateFileName) {
|
|
|
414
500
|
`# op run --env-file ${templateFileName} -- npm start`,
|
|
415
501
|
"#",
|
|
416
502
|
`# Pushed: ${formatTimestamp()}`,
|
|
417
|
-
`# Generated by env2op v${
|
|
503
|
+
`# Generated by env2op v${import__package.default.version}`,
|
|
418
504
|
"# https://github.com/tolgamorf/env2op-cli",
|
|
419
505
|
SEPARATOR,
|
|
420
506
|
""
|
|
421
507
|
];
|
|
422
508
|
}
|
|
423
|
-
function
|
|
424
|
-
const
|
|
509
|
+
function generateEnvHeader(envFileName) {
|
|
510
|
+
const templateFileName = deriveTemplateFileName(envFileName);
|
|
511
|
+
return [
|
|
512
|
+
SEPARATOR,
|
|
513
|
+
`# ${envFileName} — Environment Variables`,
|
|
514
|
+
"#",
|
|
515
|
+
"# WARNING: This file contains sensitive values. Do not commit to git!",
|
|
516
|
+
"#",
|
|
517
|
+
`# To push updates to 1Password and generate ${templateFileName}:`,
|
|
518
|
+
`# env2op ${envFileName} <vault> "<item_name>"`,
|
|
519
|
+
"#",
|
|
520
|
+
`# Pulled: ${formatTimestamp()}`,
|
|
521
|
+
`# Generated by op2env v${import__package.default.version}`,
|
|
522
|
+
"# https://github.com/tolgamorf/env2op-cli",
|
|
523
|
+
SEPARATOR,
|
|
524
|
+
"",
|
|
525
|
+
""
|
|
526
|
+
];
|
|
527
|
+
}
|
|
528
|
+
function generateTemplateContent(options, templateFileName) {
|
|
529
|
+
const { vaultId, itemId, lines: envLines, fieldIds } = options;
|
|
425
530
|
const outputLines = generateTemplateHeader(templateFileName);
|
|
426
531
|
for (const line of envLines) {
|
|
427
532
|
switch (line.type) {
|
|
@@ -438,129 +543,80 @@ function generateTemplateContent(options2, templateFileName) {
|
|
|
438
543
|
}
|
|
439
544
|
}
|
|
440
545
|
}
|
|
441
|
-
return `${outputLines.join(
|
|
546
|
+
return `${outputLines.join(`
|
|
547
|
+
`)}
|
|
442
548
|
`;
|
|
443
549
|
}
|
|
444
550
|
function writeTemplate(content, outputPath) {
|
|
445
551
|
writeFileSync(outputPath, content, "utf-8");
|
|
446
552
|
}
|
|
447
553
|
function generateUsageInstructions(templatePath) {
|
|
448
|
-
return ["
|
|
554
|
+
return ["Usage:", ` op2env ${templatePath}`, ` op run --env-file ${templatePath} -- npm start`].join(`
|
|
555
|
+
`);
|
|
449
556
|
}
|
|
450
557
|
|
|
451
558
|
// src/utils/logger.ts
|
|
452
559
|
import * as p from "@clack/prompts";
|
|
453
|
-
import
|
|
560
|
+
import pc2 from "picocolors";
|
|
454
561
|
var symbols = {
|
|
455
|
-
success:
|
|
456
|
-
error:
|
|
457
|
-
warning:
|
|
458
|
-
info:
|
|
459
|
-
arrow:
|
|
460
|
-
bullet:
|
|
562
|
+
success: pc2.green("✓"),
|
|
563
|
+
error: pc2.red("✗"),
|
|
564
|
+
warning: pc2.yellow("⚠"),
|
|
565
|
+
info: pc2.blue("ℹ"),
|
|
566
|
+
arrow: pc2.cyan("→"),
|
|
567
|
+
bullet: pc2.dim("•")
|
|
461
568
|
};
|
|
462
569
|
var logger = {
|
|
463
|
-
/**
|
|
464
|
-
* Display CLI intro banner
|
|
465
|
-
*/
|
|
466
570
|
intro(name, version, dryRun = false) {
|
|
467
|
-
const label = dryRun ?
|
|
571
|
+
const label = dryRun ? pc2.bgYellow(pc2.black(` ${name} v${version} [DRY RUN] `)) : pc2.bgCyan(pc2.black(` ${name} v${version} `));
|
|
468
572
|
p.intro(label);
|
|
469
573
|
},
|
|
470
|
-
/**
|
|
471
|
-
* Display section header
|
|
472
|
-
*/
|
|
473
574
|
section(title) {
|
|
474
575
|
console.log(`
|
|
475
|
-
${
|
|
576
|
+
${pc2.bold(pc2.underline(title))}`);
|
|
476
577
|
},
|
|
477
|
-
/**
|
|
478
|
-
* Success message
|
|
479
|
-
*/
|
|
480
578
|
success(message) {
|
|
481
579
|
p.log.success(message);
|
|
482
580
|
},
|
|
483
|
-
/**
|
|
484
|
-
* Error message
|
|
485
|
-
*/
|
|
486
581
|
error(message) {
|
|
487
582
|
p.log.error(message);
|
|
488
583
|
},
|
|
489
|
-
/**
|
|
490
|
-
* Warning message
|
|
491
|
-
*/
|
|
492
584
|
warn(message) {
|
|
493
585
|
p.log.warn(message);
|
|
494
586
|
},
|
|
495
|
-
/**
|
|
496
|
-
* Info message
|
|
497
|
-
*/
|
|
498
587
|
info(message) {
|
|
499
588
|
p.log.info(message);
|
|
500
589
|
},
|
|
501
|
-
/**
|
|
502
|
-
* Step in a process
|
|
503
|
-
*/
|
|
504
590
|
step(message) {
|
|
505
591
|
p.log.step(message);
|
|
506
592
|
},
|
|
507
|
-
/**
|
|
508
|
-
* Message (neutral)
|
|
509
|
-
*/
|
|
510
593
|
message(message) {
|
|
511
594
|
p.log.message(message);
|
|
512
595
|
},
|
|
513
|
-
/**
|
|
514
|
-
* Display key-value pair
|
|
515
|
-
*/
|
|
516
596
|
keyValue(key, value, indent = 2) {
|
|
517
|
-
console.log(`${" ".repeat(indent)}${
|
|
597
|
+
console.log(`${" ".repeat(indent)}${pc2.dim(key)}: ${pc2.cyan(value)}`);
|
|
518
598
|
},
|
|
519
|
-
/**
|
|
520
|
-
* Display list item
|
|
521
|
-
*/
|
|
522
599
|
listItem(item, indent = 2) {
|
|
523
600
|
console.log(`${" ".repeat(indent)}${symbols.bullet} ${item}`);
|
|
524
601
|
},
|
|
525
|
-
/**
|
|
526
|
-
* Display arrow item
|
|
527
|
-
*/
|
|
528
602
|
arrowItem(item, indent = 2) {
|
|
529
603
|
console.log(`${" ".repeat(indent)}${symbols.arrow} ${item}`);
|
|
530
604
|
},
|
|
531
|
-
/**
|
|
532
|
-
* Display dry run indicator
|
|
533
|
-
*/
|
|
534
605
|
dryRun(message) {
|
|
535
|
-
console.log(`${
|
|
606
|
+
console.log(`${pc2.yellow("[DRY RUN]")} ${message}`);
|
|
536
607
|
},
|
|
537
|
-
/**
|
|
538
|
-
* Create a spinner for async operations
|
|
539
|
-
*/
|
|
540
608
|
spinner() {
|
|
541
609
|
return p.spinner();
|
|
542
610
|
},
|
|
543
|
-
/**
|
|
544
|
-
* Display outro message
|
|
545
|
-
*/
|
|
546
611
|
outro(message) {
|
|
547
|
-
p.outro(
|
|
612
|
+
p.outro(pc2.green(message));
|
|
548
613
|
},
|
|
549
|
-
/**
|
|
550
|
-
* Display cancellation message
|
|
551
|
-
*/
|
|
552
614
|
cancel(message) {
|
|
553
615
|
p.cancel(message);
|
|
554
616
|
},
|
|
555
|
-
/**
|
|
556
|
-
* Display a note block
|
|
557
|
-
*/
|
|
558
617
|
note(message, title) {
|
|
559
618
|
p.note(message, title);
|
|
560
619
|
},
|
|
561
|
-
/**
|
|
562
|
-
* Format a field list for display
|
|
563
|
-
*/
|
|
564
620
|
formatFields(fields, max = 3) {
|
|
565
621
|
if (fields.length <= max) {
|
|
566
622
|
return fields.join(", ");
|
|
@@ -569,113 +625,147 @@ ${pc.bold(pc.underline(title))}`);
|
|
|
569
625
|
}
|
|
570
626
|
};
|
|
571
627
|
|
|
628
|
+
// src/utils/timing.ts
|
|
629
|
+
import { setTimeout } from "node:timers/promises";
|
|
630
|
+
var MIN_SPINNER_TIME = 500;
|
|
631
|
+
async function withMinTime(promise, minTime = MIN_SPINNER_TIME) {
|
|
632
|
+
const [result] = await Promise.all([promise, setTimeout(minTime)]);
|
|
633
|
+
return result;
|
|
634
|
+
}
|
|
635
|
+
|
|
572
636
|
// src/commands/convert.ts
|
|
573
|
-
async function runConvert(
|
|
574
|
-
const { envFile
|
|
575
|
-
const
|
|
576
|
-
logger.intro("env2op",
|
|
637
|
+
async function runConvert(options) {
|
|
638
|
+
const { envFile, vault, itemName, output, dryRun, secret, force, verbose } = options;
|
|
639
|
+
const pkg2 = await Promise.resolve().then(() => __toESM(require_package(), 1));
|
|
640
|
+
logger.intro("env2op", pkg2.version, dryRun);
|
|
577
641
|
try {
|
|
578
|
-
const parseResult = parseEnvFile(
|
|
579
|
-
validateParseResult(parseResult,
|
|
642
|
+
const parseResult = await parseEnvFile(envFile);
|
|
643
|
+
validateParseResult(parseResult, envFile);
|
|
580
644
|
const { variables, lines } = parseResult;
|
|
581
|
-
|
|
582
|
-
logger.
|
|
645
|
+
const varCount = variables.length;
|
|
646
|
+
logger.success(`Parsed ${basename(envFile)} — found ${varCount} variable${varCount === 1 ? "" : "s"}`);
|
|
583
647
|
for (const error of parseResult.errors) {
|
|
584
648
|
logger.warn(error);
|
|
585
649
|
}
|
|
586
650
|
let itemResult = null;
|
|
587
651
|
if (dryRun) {
|
|
588
|
-
logger.
|
|
589
|
-
logger.
|
|
590
|
-
logger.
|
|
652
|
+
logger.step("Checking 1Password CLI...");
|
|
653
|
+
logger.warn("Would check if 1Password CLI is installed and authenticated");
|
|
654
|
+
logger.step(`Checking for vault "${vault}"...`);
|
|
655
|
+
logger.warn(`Would check if vault "${vault}" exists`);
|
|
656
|
+
logger.step(`Checking for item "${itemName}"...`);
|
|
657
|
+
logger.warn(`Would check if item "${itemName}" exists`);
|
|
658
|
+
logger.step("Pushing environment variables...");
|
|
659
|
+
logger.warn("Would push to 1Password:");
|
|
660
|
+
logger.keyValue("Vault", vault);
|
|
661
|
+
logger.keyValue("Title", itemName);
|
|
591
662
|
logger.keyValue("Type", secret ? "password (hidden)" : "text (visible)");
|
|
592
663
|
logger.keyValue("Fields", logger.formatFields(variables.map((v) => v.key)));
|
|
593
664
|
} else {
|
|
594
|
-
const
|
|
665
|
+
const authSpinner = p2.spinner();
|
|
666
|
+
authSpinner.start("Checking 1Password CLI...");
|
|
667
|
+
const opInstalled = await checkOpCli({ verbose });
|
|
595
668
|
if (!opInstalled) {
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
"OP_CLI_NOT_INSTALLED",
|
|
599
|
-
"Install from https://1password.com/downloads/command-line/"
|
|
600
|
-
);
|
|
669
|
+
authSpinner.stop("1Password CLI not found");
|
|
670
|
+
throw new Env2OpError("1Password CLI (op) is not installed", "OP_CLI_NOT_INSTALLED", "Install from https://1password.com/downloads/command-line/");
|
|
601
671
|
}
|
|
602
|
-
|
|
672
|
+
let signedIn = await checkSignedIn({ verbose });
|
|
603
673
|
if (!signedIn) {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
674
|
+
authSpinner.message("Signing in to 1Password...");
|
|
675
|
+
const signInSuccess = await signIn({ verbose });
|
|
676
|
+
if (!signInSuccess) {
|
|
677
|
+
authSpinner.stop();
|
|
678
|
+
throw new Env2OpError("Failed to sign in to 1Password CLI", "OP_SIGNIN_FAILED", 'Try running "op signin" manually');
|
|
679
|
+
}
|
|
680
|
+
signedIn = await checkSignedIn({ verbose });
|
|
681
|
+
if (!signedIn) {
|
|
682
|
+
authSpinner.stop();
|
|
683
|
+
throw new Env2OpError("Not signed in to 1Password CLI", "OP_NOT_SIGNED_IN", 'Run "op signin" to authenticate');
|
|
684
|
+
}
|
|
609
685
|
}
|
|
610
|
-
|
|
686
|
+
authSpinner.stop("1Password CLI ready");
|
|
687
|
+
const vaultSpinner = p2.spinner();
|
|
688
|
+
vaultSpinner.start(`Checking for vault "${vault}"...`);
|
|
689
|
+
const vaultFound = await withMinTime(vaultExists(vault, { verbose }));
|
|
611
690
|
if (vaultFound) {
|
|
612
|
-
|
|
691
|
+
vaultSpinner.stop(`Vault "${vault}" found`);
|
|
613
692
|
} else {
|
|
614
693
|
if (force) {
|
|
615
|
-
|
|
616
|
-
await createVault(
|
|
617
|
-
|
|
694
|
+
vaultSpinner.message(`Vault "${vault}" not found, creating...`);
|
|
695
|
+
await createVault(vault, { verbose });
|
|
696
|
+
vaultSpinner.stop(`Created vault "${vault}"`);
|
|
618
697
|
} else {
|
|
698
|
+
vaultSpinner.stop(`Vault "${vault}" not found`);
|
|
619
699
|
const shouldCreate = await p2.confirm({
|
|
620
|
-
message: `Vault "${
|
|
700
|
+
message: `Vault "${vault}" does not exist. Create it?`
|
|
621
701
|
});
|
|
622
702
|
if (p2.isCancel(shouldCreate) || !shouldCreate) {
|
|
623
703
|
logger.cancel("Operation cancelled");
|
|
624
704
|
logger.info('Run "op vault list" to see available vaults');
|
|
625
705
|
process.exit(0);
|
|
626
706
|
}
|
|
627
|
-
const
|
|
628
|
-
|
|
629
|
-
await createVault(
|
|
630
|
-
|
|
707
|
+
const createSpinner = p2.spinner();
|
|
708
|
+
createSpinner.start(`Creating vault "${vault}"...`);
|
|
709
|
+
await createVault(vault, { verbose });
|
|
710
|
+
createSpinner.stop(`Created vault "${vault}"`);
|
|
631
711
|
}
|
|
632
712
|
}
|
|
633
|
-
const
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
713
|
+
const itemSpinner = p2.spinner();
|
|
714
|
+
itemSpinner.start(`Checking for item "${itemName}"...`);
|
|
715
|
+
const existingItemId = await withMinTime(itemExists(vault, itemName, { verbose }));
|
|
716
|
+
if (existingItemId) {
|
|
717
|
+
itemSpinner.stop(`Item "${itemName}" found`);
|
|
718
|
+
if (!force) {
|
|
639
719
|
const shouldOverwrite = await p2.confirm({
|
|
640
|
-
message: `Item "${
|
|
720
|
+
message: `Item "${itemName}" already exists in vault "${vault}". Update it?`
|
|
641
721
|
});
|
|
642
722
|
if (p2.isCancel(shouldOverwrite) || !shouldOverwrite) {
|
|
643
723
|
logger.cancel("Operation cancelled");
|
|
644
724
|
process.exit(0);
|
|
645
725
|
}
|
|
646
|
-
await deleteItem(vault2, itemName2);
|
|
647
726
|
}
|
|
727
|
+
} else {
|
|
728
|
+
itemSpinner.stop(`Item "${itemName}" not found`);
|
|
648
729
|
}
|
|
649
|
-
const
|
|
650
|
-
|
|
730
|
+
const pushSpinner = p2.spinner();
|
|
731
|
+
pushSpinner.start("Pushing environment variables...");
|
|
651
732
|
try {
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
733
|
+
if (existingItemId) {
|
|
734
|
+
itemResult = await editSecureNote({
|
|
735
|
+
vault,
|
|
736
|
+
title: itemName,
|
|
737
|
+
fields: variables,
|
|
738
|
+
secret,
|
|
739
|
+
verbose,
|
|
740
|
+
itemId: existingItemId
|
|
741
|
+
});
|
|
742
|
+
} else {
|
|
743
|
+
itemResult = await createSecureNote({
|
|
744
|
+
vault,
|
|
745
|
+
title: itemName,
|
|
746
|
+
fields: variables,
|
|
747
|
+
secret,
|
|
748
|
+
verbose
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
pushSpinner.stop(existingItemId ? `Updated "${itemResult.title}" in vault "${itemResult.vault}"` : `Created "${itemResult.title}" in vault "${itemResult.vault}"`);
|
|
659
752
|
} catch (error) {
|
|
660
|
-
|
|
753
|
+
pushSpinner.stop();
|
|
661
754
|
throw error;
|
|
662
755
|
}
|
|
663
756
|
}
|
|
664
|
-
const templatePath = output ?? join(dirname(
|
|
757
|
+
const templatePath = output ?? join(dirname(envFile), `${basename(envFile)}.tpl`);
|
|
665
758
|
const templateFileName = basename(templatePath);
|
|
666
759
|
if (dryRun) {
|
|
667
760
|
logger.warn(`Would generate template: ${templatePath}`);
|
|
668
761
|
} else if (itemResult) {
|
|
669
|
-
const templateContent = generateTemplateContent(
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
},
|
|
677
|
-
templateFileName
|
|
678
|
-
);
|
|
762
|
+
const templateContent = generateTemplateContent({
|
|
763
|
+
vaultId: itemResult.vaultId,
|
|
764
|
+
itemId: itemResult.id,
|
|
765
|
+
variables,
|
|
766
|
+
lines,
|
|
767
|
+
fieldIds: itemResult.fieldIds
|
|
768
|
+
}, templateFileName);
|
|
679
769
|
writeTemplate(templateContent, templatePath);
|
|
680
770
|
logger.success(`Generated template: ${templatePath}`);
|
|
681
771
|
}
|
|
@@ -701,10 +791,10 @@ async function runConvert(options2) {
|
|
|
701
791
|
// src/cli.ts
|
|
702
792
|
var pkg2 = await Promise.resolve().then(() => __toESM(require_package(), 1));
|
|
703
793
|
var args = process.argv.slice(2);
|
|
704
|
-
var flags =
|
|
794
|
+
var flags = new Set;
|
|
705
795
|
var positional = [];
|
|
706
796
|
var options = {};
|
|
707
|
-
for (let i = 0;
|
|
797
|
+
for (let i = 0;i < args.length; i++) {
|
|
708
798
|
const arg = args[i];
|
|
709
799
|
if (arg === "-o" || arg === "--output") {
|
|
710
800
|
const next = args[i + 1];
|
|
@@ -744,67 +834,73 @@ await runConvert({
|
|
|
744
834
|
output: options.output,
|
|
745
835
|
dryRun: flags.has("dry-run"),
|
|
746
836
|
secret: flags.has("secret"),
|
|
747
|
-
force: flags.has("f") || flags.has("force")
|
|
837
|
+
force: flags.has("f") || flags.has("force"),
|
|
838
|
+
verbose: flags.has("verbose")
|
|
748
839
|
});
|
|
749
840
|
function showHelp() {
|
|
750
|
-
const name =
|
|
751
|
-
const version =
|
|
841
|
+
const name = pc3.bold(pc3.cyan("env2op"));
|
|
842
|
+
const version = pc3.dim(`v${pkg2.version}`);
|
|
752
843
|
console.log(`
|
|
753
844
|
${name} ${version}
|
|
754
845
|
${pkg2.description}
|
|
755
846
|
|
|
756
|
-
${
|
|
757
|
-
${
|
|
847
|
+
${pc3.bold("USAGE")}
|
|
848
|
+
${pc3.cyan("$")} env2op ${pc3.yellow("<env_file>")} ${pc3.yellow("<vault>")} ${pc3.yellow("<item_name>")} ${pc3.dim("[options]")}
|
|
758
849
|
|
|
759
|
-
${
|
|
760
|
-
${
|
|
761
|
-
${
|
|
762
|
-
${
|
|
850
|
+
${pc3.bold("ARGUMENTS")}
|
|
851
|
+
${pc3.yellow("env_file")} Path to .env file
|
|
852
|
+
${pc3.yellow("vault")} 1Password vault name
|
|
853
|
+
${pc3.yellow("item_name")} Name for the Secure Note in 1Password
|
|
763
854
|
|
|
764
|
-
${
|
|
765
|
-
${
|
|
766
|
-
${
|
|
767
|
-
${
|
|
768
|
-
${
|
|
769
|
-
${
|
|
770
|
-
${
|
|
855
|
+
${pc3.bold("OPTIONS")}
|
|
856
|
+
${pc3.cyan("-o, --output")} Output template path (default: <env_file>.tpl)
|
|
857
|
+
${pc3.cyan("-f, --force")} Skip confirmation prompts
|
|
858
|
+
${pc3.cyan("--dry-run")} Preview actions without executing
|
|
859
|
+
${pc3.cyan("--secret")} Store all fields as password type (hidden)
|
|
860
|
+
${pc3.cyan("--verbose")} Show op CLI output
|
|
861
|
+
${pc3.cyan("-h, --help")} Show this help message
|
|
862
|
+
${pc3.cyan("-v, --version")} Show version
|
|
771
863
|
|
|
772
|
-
${
|
|
773
|
-
${
|
|
774
|
-
${
|
|
864
|
+
${pc3.bold("EXAMPLES")}
|
|
865
|
+
${pc3.dim("# Basic usage")}
|
|
866
|
+
${pc3.cyan("$")} env2op .env.production Personal "MyApp - Production"
|
|
775
867
|
|
|
776
|
-
${
|
|
777
|
-
${
|
|
868
|
+
${pc3.dim("# Custom output path")}
|
|
869
|
+
${pc3.cyan("$")} env2op .env Personal "MyApp" -o secrets.tpl
|
|
778
870
|
|
|
779
|
-
${
|
|
780
|
-
${
|
|
871
|
+
${pc3.dim("# Preview without making changes")}
|
|
872
|
+
${pc3.cyan("$")} env2op .env Personal "MyApp" --dry-run
|
|
781
873
|
|
|
782
|
-
${
|
|
783
|
-
${
|
|
874
|
+
${pc3.dim("# Store as hidden password fields")}
|
|
875
|
+
${pc3.cyan("$")} env2op .env Personal "MyApp" --secret
|
|
784
876
|
|
|
785
|
-
${
|
|
786
|
-
${
|
|
877
|
+
${pc3.dim("# Skip confirmation prompts (for CI/scripts)")}
|
|
878
|
+
${pc3.cyan("$")} env2op .env Personal "MyApp" -f
|
|
787
879
|
|
|
788
|
-
${
|
|
789
|
-
${
|
|
880
|
+
${pc3.bold("DOCUMENTATION")}
|
|
881
|
+
${pc3.dim("https://github.com/tolgamorf/env2op-cli")}
|
|
790
882
|
`);
|
|
791
883
|
}
|
|
792
884
|
function showMissingArgsError(provided) {
|
|
793
885
|
const missing = [];
|
|
794
|
-
if (provided.length < 1)
|
|
795
|
-
|
|
796
|
-
if (provided.length <
|
|
886
|
+
if (provided.length < 1)
|
|
887
|
+
missing.push("env_file");
|
|
888
|
+
if (provided.length < 2)
|
|
889
|
+
missing.push("vault");
|
|
890
|
+
if (provided.length < 3)
|
|
891
|
+
missing.push("item_name");
|
|
797
892
|
console.log(`
|
|
798
|
-
${
|
|
893
|
+
${pc3.red(pc3.bold("Error:"))} Missing required arguments
|
|
799
894
|
|
|
800
|
-
${
|
|
895
|
+
${pc3.bold("Usage:")} env2op ${pc3.yellow("<env_file>")} ${pc3.yellow("<vault>")} ${pc3.yellow("<item_name>")} ${pc3.dim("[options]")}
|
|
801
896
|
|
|
802
|
-
${
|
|
803
|
-
${missing.map((arg) => ` ${
|
|
897
|
+
${pc3.bold("Missing:")}
|
|
898
|
+
${missing.map((arg) => ` ${pc3.red("•")} ${pc3.yellow(arg)}`).join(`
|
|
899
|
+
`)}
|
|
804
900
|
|
|
805
|
-
${
|
|
806
|
-
${
|
|
901
|
+
${pc3.bold("Example:")}
|
|
902
|
+
${pc3.cyan("$")} env2op .env.production Personal "MyApp - Production"
|
|
807
903
|
|
|
808
|
-
Run ${
|
|
904
|
+
Run ${pc3.cyan("env2op --help")} for more information.
|
|
809
905
|
`);
|
|
810
906
|
}
|