configorama 1.2.1 → 1.3.0
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 +10 -13
- package/plugins/onepassword/README.md +44 -0
- package/plugins/onepassword/cache-ref.js +34 -0
- package/plugins/onepassword/envelope.js +45 -0
- package/plugins/onepassword/index.js +128 -10
- package/src/resolvers/valueFromCron.js +19 -37
- package/types/src/resolvers/valueFromCron.d.ts.map +1 -1
- package/types/src/resolvers/valueFromSelf.d.ts +0 -1
- package/types/src/resolvers/valueFromSelf.d.ts.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "configorama",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Variable support for configuration files",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -29,10 +29,6 @@
|
|
|
29
29
|
"scripts": {
|
|
30
30
|
"info": "repomix --include \"cli.js,src/**/*.js\" --ignore \"**/*.test.js\" --copy",
|
|
31
31
|
"docs": "node ./scripts/docs.js",
|
|
32
|
-
"site:dev": "cd site && npm run dev",
|
|
33
|
-
"site:build": "cd site && npm run build",
|
|
34
|
-
"site:validate": "cd site && npm run validate",
|
|
35
|
-
"site:deploy": "cd site && npm run deploy",
|
|
36
32
|
"t": "./scripts/run-tests.sh",
|
|
37
33
|
"test": "npm run test:slow && npm run test:lib && uvu tests \".*\\.test.js$\"",
|
|
38
34
|
"test:tests": "uvu tests \".*\\.test.js$\" ",
|
|
@@ -47,17 +43,15 @@
|
|
|
47
43
|
"publish": "git push origin && git push origin --tags",
|
|
48
44
|
"release:patch": "npm version patch && npm publish",
|
|
49
45
|
"release:minor": "npm version minor && npm publish",
|
|
50
|
-
"release:major": "npm version major && npm publish"
|
|
51
|
-
"release": "lerna publish",
|
|
52
|
-
"version:all": "lerna version --no-push --no-git-tag-version",
|
|
53
|
-
"test:all": "pnpm -r test"
|
|
46
|
+
"release:major": "npm version major && npm publish"
|
|
54
47
|
},
|
|
55
48
|
"author": "David Wells",
|
|
56
49
|
"license": "MIT",
|
|
57
50
|
"homepage": "https://github.com/DavidWells/configorama#readme",
|
|
58
51
|
"repository": {
|
|
59
52
|
"type": "git",
|
|
60
|
-
"url": "https://github.com/DavidWells/configorama"
|
|
53
|
+
"url": "https://github.com/DavidWells/configorama",
|
|
54
|
+
"directory": "packages/configorama"
|
|
61
55
|
},
|
|
62
56
|
"dependencies": {
|
|
63
57
|
"@clack/prompts": "^0.11.0",
|
|
@@ -83,8 +77,8 @@
|
|
|
83
77
|
},
|
|
84
78
|
"devDependencies": {
|
|
85
79
|
"@cdktf/hcl2json": "^0.21.0",
|
|
80
|
+
"@davidwells/op-stash": "^0.1.1",
|
|
86
81
|
"@types/node": "^24.10.1",
|
|
87
|
-
"lerna": "^9.0.0",
|
|
88
82
|
"markdown-magic": "^4.8.0",
|
|
89
83
|
"tsx": "^4.7.0",
|
|
90
84
|
"typescript": "^5.8.3",
|
|
@@ -96,10 +90,14 @@
|
|
|
96
90
|
},
|
|
97
91
|
"peerDependencies": {
|
|
98
92
|
"@cdktf/hcl2json": ">=0.20.0",
|
|
93
|
+
"@davidwells/op-stash": ">=0.1.0",
|
|
99
94
|
"ts-node": ">=10.0.0",
|
|
100
95
|
"tsx": ">=4.0.0"
|
|
101
96
|
},
|
|
102
97
|
"peerDependenciesMeta": {
|
|
98
|
+
"@davidwells/op-stash": {
|
|
99
|
+
"optional": true
|
|
100
|
+
},
|
|
103
101
|
"@cdktf/hcl2json": {
|
|
104
102
|
"optional": true
|
|
105
103
|
},
|
|
@@ -110,6 +108,5 @@
|
|
|
110
108
|
"optional": true
|
|
111
109
|
}
|
|
112
110
|
},
|
|
113
|
-
"
|
|
114
|
-
"gitHead": "2ce70bfa849433ba74c60fa3c1cd3dba942b6556"
|
|
111
|
+
"gitHead": "72471d87832a75edb5e3d530cfb9408de8e8b7c7"
|
|
115
112
|
}
|
|
@@ -50,6 +50,7 @@ Options:
|
|
|
50
50
|
| `configDir` | string | Passed to `op` as `--config` |
|
|
51
51
|
| `opPath` | string | Path to the `op` binary (defaults to `op` on `PATH`) |
|
|
52
52
|
| `skipResolution` | boolean | Record metadata and return deterministic placeholders without calling `op` |
|
|
53
|
+
| `cache` | object | Optional cache provider config. `{ provider: 'op-stash' }` enables `@davidwells/op-stash` for every supported `${op...}` syntax |
|
|
53
54
|
|
|
54
55
|
## Alias syntax (recommended)
|
|
55
56
|
|
|
@@ -160,6 +161,49 @@ configorama: requesting 3 items from 1Password (expect an authorization prompt)
|
|
|
160
161
|
|
|
161
162
|
The hint is TTY-only — silent in CI and pipes.
|
|
162
163
|
|
|
164
|
+
## Optional op-stash
|
|
165
|
+
|
|
166
|
+
For fresh-process workflows such as agents repeatedly running `configx .env -- <command>`, install `@davidwells/op-stash` and opt in explicitly:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
npm install @davidwells/op-stash
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
```js
|
|
173
|
+
const createOnePasswordResolver = require('configorama/plugins/onepassword')
|
|
174
|
+
|
|
175
|
+
module.exports = {
|
|
176
|
+
variableSources: [
|
|
177
|
+
createOnePasswordResolver({
|
|
178
|
+
cache: {
|
|
179
|
+
provider: 'op-stash',
|
|
180
|
+
ttlSeconds: 300,
|
|
181
|
+
scope: process.env.OP_STASH_SCOPE || 'user',
|
|
182
|
+
fallbackToOp: false,
|
|
183
|
+
allowServiceAccountTokenCache: false
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Every supported syntax caches when the cache option is configured — aliases, item names and IDs, private links, explicit fields, inferred fields, and structured note key paths, plus direct `op://` refs:
|
|
191
|
+
|
|
192
|
+
```yaml
|
|
193
|
+
a: ${op:npm.NPM_TOKEN} # alias + structured note key path
|
|
194
|
+
b: ${op(database-prod).password} # item name + field
|
|
195
|
+
c: ${op(<private link>).KEY} # private link + key
|
|
196
|
+
d: ${op(op://vault/item/field)} # direct secret ref
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The cache stores **final resolved values only**, never `op item get` JSON. A cached entry is exactly the string a variable resolved to, so the daemon never holds more secret material than a config actually requested, and a cache hit returns the value without any `op` call. Item JSON is deliberately not cached: whole items hold every revealed field, complicate invalidation, and would turn the cache into something 1Password-shape-aware instead of a scalar secret store.
|
|
200
|
+
|
|
201
|
+
One granularity consequence: `${op(op://vault/item/notesPlain).KEY}` caches the selected `KEY` value, not the whole `notesPlain` field. Resolving a key path that no earlier run resolved costs one fresh `op` call even when a sibling key is already cached.
|
|
202
|
+
|
|
203
|
+
Values live in daemon memory only — until TTL expiry, `op-stash clear`, or `op-stash stop`; nothing is written to disk. Short TTLs (minutes, not hours) are recommended for interactive agent workflows. `OP_STASH_DISABLED=1` bypasses all cache paths.
|
|
204
|
+
|
|
205
|
+
The plugin fails closed by default when a configured cache provider is broken. Set `fallbackToOp: true` to degrade to direct resolution. If `OP_SERVICE_ACCOUNT_TOKEN` is set, the cache is bypassed unless `allowServiceAccountTokenCache: true` is configured.
|
|
206
|
+
|
|
163
207
|
## Security model
|
|
164
208
|
|
|
165
209
|
- Secrets are fetched from `op` at resolution time. The plugin never writes them to disk, never logs CLI output, and never passes resolved values as command-line arguments. Resolved values **do** enter the config object returned to your code — treat that object as sensitive (don't log it, commit it, or emit it to an untrusted sink).
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/* Builds synthetic cache refs for final-value op-stash entries.
|
|
2
|
+
NUL-joined fixed-order dimensions hashed with SHA-256 — never raw links. */
|
|
3
|
+
const crypto = require('crypto')
|
|
4
|
+
|
|
5
|
+
// Bump whenever field-selection semantics or the envelope shape change:
|
|
6
|
+
// old entries then live under unreachable keys and expire unread.
|
|
7
|
+
const CACHE_REF_SCHEMA_VERSION = 'v1'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Deterministic cache reference for one final-value resolver operation.
|
|
11
|
+
* Dimensions are requested inputs only — never discovered outputs — so the
|
|
12
|
+
* key is computable before any op call. account/configDir/opPath are NOT
|
|
13
|
+
* included here; they travel via getOrSet opts and op-stash applies them as
|
|
14
|
+
* key dimensions, exactly as it does for read (one owner per dimension).
|
|
15
|
+
* @param {object} reference - Normalized reference ({ kind, item, vault, section, field, ref })
|
|
16
|
+
* @param {string|undefined} keyPath - Requested key path
|
|
17
|
+
* @returns {string} configorama-op://v1/<sha256hex>
|
|
18
|
+
*/
|
|
19
|
+
function buildCacheRef(reference, keyPath) {
|
|
20
|
+
const itemOrRef = reference.kind === 'secretRef' ? reference.ref : reference.item
|
|
21
|
+
const parts = [
|
|
22
|
+
CACHE_REF_SCHEMA_VERSION,
|
|
23
|
+
reference.kind || '',
|
|
24
|
+
itemOrRef || '',
|
|
25
|
+
reference.vault || '',
|
|
26
|
+
reference.section || '',
|
|
27
|
+
reference.field || '',
|
|
28
|
+
keyPath || '',
|
|
29
|
+
]
|
|
30
|
+
const hash = crypto.createHash('sha256').update(parts.join('\0')).digest('hex')
|
|
31
|
+
return `configorama-op://${CACHE_REF_SCHEMA_VERSION}/${hash}`
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = { buildCacheRef, CACHE_REF_SCHEMA_VERSION }
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/* Encodes cached final values as { value, fieldName } JSON strings.
|
|
2
|
+
Private plugin convention: op-stash stores these as opaque strings. */
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {string} value - Final resolved value
|
|
6
|
+
* @param {string|undefined} fieldName - Discovered field name for audit metadata
|
|
7
|
+
* @returns {string} Envelope JSON string
|
|
8
|
+
*/
|
|
9
|
+
function encodeEnvelope(value, fieldName) {
|
|
10
|
+
if (typeof value !== 'string') throw new Error('Envelope value must be a string.')
|
|
11
|
+
return JSON.stringify(fieldName === undefined ? { value } : { value, fieldName })
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} encoded - Envelope JSON string
|
|
16
|
+
* @returns {{value: string, fieldName: string|undefined}}
|
|
17
|
+
*/
|
|
18
|
+
function decodeEnvelope(encoded) {
|
|
19
|
+
let parsed
|
|
20
|
+
try {
|
|
21
|
+
parsed = JSON.parse(encoded)
|
|
22
|
+
} catch (err) {
|
|
23
|
+
throw new Error('Malformed op-stash envelope: not valid JSON.')
|
|
24
|
+
}
|
|
25
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed) || typeof parsed.value !== 'string') {
|
|
26
|
+
throw new Error('Malformed op-stash envelope: missing string value.')
|
|
27
|
+
}
|
|
28
|
+
return { value: parsed.value, fieldName: typeof parsed.fieldName === 'string' ? parsed.fieldName : undefined }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* validateCached hook: rejecting here makes getOrSet recompute and overwrite.
|
|
33
|
+
* @param {string} encoded - Cached string
|
|
34
|
+
* @returns {boolean}
|
|
35
|
+
*/
|
|
36
|
+
function isValidEnvelope(encoded) {
|
|
37
|
+
try {
|
|
38
|
+
decodeEnvelope(encoded)
|
|
39
|
+
return true
|
|
40
|
+
} catch (err) {
|
|
41
|
+
return false
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = { encodeEnvelope, decodeEnvelope, isValidEnvelope }
|
|
@@ -4,6 +4,8 @@ const { readSecretRef, getItem } = require('./op-cli')
|
|
|
4
4
|
const { validateAliasName, normalizeRefValue, isItemId } = require('./normalize')
|
|
5
5
|
const { selectField, trySelectField } = require('./fields')
|
|
6
6
|
const { parseStructuredSecret, getKeyPath } = require('./parser')
|
|
7
|
+
const { buildCacheRef } = require('./cache-ref')
|
|
8
|
+
const { encodeEnvelope, decodeEnvelope, isValidEnvelope } = require('./envelope')
|
|
7
9
|
|
|
8
10
|
const OP_PREFIX = 'op'
|
|
9
11
|
|
|
@@ -45,6 +47,7 @@ const opVariableSyntax = /^op(?::|\()/
|
|
|
45
47
|
* @param {string} [options.opPath] - Path to the op binary (defaults to "op" on PATH)
|
|
46
48
|
* @param {string} [options.programName] - Host tool name for the auth-prompt hint (defaults to $CONFIGORAMA_PROGRAM_NAME or "configorama")
|
|
47
49
|
* @param {boolean} [options.skipResolution] - Collect metadata and return placeholders without calling op
|
|
50
|
+
* @param {object} [options.cache] - Optional cache provider config ({ provider: 'op-stash', ttlSeconds, scope, fallbackToOp, allowServiceAccountTokenCache })
|
|
48
51
|
* @param {Function} [options.execFile] - execFile injection for tests (not serializable; unavailable in sync mode)
|
|
49
52
|
* @returns {object} Variable source configuration with resolver and metadata collector
|
|
50
53
|
*/
|
|
@@ -56,9 +59,17 @@ function createOnePasswordResolver(options = {}) {
|
|
|
56
59
|
opPath,
|
|
57
60
|
programName,
|
|
58
61
|
skipResolution = false,
|
|
62
|
+
cache,
|
|
59
63
|
execFile,
|
|
60
64
|
} = options
|
|
61
65
|
|
|
66
|
+
// An unrecognized provider fails loudly: a typo silently disabling a
|
|
67
|
+
// requested secrets cache would look identical to working caching.
|
|
68
|
+
if (cache && cache.provider !== 'op-stash') {
|
|
69
|
+
throw new Error(`Unknown 1Password cache provider "${cache.provider}". Supported: 'op-stash'.`)
|
|
70
|
+
}
|
|
71
|
+
const opStash = cache ? loadOpStash() : undefined
|
|
72
|
+
|
|
62
73
|
const aliasRefs = {}
|
|
63
74
|
for (const alias of Object.keys(refs)) {
|
|
64
75
|
validateAliasName(alias)
|
|
@@ -207,6 +218,75 @@ function createOnePasswordResolver(options = {}) {
|
|
|
207
218
|
return `[OP:item:${reference.item}${keyPath ? `:${keyPath}` : ''}]`
|
|
208
219
|
}
|
|
209
220
|
|
|
221
|
+
/**
|
|
222
|
+
* Resolve a normalized reference to its final value.
|
|
223
|
+
* Direct secret refs without a key path keep the read path (same daemon
|
|
224
|
+
* keys as standalone `op-stash read`); every other syntax caches its
|
|
225
|
+
* final resolved value under a synthetic ref.
|
|
226
|
+
* @param {object} reference - Normalized reference
|
|
227
|
+
* @param {string|undefined} keyPath - Requested key path
|
|
228
|
+
* @param {string} [label] - Human label for the auth hint
|
|
229
|
+
* @returns {Promise<{value: string, fieldName: string|undefined}>} Final value
|
|
230
|
+
*/
|
|
231
|
+
async function resolveReference(reference, keyPath, label) {
|
|
232
|
+
if (reference.kind === 'secretRef' && keyPath === undefined) {
|
|
233
|
+
const value = await cached(secretRefCache, `${scopeKey}|${reference.ref}`, () => {
|
|
234
|
+
return readSecretRefWithOptionalCache(reference.ref)
|
|
235
|
+
}, label)
|
|
236
|
+
return { value, fieldName: undefined }
|
|
237
|
+
}
|
|
238
|
+
return finalValueWithOptionalCache(reference, keyPath, label)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Final-value caching through op-stash getOrSet. The cached payload is a
|
|
243
|
+
* { value, fieldName } envelope so audit metadata survives cache hits;
|
|
244
|
+
* malformed envelopes are rejected via validateCached and recomputed.
|
|
245
|
+
* @param {object} reference - Normalized reference
|
|
246
|
+
* @param {string|undefined} keyPath - Requested key path
|
|
247
|
+
* @param {string} [label] - Human label for the auth hint
|
|
248
|
+
* @returns {Promise<{value: string, fieldName: string|undefined}>} Final value
|
|
249
|
+
*/
|
|
250
|
+
async function finalValueWithOptionalCache(reference, keyPath, label) {
|
|
251
|
+
if (!opStash || shouldBypassOpStash()) {
|
|
252
|
+
return computeFinalValue(reference, keyPath, label)
|
|
253
|
+
}
|
|
254
|
+
const cacheRef = buildCacheRef(reference, keyPath)
|
|
255
|
+
const encoded = await opStash.getOrSet(cacheRef, async () => {
|
|
256
|
+
const resolved = await computeFinalValue(reference, keyPath, label)
|
|
257
|
+
return encodeEnvelope(resolved.value, resolved.fieldName)
|
|
258
|
+
}, {
|
|
259
|
+
account,
|
|
260
|
+
configDir,
|
|
261
|
+
opPath,
|
|
262
|
+
ttlSeconds: cache.ttlSeconds,
|
|
263
|
+
scope: cache.scope,
|
|
264
|
+
fallbackToOp: cache.fallbackToOp === true,
|
|
265
|
+
validateCached: isValidEnvelope,
|
|
266
|
+
stderr: process.stderr,
|
|
267
|
+
})
|
|
268
|
+
return decodeEnvelope(encoded)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Compute the final resolver value with no persistent cache involved.
|
|
273
|
+
* This is the getOrSet producer body; the cold-start latch lives in here
|
|
274
|
+
* (via cached) so daemon hits never serialize behind it and auth hints
|
|
275
|
+
* only print when an op call actually happens.
|
|
276
|
+
* @param {object} reference - Normalized reference
|
|
277
|
+
* @param {string|undefined} keyPath - Requested key path
|
|
278
|
+
* @param {string} [label] - Human label for the auth hint
|
|
279
|
+
* @returns {Promise<{value: string, fieldName: string|undefined}>} Final value
|
|
280
|
+
*/
|
|
281
|
+
async function computeFinalValue(reference, keyPath, label) {
|
|
282
|
+
const { value, fieldName, remainingKeyPath } = await selectBackingValue(reference, keyPath, label)
|
|
283
|
+
if (remainingKeyPath === undefined) {
|
|
284
|
+
return { value, fieldName }
|
|
285
|
+
}
|
|
286
|
+
const parsedValue = parseStructuredSecret(value, { fieldName })
|
|
287
|
+
return { value: getKeyPath(parsedValue, remainingKeyPath, fieldName), fieldName }
|
|
288
|
+
}
|
|
289
|
+
|
|
210
290
|
/**
|
|
211
291
|
* Fetch and select the backing field value for a normalized reference.
|
|
212
292
|
* For items without a configured field, the first key path segment may
|
|
@@ -218,7 +298,7 @@ function createOnePasswordResolver(options = {}) {
|
|
|
218
298
|
* @param {string} [label] - Human label for the auth hint
|
|
219
299
|
* @returns {Promise<{value: string, fieldName: string, remainingKeyPath: string|undefined}>} Selected value
|
|
220
300
|
*/
|
|
221
|
-
async function
|
|
301
|
+
async function selectBackingValue(reference, keyPath, label) {
|
|
222
302
|
if (reference.kind === 'secretRef') {
|
|
223
303
|
const value = await cached(secretRefCache, `${scopeKey}|${reference.ref}`, () => {
|
|
224
304
|
return readSecretRef(reference.ref, cliOptions)
|
|
@@ -301,16 +381,11 @@ function createOnePasswordResolver(options = {}) {
|
|
|
301
381
|
|| (configPath ? configPath.split('.').pop() : undefined)
|
|
302
382
|
|| reference.field
|
|
303
383
|
|| (reference.kind === 'secretRef' ? reference.ref : reference.item)
|
|
304
|
-
const
|
|
305
|
-
if (entry.field === undefined && reference.kind !== 'secretRef') {
|
|
306
|
-
entry.field = fieldName
|
|
384
|
+
const resolved = await resolveReference(reference, keyPath, label)
|
|
385
|
+
if (entry.field === undefined && reference.kind !== 'secretRef' && resolved.fieldName !== undefined) {
|
|
386
|
+
entry.field = resolved.fieldName
|
|
307
387
|
}
|
|
308
|
-
|
|
309
|
-
if (remainingKeyPath === undefined) {
|
|
310
|
-
return value
|
|
311
|
-
}
|
|
312
|
-
const parsedValue = parseStructuredSecret(value, { fieldName })
|
|
313
|
-
return getKeyPath(parsedValue, remainingKeyPath, fieldName)
|
|
388
|
+
return resolved.value
|
|
314
389
|
}
|
|
315
390
|
|
|
316
391
|
return {
|
|
@@ -342,6 +417,7 @@ function createOnePasswordResolver(options = {}) {
|
|
|
342
417
|
*/
|
|
343
418
|
function buildSyncOptions() {
|
|
344
419
|
const syncOptions = { refs, account, configDir, opPath, skipResolution }
|
|
420
|
+
if (cache !== undefined) syncOptions.cache = cache
|
|
345
421
|
if (execFile) {
|
|
346
422
|
// Functions cannot cross the JSON boundary into the sync worker;
|
|
347
423
|
// flag it so sync-factory can fail loudly instead of silently
|
|
@@ -350,6 +426,48 @@ function createOnePasswordResolver(options = {}) {
|
|
|
350
426
|
}
|
|
351
427
|
return syncOptions
|
|
352
428
|
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* @param {string} ref - Direct op:// secret reference
|
|
432
|
+
* @returns {Promise<string>} Secret value
|
|
433
|
+
*/
|
|
434
|
+
function readSecretRefWithOptionalCache(ref) {
|
|
435
|
+
if (!opStash || shouldBypassOpStash()) {
|
|
436
|
+
return readSecretRef(ref, cliOptions)
|
|
437
|
+
}
|
|
438
|
+
return opStash.read(ref, {
|
|
439
|
+
account,
|
|
440
|
+
configDir,
|
|
441
|
+
opPath,
|
|
442
|
+
ttlSeconds: cache.ttlSeconds,
|
|
443
|
+
scope: cache.scope,
|
|
444
|
+
fallbackToOp: cache.fallbackToOp === true,
|
|
445
|
+
stderr: process.stderr,
|
|
446
|
+
})
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* @returns {boolean} Whether cache must be bypassed for this read
|
|
451
|
+
*/
|
|
452
|
+
function shouldBypassOpStash() {
|
|
453
|
+
if (process.env.OP_STASH_DISABLED === '1') return true
|
|
454
|
+
if (execFile) return true
|
|
455
|
+
if (process.env.OP_SERVICE_ACCOUNT_TOKEN && !(cache && cache.allowServiceAccountTokenCache === true)) {
|
|
456
|
+
return true
|
|
457
|
+
}
|
|
458
|
+
return false
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* @returns {object} @davidwells/op-stash API
|
|
464
|
+
*/
|
|
465
|
+
function loadOpStash() {
|
|
466
|
+
try {
|
|
467
|
+
return require('@davidwells/op-stash')
|
|
468
|
+
} catch (err) {
|
|
469
|
+
throw new Error('1Password op-stash provider requested but @davidwells/op-stash is not installed. Install it with: npm install @davidwells/op-stash')
|
|
470
|
+
}
|
|
353
471
|
}
|
|
354
472
|
|
|
355
473
|
module.exports = createOnePasswordResolver
|
|
@@ -78,15 +78,7 @@ function parseCronExpression(input) {
|
|
|
78
78
|
// Parse "at X:XX" patterns (e.g., "at 9:30", "at 14:00")
|
|
79
79
|
const atTimeMatch = normalizedInput.match(/^at (\d{1,2}):(\d{2})(\s*(am|pm))?$/i)
|
|
80
80
|
if (atTimeMatch) {
|
|
81
|
-
|
|
82
|
-
const minute = parseInt(atTimeMatch[2])
|
|
83
|
-
const amPm = atTimeMatch[4]
|
|
84
|
-
|
|
85
|
-
if (amPm && amPm.toLowerCase() === 'pm' && hour !== 12) {
|
|
86
|
-
hour += 12
|
|
87
|
-
} else if (amPm && amPm.toLowerCase() === 'am' && hour === 12) {
|
|
88
|
-
hour = 0
|
|
89
|
-
}
|
|
81
|
+
const { minute, hour } = parseTimeMatch(atTimeMatch, 1, 2, 4)
|
|
90
82
|
|
|
91
83
|
return `${minute} ${hour} * * *`
|
|
92
84
|
}
|
|
@@ -118,15 +110,7 @@ function parseCronExpression(input) {
|
|
|
118
110
|
const ordinalDateMatch = normalizedInput.match(/^on (\d+)(?:st|nd|rd|th) of month at (\d{1,2}):(\d{2})(\s*(am|pm))?$/i)
|
|
119
111
|
if (ordinalDateMatch) {
|
|
120
112
|
const dayOfMonth = parseInt(ordinalDateMatch[1])
|
|
121
|
-
|
|
122
|
-
const minute = parseInt(ordinalDateMatch[3])
|
|
123
|
-
const amPm = ordinalDateMatch[5]
|
|
124
|
-
|
|
125
|
-
if (amPm && amPm.toLowerCase() === 'pm' && hour !== 12) {
|
|
126
|
-
hour += 12
|
|
127
|
-
} else if (amPm && amPm.toLowerCase() === 'am' && hour === 12) {
|
|
128
|
-
hour = 0
|
|
129
|
-
}
|
|
113
|
+
const { minute, hour } = parseTimeMatch(ordinalDateMatch, 2, 3, 5)
|
|
130
114
|
|
|
131
115
|
return `${minute} ${hour} ${dayOfMonth} * *`
|
|
132
116
|
}
|
|
@@ -143,15 +127,7 @@ function parseCronExpression(input) {
|
|
|
143
127
|
const days = weekdayTimeMatch[1].split(',').map(day => day.trim())
|
|
144
128
|
const dayOfWeek = days.map(day => dayMap[day.toLowerCase()]).join(',')
|
|
145
129
|
|
|
146
|
-
|
|
147
|
-
const minute = parseInt(weekdayTimeMatch[3])
|
|
148
|
-
const amPm = weekdayTimeMatch[5]
|
|
149
|
-
|
|
150
|
-
if (amPm && amPm.toLowerCase() === 'pm' && hour !== 12) {
|
|
151
|
-
hour += 12
|
|
152
|
-
} else if (amPm && amPm.toLowerCase() === 'am' && hour === 12) {
|
|
153
|
-
hour = 0
|
|
154
|
-
}
|
|
130
|
+
const { minute, hour } = parseTimeMatch(weekdayTimeMatch, 2, 3, 5)
|
|
155
131
|
|
|
156
132
|
return `${minute} ${hour} * * ${dayOfWeek}`
|
|
157
133
|
}
|
|
@@ -160,15 +136,7 @@ function parseCronExpression(input) {
|
|
|
160
136
|
const weekdaysTimeMatch = normalizedInput.match(/^on (weekdays|weekends) at (\d{1,2}):(\d{2})(\s*(am|pm))?$/i)
|
|
161
137
|
if (weekdaysTimeMatch) {
|
|
162
138
|
const dayRange = weekdaysTimeMatch[1].toLowerCase() === 'weekdays' ? '1-5' : '0,6'
|
|
163
|
-
|
|
164
|
-
const minute = parseInt(weekdaysTimeMatch[3])
|
|
165
|
-
const amPm = weekdaysTimeMatch[5]
|
|
166
|
-
|
|
167
|
-
if (amPm && amPm.toLowerCase() === 'pm' && hour !== 12) {
|
|
168
|
-
hour += 12
|
|
169
|
-
} else if (amPm && amPm.toLowerCase() === 'am' && hour === 12) {
|
|
170
|
-
hour = 0
|
|
171
|
-
}
|
|
139
|
+
const { minute, hour } = parseTimeMatch(weekdaysTimeMatch, 2, 3, 5)
|
|
172
140
|
|
|
173
141
|
return `${minute} ${hour} * * ${dayRange}`
|
|
174
142
|
}
|
|
@@ -187,6 +155,20 @@ function parseCronExpression(input) {
|
|
|
187
155
|
throw new Error(`Unrecognized cron pattern: "${input}". Supported patterns include: ${suggestions}`)
|
|
188
156
|
}
|
|
189
157
|
|
|
158
|
+
function parseTimeMatch(match, hourIndex, minuteIndex, amPmIndex) {
|
|
159
|
+
let hour = parseInt(match[hourIndex])
|
|
160
|
+
const minute = parseInt(match[minuteIndex])
|
|
161
|
+
const amPm = match[amPmIndex]
|
|
162
|
+
|
|
163
|
+
if (amPm && amPm.toLowerCase() === 'pm' && hour !== 12) {
|
|
164
|
+
hour += 12
|
|
165
|
+
} else if (amPm && amPm.toLowerCase() === 'am' && hour === 12) {
|
|
166
|
+
hour = 0
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return { minute, hour }
|
|
170
|
+
}
|
|
171
|
+
|
|
190
172
|
function getValueFromCron(variableString) {
|
|
191
173
|
// Get value from cron(expression)
|
|
192
174
|
const cronExpression = variableString.match(/cron\((.*)\)/)[1]
|
|
@@ -232,4 +214,4 @@ module.exports = {
|
|
|
232
214
|
resolver: getValueFromCron,
|
|
233
215
|
// Export the parser for testing
|
|
234
216
|
_parseCronExpression: parseCronExpression
|
|
235
|
-
}
|
|
217
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"valueFromCron.d.ts","sourceRoot":"","sources":["../../../src/resolvers/valueFromCron.js"],"names":[],"mappings":"AACA,oCAAkF;
|
|
1
|
+
{"version":3,"file":"valueFromCron.d.ts","sourceRoot":"","sources":["../../../src/resolvers/valueFromCron.js"],"names":[],"mappings":"AACA,oCAAkF;AA0KlF,8EAiCC;AAzMD;;;GAGG;AACH,sDAoJC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
//# sourceMappingURL=valueFromSelf.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"valueFromSelf.d.ts","sourceRoot":"","sources":["../../../src/resolvers/valueFromSelf.js"],"names":[],"mappings":""}
|