@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/index.js
ADDED
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
// src/core/env-parser.ts
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
|
|
4
|
+
// src/utils/errors.ts
|
|
5
|
+
class Env2OpError extends Error {
|
|
6
|
+
code;
|
|
7
|
+
suggestion;
|
|
8
|
+
constructor(message, code, suggestion) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.suggestion = suggestion;
|
|
12
|
+
this.name = "Env2OpError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
var ErrorCodes = {
|
|
16
|
+
ENV_FILE_NOT_FOUND: "ENV_FILE_NOT_FOUND",
|
|
17
|
+
ENV_FILE_EMPTY: "ENV_FILE_EMPTY",
|
|
18
|
+
OP_CLI_NOT_INSTALLED: "OP_CLI_NOT_INSTALLED",
|
|
19
|
+
OP_NOT_SIGNED_IN: "OP_NOT_SIGNED_IN",
|
|
20
|
+
OP_SIGNIN_FAILED: "OP_SIGNIN_FAILED",
|
|
21
|
+
VAULT_NOT_FOUND: "VAULT_NOT_FOUND",
|
|
22
|
+
VAULT_CREATE_FAILED: "VAULT_CREATE_FAILED",
|
|
23
|
+
ITEM_EXISTS: "ITEM_EXISTS",
|
|
24
|
+
ITEM_CREATE_FAILED: "ITEM_CREATE_FAILED",
|
|
25
|
+
ITEM_EDIT_FAILED: "ITEM_EDIT_FAILED",
|
|
26
|
+
PARSE_ERROR: "PARSE_ERROR",
|
|
27
|
+
TEMPLATE_NOT_FOUND: "TEMPLATE_NOT_FOUND",
|
|
28
|
+
INJECT_FAILED: "INJECT_FAILED"
|
|
29
|
+
};
|
|
30
|
+
var errors = {
|
|
31
|
+
envFileNotFound: (path) => new Env2OpError(`File not found: ${path}`, ErrorCodes.ENV_FILE_NOT_FOUND, "Check that the file path is correct"),
|
|
32
|
+
envFileEmpty: (path) => new Env2OpError(`No valid environment variables found in ${path}`, ErrorCodes.ENV_FILE_EMPTY, "Ensure the file contains KEY=value pairs"),
|
|
33
|
+
opCliNotInstalled: () => new Env2OpError("1Password CLI (op) is not installed", ErrorCodes.OP_CLI_NOT_INSTALLED, "Install it from https://1password.com/downloads/command-line/"),
|
|
34
|
+
opNotSignedIn: () => new Env2OpError("Not signed in to 1Password CLI", ErrorCodes.OP_NOT_SIGNED_IN, 'Run "op signin" to authenticate'),
|
|
35
|
+
vaultNotFound: (vault) => new Env2OpError(`Vault not found: ${vault}`, ErrorCodes.VAULT_NOT_FOUND, 'Run "op vault list" to see available vaults'),
|
|
36
|
+
vaultCreateFailed: (message) => new Env2OpError(`Failed to create vault: ${message}`, ErrorCodes.VAULT_CREATE_FAILED),
|
|
37
|
+
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"),
|
|
38
|
+
itemCreateFailed: (message) => new Env2OpError(`Failed to create 1Password item: ${message}`, ErrorCodes.ITEM_CREATE_FAILED),
|
|
39
|
+
itemEditFailed: (message) => new Env2OpError(`Failed to edit 1Password item: ${message}`, ErrorCodes.ITEM_EDIT_FAILED),
|
|
40
|
+
parseError: (line, message) => new Env2OpError(`Parse error at line ${line}: ${message}`, ErrorCodes.PARSE_ERROR)
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// src/core/env-parser.ts
|
|
44
|
+
var HEADER_SEPARATOR = "# ===========================================================================";
|
|
45
|
+
function stripHeaders(content) {
|
|
46
|
+
const lines = content.split(`
|
|
47
|
+
`);
|
|
48
|
+
const result = [];
|
|
49
|
+
let inHeader = false;
|
|
50
|
+
for (const line of lines) {
|
|
51
|
+
const trimmed = line.trim();
|
|
52
|
+
if (trimmed === HEADER_SEPARATOR) {
|
|
53
|
+
if (!inHeader) {
|
|
54
|
+
inHeader = true;
|
|
55
|
+
} else {
|
|
56
|
+
inHeader = false;
|
|
57
|
+
}
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (!inHeader) {
|
|
61
|
+
result.push(line);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
while (result.length > 0 && result[0]?.trim() === "") {
|
|
65
|
+
result.shift();
|
|
66
|
+
}
|
|
67
|
+
return result.join(`
|
|
68
|
+
`);
|
|
69
|
+
}
|
|
70
|
+
function parseValue(raw) {
|
|
71
|
+
const trimmed = raw.trim();
|
|
72
|
+
if (trimmed.startsWith('"')) {
|
|
73
|
+
const endQuote = trimmed.indexOf('"', 1);
|
|
74
|
+
if (endQuote !== -1) {
|
|
75
|
+
return trimmed.slice(1, endQuote);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (trimmed.startsWith("'")) {
|
|
79
|
+
const endQuote = trimmed.indexOf("'", 1);
|
|
80
|
+
if (endQuote !== -1) {
|
|
81
|
+
return trimmed.slice(1, endQuote);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const parts = trimmed.split(/\s+#/);
|
|
85
|
+
return (parts[0] ?? trimmed).trim();
|
|
86
|
+
}
|
|
87
|
+
function stripBom(content) {
|
|
88
|
+
if (content.charCodeAt(0) === 65279) {
|
|
89
|
+
return content.slice(1);
|
|
90
|
+
}
|
|
91
|
+
return content;
|
|
92
|
+
}
|
|
93
|
+
async function parseEnvFile(filePath) {
|
|
94
|
+
let rawContent;
|
|
95
|
+
try {
|
|
96
|
+
rawContent = await readFile(filePath, "utf-8");
|
|
97
|
+
} catch {
|
|
98
|
+
throw errors.envFileNotFound(filePath);
|
|
99
|
+
}
|
|
100
|
+
const content = stripHeaders(stripBom(rawContent));
|
|
101
|
+
const rawLines = content.split(`
|
|
102
|
+
`);
|
|
103
|
+
const variables = [];
|
|
104
|
+
const lines = [];
|
|
105
|
+
const parseErrors = [];
|
|
106
|
+
let currentComment = "";
|
|
107
|
+
for (let i = 0;i < rawLines.length; i++) {
|
|
108
|
+
const line = rawLines[i] ?? "";
|
|
109
|
+
const trimmed = line.trim();
|
|
110
|
+
const lineNumber = i + 1;
|
|
111
|
+
if (!trimmed) {
|
|
112
|
+
lines.push({ type: "empty" });
|
|
113
|
+
currentComment = "";
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (trimmed.startsWith("#")) {
|
|
117
|
+
lines.push({ type: "comment", content: line });
|
|
118
|
+
currentComment = trimmed.slice(1).trim();
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
|
|
122
|
+
if (match?.[1]) {
|
|
123
|
+
const key = match[1];
|
|
124
|
+
const rawValue = match[2] ?? "";
|
|
125
|
+
const value = parseValue(rawValue);
|
|
126
|
+
variables.push({
|
|
127
|
+
key,
|
|
128
|
+
value,
|
|
129
|
+
comment: currentComment || undefined,
|
|
130
|
+
line: lineNumber
|
|
131
|
+
});
|
|
132
|
+
lines.push({ type: "variable", key, value });
|
|
133
|
+
currentComment = "";
|
|
134
|
+
} else if (trimmed.includes("=")) {
|
|
135
|
+
parseErrors.push(`Line ${lineNumber}: Invalid variable name`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return { variables, lines, errors: parseErrors };
|
|
139
|
+
}
|
|
140
|
+
function validateParseResult(result, filePath) {
|
|
141
|
+
if (result.variables.length === 0) {
|
|
142
|
+
throw errors.envFileEmpty(filePath);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// src/utils/shell.ts
|
|
146
|
+
import { spawn } from "node:child_process";
|
|
147
|
+
import pc from "picocolors";
|
|
148
|
+
function quoteArg(arg) {
|
|
149
|
+
if (/[ [\]'"\\=]/.test(arg)) {
|
|
150
|
+
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
151
|
+
}
|
|
152
|
+
return arg;
|
|
153
|
+
}
|
|
154
|
+
async function exec(command, args = [], options = {}) {
|
|
155
|
+
const { verbose = false } = options;
|
|
156
|
+
const fullCommand = `${command} ${args.map(quoteArg).join(" ")}`;
|
|
157
|
+
if (verbose) {
|
|
158
|
+
console.log(pc.dim(`$ ${fullCommand}`));
|
|
159
|
+
}
|
|
160
|
+
return new Promise((resolve) => {
|
|
161
|
+
const proc = spawn(command, args, {
|
|
162
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
163
|
+
});
|
|
164
|
+
const stdoutChunks = [];
|
|
165
|
+
const stderrChunks = [];
|
|
166
|
+
proc.stdout?.on("data", (data) => {
|
|
167
|
+
const text = Buffer.isBuffer(data) ? data.toString() : String(data);
|
|
168
|
+
stdoutChunks.push(text);
|
|
169
|
+
if (verbose) {
|
|
170
|
+
process.stdout.write(text);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
proc.stderr?.on("data", (data) => {
|
|
174
|
+
const text = Buffer.isBuffer(data) ? data.toString() : String(data);
|
|
175
|
+
stderrChunks.push(text);
|
|
176
|
+
if (verbose) {
|
|
177
|
+
process.stderr.write(text);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
proc.on("close", (code) => {
|
|
181
|
+
resolve({
|
|
182
|
+
stdout: stdoutChunks.join(""),
|
|
183
|
+
stderr: stderrChunks.join(""),
|
|
184
|
+
exitCode: code ?? 1
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
proc.on("error", (err) => {
|
|
188
|
+
stderrChunks.push(err.message);
|
|
189
|
+
resolve({
|
|
190
|
+
stdout: stdoutChunks.join(""),
|
|
191
|
+
stderr: stderrChunks.join(""),
|
|
192
|
+
exitCode: 1
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
async function execWithStdin(command, args = [], options) {
|
|
198
|
+
const { stdin: stdinContent, verbose = false } = options;
|
|
199
|
+
if (verbose) {
|
|
200
|
+
const fullCommand = `${command} ${args.map(quoteArg).join(" ")}`;
|
|
201
|
+
console.log(pc.dim(`$ echo '...' | ${fullCommand}`));
|
|
202
|
+
}
|
|
203
|
+
return new Promise((resolve) => {
|
|
204
|
+
const proc = spawn(command, args, {
|
|
205
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
206
|
+
});
|
|
207
|
+
const stdoutChunks = [];
|
|
208
|
+
const stderrChunks = [];
|
|
209
|
+
proc.stdin?.write(stdinContent);
|
|
210
|
+
proc.stdin?.end();
|
|
211
|
+
proc.stdout?.on("data", (data) => {
|
|
212
|
+
const text = Buffer.isBuffer(data) ? data.toString() : String(data);
|
|
213
|
+
stdoutChunks.push(text);
|
|
214
|
+
if (verbose)
|
|
215
|
+
process.stdout.write(text);
|
|
216
|
+
});
|
|
217
|
+
proc.stderr?.on("data", (data) => {
|
|
218
|
+
const text = Buffer.isBuffer(data) ? data.toString() : String(data);
|
|
219
|
+
stderrChunks.push(text);
|
|
220
|
+
if (verbose)
|
|
221
|
+
process.stderr.write(text);
|
|
222
|
+
});
|
|
223
|
+
proc.on("close", (code) => {
|
|
224
|
+
resolve({
|
|
225
|
+
stdout: stdoutChunks.join(""),
|
|
226
|
+
stderr: stderrChunks.join(""),
|
|
227
|
+
exitCode: code ?? 1
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
proc.on("error", (err) => {
|
|
231
|
+
stderrChunks.push(err.message);
|
|
232
|
+
resolve({
|
|
233
|
+
stdout: stdoutChunks.join(""),
|
|
234
|
+
stderr: stderrChunks.join(""),
|
|
235
|
+
exitCode: 1
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// src/core/onepassword.ts
|
|
242
|
+
async function checkOpCli(options = {}) {
|
|
243
|
+
const result = await exec("op", ["--version"], options);
|
|
244
|
+
return result.exitCode === 0;
|
|
245
|
+
}
|
|
246
|
+
async function checkSignedIn(options = {}) {
|
|
247
|
+
const result = await exec("op", ["whoami", "--format", "json"], options);
|
|
248
|
+
return result.exitCode === 0;
|
|
249
|
+
}
|
|
250
|
+
async function signIn(options = {}) {
|
|
251
|
+
const result = await exec("op", ["signin"], options);
|
|
252
|
+
return result.exitCode === 0;
|
|
253
|
+
}
|
|
254
|
+
async function itemExists(vault, title, options = {}) {
|
|
255
|
+
const result = await exec("op", ["item", "list", "--vault", vault, "--format", "json"], options);
|
|
256
|
+
if (result.exitCode !== 0) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
260
|
+
const items = JSON.parse(result.stdout);
|
|
261
|
+
const item = items.find((item2) => item2.title === title);
|
|
262
|
+
return item?.id ?? null;
|
|
263
|
+
} catch {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
async function vaultExists(vault, options = {}) {
|
|
268
|
+
const result = await exec("op", ["vault", "list", "--format", "json"], options);
|
|
269
|
+
if (result.exitCode !== 0) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
const vaults = JSON.parse(result.stdout);
|
|
274
|
+
return vaults.some((v) => v.name === vault);
|
|
275
|
+
} catch {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
async function createVault(name, options = {}) {
|
|
280
|
+
try {
|
|
281
|
+
await exec("op", ["vault", "create", name], options);
|
|
282
|
+
} catch (error) {
|
|
283
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
284
|
+
throw errors.vaultCreateFailed(message);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
function buildItemTemplate(title, vault, fields, secret) {
|
|
288
|
+
const fieldType = secret ? "CONCEALED" : "STRING";
|
|
289
|
+
return {
|
|
290
|
+
title,
|
|
291
|
+
vault: { name: vault },
|
|
292
|
+
category: "SECURE_NOTE",
|
|
293
|
+
fields: fields.map(({ key, value }) => ({
|
|
294
|
+
type: fieldType,
|
|
295
|
+
label: key,
|
|
296
|
+
value
|
|
297
|
+
}))
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
async function createSecureNote(options) {
|
|
301
|
+
const { vault, title, fields, secret, verbose } = options;
|
|
302
|
+
const template = buildItemTemplate(title, vault, fields, secret);
|
|
303
|
+
const json = JSON.stringify(template);
|
|
304
|
+
try {
|
|
305
|
+
const result = await execWithStdin("op", ["item", "create", "--format", "json"], { stdin: json, verbose });
|
|
306
|
+
if (result.exitCode !== 0) {
|
|
307
|
+
throw new Error(result.stderr || "Failed to create item");
|
|
308
|
+
}
|
|
309
|
+
const item = JSON.parse(result.stdout);
|
|
310
|
+
const fieldIds = {};
|
|
311
|
+
for (const field of item.fields ?? []) {
|
|
312
|
+
if (field.label && field.id) {
|
|
313
|
+
fieldIds[field.label] = field.id;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
id: item.id,
|
|
318
|
+
title: item.title,
|
|
319
|
+
vault: item.vault?.name ?? vault,
|
|
320
|
+
vaultId: item.vault?.id ?? "",
|
|
321
|
+
fieldIds
|
|
322
|
+
};
|
|
323
|
+
} catch (error) {
|
|
324
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
325
|
+
throw errors.itemCreateFailed(message);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
async function editSecureNote(options) {
|
|
329
|
+
const { vault, title, fields, secret, verbose, itemId } = options;
|
|
330
|
+
const template = buildItemTemplate(title, vault, fields, secret);
|
|
331
|
+
const json = JSON.stringify(template);
|
|
332
|
+
try {
|
|
333
|
+
const result = await execWithStdin("op", ["item", "edit", itemId, "--format", "json"], {
|
|
334
|
+
stdin: json,
|
|
335
|
+
verbose
|
|
336
|
+
});
|
|
337
|
+
if (result.exitCode !== 0) {
|
|
338
|
+
throw new Error(result.stderr || "Failed to edit item");
|
|
339
|
+
}
|
|
340
|
+
const item = JSON.parse(result.stdout);
|
|
341
|
+
const fieldIds = {};
|
|
342
|
+
for (const field of item.fields ?? []) {
|
|
343
|
+
if (field.label && field.id) {
|
|
344
|
+
fieldIds[field.label] = field.id;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return {
|
|
348
|
+
id: item.id,
|
|
349
|
+
title: item.title,
|
|
350
|
+
vault: item.vault?.name ?? vault,
|
|
351
|
+
vaultId: item.vault?.id ?? "",
|
|
352
|
+
fieldIds
|
|
353
|
+
};
|
|
354
|
+
} catch (error) {
|
|
355
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
356
|
+
throw errors.itemEditFailed(message);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
// src/core/template-generator.ts
|
|
360
|
+
import { writeFileSync } from "node:fs";
|
|
361
|
+
// package.json
|
|
362
|
+
var package_default = {
|
|
363
|
+
name: "@tolgamorf/env2op-cli",
|
|
364
|
+
version: "0.2.1",
|
|
365
|
+
description: "Convert .env files to 1Password Secure Notes and generate templates for op inject/run",
|
|
366
|
+
type: "module",
|
|
367
|
+
main: "dist/index.js",
|
|
368
|
+
module: "dist/index.js",
|
|
369
|
+
types: "dist/index.d.ts",
|
|
370
|
+
exports: {
|
|
371
|
+
".": {
|
|
372
|
+
import: "./dist/index.js",
|
|
373
|
+
types: "./dist/index.d.ts"
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
bin: {
|
|
377
|
+
env2op: "dist/cli.js",
|
|
378
|
+
op2env: "dist/op2env-cli.js"
|
|
379
|
+
},
|
|
380
|
+
files: [
|
|
381
|
+
"dist",
|
|
382
|
+
"LICENSE",
|
|
383
|
+
"README.md"
|
|
384
|
+
],
|
|
385
|
+
scripts: {
|
|
386
|
+
dev: "bun run src/cli.ts",
|
|
387
|
+
build: "bunup",
|
|
388
|
+
test: "bun test",
|
|
389
|
+
"test:watch": "bun test --watch",
|
|
390
|
+
"test:coverage": "bun test --coverage",
|
|
391
|
+
typecheck: "tsc --noEmit",
|
|
392
|
+
lint: "bunx biome check .",
|
|
393
|
+
"lint:fix": "bunx biome check . --write",
|
|
394
|
+
format: "bunx biome format --write .",
|
|
395
|
+
"format:check": "bunx biome format .",
|
|
396
|
+
prepublishOnly: "bun run build",
|
|
397
|
+
release: "bun run scripts/release.ts"
|
|
398
|
+
},
|
|
399
|
+
keywords: [
|
|
400
|
+
"env",
|
|
401
|
+
"1password",
|
|
402
|
+
"op",
|
|
403
|
+
"cli",
|
|
404
|
+
"secrets",
|
|
405
|
+
"environment-variables",
|
|
406
|
+
"dotenv",
|
|
407
|
+
"secure-notes",
|
|
408
|
+
"bun",
|
|
409
|
+
"op-inject",
|
|
410
|
+
"op-run",
|
|
411
|
+
"template"
|
|
412
|
+
],
|
|
413
|
+
author: {
|
|
414
|
+
name: "Tolga O.",
|
|
415
|
+
url: "https://github.com/tolgamorf"
|
|
416
|
+
},
|
|
417
|
+
license: "MIT",
|
|
418
|
+
repository: {
|
|
419
|
+
type: "git",
|
|
420
|
+
url: "git+https://github.com/tolgamorf/env2op-cli.git"
|
|
421
|
+
},
|
|
422
|
+
bugs: {
|
|
423
|
+
url: "https://github.com/tolgamorf/env2op-cli/issues"
|
|
424
|
+
},
|
|
425
|
+
homepage: "https://github.com/tolgamorf/env2op-cli#readme",
|
|
426
|
+
engines: {
|
|
427
|
+
node: ">=18.0.0"
|
|
428
|
+
},
|
|
429
|
+
dependencies: {
|
|
430
|
+
"@clack/prompts": "^0.11.0",
|
|
431
|
+
picocolors: "^1.1.1"
|
|
432
|
+
},
|
|
433
|
+
devDependencies: {
|
|
434
|
+
"@biomejs/biome": "^2.3.10",
|
|
435
|
+
"@tsconfig/bun": "^1.0.10",
|
|
436
|
+
"@types/bun": "^1.3.5",
|
|
437
|
+
bunup: "^0.16.17",
|
|
438
|
+
typescript: "^5.9.3"
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
// src/core/template-generator.ts
|
|
443
|
+
var SEPARATOR = "# ===========================================================================";
|
|
444
|
+
function deriveEnvFileName(templateFileName) {
|
|
445
|
+
if (templateFileName.endsWith(".tpl")) {
|
|
446
|
+
return templateFileName.slice(0, -4);
|
|
447
|
+
}
|
|
448
|
+
return templateFileName;
|
|
449
|
+
}
|
|
450
|
+
function formatTimestamp() {
|
|
451
|
+
return new Date().toISOString().replace("T", " ").replace(/\.\d{3}Z$/, " UTC");
|
|
452
|
+
}
|
|
453
|
+
function generateTemplateHeader(templateFileName) {
|
|
454
|
+
const envFileName = deriveEnvFileName(templateFileName);
|
|
455
|
+
return [
|
|
456
|
+
SEPARATOR,
|
|
457
|
+
`# ${templateFileName} — 1Password Secret References`,
|
|
458
|
+
"#",
|
|
459
|
+
"# This template contains references to secrets stored in 1Password.",
|
|
460
|
+
"# The actual values are not stored here — only secret references.",
|
|
461
|
+
"#",
|
|
462
|
+
`# To generate ${envFileName} with real values:`,
|
|
463
|
+
`# op2env ${templateFileName}`,
|
|
464
|
+
"#",
|
|
465
|
+
"# To run a command with secrets injected:",
|
|
466
|
+
`# op run --env-file ${templateFileName} -- npm start`,
|
|
467
|
+
"#",
|
|
468
|
+
`# Pushed: ${formatTimestamp()}`,
|
|
469
|
+
`# Generated by env2op v${package_default.version}`,
|
|
470
|
+
"# https://github.com/tolgamorf/env2op-cli",
|
|
471
|
+
SEPARATOR,
|
|
472
|
+
""
|
|
473
|
+
];
|
|
474
|
+
}
|
|
475
|
+
function generateTemplateContent(options, templateFileName) {
|
|
476
|
+
const { vaultId, itemId, lines: envLines, fieldIds } = options;
|
|
477
|
+
const outputLines = generateTemplateHeader(templateFileName);
|
|
478
|
+
for (const line of envLines) {
|
|
479
|
+
switch (line.type) {
|
|
480
|
+
case "empty":
|
|
481
|
+
outputLines.push("");
|
|
482
|
+
break;
|
|
483
|
+
case "comment":
|
|
484
|
+
outputLines.push(line.content);
|
|
485
|
+
break;
|
|
486
|
+
case "variable": {
|
|
487
|
+
const fieldId = fieldIds[line.key] ?? line.key;
|
|
488
|
+
outputLines.push(`${line.key}=op://${vaultId}/${itemId}/${fieldId}`);
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return `${outputLines.join(`
|
|
494
|
+
`)}
|
|
495
|
+
`;
|
|
496
|
+
}
|
|
497
|
+
function writeTemplate(content, outputPath) {
|
|
498
|
+
writeFileSync(outputPath, content, "utf-8");
|
|
499
|
+
}
|
|
500
|
+
function generateUsageInstructions(templatePath) {
|
|
501
|
+
return ["Usage:", ` op2env ${templatePath}`, ` op run --env-file ${templatePath} -- npm start`].join(`
|
|
502
|
+
`);
|
|
503
|
+
}
|
|
504
|
+
export {
|
|
505
|
+
writeTemplate,
|
|
506
|
+
vaultExists,
|
|
507
|
+
validateParseResult,
|
|
508
|
+
signIn,
|
|
509
|
+
parseEnvFile,
|
|
510
|
+
itemExists,
|
|
511
|
+
generateUsageInstructions,
|
|
512
|
+
generateTemplateContent,
|
|
513
|
+
errors,
|
|
514
|
+
editSecureNote,
|
|
515
|
+
createVault,
|
|
516
|
+
createSecureNote,
|
|
517
|
+
checkSignedIn,
|
|
518
|
+
checkOpCli,
|
|
519
|
+
ErrorCodes,
|
|
520
|
+
Env2OpError
|
|
521
|
+
};
|