@qui-cli/env-1password 1.2.8 → 1.2.9
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/CHANGELOG.md +2 -0
- package/README.md +30 -27
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -4
- package/package.json +5 -13
- package/dist/1Password.d.ts +0 -31
- package/dist/1Password.js +0 -191
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## [1.2.9](https://github.com/battis/qui-cli/compare/env-1password/1.2.8...env-1password/1.2.9) (2026-01-19)
|
|
6
|
+
|
|
5
7
|
## [1.2.8](https://github.com/battis/qui-cli/compare/env-1password/1.2.7...env-1password/1.2.8) (2026-01-18)
|
|
6
8
|
|
|
7
9
|
|
package/README.md
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
# @qui-cli/env-1password
|
|
2
2
|
|
|
3
|
-
@qui-cli Plugin: Standardized environment configuration
|
|
3
|
+
@qui-cli Plugin: Standardized environment configuration with 1Password support
|
|
4
4
|
|
|
5
5
|
[](https://npmjs.com/package/@qui-cli/env-1password)
|
|
6
6
|
[](https://nodejs.org/api/esm.html)
|
|
7
7
|
|
|
8
|
+
## Note
|
|
9
|
+
|
|
10
|
+
This plugin is identical to [@qui-cli/env](https://npmjs.com/package/@qui-cli/env), with the exception that it includes that package's optional peer dependency [@1password/sdk](https://www.npmjs.com/package/@1password/sdk).
|
|
11
|
+
|
|
8
12
|
## Install
|
|
9
13
|
|
|
10
14
|
```sh
|
|
11
|
-
npm install @qui-cli/env
|
|
15
|
+
npm install @qui-cli/env
|
|
12
16
|
```
|
|
13
17
|
|
|
14
18
|
## Usage
|
|
15
19
|
|
|
16
20
|
```ts
|
|
17
|
-
import { Env } from '@qui-cli/env
|
|
21
|
+
import { Env } from '@qui-cli/env';
|
|
18
22
|
|
|
19
23
|
// configure desired environment path
|
|
20
24
|
Env.configure({ path: '../../.env' });
|
|
@@ -31,14 +35,19 @@ Any plugin that depends on this plugin can assume that the `.env` file environem
|
|
|
31
35
|
|
|
32
36
|
### 1Password integration
|
|
33
37
|
|
|
34
|
-
|
|
38
|
+
For full integration, also install the [1Password CLI](https://developer.1password.com/docs/cli/) which will allow you to look up a [1Password service account](https://developer.1password.com/docs/service-accounts/security/) token by identifier.
|
|
35
39
|
|
|
36
|
-
|
|
37
|
-
2. Install [@1password/sdk](https://www.npmjs.com/package/@1password/sdk) as a peer of `@qui-cli/env`.
|
|
38
|
-
3. Update environment variables to be [secret references](https://developer.1password.com/docs/cli/secret-references)
|
|
39
|
-
4. Run!
|
|
40
|
+
The configuration options `opToken`, `opItem`, and `opAccount` may all be passed as command-line options. For example:
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
```sh
|
|
43
|
+
example --opToken "$(op item get ServiceAccountToken)"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
If the [1Password CLI tool](https://developer.1password.com/docs/cli) is installed, then `opItem` and `opAccount` can be used:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
example --opAccount example.1password.com --opitem "My Token Identifier"
|
|
50
|
+
```
|
|
42
51
|
|
|
43
52
|
## Configuration
|
|
44
53
|
|
|
@@ -62,40 +71,34 @@ Whether or not to load the `.env` file into `process.env` immediately. Defaults
|
|
|
62
71
|
|
|
63
72
|
Path to desired `.env` file relative to `root`. Defaults to `'.env'`;
|
|
64
73
|
|
|
65
|
-
###
|
|
74
|
+
### 1Password configuration
|
|
66
75
|
|
|
67
|
-
1Password service account token
|
|
76
|
+
If 1Password secret references are stored in the environment, a 1Password service account token is required to access the secret values.
|
|
68
77
|
|
|
69
|
-
|
|
78
|
+
#### `opToken`
|
|
70
79
|
|
|
71
|
-
|
|
80
|
+
1Password service account token; will use environment variable `OP_TOKEN` if present
|
|
72
81
|
|
|
73
|
-
|
|
82
|
+
#### `opItem`
|
|
74
83
|
|
|
75
|
-
1Password account
|
|
84
|
+
Name or ID of the 1Password API Credential item storing the 1Password service account token; will use environment variable `OP_ITEM` if present. Requires the [1Password CLI tool](https://developer.1password.com/docs/cli).
|
|
76
85
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
The configuration options `opToken`, `opItem`, and `opAccount` may all be passed as command-line options. For example:
|
|
86
|
+
#### `opAccount`
|
|
80
87
|
|
|
81
|
-
|
|
82
|
-
example --opToken "$(op item get ServiceAccountToken)"
|
|
83
|
-
```
|
|
88
|
+
1Password account to use (if signed into multiple); will use environment variable `OP_ACCOUNT` if present
|
|
84
89
|
|
|
85
|
-
|
|
90
|
+
## Options
|
|
86
91
|
|
|
87
|
-
|
|
88
|
-
example --opItem ServiceAccountToken
|
|
89
|
-
```
|
|
92
|
+
When the optional peer dependency [@1password/sdk](https://www.npmjs.com/package/@1password/sdk) is installed, `Env` exposes `opAccount`, `opItem`, and `opToken` as command-line options.
|
|
90
93
|
|
|
91
94
|
## Initialization
|
|
92
95
|
|
|
93
|
-
`Env` loads the specified `.env` file into `process.env`.
|
|
96
|
+
`Env` loads the specified `.env` file into `process.env`.
|
|
94
97
|
|
|
95
98
|
## API
|
|
96
99
|
|
|
97
100
|
```ts
|
|
98
|
-
import { Env } from '@qui-cli/env
|
|
101
|
+
import { Env } from '@qui-cli/env';
|
|
99
102
|
```
|
|
100
103
|
|
|
101
104
|
### `Env.parse(file?)`
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { Env };
|
|
1
|
+
export * from '@qui-cli/env';
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qui-cli/env-1password",
|
|
3
|
-
"version": "1.2.
|
|
4
|
-
"description": "@qui-cli Plugin: Standardized environment configuration",
|
|
3
|
+
"version": "1.2.9",
|
|
4
|
+
"description": "@qui-cli Plugin: Standardized environment configuration with 1Password support",
|
|
5
5
|
"homepage": "https://github.com/battis/qui-cli/tree/main/packages/env-1password#readme",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -18,9 +18,7 @@
|
|
|
18
18
|
"types": "./dist/index.d.ts",
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@1password/sdk": "^0.3.1",
|
|
21
|
-
"@battis/import-package-json": "^0.1.7"
|
|
22
|
-
"dotenv": "^17.2.3",
|
|
23
|
-
"@qui-cli/colors": "3.2.3"
|
|
21
|
+
"@battis/import-package-json": "^0.1.7"
|
|
24
22
|
},
|
|
25
23
|
"devDependencies": {
|
|
26
24
|
"@tsconfig/node24": "^24.0.4",
|
|
@@ -29,17 +27,11 @@
|
|
|
29
27
|
"del-cli": "^7.0.0",
|
|
30
28
|
"npm-run-all": "^4.1.5",
|
|
31
29
|
"typescript": "^5.9.3",
|
|
32
|
-
"@qui-cli/
|
|
33
|
-
"@qui-cli/
|
|
34
|
-
"@qui-cli/env": "5.0.4",
|
|
35
|
-
"@qui-cli/root": "3.1.0",
|
|
36
|
-
"@qui-cli/shell": "3.1.2"
|
|
30
|
+
"@qui-cli/shell": "3.1.2",
|
|
31
|
+
"@qui-cli/env": "5.1.0"
|
|
37
32
|
},
|
|
38
33
|
"peerDependencies": {
|
|
39
34
|
"@qui-cli/env": ">=5",
|
|
40
|
-
"@qui-cli/log": ">=3",
|
|
41
|
-
"@qui-cli/plugin": ">=3",
|
|
42
|
-
"@qui-cli/root": ">=3",
|
|
43
35
|
"@qui-cli/shell": ">=3"
|
|
44
36
|
},
|
|
45
37
|
"target": "node",
|
package/dist/1Password.d.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { Env } from '@qui-cli/env';
|
|
2
|
-
import * as Plugin from '@qui-cli/plugin';
|
|
3
|
-
export type Configuration = Plugin.Configuration & Env.Configuration & {
|
|
4
|
-
/**
|
|
5
|
-
* 1Password service account token; will use the environment variable
|
|
6
|
-
* OP_TOKEN if present
|
|
7
|
-
*/
|
|
8
|
-
opToken?: string;
|
|
9
|
-
/**
|
|
10
|
-
* Name or ID of the 1Password API Credential item storing the 1Password
|
|
11
|
-
* service account token; will use environment variable OP_ITEM if present
|
|
12
|
-
*/
|
|
13
|
-
opItem?: string;
|
|
14
|
-
/**
|
|
15
|
-
* 1Password account to use (if signed into multiple); will use environment
|
|
16
|
-
* variable OP_ACCOUNT if present
|
|
17
|
-
*/
|
|
18
|
-
opAccount?: string;
|
|
19
|
-
/** @deprecated Use {@link opToken} */
|
|
20
|
-
serviceAccountToken?: string;
|
|
21
|
-
};
|
|
22
|
-
export declare const name = "env-1password";
|
|
23
|
-
export declare function configure(proposal?: Configuration): Promise<void>;
|
|
24
|
-
export declare function options(): Plugin.Options;
|
|
25
|
-
export declare function init({ values }: Plugin.ExpectedArguments<typeof options>): Promise<void>;
|
|
26
|
-
export declare function parse(file?: string): Promise<import("dotenv").DotenvParseOutput>;
|
|
27
|
-
export declare function get({ key, file }: Env.GetOptions): Promise<string>;
|
|
28
|
-
export declare function exists({ key, file }: Parameters<(typeof Env)['exists']>[0]): Promise<boolean>;
|
|
29
|
-
/** Requires a service account with write privileges */
|
|
30
|
-
export declare function set({ key, value, file, ...rest }: Env.SetOptions): Promise<void>;
|
|
31
|
-
export declare const remove: typeof Env.remove;
|
package/dist/1Password.js
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import { createClient } from '@1password/sdk';
|
|
2
|
-
import { importLocal } from '@battis/import-package-json';
|
|
3
|
-
import { Colors } from '@qui-cli/colors';
|
|
4
|
-
import { Env } from '@qui-cli/env';
|
|
5
|
-
import { Log } from '@qui-cli/log';
|
|
6
|
-
import { Shell } from '@qui-cli/shell';
|
|
7
|
-
import path from 'node:path';
|
|
8
|
-
export const name = 'env-1password';
|
|
9
|
-
let client = undefined;
|
|
10
|
-
const config = {};
|
|
11
|
-
export async function configure(proposal = {}) {
|
|
12
|
-
for (const key in proposal) {
|
|
13
|
-
if (proposal[key] !== undefined) {
|
|
14
|
-
config[key] = proposal[key];
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
config.opToken = config.opToken || config.serviceAccountToken;
|
|
18
|
-
await Env.configure({ ...config, load: false });
|
|
19
|
-
if (config.opItem && !config.opToken) {
|
|
20
|
-
const silent = Shell.isSilent();
|
|
21
|
-
const showCommands = Shell.commandsShown();
|
|
22
|
-
const logging = Shell.isLogging();
|
|
23
|
-
Shell.configure({ silent: true, showCommands: false, logging: false });
|
|
24
|
-
const { stdout, stderr } = Shell.exec(`op item get ${config.opAccount ? `--account "${config.opAccount}" ` : ''}--reveal --fields credential "${config.opItem}"`);
|
|
25
|
-
if (stdout.length) {
|
|
26
|
-
config.opToken = stdout.trim();
|
|
27
|
-
}
|
|
28
|
-
else {
|
|
29
|
-
Log.fatal(stderr);
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
32
|
-
Shell.configure({ silent, showCommands, logging });
|
|
33
|
-
}
|
|
34
|
-
if (config.opToken) {
|
|
35
|
-
const pkg = await importLocal(path.join(import.meta.dirname, '../package.json'));
|
|
36
|
-
client = await createClient({
|
|
37
|
-
auth: config.opToken,
|
|
38
|
-
integrationName: pkg.name.replace(/^(\/|@)/, '').replace(/[/@]+/g, '-'),
|
|
39
|
-
integrationVersion: pkg.version
|
|
40
|
-
});
|
|
41
|
-
if (config.load) {
|
|
42
|
-
await parse();
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
else if (config.load) {
|
|
46
|
-
await Env.parse();
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
export function options() {
|
|
50
|
-
return {
|
|
51
|
-
man: [
|
|
52
|
-
{
|
|
53
|
-
level: 1,
|
|
54
|
-
text: '1Password environment integration'
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
text: 'Store 1Password secret references in your environment, rather than the actual secrets.'
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
text: `If 1Password secret references are stored in the environment, a 1Password service account token is required to access the secret values, which will be loaded into ${Colors.varName('process.env')}. The service account token can be passed directly as the ${Colors.optionArg('--opToken')} argument (e.g. ${Colors.command(`example --opToken "$(${Colors.keyword('op')} item get myToken)"`, Colors.keyword)}) or, if the 1Password CLI tool is also installed, by simply passing the name or ID of the API Credential in your 1Password vault that holds the service account token (e.g. ${Colors.command(`example --opItem myToken`, Colors.keyword)}). If you are signed into multiple 1Password account, use the ${Colors.optionArg('--opAccount')} argument to specify the account containing the token.`
|
|
61
|
-
},
|
|
62
|
-
{ text: Colors.url('https://developer.1password.com/docs/cli') }
|
|
63
|
-
],
|
|
64
|
-
opt: {
|
|
65
|
-
opAccount: {
|
|
66
|
-
description: `1Password account to use (if signed into multiple); will use environment variable ${Colors.varName('OP_ACCOUNT')} if present`,
|
|
67
|
-
hint: 'example.1password.com',
|
|
68
|
-
default: config.opAccount
|
|
69
|
-
},
|
|
70
|
-
opItem: {
|
|
71
|
-
description: `Name or ID of the 1Password API Credential item storing the 1Password service account token; will use environment variable ${Colors.varName('OP_ITEM')} if present`,
|
|
72
|
-
hint: '1Password unique identifier',
|
|
73
|
-
default: config.opItem
|
|
74
|
-
},
|
|
75
|
-
opToken: {
|
|
76
|
-
description: `1Password service account token; will use environment variable ${Colors.varName('OP_TOKEN')} if present`,
|
|
77
|
-
hint: 'token value',
|
|
78
|
-
secret: true,
|
|
79
|
-
default: config.opToken || config.serviceAccountToken
|
|
80
|
-
},
|
|
81
|
-
serviceAccountToken: {
|
|
82
|
-
description: `1Password service account token (deprecated, use ${Colors.optionArg('--opToken')})`,
|
|
83
|
-
hint: 'token value',
|
|
84
|
-
secret: true,
|
|
85
|
-
default: config.serviceAccountToken
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
export async function init({ values }) {
|
|
91
|
-
const { opAccount = process.env.OP_ACCOUNT, opItem = process.env.OP_ITEM, opToken = process.env.OP_TOKEN } = values;
|
|
92
|
-
await configure({ opAccount, opItem, opToken, ...values });
|
|
93
|
-
parse();
|
|
94
|
-
}
|
|
95
|
-
function isSecretReference(value) {
|
|
96
|
-
return (value &&
|
|
97
|
-
value !== null &&
|
|
98
|
-
typeof value === 'string' &&
|
|
99
|
-
/^op:\/\//.test(value));
|
|
100
|
-
}
|
|
101
|
-
function secretReferences(parsed) {
|
|
102
|
-
return Object.fromEntries(Object.entries(parsed).filter((entry) => isSecretReference(entry[1])));
|
|
103
|
-
}
|
|
104
|
-
// FIXME parse is not loading 1Password secrets into process.env
|
|
105
|
-
// Issue URL: https://github.com/battis/qui-cli/issues/81
|
|
106
|
-
export async function parse(file) {
|
|
107
|
-
const parsed = await Env.parse(file);
|
|
108
|
-
if (client) {
|
|
109
|
-
for (const key in secretReferences(parsed)) {
|
|
110
|
-
parsed[key] = await client.secrets.resolve(parsed[key]);
|
|
111
|
-
process.env[key] = parsed[key];
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
else if (Object.keys(secretReferences(parsed)).length) {
|
|
115
|
-
throw new Error('Attempt to parse .env file containing secret reference without a 1Password client');
|
|
116
|
-
}
|
|
117
|
-
return parsed;
|
|
118
|
-
}
|
|
119
|
-
export async function get({ key, file }) {
|
|
120
|
-
return (await parse(file))[key];
|
|
121
|
-
}
|
|
122
|
-
export async function exists({ key, file }) {
|
|
123
|
-
return Env.exists({ key, file });
|
|
124
|
-
}
|
|
125
|
-
function secretFrom(secretReference) {
|
|
126
|
-
// eslint-disable-next-line prefer-const
|
|
127
|
-
let [vault, item, section, field] = secretReference
|
|
128
|
-
.replace(/^op:\/\//, '')
|
|
129
|
-
.split('/');
|
|
130
|
-
if (field === undefined) {
|
|
131
|
-
field = section;
|
|
132
|
-
section = '';
|
|
133
|
-
}
|
|
134
|
-
return { vault, item, section, field };
|
|
135
|
-
}
|
|
136
|
-
async function itemFrom(secret) {
|
|
137
|
-
if (client) {
|
|
138
|
-
const vault = (await client.vaults.list())
|
|
139
|
-
.filter((vault) => vault.title == secret.vault)
|
|
140
|
-
.shift();
|
|
141
|
-
if (vault) {
|
|
142
|
-
const item = (await client.items.list(vault.id))
|
|
143
|
-
.filter((item) => item.title === secret.item || item.id === secret.item)
|
|
144
|
-
.shift();
|
|
145
|
-
if (item) {
|
|
146
|
-
return await client.items.get(vault.id, item.id);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
return undefined;
|
|
151
|
-
}
|
|
152
|
-
/** Requires a service account with write privileges */
|
|
153
|
-
export async function set({ key, value, file, ...rest }) {
|
|
154
|
-
const prev = await Env.get({ key, file });
|
|
155
|
-
if (prev && isSecretReference(prev)) {
|
|
156
|
-
if (client) {
|
|
157
|
-
const secret = secretFrom(prev);
|
|
158
|
-
const item = await itemFrom(secret);
|
|
159
|
-
if (item) {
|
|
160
|
-
const updated = {
|
|
161
|
-
...item,
|
|
162
|
-
fields: item.fields.map((field) => {
|
|
163
|
-
const section = item.sections.find((s) => s.id === field.sectionId);
|
|
164
|
-
if (secret.field === field.title || secret.field === field.id) {
|
|
165
|
-
if ((!secret.section &&
|
|
166
|
-
(field.sectionId === '' || field.sectionId === 'add more')) ||
|
|
167
|
-
secret.section === section?.title ||
|
|
168
|
-
secret.section === section?.id) {
|
|
169
|
-
return { ...field, value };
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
return field;
|
|
173
|
-
})
|
|
174
|
-
};
|
|
175
|
-
await client.items.put(updated);
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
throw new Error('1Password item could not be found');
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
throw new Error('Attempt to update .env file value that is currently a secret reference without a 1Password client');
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
else {
|
|
186
|
-
// FIXME handle creating a new secret reference
|
|
187
|
-
// Issue URL: https://github.com/battis/qui-cli/issues/63
|
|
188
|
-
return await Env.set({ key, value, file, ...rest });
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
export const remove = Env.remove;
|