@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/env-file.d.ts +98 -96
- package/dist/env-file.js +199 -199
- package/dist/index.d.ts +8 -5
- package/dist/index.js +114 -107
- package/dist/onepassword.d.ts +98 -92
- package/dist/onepassword.js +197 -197
- package/package.json +2 -2
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
27
|
-
|
|
29
|
+
const content = await Bun.file(filePath).text();
|
|
30
|
+
return EnvFile.parse(content);
|
|
28
31
|
};
|
|
29
32
|
const writeEnvFile = async (filePath, envFile) => {
|
|
30
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
});
|
package/dist/onepassword.d.ts
CHANGED
|
@@ -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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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 {};
|