@townco/secret 0.1.8 → 0.1.10

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/dist/index.js CHANGED
@@ -1,130 +1,137 @@
1
1
  import * as path from "node:path";
2
2
  import { EnvFile } from "./env-file";
3
3
  import { OnePassword } from "./onepassword";
4
+
4
5
  export { EnvFile } from "./env-file";
5
6
  export { OnePassword as OpItem } from "./onepassword";
7
+
6
8
  const ROOT_MARKER = ".root";
7
9
  const SECRET_FILE = ".env.in";
8
10
  const OP_VAULT = "app";
9
11
  const OP_ITEM = "dev";
10
12
  const findRoot = async (start = ".") => {
11
- const startResolved = path.resolve(start);
12
- const rootPath = path.join(startResolved, ROOT_MARKER);
13
- if (await Bun.file(rootPath).exists())
14
- return startResolved;
15
- const parent = path.dirname(startResolved);
16
- if (parent === "/") {
17
- throw new Error("No root found");
18
- }
19
- return await findRoot(parent);
20
- };
21
- const withOpSignIn = (fn) => async (...args) => {
22
- await OnePassword.signin();
23
- return fn(...args);
13
+ const startResolved = path.resolve(start);
14
+ const rootPath = path.join(startResolved, ROOT_MARKER);
15
+ if (await Bun.file(rootPath).exists()) return startResolved;
16
+ const parent = path.dirname(startResolved);
17
+ if (parent === "/") {
18
+ throw new Error("No root found");
19
+ }
20
+ return await findRoot(parent);
24
21
  };
22
+ const withOpSignIn =
23
+ (fn) =>
24
+ async (...args) => {
25
+ await OnePassword.signin();
26
+ return fn(...args);
27
+ };
25
28
  const readEnvFile = async (filePath) => {
26
- const content = await Bun.file(filePath).text();
27
- return EnvFile.parse(content);
29
+ const content = await Bun.file(filePath).text();
30
+ return EnvFile.parse(content);
28
31
  };
29
32
  const writeEnvFile = async (filePath, envFile) => {
30
- await Bun.write(filePath, envFile.toString());
31
- };
32
- const withEnvFile = (fn) => async (...args) => {
33
- const filePath = path.join(await findRoot(), SECRET_FILE);
34
- const envFile = await readEnvFile(filePath);
35
- const originalRecord = envFile.toRecord();
36
- const result = fn({ ...originalRecord }, ...args);
37
- // If the result is a Record<string, string>, write it back
38
- if (result && typeof result === "object" && !Array.isArray(result)) {
39
- const resultRecord = result;
40
- // Check if this looks like a secrets record (all string values)
41
- const isSecretsRecord = Object.values(resultRecord).every((v) => typeof v === "string");
42
- if (isSecretsRecord) {
43
- const resultSecrets = resultRecord;
44
- // Delete keys that are in original but not in result
45
- for (const key of Object.keys(originalRecord)) {
46
- if (!(key in resultSecrets)) {
47
- envFile.delete(key);
48
- }
49
- }
50
- // Update or add keys from result
51
- for (const [key, value] of Object.entries(resultSecrets)) {
52
- envFile.set(key, value);
53
- }
54
- await writeEnvFile(filePath, envFile);
55
- }
56
- }
57
- return result;
33
+ await Bun.write(filePath, envFile.toString());
58
34
  };
35
+ const withEnvFile =
36
+ (fn) =>
37
+ async (...args) => {
38
+ const filePath = path.join(await findRoot(), SECRET_FILE);
39
+ const envFile = await readEnvFile(filePath);
40
+ const originalRecord = envFile.toRecord();
41
+ const result = fn({ ...originalRecord }, ...args);
42
+ // If the result is a Record<string, string>, write it back
43
+ if (result && typeof result === "object" && !Array.isArray(result)) {
44
+ const resultRecord = result;
45
+ // Check if this looks like a secrets record (all string values)
46
+ const isSecretsRecord = Object.values(resultRecord).every(
47
+ (v) => typeof v === "string",
48
+ );
49
+ if (isSecretsRecord) {
50
+ const resultSecrets = resultRecord;
51
+ // Delete keys that are in original but not in result
52
+ for (const key of Object.keys(originalRecord)) {
53
+ if (!(key in resultSecrets)) {
54
+ envFile.delete(key);
55
+ }
56
+ }
57
+ // Update or add keys from result
58
+ for (const [key, value] of Object.entries(resultSecrets)) {
59
+ envFile.set(key, value);
60
+ }
61
+ await writeEnvFile(filePath, envFile);
62
+ }
63
+ }
64
+ return result;
65
+ };
59
66
  export const listSecrets = withOpSignIn(async () => {
60
- const root = await findRoot();
61
- const filePath = path.join(root, SECRET_FILE);
62
- const envFile = await readEnvFile(filePath);
63
- const secrets = envFile.toRecord();
64
- // Fetch the dev item using OpItem
65
- const opItem = await OnePassword.fetch(OP_VAULT, OP_ITEM);
66
- const validations = [];
67
- for (const [key, value] of Object.entries(secrets)) {
68
- // Strip quotes from value if present
69
- let unquotedValue = value;
70
- if (value.startsWith('"') && value.endsWith('"')) {
71
- unquotedValue = value.slice(1, -1);
72
- }
73
- const validation = {
74
- key,
75
- value: unquotedValue,
76
- valid: true,
77
- };
78
- if (!validation.value.startsWith("op://")) {
79
- validations.push(validation);
80
- continue;
81
- }
82
- // Check if field exists in 1Password item
83
- if (opItem.has(key)) {
84
- validation.valid = false;
85
- validation.error = `Field '${key}' not found in 1Password item`;
86
- validations.push(validation);
87
- continue;
88
- }
89
- // Build expected op:// reference
90
- const expectedRef = opItem.getReference(key);
91
- // Compare the value from .env.in with expected reference
92
- if (unquotedValue !== expectedRef) {
93
- validation.valid = false;
94
- validation.error = `Reference mismatch: expected '${expectedRef}', got '${unquotedValue}'`;
95
- validations.push(validation);
96
- continue;
97
- }
98
- validations.push(validation);
99
- }
100
- return validations;
67
+ const root = await findRoot();
68
+ const filePath = path.join(root, SECRET_FILE);
69
+ const envFile = await readEnvFile(filePath);
70
+ const secrets = envFile.toRecord();
71
+ // Fetch the dev item using OpItem
72
+ const opItem = await OnePassword.fetch(OP_VAULT, OP_ITEM);
73
+ const validations = [];
74
+ for (const [key, value] of Object.entries(secrets)) {
75
+ // Strip quotes from value if present
76
+ let unquotedValue = value;
77
+ if (value.startsWith('"') && value.endsWith('"')) {
78
+ unquotedValue = value.slice(1, -1);
79
+ }
80
+ const validation = {
81
+ key,
82
+ value: unquotedValue,
83
+ valid: true,
84
+ };
85
+ if (!validation.value.startsWith("op://")) {
86
+ validations.push(validation);
87
+ continue;
88
+ }
89
+ // Check if field exists in 1Password item
90
+ if (opItem.has(key)) {
91
+ validation.valid = false;
92
+ validation.error = `Field '${key}' not found in 1Password item`;
93
+ validations.push(validation);
94
+ continue;
95
+ }
96
+ // Build expected op:// reference
97
+ const expectedRef = opItem.getReference(key);
98
+ // Compare the value from .env.in with expected reference
99
+ if (unquotedValue !== expectedRef) {
100
+ validation.valid = false;
101
+ validation.error = `Reference mismatch: expected '${expectedRef}', got '${unquotedValue}'`;
102
+ validations.push(validation);
103
+ continue;
104
+ }
105
+ validations.push(validation);
106
+ }
107
+ return validations;
101
108
  });
102
109
  export const createSecret = withOpSignIn(async (name, value) => {
103
- // Fetch the item, update it, and sync
104
- const opItem = await OnePassword.fetch(OP_VAULT, OP_ITEM);
105
- opItem.set(name, value);
106
- await opItem.sync();
107
- // Update the env file with the secret reference
108
- await withEnvFile((secrets) => {
109
- secrets[name] = opItem.getReference(name);
110
- return secrets;
111
- })();
110
+ // Fetch the item, update it, and sync
111
+ const opItem = await OnePassword.fetch(OP_VAULT, OP_ITEM);
112
+ opItem.set(name, value);
113
+ await opItem.sync();
114
+ // Update the env file with the secret reference
115
+ await withEnvFile((secrets) => {
116
+ secrets[name] = opItem.getReference(name);
117
+ return secrets;
118
+ })();
112
119
  });
113
120
  export const deleteSecret = withOpSignIn(async (name) => {
114
- // Fetch the item, delete the field, and sync
115
- const opItem = await OnePassword.fetch(OP_VAULT, OP_ITEM);
116
- opItem.delete(name);
117
- await opItem.sync();
118
- // Update the env file by removing the secret
119
- await withEnvFile((secrets, fieldName) => {
120
- delete secrets[fieldName];
121
- return secrets;
122
- })(name);
121
+ // Fetch the item, delete the field, and sync
122
+ const opItem = await OnePassword.fetch(OP_VAULT, OP_ITEM);
123
+ opItem.delete(name);
124
+ await opItem.sync();
125
+ // Update the env file by removing the secret
126
+ await withEnvFile((secrets, fieldName) => {
127
+ delete secrets[fieldName];
128
+ return secrets;
129
+ })(name);
123
130
  });
124
131
  export const genenv = withOpSignIn(async () => {
125
- const root = await findRoot();
126
- const inputPath = path.join(root, SECRET_FILE);
127
- const outputPath = path.join(root, ".env");
128
- // Use OpItem to inject and resolve secret references
129
- await OnePassword.inject(inputPath, outputPath);
132
+ const root = await findRoot();
133
+ const inputPath = path.join(root, SECRET_FILE);
134
+ const outputPath = path.join(root, ".env");
135
+ // Use OpItem to inject and resolve secret references
136
+ await OnePassword.inject(inputPath, outputPath);
130
137
  });
@@ -2,101 +2,107 @@ import type { FullItem } from "@1password/connect";
2
2
  /**
3
3
  * Represents a change to be made to a 1Password item
4
4
  */
5
- type OnePasswordChange = {
6
- type: "add";
7
- key: string;
8
- value: string;
9
- } | {
10
- type: "update";
11
- key: string;
12
- value: string;
13
- } | {
14
- type: "delete";
15
- key: string;
16
- };
5
+ type OnePasswordChange =
6
+ | {
7
+ type: "add";
8
+ key: string;
9
+ value: string;
10
+ }
11
+ | {
12
+ type: "update";
13
+ key: string;
14
+ value: string;
15
+ }
16
+ | {
17
+ type: "delete";
18
+ key: string;
19
+ };
17
20
  /**
18
21
  * A data structure that represents a 1Password item and provides
19
22
  * methods to detect changes and generate appropriate `op` CLI commands.
20
23
  */
21
24
  export declare class OnePassword {
22
- private vault;
23
- private item;
24
- private fields;
25
- private originalFields;
26
- constructor(vault: string, item: string, fields?: Map<string, string>);
27
- /**
28
- * Create an OpItem from a 1Password FullItem JSON response
29
- */
30
- static fromFullItem(vault: string, itemName: string, fullItem: FullItem): OnePassword;
31
- /**
32
- * Fetch an OpItem from 1Password using the CLI
33
- */
34
- static fetch(vault: string, item: string): Promise<OnePassword>;
35
- /**
36
- * Sign in to 1Password CLI
37
- */
38
- static signin(): Promise<void>;
39
- /**
40
- * Get a field value
41
- */
42
- get(key: string): string | undefined;
43
- /**
44
- * Set a field value (marks as changed)
45
- */
46
- set(key: string, value: string): this;
47
- /**
48
- * Delete a field (marks as deleted)
49
- */
50
- delete(key: string): this;
51
- /**
52
- * Check if a field exists
53
- */
54
- has(key: string): boolean;
55
- /**
56
- * Get all field keys
57
- */
58
- keys(): string[];
59
- /**
60
- * Get all fields as a record
61
- */
62
- toRecord(): Record<string, string>;
63
- /**
64
- * Get all field entries
65
- */
66
- entries(): IterableIterator<[string, string]>;
67
- /**
68
- * Build an op:// reference for a field
69
- */
70
- getReference(key: string): string;
71
- /**
72
- * Detect changes between original and current state
73
- */
74
- detectChanges(): OnePasswordChange[];
75
- /**
76
- * Generate op CLI arguments for a single change
77
- */
78
- private buildEditArgs;
79
- /**
80
- * Apply all changes to 1Password using the op CLI
81
- * Returns the number of changes applied
82
- */
83
- sync(): Promise<number>;
84
- /**
85
- * Inject secrets from a template file to an output file
86
- * This resolves op:// references to actual values
87
- */
88
- static inject(inputPath: string, outputPath: string): Promise<void>;
89
- /**
90
- * Create a clone of this OpItem
91
- */
92
- clone(): OnePassword;
93
- /**
94
- * Reset to original state (discard changes)
95
- */
96
- reset(): this;
97
- /**
98
- * Check if there are unsaved changes
99
- */
100
- hasChanges(): boolean;
25
+ private vault;
26
+ private item;
27
+ private fields;
28
+ private originalFields;
29
+ constructor(vault: string, item: string, fields?: Map<string, string>);
30
+ /**
31
+ * Create an OpItem from a 1Password FullItem JSON response
32
+ */
33
+ static fromFullItem(
34
+ vault: string,
35
+ itemName: string,
36
+ fullItem: FullItem,
37
+ ): OnePassword;
38
+ /**
39
+ * Fetch an OpItem from 1Password using the CLI
40
+ */
41
+ static fetch(vault: string, item: string): Promise<OnePassword>;
42
+ /**
43
+ * Sign in to 1Password CLI
44
+ */
45
+ static signin(): Promise<void>;
46
+ /**
47
+ * Get a field value
48
+ */
49
+ get(key: string): string | undefined;
50
+ /**
51
+ * Set a field value (marks as changed)
52
+ */
53
+ set(key: string, value: string): this;
54
+ /**
55
+ * Delete a field (marks as deleted)
56
+ */
57
+ delete(key: string): this;
58
+ /**
59
+ * Check if a field exists
60
+ */
61
+ has(key: string): boolean;
62
+ /**
63
+ * Get all field keys
64
+ */
65
+ keys(): string[];
66
+ /**
67
+ * Get all fields as a record
68
+ */
69
+ toRecord(): Record<string, string>;
70
+ /**
71
+ * Get all field entries
72
+ */
73
+ entries(): IterableIterator<[string, string]>;
74
+ /**
75
+ * Build an op:// reference for a field
76
+ */
77
+ getReference(key: string): string;
78
+ /**
79
+ * Detect changes between original and current state
80
+ */
81
+ detectChanges(): OnePasswordChange[];
82
+ /**
83
+ * Generate op CLI arguments for a single change
84
+ */
85
+ private buildEditArgs;
86
+ /**
87
+ * Apply all changes to 1Password using the op CLI
88
+ * Returns the number of changes applied
89
+ */
90
+ sync(): Promise<number>;
91
+ /**
92
+ * Inject secrets from a template file to an output file
93
+ * This resolves op:// references to actual values
94
+ */
95
+ static inject(inputPath: string, outputPath: string): Promise<void>;
96
+ /**
97
+ * Create a clone of this OpItem
98
+ */
99
+ clone(): OnePassword;
100
+ /**
101
+ * Reset to original state (discard changes)
102
+ */
103
+ reset(): this;
104
+ /**
105
+ * Check if there are unsaved changes
106
+ */
107
+ hasChanges(): boolean;
101
108
  }
102
- export {};