@tolgamorf/env2op-cli 0.1.0 → 0.1.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/package.json CHANGED
@@ -1,69 +1,74 @@
1
1
  {
2
- "name": "@tolgamorf/env2op-cli",
3
- "version": "0.1.0",
4
- "description": "Convert .env files to 1Password Secure Notes and generate templates for op inject/run",
5
- "type": "module",
6
- "main": "src/index.ts",
7
- "module": "src/index.ts",
8
- "types": "src/index.ts",
9
- "exports": {
10
- ".": {
11
- "import": "./src/index.ts",
12
- "types": "./src/index.ts"
13
- }
14
- },
15
- "bin": {
16
- "env2op": "src/cli.ts"
17
- },
18
- "files": ["src", "LICENSE", "README.md"],
19
- "scripts": {
20
- "dev": "bun run src/cli.ts",
21
- "test": "bun test",
22
- "typecheck": "tsc --noEmit",
23
- "lint": "bunx biome check .",
24
- "lint:fix": "bunx biome check . --write",
25
- "format": "bunx biome format --write .",
26
- "format:check": "bunx biome format .",
27
- "prepublishOnly": "bun run typecheck"
28
- },
29
- "keywords": [
30
- "env",
31
- "1password",
32
- "op",
33
- "cli",
34
- "secrets",
35
- "environment-variables",
36
- "dotenv",
37
- "secure-notes",
38
- "bun",
39
- "op-inject",
40
- "op-run",
41
- "template"
42
- ],
43
- "author": {
44
- "name": "Tolga O.",
45
- "url": "https://github.com/tolgamorf"
46
- },
47
- "license": "MIT",
48
- "repository": {
49
- "type": "git",
50
- "url": "git+https://github.com/tolgamorf/env2op-cli.git"
51
- },
52
- "bugs": {
53
- "url": "https://github.com/tolgamorf/env2op-cli/issues"
54
- },
55
- "homepage": "https://github.com/tolgamorf/env2op-cli#readme",
56
- "engines": {
57
- "bun": ">=1.0.0"
58
- },
59
- "dependencies": {
60
- "@clack/prompts": "^0.11.0",
61
- "picocolors": "^1.1.1"
62
- },
63
- "devDependencies": {
64
- "@types/bun": "latest",
65
- "typescript": "^5.9.3",
66
- "@tsconfig/bun": "^1.0.10",
67
- "@biomejs/biome": "^1.9.4"
68
- }
2
+ "name": "@tolgamorf/env2op-cli",
3
+ "version": "0.1.1",
4
+ "description": "Convert .env files to 1Password Secure Notes and generate templates for op inject/run",
5
+ "type": "module",
6
+ "main": "src/index.ts",
7
+ "module": "src/index.ts",
8
+ "types": "src/index.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./src/index.ts",
12
+ "types": "./src/index.ts"
13
+ }
14
+ },
15
+ "bin": {
16
+ "env2op": "src/cli.ts"
17
+ },
18
+ "files": [
19
+ "src",
20
+ "LICENSE",
21
+ "README.md"
22
+ ],
23
+ "scripts": {
24
+ "dev": "bun run src/cli.ts",
25
+ "test": "bun test",
26
+ "typecheck": "tsc --noEmit",
27
+ "lint": "bunx biome check .",
28
+ "lint:fix": "bunx biome check . --write",
29
+ "format": "bunx biome format --write .",
30
+ "format:check": "bunx biome format .",
31
+ "prepublishOnly": "bun run typecheck",
32
+ "release": "bun run scripts/release.ts"
33
+ },
34
+ "keywords": [
35
+ "env",
36
+ "1password",
37
+ "op",
38
+ "cli",
39
+ "secrets",
40
+ "environment-variables",
41
+ "dotenv",
42
+ "secure-notes",
43
+ "bun",
44
+ "op-inject",
45
+ "op-run",
46
+ "template"
47
+ ],
48
+ "author": {
49
+ "name": "Tolga O.",
50
+ "url": "https://github.com/tolgamorf"
51
+ },
52
+ "license": "MIT",
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "git+https://github.com/tolgamorf/env2op-cli.git"
56
+ },
57
+ "bugs": {
58
+ "url": "https://github.com/tolgamorf/env2op-cli/issues"
59
+ },
60
+ "homepage": "https://github.com/tolgamorf/env2op-cli#readme",
61
+ "engines": {
62
+ "bun": ">=1.0.0"
63
+ },
64
+ "dependencies": {
65
+ "@clack/prompts": "^0.11.0",
66
+ "picocolors": "^1.1.1"
67
+ },
68
+ "devDependencies": {
69
+ "@types/bun": "^1.3.5",
70
+ "typescript": "^5.9.3",
71
+ "@tsconfig/bun": "^1.0.10",
72
+ "@biomejs/biome": "^2.3.10"
73
+ }
69
74
  }
package/src/cli.ts CHANGED
@@ -11,16 +11,16 @@ const flags = new Set<string>();
11
11
  const positional: string[] = [];
12
12
 
13
13
  for (const arg of args) {
14
- if (arg.startsWith("--")) {
15
- flags.add(arg.slice(2));
16
- } else if (arg.startsWith("-")) {
17
- // Handle short flags
18
- for (const char of arg.slice(1)) {
19
- flags.add(char);
20
- }
21
- } else {
22
- positional.push(arg);
23
- }
14
+ if (arg.startsWith("--")) {
15
+ flags.add(arg.slice(2));
16
+ } else if (arg.startsWith("-")) {
17
+ // Handle short flags
18
+ for (const char of arg.slice(1)) {
19
+ flags.add(char);
20
+ }
21
+ } else {
22
+ positional.push(arg);
23
+ }
24
24
  }
25
25
 
26
26
  // Check for help/version flags first
@@ -28,36 +28,36 @@ const hasHelp = flags.has("h") || flags.has("help");
28
28
  const hasVersion = flags.has("v") || flags.has("version");
29
29
 
30
30
  if (hasVersion) {
31
- console.log(pkg.version);
32
- process.exit(0);
31
+ console.log(pkg.version);
32
+ process.exit(0);
33
33
  }
34
34
 
35
35
  if (hasHelp || positional.length === 0) {
36
- showHelp();
37
- process.exit(0);
36
+ showHelp();
37
+ process.exit(0);
38
38
  }
39
39
 
40
40
  if (positional.length < 3) {
41
- showMissingArgsError(positional);
42
- process.exit(1);
41
+ showMissingArgsError(positional);
42
+ process.exit(1);
43
43
  }
44
44
 
45
45
  // All positional args present, run the command
46
46
  const [envFile, vault, itemName] = positional as [string, string, string];
47
47
  await runConvert({
48
- envFile,
49
- vault,
50
- itemName,
51
- dryRun: flags.has("dry-run"),
52
- secret: flags.has("secret"),
53
- yes: flags.has("y") || flags.has("yes"),
48
+ envFile,
49
+ vault,
50
+ itemName,
51
+ dryRun: flags.has("dry-run"),
52
+ secret: flags.has("secret"),
53
+ yes: flags.has("y") || flags.has("yes"),
54
54
  });
55
55
 
56
56
  function showHelp(): void {
57
- const name = pc.bold(pc.cyan("env2op"));
58
- const version = pc.dim(`v${pkg.version}`);
57
+ const name = pc.bold(pc.cyan("env2op"));
58
+ const version = pc.dim(`v${pkg.version}`);
59
59
 
60
- console.log(`
60
+ console.log(`
61
61
  ${name} ${version}
62
62
  ${pkg.description}
63
63
 
@@ -95,13 +95,13 @@ ${pc.bold("DOCUMENTATION")}
95
95
  }
96
96
 
97
97
  function showMissingArgsError(provided: string[]): void {
98
- const missing: string[] = [];
98
+ const missing: string[] = [];
99
99
 
100
- if (provided.length < 1) missing.push("env_file");
101
- if (provided.length < 2) missing.push("vault");
102
- if (provided.length < 3) missing.push("item_name");
100
+ if (provided.length < 1) missing.push("env_file");
101
+ if (provided.length < 2) missing.push("vault");
102
+ if (provided.length < 3) missing.push("item_name");
103
103
 
104
- console.log(`
104
+ console.log(`
105
105
  ${pc.red(pc.bold("Error:"))} Missing required arguments
106
106
 
107
107
  ${pc.bold("Usage:")} env2op ${pc.yellow("<env_file>")} ${pc.yellow("<vault>")} ${pc.yellow("<item_name>")} ${pc.dim("[options]")}
@@ -2,19 +2,15 @@ import { basename, dirname, join } from "node:path";
2
2
  import * as p from "@clack/prompts";
3
3
  import { parseEnvFile, validateParseResult } from "../core/env-parser";
4
4
  import {
5
- checkOpCli,
6
- checkSignedIn,
7
- createSecureNote,
8
- createVault,
9
- deleteItem,
10
- itemExists,
11
- vaultExists,
5
+ checkOpCli,
6
+ checkSignedIn,
7
+ createSecureNote,
8
+ createVault,
9
+ deleteItem,
10
+ itemExists,
11
+ vaultExists,
12
12
  } from "../core/onepassword";
13
- import {
14
- generateTemplateContent,
15
- generateUsageInstructions,
16
- writeTemplate,
17
- } from "../core/template-generator";
13
+ import { generateTemplateContent, generateUsageInstructions, writeTemplate } from "../core/template-generator";
18
14
  import type { ConvertOptions } from "../core/types";
19
15
  import { Env2OpError } from "../utils/errors";
20
16
  import { logger } from "../utils/logger";
@@ -23,160 +19,159 @@ import { logger } from "../utils/logger";
23
19
  * Execute the convert operation
24
20
  */
25
21
  export async function runConvert(options: ConvertOptions): Promise<void> {
26
- const { envFile, vault, itemName, dryRun, secret, yes } = options;
27
-
28
- // Display intro
29
- const pkg = await import("../../package.json");
30
- logger.intro("env2op", pkg.version, dryRun);
31
-
32
- try {
33
- // Step 1: Parse .env file
34
- const parseResult = parseEnvFile(envFile);
35
- validateParseResult(parseResult, envFile);
36
-
37
- const { variables } = parseResult;
38
-
39
- logger.success(`Parsed ${basename(envFile)}`);
40
- logger.message(
41
- `Found ${variables.length} environment variable${variables.length === 1 ? "" : "s"}`,
42
- );
43
-
44
- // Show parse errors as warnings
45
- for (const error of parseResult.errors) {
46
- logger.warn(error);
47
- }
48
-
49
- // Step 2: Create 1Password Secure Note
50
- if (dryRun) {
51
- logger.warn("Would create Secure Note");
52
- logger.keyValue("Vault", vault);
53
- logger.keyValue("Title", itemName);
54
- logger.keyValue("Type", secret ? "password (hidden)" : "text (visible)");
55
- logger.keyValue(
56
- "Fields",
57
- logger.formatFields(variables.map((v) => v.key)),
58
- );
59
- } else {
60
- // Check 1Password CLI before attempting
61
- const opInstalled = await checkOpCli();
62
- if (!opInstalled) {
63
- throw new Env2OpError(
64
- "1Password CLI (op) is not installed",
65
- "OP_CLI_NOT_INSTALLED",
66
- "Install from https://1password.com/downloads/command-line/",
67
- );
68
- }
69
-
70
- const signedIn = await checkSignedIn();
71
- if (!signedIn) {
72
- throw new Env2OpError(
73
- "Not signed in to 1Password CLI",
74
- "OP_NOT_SIGNED_IN",
75
- 'Run "op signin" to authenticate',
76
- );
77
- }
78
-
79
- // Check if vault exists
80
- const vaultFound = await vaultExists(vault);
81
-
82
- if (!vaultFound) {
83
- if (yes) {
84
- // Auto-create vault
85
- logger.warn(`Vault "${vault}" not found, creating...`);
86
- await createVault(vault);
87
- logger.success(`Created vault "${vault}"`);
88
- } else {
89
- // Ask for confirmation to create vault
90
- const shouldCreate = await p.confirm({
91
- message: `Vault "${vault}" does not exist. Create it?`,
92
- });
93
-
94
- if (p.isCancel(shouldCreate) || !shouldCreate) {
95
- logger.cancel("Operation cancelled");
96
- logger.info('Run "op vault list" to see available vaults');
97
- process.exit(0);
98
- }
99
-
100
- const spinner = logger.spinner();
101
- spinner.start(`Creating vault "${vault}"...`);
102
- await createVault(vault);
103
- spinner.stop(`Created vault "${vault}"`);
104
- }
105
- }
106
-
107
- // Check if item already exists
108
- const exists = await itemExists(vault, itemName);
109
-
110
- if (exists) {
111
- if (yes) {
112
- // Auto-accept: delete and recreate
113
- logger.warn(`Item "${itemName}" already exists, overwriting...`);
114
- await deleteItem(vault, itemName);
115
- } else {
116
- // Ask for confirmation
117
- const shouldOverwrite = await p.confirm({
118
- message: `Item "${itemName}" already exists in vault "${vault}". Overwrite?`,
119
- });
120
-
121
- if (p.isCancel(shouldOverwrite) || !shouldOverwrite) {
122
- logger.cancel("Operation cancelled");
123
- process.exit(0);
124
- }
125
-
126
- await deleteItem(vault, itemName);
127
- }
128
- }
129
-
130
- const spinner = logger.spinner();
131
- spinner.start("Creating 1Password Secure Note...");
132
-
133
- try {
134
- const result = await createSecureNote({
135
- vault,
136
- title: itemName,
137
- fields: variables,
138
- secret,
139
- });
140
-
141
- spinner.stop(`Created "${result.title}" in vault "${result.vault}"`);
142
- } catch (error) {
143
- spinner.stop("Failed to create Secure Note");
144
- throw error;
145
- }
146
- }
147
-
148
- // Step 3: Generate template file
149
- const templateContent = generateTemplateContent({
150
- vault,
151
- itemTitle: itemName,
152
- variables,
153
- });
154
-
155
- const templatePath = join(dirname(envFile), `${basename(envFile)}.tpl`);
156
-
157
- if (dryRun) {
158
- logger.warn(`Would generate template: ${templatePath}`);
159
- } else {
160
- writeTemplate(templateContent, templatePath);
161
- logger.success(`Generated template: ${templatePath}`);
162
- }
163
-
164
- // Step 4: Show usage instructions
165
- if (dryRun) {
166
- logger.outro("Dry run complete. No changes made.");
167
- } else {
168
- const usage = generateUsageInstructions(templatePath);
169
- logger.note(usage, "Next steps");
170
- logger.outro("Done! Your secrets are now in 1Password");
171
- }
172
- } catch (error) {
173
- if (error instanceof Env2OpError) {
174
- logger.error(error.message);
175
- if (error.suggestion) {
176
- logger.info(`Suggestion: ${error.suggestion}`);
177
- }
178
- process.exit(1);
179
- }
180
- throw error;
181
- }
22
+ const { envFile, vault, itemName, dryRun, secret, yes } = options;
23
+
24
+ // Display intro
25
+ const pkg = await import("../../package.json");
26
+ logger.intro("env2op", pkg.version, dryRun);
27
+
28
+ try {
29
+ // Step 1: Parse .env file
30
+ const parseResult = parseEnvFile(envFile);
31
+ validateParseResult(parseResult, envFile);
32
+
33
+ const { variables, lines } = parseResult;
34
+
35
+ logger.success(`Parsed ${basename(envFile)}`);
36
+ logger.message(`Found ${variables.length} environment variable${variables.length === 1 ? "" : "s"}`);
37
+
38
+ // Show parse errors as warnings
39
+ for (const error of parseResult.errors) {
40
+ logger.warn(error);
41
+ }
42
+
43
+ // Step 2: Create 1Password Secure Note
44
+ if (dryRun) {
45
+ logger.warn("Would create Secure Note");
46
+ logger.keyValue("Vault", vault);
47
+ logger.keyValue("Title", itemName);
48
+ logger.keyValue("Type", secret ? "password (hidden)" : "text (visible)");
49
+ logger.keyValue("Fields", logger.formatFields(variables.map((v) => v.key)));
50
+ } else {
51
+ // Check 1Password CLI before attempting
52
+ const opInstalled = await checkOpCli();
53
+ if (!opInstalled) {
54
+ throw new Env2OpError(
55
+ "1Password CLI (op) is not installed",
56
+ "OP_CLI_NOT_INSTALLED",
57
+ "Install from https://1password.com/downloads/command-line/",
58
+ );
59
+ }
60
+
61
+ const signedIn = await checkSignedIn();
62
+ if (!signedIn) {
63
+ throw new Env2OpError(
64
+ "Not signed in to 1Password CLI",
65
+ "OP_NOT_SIGNED_IN",
66
+ 'Run "op signin" to authenticate',
67
+ );
68
+ }
69
+
70
+ // Check if vault exists
71
+ const vaultFound = await vaultExists(vault);
72
+
73
+ if (!vaultFound) {
74
+ if (yes) {
75
+ // Auto-create vault
76
+ logger.warn(`Vault "${vault}" not found, creating...`);
77
+ await createVault(vault);
78
+ logger.success(`Created vault "${vault}"`);
79
+ } else {
80
+ // Ask for confirmation to create vault
81
+ const shouldCreate = await p.confirm({
82
+ message: `Vault "${vault}" does not exist. Create it?`,
83
+ });
84
+
85
+ if (p.isCancel(shouldCreate) || !shouldCreate) {
86
+ logger.cancel("Operation cancelled");
87
+ logger.info('Run "op vault list" to see available vaults');
88
+ process.exit(0);
89
+ }
90
+
91
+ const spinner = logger.spinner();
92
+ spinner.start(`Creating vault "${vault}"...`);
93
+ await createVault(vault);
94
+ spinner.stop(`Created vault "${vault}"`);
95
+ }
96
+ }
97
+
98
+ // Check if item already exists
99
+ const exists = await itemExists(vault, itemName);
100
+
101
+ if (exists) {
102
+ if (yes) {
103
+ // Auto-accept: delete and recreate
104
+ logger.warn(`Item "${itemName}" already exists, overwriting...`);
105
+ await deleteItem(vault, itemName);
106
+ } else {
107
+ // Ask for confirmation
108
+ const shouldOverwrite = await p.confirm({
109
+ message: `Item "${itemName}" already exists in vault "${vault}". Overwrite?`,
110
+ });
111
+
112
+ if (p.isCancel(shouldOverwrite) || !shouldOverwrite) {
113
+ logger.cancel("Operation cancelled");
114
+ process.exit(0);
115
+ }
116
+
117
+ await deleteItem(vault, itemName);
118
+ }
119
+ }
120
+
121
+ const spinner = logger.spinner();
122
+ spinner.start("Creating 1Password Secure Note...");
123
+
124
+ try {
125
+ const result = await createSecureNote({
126
+ vault,
127
+ title: itemName,
128
+ fields: variables,
129
+ secret,
130
+ });
131
+
132
+ spinner.stop(`Created "${result.title}" in vault "${result.vault}"`);
133
+ } catch (error) {
134
+ spinner.stop("Failed to create Secure Note");
135
+ throw error;
136
+ }
137
+ }
138
+
139
+ // Step 3: Generate template file
140
+ const templateFileName = `${basename(envFile)}.tpl`;
141
+ const templatePath = join(dirname(envFile), templateFileName);
142
+ const templateContent = generateTemplateContent(
143
+ {
144
+ vault,
145
+ itemTitle: itemName,
146
+ variables,
147
+ lines,
148
+ },
149
+ templateFileName,
150
+ );
151
+
152
+ if (dryRun) {
153
+ logger.warn(`Would generate template: ${templatePath}`);
154
+ } else {
155
+ writeTemplate(templateContent, templatePath);
156
+ logger.success(`Generated template: ${templatePath}`);
157
+ }
158
+
159
+ // Step 4: Show usage instructions
160
+ if (dryRun) {
161
+ logger.outro("Dry run complete. No changes made.");
162
+ } else {
163
+ const usage = generateUsageInstructions(templatePath);
164
+ logger.note(usage, "Next steps");
165
+ logger.outro("Done! Your secrets are now in 1Password");
166
+ }
167
+ } catch (error) {
168
+ if (error instanceof Env2OpError) {
169
+ logger.error(error.message);
170
+ if (error.suggestion) {
171
+ logger.info(`Suggestion: ${error.suggestion}`);
172
+ }
173
+ process.exit(1);
174
+ }
175
+ throw error;
176
+ }
182
177
  }