From abdaf0ce8cb0edc1f05f6cc0bf13ef0a347fdacd Mon Sep 17 00:00:00 2001 From: Theo Ephraim Date: Sat, 17 Aug 2024 15:46:53 -0700 Subject: [PATCH] 1pass plugin improvements (#125) 1password improvements! - using sdk for service accounts, system cli with ambient auth otherwise - use field ids instead of paths - better error mesages - better JSdoc comments - update 1pass plugin docs General improvements - expose nested resolver branch resolution errors - general cleanup of nested/branched resolvers - adjust how plugins detect their current name/version --------- Co-authored-by: Phil Miller --- .changeset/big-swans-own.md | 7 + example-repo/.dmno/config.mts | 46 ++- .../packages/astro-web/.dmno/config.mts | 1 + .../packages/astro-web/astro.config.ts | 4 +- .../core/src/config-engine/config-engine.ts | 14 +- packages/core/src/config-engine/errors.ts | 25 +- packages/core/src/config-engine/plugins.ts | 27 +- .../src/config-engine/resolvers/resolvers.ts | 110 ++++-- .../plugins/1password/blob-item-example.png | Bin 0 -> 101035 bytes .../content/docs/docs/plugins/1password.mdx | 188 +++++++--- packages/plugins/1password/package.json | 7 +- .../plugins/1password/scripts/install-cli.mjs | 89 ----- packages/plugins/1password/src/plugin.ts | 344 ++++++++++++++---- packages/plugins/1password/tsconfig.json | 2 - .../plugins/encrypted-vault/src/plugin.ts | 3 + pnpm-lock.yaml | 248 ++++++++++--- 16 files changed, 766 insertions(+), 349 deletions(-) create mode 100644 .changeset/big-swans-own.md create mode 100644 packages/docs-site/src/assets/docs-images/plugins/1password/blob-item-example.png delete mode 100644 packages/plugins/1password/scripts/install-cli.mjs diff --git a/.changeset/big-swans-own.md b/.changeset/big-swans-own.md new file mode 100644 index 00000000..a163aad2 --- /dev/null +++ b/.changeset/big-swans-own.md @@ -0,0 +1,7 @@ +--- +"@dmno/encrypted-vault-plugin": patch +"@dmno/1password-plugin": patch +"dmno": patch +--- + +1password plugin improvements, related refactoring diff --git a/example-repo/.dmno/config.mts b/example-repo/.dmno/config.mts index a5f7fa9e..1c934941 100644 --- a/example-repo/.dmno/config.mts +++ b/example-repo/.dmno/config.mts @@ -7,25 +7,26 @@ import { EncryptedVaultDmnoPlugin, EncryptedVaultTypes } from '@dmno/encrypted-v const OnePassSecretsProd = new OnePasswordDmnoPlugin('1pass/prod', { token: configPath('OP_TOKEN'), envItemLink: 'https://start.1password.com/open/i?a=I3GUA2KU6BD3FBHA47QNBIVEV4&v=ut2dftalm3ugmxc6klavms6tfq&i=n4wmgfq77mydg5lebtroa3ykvm&h=dmnoinc.1password.com', + // token: InjectPluginInputByType, // token: 'asdf', }); const OnePassSecretsDev = new OnePasswordDmnoPlugin('1pass', { token: configPath('OP_TOKEN'), - envItemLink: 'https://start.1password.com/open/i?a=I3GUA2KU6BD3FBHA47QNBIVEV4&v=ut2dftalm3ugmxc6klavms6tfq&i=n4wmgfq77mydg5lebtroa3ykvm&h=dmnoinc.1password.com', + envItemLink: 'https://start.1password.com/open/i?a=I3GUA2KU6BD3FBHA47QNBIVEV4&v=ut2dftalm3ugmxc6klavms6tfq&i=4u4klfhpldobgdxrcjwb2bqsta&h=dmnoinc.1password.com', // token: InjectPluginInputByType, // token: 'asdf', }); -const ProdVault = new EncryptedVaultDmnoPlugin('vault/prod', { +const EncryptedVaultSecrets = new EncryptedVaultDmnoPlugin('vault/prod', { key: configPath('DMNO_VAULT_KEY'), name: 'prod', }); -const NonProdVault = new EncryptedVaultDmnoPlugin('vault/dev', { - key: configPath('DMNO_VAULT_KEY'), - name: 'dev', -}); +// const NonProdVault = new EncryptedVaultDmnoPlugin('vault/dev', { +// key: configPath('DMNO_VAULT_KEY'), +// name: 'dev', +// }); @@ -47,8 +48,24 @@ export default defineDmnoService({ OP_TOKEN: { extends: OnePasswordTypes.serviceAccountToken, }, - OP_TOKEN_PROD: { - extends: OnePasswordTypes.serviceAccountToken, + // OP_TOKEN_PROD: { + // extends: OnePasswordTypes.serviceAccountToken, + // }, + + OP_ITEM_1: { + value: switchBy('DMNO_ENV', { + _default: OnePassSecretsDev.item(), + production: OnePassSecretsProd.item(), + }), + }, + OP_ITEM_BY_ID: { + value: OnePassSecretsDev.itemById("ut2dftalm3ugmxc6klavms6tfq", "bphvvrqjegfmd5yoz4buw2aequ", "username"), + }, + OP_ITEM_BY_LINK: { + value: OnePassSecretsDev.itemByLink("https://start.1password.com/open/i?a=I3GUA2KU6BD3FBHA47QNBIVEV4&v=ut2dftalm3ugmxc6klavms6tfq&i=bphvvrqjegfmd5yoz4buw2aequ&h=dmnoinc.1password.com", "helturjryuy73yjbnaovlce5fu"), + }, + OP_ITEM_BY_REFERENCE: { + value: OnePassSecretsDev.itemByReference("op://dev test/example/username"), }, SOME_API_KEY: { @@ -58,8 +75,6 @@ export default defineDmnoService({ }), }, - - DMNO_VAULT_KEY: { extends: EncryptedVaultTypes.encryptionKey, // required: true @@ -71,16 +86,16 @@ export default defineDmnoService({ }, VAULT_ITEM_1: { - value: ProdVault.item(), + value: EncryptedVaultSecrets.item(), }, VAULT_ITEM_WITH_SWITCH: { value: switchByNodeEnv({ - _default: NonProdVault.item(), + _default: EncryptedVaultSecrets.item(), staging: switchBy('CONTEXT', { - 'branch-preview': ProdVault.item(), - 'pr-preview': ProdVault.item(), + 'branch-preview': EncryptedVaultSecrets.item(), + 'pr-preview': EncryptedVaultSecrets.item(), }), - production: ProdVault.item() + production: EncryptedVaultSecrets.item() }), }, @@ -92,7 +107,6 @@ export default defineDmnoService({ extends: DmnoBaseTypes.email({ normalize: true, }), - // required: true, value: 'Test@test.com' }, diff --git a/example-repo/packages/astro-web/.dmno/config.mts b/example-repo/packages/astro-web/.dmno/config.mts index 92ee4659..31475f7a 100644 --- a/example-repo/packages/astro-web/.dmno/config.mts +++ b/example-repo/packages/astro-web/.dmno/config.mts @@ -16,6 +16,7 @@ export default defineDmnoService({ source: 'api', key: 'API_URL', }, + 'SOME_API_KEY', ], schema: { OP_TOKEN: { extends: OnePasswordTypes.serviceAccountToken }, diff --git a/example-repo/packages/astro-web/astro.config.ts b/example-repo/packages/astro-web/astro.config.ts index 59bf40dc..ad1131e7 100644 --- a/example-repo/packages/astro-web/astro.config.ts +++ b/example-repo/packages/astro-web/astro.config.ts @@ -14,8 +14,8 @@ console.log('> secret value =', DMNO_CONFIG.SECRET_FOO); console.log('> secret value in obj', { secret: DMNO_CONFIG.SECRET_FOO }); console.log('> secret value in array', ['secret', DMNO_CONFIG.SECRET_FOO ]); -console.log('\nthe secret on the next line should not'); -console.log('> secret value =', unredact(DMNO_CONFIG.SECRET_FOO)); +// console.log('\nthe secret on the next line should not'); +// console.log('> secret value =', unredact(DMNO_CONFIG.SECRET_FOO)); // https://astro.build/config export default defineConfig({ diff --git a/packages/core/src/config-engine/config-engine.ts b/packages/core/src/config-engine/config-engine.ts index dbad9884..59c28502 100644 --- a/packages/core/src/config-engine/config-engine.ts +++ b/packages/core/src/config-engine/config-engine.ts @@ -269,7 +269,7 @@ class CacheEntry { this.encryptedValue = more?.encryptedValue; } async getEncryptedValue() { - return encrypt(CacheEntry.encryptionKey, this.value, CacheEntry.encryptionKeyName); + return encrypt(CacheEntry.encryptionKey, JSON.stringify(this.value), CacheEntry.encryptionKeyName); } // have to make this async because of the encryption call async getJSON(): Promise { @@ -283,9 +283,9 @@ class CacheEntry { static async fromSerialized(itemKey: string, raw: SerializedCacheEntry) { // currently this setup means the encryptedValue changes on every run... // we could instead store the encryptedValue and reuse it if it has not changed - const value = await decrypt(CacheEntry.encryptionKey, raw.encryptedValue, CacheEntry.encryptionKeyName); + const valueStr = await decrypt(CacheEntry.encryptionKey, raw.encryptedValue, CacheEntry.encryptionKeyName); // we are also tossing out the saved "usedBy" entries since we'll have new ones after this config run - return new CacheEntry(itemKey, value, { + return new CacheEntry(itemKey, JSON.parse(valueStr), { updatedAt: new Date(raw.updatedAt), encryptedValue: raw.encryptedValue, }); @@ -628,7 +628,7 @@ export class DmnoWorkspace { return this.valueCache[key].value; } } - async setCacheItem(key: string, value: string, usedBy?: string) { + async setCacheItem(key: string, value: any, usedBy?: string) { if (this.cacheMode === 'skip') return undefined; this.valueCache[key] = new CacheEntry(key, value, { usedBy }); } @@ -952,9 +952,9 @@ export class ResolverContext { async setCacheItem(key: string, value: ConfigValue) { if (process.env.DISABLE_DMNO_CACHE) return; if (value === undefined || value === null) return; - return this.service?.workspace.setCacheItem(key, value.toString(), this.itemFullPath); + return this.service?.workspace.setCacheItem(key, value, this.itemFullPath); } - async getOrSetCacheItem(key: string, getValToWrite: () => Promise) { + async getOrSetCacheItem(key: string, getValToWrite: () => Promise) { if (!process.env.DISABLE_DMNO_CACHE) { const cachedValue = await this.getCacheItem(key); if (cachedValue) return cachedValue; @@ -990,7 +990,7 @@ export abstract class DmnoConfigItemBase { /** error encountered during resolution */ get resolutionError(): ResolutionError | undefined { - return this.valueResolver?.resolutionError; + return this.valueResolver?.selfOrChildResolutionError; } /** resolved value _after_ coercion logic applied */ diff --git a/packages/core/src/config-engine/errors.ts b/packages/core/src/config-engine/errors.ts index f72d2a92..6501ec6d 100644 --- a/packages/core/src/config-engine/errors.ts +++ b/packages/core/src/config-engine/errors.ts @@ -39,19 +39,28 @@ export class DmnoError extends Error { icon = '❌'; - constructor(err: string | Error, readonly more?: { - tip?: string, + constructor(errOrMessage: string | Error, readonly more?: { + tip?: string | Array, + err?: Error, }) { - if (_.isError(err)) { - super(err.message); - this.originalError = err; + if (_.isError(errOrMessage)) { + super(errOrMessage.message); + this.originalError = errOrMessage; this.icon = '💥'; - } else { - super(err); + } else { // string + super(errOrMessage); + this.originalError = more?.err; } + if (Array.isArray(more?.tip)) more.tip = more.tip.join('\n'); this.name = this.constructor.name; } + get tip() { + if (!this.more?.tip) return undefined; + if (Array.isArray(this.more.tip)) return this.more.tip.join('\n'); + return this.more.tip; + } + toJSON() { return { icon: this.icon, @@ -59,7 +68,7 @@ export class DmnoError extends Error { name: this.name, message: this.message, isUnexpected: this.isUnexpected, - ...this.more, + ...this.tip && { tip: this.tip }, }; } } diff --git a/packages/core/src/config-engine/plugins.ts b/packages/core/src/config-engine/plugins.ts index ed7988b5..ef5d5c28 100644 --- a/packages/core/src/config-engine/plugins.ts +++ b/packages/core/src/config-engine/plugins.ts @@ -215,17 +215,12 @@ export abstract class DmnoPlugin< initializedPluginInstanceNames.push(instanceName); - // const callStack = new Error('').stack!.split('\n'); - // const pluginDefinitionPath = callStack[2] - // .replace(/.*\(/, '') - // .replace(/:.*\)/, ''); - // // special case for local dev when we have the plugins symlinked by pnpm - // if (pluginDefinitionPath.includes('/core/packages/plugins/')) { - // const pluginPackageName - // } else { - - // } - // console.log(pluginDefinitionPath); + // ideally we would detect the current package name and version automatically here but I dont think it's possible + // instead we made static properties, which really should be abstract, but that is not supported + // so here we have some runtime checks to ensure they have been set + // see https://github.com/microsoft/TypeScript/issues/34516 + if (!this.pluginPackageName) throw new Error('DmnoPlugin class must set `static pluginPackageName` prop'); + if (!this.pluginPackageVersion) throw new Error('DmnoPlugin class must set `static pluginPackageVersion` prop'); } /** name of the plugin itself - which is the name of the class */ @@ -234,10 +229,16 @@ export abstract class DmnoPlugin< icon?: string; static cliPath?: string; - get cliPath() { + // these 2 should be required, but TS currently does not support static abstract + static pluginPackageName: string; + static pluginPackageVersion: string; + private getStaticProp(key: 'cliPath' | 'pluginPackageName' | 'pluginPackageVersion') { const PluginClass = this.constructor as typeof DmnoPlugin; - return PluginClass.cliPath; + return PluginClass[key]; } + get cliPath() { return this.getStaticProp('cliPath'); } + get pluginPackageName() { return this.getStaticProp('pluginPackageName')!; } + get pluginPackageVersion() { return this.getStaticProp('pluginPackageVersion')!; } /** * reference back to the service this plugin was initialized in diff --git a/packages/core/src/config-engine/resolvers/resolvers.ts b/packages/core/src/config-engine/resolvers/resolvers.ts index 642b3ef6..579c8283 100644 --- a/packages/core/src/config-engine/resolvers/resolvers.ts +++ b/packages/core/src/config-engine/resolvers/resolvers.ts @@ -60,7 +60,7 @@ type ResolverDefinition = { ({ resolve: (ctx: ResolverContext) => MaybePromise, } | { - resolveBranches: Array + resolveBranches: Array }); export function createResolver(def: ResolverDefinition) { @@ -70,13 +70,12 @@ export function createResolver(def: ResolverDefinition) { -type ResolverBranch = { +type ResolverBranchDefinition = { id: string, label: string; resolver: ConfigValueResolver; condition: (ctx: ResolverContext) => boolean; isDefault: boolean; - isActive?: boolean; }; export class ConfigValueResolver { @@ -90,18 +89,29 @@ export class ConfigValueResolver { // and to this parent resolver // so they can access the branch path if needed if ('resolveBranches' in this.def) { - _.each(this.def.resolveBranches, (branchDef) => { - branchDef.resolver.branchDef = branchDef; - branchDef.resolver.parentResolver = this; + this.branches = this.def.resolveBranches.map((branchDef) => { + return new ConfigValueResolverBranch(branchDef, this); }); } } + // the parent/linked resolver branch, if this is a child of branched resolver + linkedBranch?: ConfigValueResolverBranch; + // child resolver branches - for something like `switchBy` + branches: Array | undefined; isResolved = false; resolvedValue?: ConfigValue; isUsingCache = false; resolutionError?: ResolutionError; + get selfOrChildResolutionError(): ResolutionError | undefined { + if (this.resolutionError) return this.resolutionError; + if (!this.branches) return; + for (const b of this.branches) { + const branchResolutionError = b.def.resolver.selfOrChildResolutionError; + if (branchResolutionError) return branchResolutionError; + } + } icon?: string; label?: string; @@ -109,28 +119,28 @@ export class ConfigValueResolver { private _configItem?: DmnoConfigItemBase; set configItem(configItem: DmnoConfigItemBase | undefined) { this._configItem = configItem; - if ('resolveBranches' in this.def) { - _.each(this.def.resolveBranches, (branch) => { - branch.resolver.configItem = configItem; - }); - } + this.branches?.forEach((branch) => { + branch.def.resolver.configItem = configItem; + }); } get configItem() { return this._configItem; } - parentResolver?: ConfigValueResolver; - branchDef?: ResolverBranch; + get parentResolver() { + return this.linkedBranch?.parentResolver; + } get branchIdPath(): string | undefined { - if (!this.branchDef) return undefined; + if (!this.linkedBranch) return undefined; + const thisBranchId = this.linkedBranch.def.id; if (this.parentResolver) { const parentBranchIdPath = this.parentResolver.branchIdPath; if (parentBranchIdPath) { - return `${this.parentResolver.branchIdPath}/${this.branchDef.id}`; + return `${this.parentResolver.branchIdPath}/${thisBranchId}`; } } - return this.branchDef?.id; + return thisBranchId; } getFullPath() { @@ -140,7 +150,6 @@ export class ConfigValueResolver { ]).join('#'); } - async resolve(ctx: ResolverContext) { if (_.isFunction(this.def.icon)) this.icon = this.def.icon(ctx); if (_.isFunction(this.def.label)) this.label = this.def.label(ctx); @@ -168,28 +177,44 @@ export class ConfigValueResolver { let resolutionResult: ConfigValueResolver | ValueResolverResult; // deal with branched case (ex: switch / if-else) - if ('resolveBranches' in this.def) { + if (this.branches) { // find first branch that passes - let matchingBranch = _.find(this.def.resolveBranches, (branch) => { - if (branch.isDefault) return false; - return branch.condition(ctx); + let matchingBranch = _.find(this.branches, (branch) => { + if (branch.def.isDefault) return false; + try { + return branch.def.condition(ctx); + } catch (err) { + this.resolutionError = new ResolutionError(`Error in resolver branch condition (${branch.def.label})`, { err: err as Error }); + } + return false; }); + // bail early if we failed evaluating resolver conditions + if (this.resolutionError) { + this.isResolved = false; + return; + } + if (!matchingBranch) { - matchingBranch = _.find(this.def.resolveBranches, (branch) => branch.isDefault); + matchingBranch = _.find(this.branches, (branch) => branch.def.isDefault); } - _.each(this.def.resolveBranches, (branch) => { + _.each(this.branches, (branch) => { branch.isActive = branch === matchingBranch; }); // TODO: might be able to force a default to be defined? if (!matchingBranch) { - throw new Error('no matching resolver branch found and no default'); + throw new ResolutionError('no matching resolver branch found and no default'); } - resolutionResult = matchingBranch.resolver || undefined; + resolutionResult = matchingBranch.def.resolver || undefined; // deal with normal case } else { + // should always be the case, since resolvers must have branches or a resolve fn + if (!('resolve' in this.def)) { + throw new Error('expected `resolve` fn in resolver definition'); + } + // actually call the resolver try { resolutionResult = await this.def.resolve(ctx); @@ -234,20 +259,39 @@ export class ConfigValueResolver { createdByPluginInstanceName: this.def.createdByPlugin?.instanceName, // itemPath: this.configItem?.getFullPath(), // branchIdPath: this.branchIdPath, - ...'resolveBranches' in this.def && { - branches: _.map(this.def.resolveBranches, (b) => ({ - id: b.id, - label: b.label, - isDefault: b.isDefault, - isActive: b.isActive, - resolver: b.resolver.toJSON(), - })), + ...this.branches && { + branches: this.branches.map((b) => b.toJSON()), }, resolvedValue: this.resolvedValue, resolutionError: this.resolutionError?.toJSON(), }; } } +export class ConfigValueResolverBranch { + constructor( + readonly def: ResolverBranchDefinition, + readonly parentResolver: ConfigValueResolver, + ) { + // link the branch definition resolver back to this object + this.def.resolver.linkedBranch = this; + } + + isActive?: boolean; + get id() { return this.def.id; } + get label() { return this.def.label; } + get isDefault() { return this.def.isDefault; } + get resolver() { return this.def.resolver; } + + toJSON() { + return { + id: this.id, + label: this.label, + isDefault: this.isDefault, + isActive: this.isActive, + resolver: this.resolver.toJSON(), + }; + } +} export function processInlineResolverDef(resolverDef: InlineValueResolverDef) { // set up value resolver diff --git a/packages/docs-site/src/assets/docs-images/plugins/1password/blob-item-example.png b/packages/docs-site/src/assets/docs-images/plugins/1password/blob-item-example.png new file mode 100644 index 0000000000000000000000000000000000000000..3d1b2a5ae0f000a38a7cad8caf14dd70a9b71a82 GIT binary patch literal 101035 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 literal 0 HcmV?d00001 diff --git a/packages/docs-site/src/content/docs/docs/plugins/1password.mdx b/packages/docs-site/src/content/docs/docs/plugins/1password.mdx index 7fcb0da3..095be0bf 100644 --- a/packages/docs-site/src/content/docs/docs/plugins/1password.mdx +++ b/packages/docs-site/src/content/docs/docs/plugins/1password.mdx @@ -6,96 +6,148 @@ description: DMNO's 1Password plugin allows you to securely access your stored s import { Steps, Icon } from '@astrojs/starlight/components'; import TabbedCode from '@/components/TabbedCode.astro'; -DMNO's 1Password plugin allows you to securely access your stored secrets in 1Password. This plugin uses the 1Password CLI by means of a [Service Account](https://developer.1password.com/docs/service-accounts). It is compatible with any account type. Note that rate limits vary by account type, you can read more about that in the [1Password Developer documentation](https://developer.1password.com/docs/service-accounts/rate-limits/). +DMNO's [1Password](https://1password.com/) plugin allows you to securely access your secrets stored in 1Password. This plugin uses their [JavaScript SDK](https://github.com/1Password/onepassword-sdk-js/) to authenticate using a [service account](https://developer.1password.com/docs/service-accounts). Additionally, for local development, you can opt-in to use your system-installed [1Password CLI](https://developer.1password.com/docs/cli/get-started/) and its [integration with the 1Password desktop app](https://developer.1password.com/docs/cli/get-started/#step-2-turn-on-the-1password-desktop-app-integration). This plugin is compatible with any 1Password account type (personal, family, teams, business), but note that [rate limits](https://developer.1password.com/docs/service-accounts/rate-limits/) vary by account type. -## Installation +## Installation & setup -Install the package in your service(s) that will use config from 1password. +Install the package in the service(s) that will use config from 1Password. ----- -After installation, you'll need to initialize the plugin in your dmno config and wire it up to the config path that will hold your 1password service account token. It's ok if you have not created this service account yet - we'll do that in the next section. +After installation, you'll need to initialize the plugin in your dmno config and wire it up to the config path that will hold your 1Password service account token. It's ok if you have not created this service account yet - we'll do that in the next section. -```typescript title='.dmno/config.mts' -import { OnePasswordDmnoPlugin, OnePasswordTypes } from '@dmno/1password-plugin'; +```diff lang="ts" title='.dmno/config.mts' ++import { OnePasswordDmnoPlugin, OnePasswordTypes } from '@dmno/1password-plugin'; -const OnePassBackend = new OnePasswordDmnoPlugin('1pass/prod', { - token: configPath('OP_TOKEN'), -}); ++const OnePassBackend = new OnePasswordDmnoPlugin('1pass', { ++ token: configPath('OP_TOKEN'), ++}); export default defineDmnoService({ schema: { - OP_TOKEN: { - extends: OnePasswordTypes.serviceAccountToken, - // NOTE - the type itself is already marked as sensitive 🔐 - }, ++ OP_TOKEN: { ++ extends: OnePasswordTypes.serviceAccountToken, ++ // NOTE - the type itself is already marked as sensitive 🔐 ++ }, }, }); ``` -:::tip -You must give each plugin instance a unique id so we can refer to it in other services and the CLI. In this case we used `1pass/prod`, as you can imagine using multiple 1password vaults and having another plugin instance of `1pass/non-prod`. +:::tip[Plugin instance IDs] +You must give each plugin instance a unique id so we can refer to it in other services and the [`dmno` CLI](/docs/reference/cli/plugin/). + +In this case we used `1pass`, but you can imagine splitting vaults and access, and having multiple plugin instances - for example `1pass/prod` for highly sensitive production secrets and `1pass/dev` for everything else. ::: ### Injecting the plugin in monorepo services -In a monorepo, if you are managing config for multiple services in a single vault, you should initialize the plugin instance once in your root service as seen above, and then _inject_ it in the child services. Note we need that same id we set during initialization. +In a monorepo, you are likely managing secrets for multiple services. If you will be using the same service account(s) to access those secrets, you can initialize a plugin instance once in your root service as seen above, and then _inject_ it in child services. Note we must use that same id we set during initialization. ```typescript title='apps/some-service/.dmno/config.mts' import { OnePasswordDmnoPlugin } from '@dmno/1password-plugin'; -// inject the already initialized plugin instead of re-initializing it -const OnePassBackend = OnePasswordDmnoPlugin.injectInstance('1pass/prod'); +// 💉 inject the already initialized plugin instead of re-initializing it +const OnePassBackend = OnePasswordDmnoPlugin.injectInstance('1pass'); ``` -:::tip -For more on 1Password security, see their best practices for [CLI](https://developer.1password.com/docs/cli/best-practices/) and [business accounts](https://support.1password.com/business-security-practices/#access-management-and-the-principle-of-least-privilege). -::: - - ------------ ## Setup vault & service account +If you already use 1password and your secrets live in a vault that holds other important passwords and info, you should create a new vault and move your secrets to it, because **the access system of 1password is based on vaults, not individual items**. + -1. **Create a vault** in your 1Password account. This is where you'll store your secrets. You can create multiple vaults for different environments or services. [link](https://support.1password.com/create-share-vaults/#create-a-vault) +1. **Create a vault** in your 1Password account which will be used to hold your secrets. You can create multiple vaults to segment access to different environments, services, etc. This can be done using any 1password app, the web app, or the CLI. [link](https://support.1password.com/create-share-vaults/#create-a-vault) + +2. **Create a new service account** and grant access to necessary vault(s). This is a special account used for machine-to-machine communication. This can only be done in the 1Password web interface. Be sure to copy the new service account token or save it in another vault. [link](https://developer.1password.com/docs/service-accounts/get-started/) + :::note[Vault access set during creation only] + Vault access rules cannot be edited after creation, so if your vault setup changes, you will need to create new service account(s) and update the tokens. + ::: -2. **Create a service account** in your 1Password account. This is a separate account that has access to the vault(s) you created. You can create multiple service accounts for different environments or services. [link](https://developer.1password.com/docs/service-accounts/get-started/) +3. **Grant vault access to users/teams (optional)**. Your developers may need access to at least some of your vaults, especially if using the `op` cli based auth mentioned below. [link](https://support.1password.com/create-share-vaults-teams/#share-a-vault) -3. **Grant vault access to the service account**. This is done in the 1Password web interface. You can add multiple service accounts to a single vault. [link](https://developer.1password.com/docs/service-accounts/manage-service-accounts/#manage-access) +4. **Ensure vault service account access is enabled (optional)**. Each vault has a toggle to disable service account access _in general_. It is on by default, so you will likely not need to do anything. [link](https://developer.1password.com/docs/service-accounts/manage-service-accounts/#manage-access) -This service account token will now serve as your "secret-zero" - which grants access to the rest of your sensitive config stored in 1password. It must be set locally and in deployed environments, and as it is sensitive, we must pass in the value as an _override_ rather than storing it within the config. Locally this usually means storing it in your `.env.local` and on a deployed environment you'll usually set it within some kind of UI, wherever you would normally pass in secrets. +This service account token will now serve as your "secret-zero" - which grants access to the rest of your sensitive config stored in 1Password. It must be set locally (unless relying on cli-based auth) and in deployed environments. It is sensitive so we must pass in the value as an _override_ rather than storing it within the config. Locally this usually means storing it in your [`.env.local` file](/docs/guides/env-files/) and on a deployed environment you'll usually set it within some kind of UI, wherever you would normally pass in environment variables. ```diff title=".dmno/.env.local" +OP_TOKEN=ops_abc123... ``` -Note that the config path of `OP_TOKEN` is arbitrary and you can see how it was wired up from the config to the plugin input above. If you are using multiple vaults and service accounts, you may have something more like `OP_PROD_TOKEN` and `OP_NON_PROD_TOKEN`. +Note that the config path of `OP_TOKEN` is arbitrary and you can see how it was wired up from your config schema to the plugin input in the example above. If you are using multiple vaults and service accounts, you may have something more like `OP_TOKEN_PROD` and `OP_TOKEN_DEV`. + +:::tip[Vault organization best practices] +Consider how you want to organize your vaults and service accounts, keeping in mind [best practices](https://support.1password.com/business-security-practices/#access-management-and-the-principle-of-least-privilege). At a minimum, DMNO recommends having a vault for highly sensitive production secrets and another for everything else. +::: + + +### Desktop app / CLI integration (optional) + +During local development, you may find it convenient to skip the service account tokens and instead rely on your system's `op` CLI and its [integration with the 1Password desktop app](https://developer.1password.com/docs/cli/get-started/#step-2-turn-on-the-1password-desktop-app-integration). This means you will be connecting to 1Password as if you were using your local 1Password desktop application, including using its biometric unlocking features. + + +1. **Opt-in while initializing the plugin** + ```diff lang="ts" title='.dmno/config.mts' + const OnePassBackend = new OnePasswordDmnoPlugin('1pass/dev', { + token: configPath('OP_TOKEN'), + + fallbackToCliBasedAuth: true, + }); + ``` + + _Of course you can also point to a `configPath` in your schema and toggle the opt-in based on some other logic if you'd like._ -:::tip -Consider how you want to organize your vaults and service accounts. You might have a single vault for all your secrets per environment, or you might have separate vaults for each service and environment. At a minimum, DMNO recommends having separate vaults for production and non-production environments. +2. **Ensure the `op` CLI is installed**. [docs](https://developer.1password.com/docs/cli/get-started/) + +3. **Enable the desktop app + CLI integration**. [docs](https://developer.1password.com/docs/cli/get-started/#step-2-turn-on-the-1password-desktop-app-integration) + +4. **Run `op signin` to sign in on the CLI**. Ensure you are logged in to the correct account. You can run `op whoami` to see which account is currently connected to the CLI. + + +With this option enabled, if the resolved service account token is empty, we will call out to the `op` cli installed on your machine (it must be in your `$PATH`) and use the auth it provides. With the desktop app integration enabled, it will call out and may trigger biometric verification to unlock. It is secure and very convenient! + +:::caution[Connecting as yourself] +Keep in mind that this method is connecting as _YOU_ who may have more access than a tightly scoped service account. Consider only enabling this method for a plugin instance that will be handling non-production secrets. ::: ------ -## Add your items +## Add items to your schema -DMNO supports a few different ways to reference items in 1Password. +With the plugin initialized and access wired up, now we must update our config schema to connect specific config values to data stored in 1Password. DMNO supports a few different ways to reference items in 1Password: -### Using a env blob (recommended) +### Using a `.env` blob -Managing lots of individual 1password items and connecting them to your config can be a bit tedious, so we recommend storing multiple items together in a `.env` style text blob. Using this method, we'll have a single 1password item that can have one text entry per service containing the `.env` blob. This would be similar to applying a `.env.local` file as overrides, except they are secured and shared via 1password. This also makes it incredibly easy to migrate from using local `.env` files. +Managing lots of individual 1Password items and connecting them to your config can be a bit tedious. So, when getting started, we recommend storing multiple items together in a `.env` style text blob. Using this method, we'll have a single 1Password item that can have one text entry per service containing the `.env` blob and look up items by their key - similar to applying a `.env.local` file as overrides, except they are secured and shared via 1Password. This also makes it easier to migrate from passing around `.env` files. -To use this method, we need to tell the plugin which 1password item will store our `.env` blob(s). As this value is static and not sensitive, we can use a static value as our plugin input. + -```diff lang="ts" title=".dmno/config.mts" -import { OnePasswordDmnoPlugin, OnePasswordTypes } from '@dmno/1password-plugin'; +1. **Create a new item within your vault**. Select `Secure Note` as the item type and be sure to give it a descriptive name (e.g., `Prod secrets`). + +2. **Create a new field in the item**. Click `+ add more` and select `Text` to add a new multi-line text field. Change the default label of `text` to the [service name](/docs/guides/schema/#service-names) you want to store secrets for (e.g., `root`). You can also use the special name `_default` if you are only dealing with a single service. + :::note[Multiple services] + In a monorepo, you can initialize a single plugin instance in your root service and inject it into each child service. In this case, add a field for each service. + ::: +3. **Add your secrets to the text field** as if it was another `.env` file that would be loaded as overrides. For example: + ``` + SOME_API_KEY=super-secret-key + ANOTHER_ITEM="quotes work too" + ``` + _You can also come back and do this later._ + +3. **Wire up plugin instance to the new item** using its _private link_. While viewing the item in the 1Password app, click the 3 dots in the top right and click `Copy Private Link`. As this link does not contain anything sensitive, we can use a static value as our plugin input. + +4. **Update items in your config schema** to use the `.item()` value resolver for anything that will be stored in the linked 1Password item. When we resolve your config values, if a match is not found, it will result in a `ResolutionError` with helpful info about how to fix it. + + + +Your dmno config should end up looking like this: +```diff lang="ts" title=".dmno/config.mts" const OnePassBackend = new OnePasswordDmnoPlugin('1pass/prod', { token: configPath('OP_TOKEN'), + envItemLink: 'https://start.1password.com/open/i?a=I3GUA2KU6BD3FBHA47QNBIVEV4&v=ut2dftalm3ugmxc6klavms6tfq&i=n4wmgfq77mydg5lebtroa3ykvm&h=dmnoinc.1password.com', @@ -114,11 +166,37 @@ export default defineDmnoService({ }); ``` -{/* TODO: add screenshot of 1pass item showing the entry? */} +You can also override the key used to lookup the value in the `.env` blob. This can be useful if you need to save multiple values toggled by some other logic. -### Using specific 1password items +```typescript title=".dmno/config.mts" +export default defineDmnoService({ + schema: { + SOME_API_SECRET: { + sensitive: true, + value: switchBy('APP_ENV', { + // uses the default key of "SOME_API_SECRET" + _default: OnePassBackendDev.item(), + // uses overridden key + staging: OnePassBackendDev.item('SOME_API_SECRET_STAGING'), + // uses the default key but looking in a different 1pass item + staging: OnePassBackendProduction.item(), + }), + }, + }, +}); +``` + +#### Lookup convention example +Values are looked up within the linked 1Password item using a simple convention. We expect to find a _text field_ within the item with a label set to the current [service name](/docs/guides/schema/#service-names). The contents of that item are parsed as a [`.env` file](https://dotenvx.com/docs/env-file), and we look up items using the config item key. If no match is found, we will also look in an additional field with the label `_default`. + +![1Password blob item example](../../../../assets/docs-images/plugins/1password/blob-item-example.png) + +For example, in the item above, an item with the key `ONE_MORE` would fallback to the value in the `_default` field in any service that wasn't named `root`. + + +### Using specific 1Password items -If you already have lots of indivdual items in 1password, or you just don't want to use the blob method, you can wire up invididual config items to specific 1password items. We provide several methods to do so. Note that while a 1password reference (e.g., `op://vaultname/itemname/path`) points all the way to a specific value, the other methods only get us to an item which usually contains multiple entries (account id, secret key, etc). In these cases you must also pass in an additional path to the specific entry. These paths use the entry labels. +If you already have lots of individual items in 1Password, or you just don't want to use the blob method, we provide several methods to wire up individual config items to specific values in 1Password. Note that while 1Password reference URIs (e.g., `op://vaultname/itemname/path`) are easier to use in some ways, they are based on field labels and are not stable, so the other methods are preferred. ```ts export default defineDmnoService({ @@ -127,14 +205,14 @@ export default defineDmnoService({ ITEM_WITH_LINK: { value: OnePassBackend.itemByLink( 'https://start.1password.com/open/i?a=I3GUA2KU6BD3FBHA47QNBIVEV4&v=ut2dftalm3ugmxc6klavms6tfq&i=n4wmgfq77mydg5lebtroa3ykvm&h=dmnoinc.1password.com', - 'somepath', + 'somefieldid', ), }, - // using vault + item UUIDs + // using UUIDs ITEM_WITH_IDS: { - value: OnePassBackend.itemById('vaultUuid', 'itemUuid', 'somepath'), + value: OnePassBackend.itemById('vaultUuid', 'itemUuid', 'somefieldid'), }, - // using item reference + // using item reference url ITEM_WITH_REFERENCE: { value: OnePassBackend.itemByReference('op://vaultname/itemname/path'), }, @@ -143,11 +221,27 @@ export default defineDmnoService({ ``` :::tip[Where to find an item private link] -You can find the private link by clicking the 3 dots on the item in the 1Password interface and selecting `Copy Private Link`. +You can find the private link by clicking the 3 dots **on the item** in the 1Password interface and selecting `Copy Private Link`. +::: + +:::tip[Where to find field IDs] +Field IDs are not easy to get from the 1Password UI. Luckily when the supplied field ID is not found, our error message includes a list of all the possible IDs in the item. Start with an empty string or a bogus ID like `"?"` and use the DMNO error message to find the right field ID. ::: :::tip[Where to find an item reference] -The secret reference for invidivual items can be found by clicking on the down arrow icon on the item and selecting `Copy Secret Reference`. +The secret reference for invidivual fields within an item can be found by clicking on the down arrow icon **on the field** and selecting `Copy Secret Reference`. +::: + +## Caching +In order to avoid rate limits and keep dev server restarts extremely fast, we heavily cache data fetched from external sources. After updating secrets in 1password, if the item has been cached, you'll need to clear the cache to see it take effect. + +- Use the [`dmno clear-cache` command](/docs/reference/cli/clear-cache/) to clear the cache once +- The [`dmno resolve`](/docs/reference/cli/resolve/) and [`dmno run`](/docs/reference/cli/run/) commands have cache related flags: + - `--skip-cache` - skips caching logic altogether + - `--clear-cache` - clears the cache once before continuing as normal + +:::tip[Active config iteration] +While you are actively working on the config itself, `dmno resolve -w --skip-cache` will combine watch mode with skipping cache logic. -If you don't see the menu option, you may need to enable it in the 1Password Developer settings and you may need to install the 1Password CLI as well. +Once you are satisfied, _clear_ the cache once more and you are good to go. ::: diff --git a/packages/plugins/1password/package.json b/packages/plugins/1password/package.json index 1e20107f..8bd265e0 100644 --- a/packages/plugins/1password/package.json +++ b/packages/plugins/1password/package.json @@ -29,16 +29,14 @@ } }, "files": [ - "/dist", - "/scripts" + "/dist" ], "scripts": { "build": "tsup", "build:ifnodist": "[ -d \"./dist\" ] && echo 'dist exists' || pnpm build", "dev": "pnpm run build --watch", "lint": "eslint src --ext .ts,.cjs,.mjs", - "lint:fix": "pnpm run lint --fix", - "postinstall": "node scripts/install-cli.mjs" + "lint:fix": "pnpm run lint --fix" }, "devDependencies": { "@dmno/eslint-config": "workspace:*", @@ -52,6 +50,7 @@ "typescript": "^5.4.5" }, "dependencies": { + "@1password/sdk": "^0.1.1", "async": "^3.2.5", "kleur": "^4.1.5", "lodash-es": "^4.17.21", diff --git a/packages/plugins/1password/scripts/install-cli.mjs b/packages/plugins/1password/scripts/install-cli.mjs deleted file mode 100644 index 514773d2..00000000 --- a/packages/plugins/1password/scripts/install-cli.mjs +++ /dev/null @@ -1,89 +0,0 @@ -import os from 'node:os'; -import fs from 'node:fs'; -import stream from 'node:stream'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { execSync } from 'node:child_process'; -import unzip from 'unzip-stream'; - - -// see links on https://app-updates.agilebits.com/product_history/CLI2 for where this comes from -const SUPPORT_MATRIX = { - darwin: ['amd64', 'arm64'], - freebsd: ['386', 'amd64', 'arm', 'arm64'], - linux: ['386', 'amd64', 'arm', 'arm64'], - openbsd: ['386', 'amd64', 'arm_64'], - windows: ['386', 'amd64'], -}; - -async function install1passwordCli(forceVersion = 'latest') { - let version; - if (forceVersion === 'latest') { - const checkVersionRequest = await fetch('https://app-updates.agilebits.com/check/1/0/CLI2/en/2.0.0/N'); - const versionJson = await checkVersionRequest.json(); - version = versionJson.version; - } else { - version = forceVersion; - } - - // we install into module's folder so that pnpm will cache it (previously was putting into node_modules/.bin) - const installToPath = path.resolve(fileURLToPath(import.meta.url), '../../op-cli'); - - if (fs.existsSync(installToPath)) { - try { - const installedVersion = execSync(`${installToPath} -v`).toString().trim(); - if (installedVersion === version) { - // console.log('correct 1pass cli version already installed'); - return; - } - } catch (err) { - // can fail silently since we will re-install below - } - } - - - - const platform = os.platform(); - let arch = os.arch(); - if (arch === 'x64') arch = 'amd64'; // netlify build servers are x64 - if (!(SUPPORT_MATRIX)[platform]) { - throw new Error(`Unsupported platform - ${platform}`); - } - - if (!(SUPPORT_MATRIX)[platform].includes(arch)) { - throw new Error(`Unsupported architecture - ${platform}/${arch}`); - } - - const zipReq = await fetch(`https://cache.agilebits.com/dist/1P/op2/pkg/v${version}/op_${platform}_${arch}_v${version}.zip`); - - if (!zipReq.body) { - throw new Error('fetching op cli zip failed'); - } - - - fs.openSync(installToPath, 'w', 0o755); - - // pipe the response stream through unzip - // and write the `op` cli to our node_modules/.bin folder - stream.Readable.fromWeb(zipReq.body) - .pipe(unzip.Parse()) - .pipe(new stream.Transform({ - objectMode: true, - transform(entry, e, cb) { - if (entry.type === 'File' && entry.path === 'op') { - entry.pipe(fs.createWriteStream(installToPath, { mode: 0o755 })) - .on('finish', cb); - } else if (entry.type === 'File' && entry.path === 'op.sig') { - // TODO: check signature - entry.autodrain(); - cb(); - } else { - entry.autodrain(); - cb(); - } - }, - })); -} - -await install1passwordCli(); - diff --git a/packages/plugins/1password/src/plugin.ts b/packages/plugins/1password/src/plugin.ts index 99dab8e6..f7fba7b8 100644 --- a/packages/plugins/1password/src/plugin.ts +++ b/packages/plugins/1password/src/plugin.ts @@ -1,29 +1,81 @@ -import { execSync } from 'child_process'; -import { fileURLToPath } from 'url'; -import path from 'path'; +import { spawnSync } from 'child_process'; import _ from 'lodash-es'; import kleur from 'kleur'; import { - ConfigValueResolver, DmnoPlugin, ResolverContext, + DmnoPlugin, ResolverContext, DmnoPluginInputSchema, DmnoPluginInputMap, ResolutionError, SchemaError, - GetPluginInputTypes, - createResolver, _PluginInputTypesSymbol, loadDotEnvIntoObject, } from 'dmno'; +import { Client, createClient } from '@1password/sdk'; +import { name as thisPackageName, version as thisPackageVersion } from '../package.json'; import { OnePasswordTypes } from './data-types'; +type FieldId = string; type ItemId = string; type VaultId = string; type VaultName = string; type ReferenceUrl = string; type ServiceAccountToken = string; -const CLI_PATH = path.resolve(fileURLToPath(import.meta.url), '../../op-cli'); +async function execOpCliCommand(cmdArgs: Array) { + // using system-installed copy of `op` + const cmd = spawnSync('op', cmdArgs); + if (cmd.status === 0) { + return cmd.stdout.toString(); + } else if (cmd.error) { + if ((cmd.error as any).code === 'ENOENT') { + throw new ResolutionError('1password cli `op` not found', { + tip: [ + 'By not using a service account token, you are relying on your local 1password cli installation for ambient auth.', + 'But your local 1password cli (`op`) was not found. Install it here - https://developer.1password.com/docs/cli/get-started/', + ], + }); + } else { + throw new ResolutionError(`Problem invoking 1password cli: ${cmd.error.message}`); + } + } else { + let errMessage = cmd.stderr.toString(); + // get rid of "[ERROR] 2024/01/23 12:34:56 " before actual message + if (errMessage.startsWith('[ERROR]')) errMessage = errMessage.substring(28); + if (errMessage.includes('authorization prompt dismissed')) { + throw new ResolutionError('1password app authorization prompt dismissed by user', { + tip: [ + 'By not using a service account token, you are relying on your local 1password installation', + 'When the authorization prompt appears, you must authorize/unlock 1password to allow access', + ], + }); + } else if (errMessage.includes("isn't a vault in this account")) { + throw new ResolutionError('1password vault not found in account connected to op cli', { + tip: [ + 'By not using a service account token, you are relying on your local 1password cli installation and authentication.', + 'The account currently connected to the cli does not contain (or have access to) the selected vault', + 'This must be resolved in your terminal - try running `op whoami` to see which account is connected to your `op` cli.', + 'You may need to call `op signout` and `op signin` to select the correct account.', + ], + }); + } + // when the desktop app integration is not connected, some interactive CLI help is displayed + // however if it dismissed, we get an error with no message + // TODO: figure out the right workflow here? + if (!errMessage) { + throw new ResolutionError('1password cli not configured', { + tip: [ + 'By not using a service account token, you are relying on your local 1password cli installation and authentication.', + 'You many need to enable the 1password Desktop app integration, see https://developer.1password.com/docs/cli/get-started/#step-2-turn-on-the-1password-desktop-app-integration', + 'Try running `op whoami` to make sure the cli is connected to the correct account', + 'You may need to call `op signout` and `op signin` to select the correct account.', + ], + }); + } + + throw new Error(`1password cli error - ${errMessage || 'unknown'}`); + } +} // Typescript has some limitations around generics and how things work across parent/child classes // so unfortunately, we have to add some extra type annotations, but it's not too bad @@ -34,20 +86,34 @@ const CLI_PATH = path.resolve(fileURLToPath(import.meta.url), '../../op-cli'); // export class OnePasswordDmnoPlugin extends DmnoPlugin< // typeof OnePasswordDmnoPlugin.inputSchema, typeof OnePasswordDmnoPlugin.INPUT_TYPES // > { + +/** + * DMNO plugin to retrieve secrets from 1Password + * + * @see https://dmno.dev/docs/plugins/1password/ + */ export class OnePasswordDmnoPlugin extends DmnoPlugin { icon = 'simple-icons:1password'; + // would be great to do this automatically as part of `DmnoPlugin` but I don't think it's possible + // so instead we add some runtime checks in DmnoPlugin + static pluginPackageName = thisPackageName; + static pluginPackageVersion = thisPackageVersion; + static readonly inputSchema = { token: { description: 'this service account token will be used via the CLI to communicate with 1password', extends: OnePasswordTypes.serviceAccountToken, - required: true, + // TODO: add validation, token must be set unless `fallbackToCliBasedAuth` is true + // required: true, }, envItemLink: { description: 'link to secure note item containing dotenv style values', extends: OnePasswordTypes.itemLink, }, - + fallbackToCliBasedAuth: { + description: "if token is empty, use system's `op` CLI to communicate with 1password", + }, } satisfies DmnoPluginInputSchema; // ^^ note this explicit `satisfies` is needed to give us better typing on our inputSchema @@ -63,7 +129,80 @@ export class OnePasswordDmnoPlugin extends DmnoPlugin { this.setInputMap(inputs); } + private opClient: Client | undefined; + private async initOpClientIfNeeded() { + if (!this.inputValues.token) return; + if (!this.opClient) { + this.opClient = await createClient({ + auth: this.inputValues.token, + integrationName: this.pluginPackageName.replaceAll('@', '').replaceAll('/', ' '), + integrationVersion: this.pluginPackageVersion, + }); + } + } + + private async getOpItemById(ctx: ResolverContext, vaultId: VaultId, itemId: ItemId) { + await this.initOpClientIfNeeded(); + // using sdk + if (this.opClient) { + return await ctx.getOrSetCacheItem(`1pass-sdk:V|${vaultId}/I|${itemId}`, async () => { + // TODO: better error handling to tell you what went wrong? no access, non existant, etc + try { + const opItem = await this.opClient!.items.get(vaultId, itemId); + return JSON.parse(JSON.stringify(opItem)); // convert to plain object + } catch (err) { + // 1pass sdk throws strings as errors... + if (_.isString(err)) { + if (err.includes('not a valid UUID')) { + throw new ResolutionError('Either the Vault ID or the item ID is not a valid UUID'); + } else if (err === 'error when retrieving vault metadata: http error: unexpected http status: 404 Not Found') { + throw new ResolutionError(`Vault ID "${vaultId}" not found in this account`); + } else if (err === 'error when retrieving item details: http error: unexpected http status: 404 Not Found') { + throw new ResolutionError(`Item ID "${itemId}" not found within Vault ID "${vaultId}"`); + } + throw new ResolutionError(`1password SDK error - ${err}`); + } + throw err; + } + }); + } + // using cli + return await ctx.getOrSetCacheItem(`1pass-cli:V|${vaultId}/I|${itemId}`, async () => { + const itemJson = await execOpCliCommand([ + 'item', 'get', itemId, + `--vault=${vaultId}`, + '--format=json', + ]); + return JSON.parse(itemJson); + }); + } + private async getOpItemByReference(ctx: ResolverContext, referenceUrl: ReferenceUrl) { + await this.initOpClientIfNeeded(); + // using sdk + if (this.opClient) { + try { + return await ctx.getOrSetCacheItem(`1pass-sdk:R|${referenceUrl}`, async () => { + // TODO: better error handling to tell you what went wrong? no access, non existant, etc + return await this.opClient!.secrets.resolve(referenceUrl); + }); + } catch (err) { + // 1pass sdk throws strings as errors... + if (_.isString(err)) { + throw new ResolutionError(`1password SDK error - ${err}`); + } + throw err; + } + } + // using op CLI + return await ctx.getOrSetCacheItem(`1pass-cli:R|${referenceUrl}`, async () => { + return await execOpCliCommand([ + 'read', referenceUrl, + '--force', + '--no-newline', + ]); + }); + } private envItemsByService: Record> | undefined; private async loadEnvItems(ctx: ResolverContext) { @@ -72,26 +211,18 @@ export class OnePasswordDmnoPlugin extends DmnoPlugin { const vaultId = url.searchParams.get('v')!; const itemId = url.searchParams.get('i')!; - const envItemJsonStr = await ctx.getOrSetCacheItem(`1pass:V|${vaultId}/I|${itemId}`, async () => { - return await execSync([ - `OP_SERVICE_ACCOUNT_TOKEN=${this.inputValues.token}`, - CLI_PATH, - `item get ${itemId}`, - `--vault=${vaultId}`, - '--format json', - ].join(' ')).toString(); - }); - const envItemsObj = JSON.parse(envItemJsonStr); + const envItemsObj = await this.getOpItemById(ctx, vaultId, itemId); const loadedEnvByService: typeof this.envItemsByService = {}; _.each(envItemsObj.fields, (field) => { + if (field.purpose === 'NOTES') return; // the "default" items on a secure note get added to an invisible "add more" section // we could force users to only add in there? but it might get confusing...? - const serviceName = field.label; + const serviceName = field.label || field.title; // cli uses "label", sdk uses "title" // make sure we dont have a duplicate if (loadedEnvByService[serviceName]) { - throw new ResolutionError(`Duplicate env item found - ${serviceName} `); + throw new ResolutionError(`Duplicate service entries found in 1pass item - ${serviceName} `); } const dotEnvObj = loadDotEnvIntoObject(field.value); loadedEnvByService[serviceName] = dotEnvObj; @@ -100,7 +231,25 @@ export class OnePasswordDmnoPlugin extends DmnoPlugin { }); this.envItemsByService = loadedEnvByService; } - item() { + + + /** + * resolver to fetch a 1password value from a .env blob within a text field. + * + * Plugin instance must be initialized with `envItemLink` input set to use this resolver. + * + * Items are looked up within the blob using their key + * + * @see https://dmno.dev/docs/plugins/1password/ + */ + item( + /** + * optionally override the key used to look up the item within the dotenv blob + * + * _not often necessary!_ + * */ + overrideLookupKey?: string, + ) { // make sure the user has mapped up an input for where the env data is stored if (!this.inputItems.envItemLink.resolutionMethod) { throw new SchemaError('You must set an `envItemLink` plugin input to use the .item() resolver'); @@ -108,14 +257,16 @@ export class OnePasswordDmnoPlugin extends DmnoPlugin { return this.createResolver({ label: (ctx) => { - return `env blob item > ${ctx.serviceName} > ${ctx.itemPath}`; + return `env blob item > ${ctx.serviceName} > ${overrideLookupKey || ctx.itemPath}`; }, resolve: async (ctx) => { if (!this.envItemsByService) await this.loadEnvItems(ctx); - const itemValue = this.envItemsByService?.[ctx.serviceName!]?.[ctx.itemPath] + const lookupKey = overrideLookupKey || ctx.itemPath; + + const itemValue = this.envItemsByService?.[ctx.serviceName!]?.[lookupKey] // the label "_default" is used to signal a fallback / default to apply to all services - || this.envItemsByService?._default?.[ctx.itemPath]; + || this.envItemsByService?._default?.[lookupKey]; if (itemValue === undefined) { throw new ResolutionError('Unable to find config item in 1password', { @@ -124,8 +275,8 @@ export class OnePasswordDmnoPlugin extends DmnoPlugin { kleur.gray(`🔗 ${this.inputValues.envItemLink}`), `Find entry with label ${kleur.bold().cyan(ctx.serviceName!)} (or create it)`, 'Add this secret like you would add it to a .env file', - `For example: \`${ctx.itemPath}="your-secret-value"\``, - ].join('\n'), + `For example: \`${lookupKey}="your-secret-value"\``, + ], }); } @@ -135,13 +286,24 @@ export class OnePasswordDmnoPlugin extends DmnoPlugin { }); } - /** - * reference an item using a "private link" (and json path) + * resolver to fetch a 1password value using a "private link" and field ID * - * To get an item's link, right click on the item and select "Copy Private Link" (or select the item and click the ellipses / more options menu) - * */ - itemByLink(privateLink: string, path?: string) { + * To get an item's link, right click on the item and select `Copy Private Link` (or select the item and click the ellipses / more options menu) + * + * @see https://dmno.dev/docs/plugins/1password/ + * @see https://support.1password.com/item-links/ + */ + itemByLink( + /** + * 1password item _Private Link_ + * + * @example "https://start.1password.com/open/i?a=..." + */ + privateLink: string, + /** 1password Item Field ID (or path) */ + fieldIdOrPath: FieldId | { path: string }, + ) { const linkValidationResult = OnePasswordTypes.itemLink().validate(privateLink); if (linkValidationResult !== true) { @@ -153,19 +315,31 @@ export class OnePasswordDmnoPlugin extends DmnoPlugin { const vaultId = url.searchParams.get('v')!; const itemId = url.searchParams.get('i')!; - return this.itemById(vaultId, itemId, path); + return this.itemById(vaultId, itemId, fieldIdOrPath); } - // can read items by id - need a vault id, item id - // and then need to grab the specific data from a big json blob - // cli command `op item get bphvvrqjegfmd5yoz4buw2aequ --vault=ut2dftalm3ugmxc6klavms6tfq --format json` - itemById(vaultId: VaultId, itemId: ItemId, path?: string) { + /** + * resolver to fetch a 1password value using UUIDs and a field ID + * + * @see https://dmno.dev/docs/plugins/1password/ + */ + itemById( + /** 1password Vault UUID */ + vaultId: VaultId, + /** 1password Item UUID */ + itemId: ItemId, + /** 1password Item Field id (or path) */ + fieldIdOrPath: FieldId | { path: string }, + ) { + const fieldId = _.isString(fieldIdOrPath) ? fieldIdOrPath : undefined; + const path = _.isObject(fieldIdOrPath) ? fieldIdOrPath.path : undefined; return this.createResolver({ label: (ctx) => { return _.compact([ `Vault: ${vaultId}`, `Item: ${itemId}`, + fieldId && `Field: ${fieldId}`, path && `Path: ${path}`, ]).join(', '); }, @@ -173,59 +347,81 @@ export class OnePasswordDmnoPlugin extends DmnoPlugin { // we've already checked that the defaultVaultId is set above if it's needed // and the plugin will have a schema error if the resolution failed - const valueJsonStr = await ctx.getOrSetCacheItem(`1pass:V|${vaultId}/I|${itemId}`, async () => { - return await execSync([ - `OP_SERVICE_ACCOUNT_TOKEN=${this.inputValues.token}`, - CLI_PATH, - `item get ${itemId}`, - `--vault=${vaultId}`, - '--format json', - ].join(' ')).toString(); - }); + const itemObj = await this.getOpItemById(ctx, vaultId, itemId); - const valueObj = JSON.parse(valueJsonStr); - if (!valueObj) { - throw new Error('Unable to resolve item'); - } + const sectionsById = _.keyBy(itemObj.sections, (s) => s.id); + // field selection by id + if (fieldId !== undefined) { + const field = _.find(itemObj.fields, (f) => f.id === fieldId); + if (field) { + // do we want to throw an error if we found the value but its empty? + return field.value; + } + // console.log(itemObj); + const possibleFieldIds = _.compact(_.map(itemObj.fields, (f) => { + if (f.value === undefined || f.value === '' || f.purpose === 'NOTES') return undefined; + const section = sectionsById[f.sectionId || f.section?.id]; + return { id: f.id, label: f.label || f.title, sectionLabel: section?.label || section?.title }; + })); + throw new ResolutionError(`Unable to find field ID "${fieldId}" in item`, { + tip: [ + 'Perhaps you meant one of', + ...possibleFieldIds.map((f) => [ + '- ', + f.sectionLabel ? `${f.sectionLabel} > ` : '', + f.label, + ` - ID = ${f.id}`, + ].join('')), + ], + }); + } + // field selection by path if (path) { - // TOOD: this logic is not right... - const valueAtPath = _.find(valueObj.fields, (i) => { - return i.reference.endsWith(path); + const valueAtPath = _.find(itemObj.fields, (i) => { + // using the cli, each item has the reference included + if (i.reference) { + // TODO: checking the reference ending is naive... + return i.reference.endsWith(path); + // using the sdk, we have to awkwardly reconstruct it + } else { + if (i.sectionId) return `${sectionsById[i.sectionId].title}/${i.title}` === path; + else return i.title === path; + } }); if (!valueAtPath) { - throw new Error(`Unable to resolve value from path ${path}`); + throw new Error(`Unable to resolve value at path ${path}`); } return valueAtPath.value; } - - // TODO: better error handling to tell you what went wrong? no access, non existant, etc - - - return valueObj; + throw new Error('Resolver must be passed a field ID or a path object'); + // should we fallback to first item or? }, }); } - // items have a "reference" which is like a URL that includes vault, item, and path to specific data - // however these are not necessarily stable... - // cli command `op read "op://dev test/example/username"` - itemByReference(referenceUrl: ReferenceUrl) { + /** + * resolver to fetch a 1password value using a secret reference URI + * + * @see https://dmno.dev/docs/plugins/1password/ + * @see https://developer.1password.com/docs/cli/secret-reference-syntax + */ + itemByReference( + /** + * 1Password secret reference URI of the secret value + * + * 📚 {@link https://developer.1password.com/docs/cli/secret-reference-syntax/#get-secret-references | 1Password docs } + */ + referenceUrl: ReferenceUrl, + ) { // TODO: validate the reference url looks ok? return this.createResolver({ label: referenceUrl, resolve: async (ctx) => { - const value = await ctx.getOrSetCacheItem(`1pass:R|${referenceUrl}`, async () => { - return await execSync([ - `OP_SERVICE_ACCOUNT_TOKEN=${this.inputValues.token}`, - CLI_PATH, - `read "${referenceUrl}"`, - '--force --no-newline', - ].join(' ')).toString(); - }); + const value = await this.getOpItemByReference(ctx, referenceUrl); // TODO: better error handling to tell you what went wrong? no access, non existant, etc @@ -242,10 +438,12 @@ export class OnePasswordDmnoPlugin extends DmnoPlugin { // TODO: this should be autogenerated from the inputSchema and live in .dmno/.typegen folder export interface OnePasswordDmnoPlugin { [_PluginInputTypesSymbol]: { - /** token to be used... more jsdoc info... */ + /** 1password service account token used to fetch secrets */ token: string, - /** private link to item containing dotenv style values */ + /** private link to item containing dotenv style values (optional) */ envItemLink?: string; + /** rely on auth from system installed `op` cli instead of a service account */ + fallbackToCliBasedAuth?: boolean, } } diff --git a/packages/plugins/1password/tsconfig.json b/packages/plugins/1password/tsconfig.json index e12c5db3..52e0efbf 100644 --- a/packages/plugins/1password/tsconfig.json +++ b/packages/plugins/1password/tsconfig.json @@ -10,8 +10,6 @@ "include": [ "src/**/*.ts", "src/**/*.d.ts", - "scripts/*.mjs", - "scripts/*.js", "./.dmno/.typegen/extension-types.d.ts" ] } diff --git a/packages/plugins/encrypted-vault/src/plugin.ts b/packages/plugins/encrypted-vault/src/plugin.ts index 149474b5..d8b0ec18 100644 --- a/packages/plugins/encrypted-vault/src/plugin.ts +++ b/packages/plugins/encrypted-vault/src/plugin.ts @@ -18,6 +18,7 @@ import { import { decrypt, encrypt, generateEncryptionKeyString, importDmnoEncryptionKeyString, } from '@dmno/encryption-lib'; +import { name as thisPackageName, version as thisPackageVersion } from '../package.json'; import { EncryptedVaultTypes } from './data-types'; @@ -64,6 +65,8 @@ export class EncryptedVaultDmnoPlugin extends DmnoPlugin=0.10.0'} @@ -759,6 +768,9 @@ packages: '@antfu/install-pkg@0.3.3': resolution: {integrity: sha512-nHHsk3NXQ6xkCfiRRC8Nfrg8pU5kkr3P3Y9s9dKqiuRmBD0Yap7fymNDjGFKeWhZQHqqbCS5CfeMy9wtExM24w==} + '@antfu/utils@0.7.10': + resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==} + '@antfu/utils@0.7.7': resolution: {integrity: sha512-gFPqTG7otEJ8uP6wrhDv6mqwGWYZKNvAcCq6u9hOj0c+IKCEsY4L1oC9trPq2SaWIzAfHvqfBDxF591JkMf+kg==} @@ -2256,6 +2268,7 @@ packages: '@humanwhocodes/config-array@0.11.14': resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} @@ -2267,6 +2280,7 @@ packages: '@humanwhocodes/object-schema@2.0.3': resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead '@iarna/toml@2.2.5': resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} @@ -2274,8 +2288,8 @@ packages: '@iconify/json@2.2.212': resolution: {integrity: sha512-d1IXjpbSwk8V3C9D+mruRTJRPqZZpCnOWh9zyG/Zc5zJmPDLBEHNTKz+ZmeiJQ6LdzgjwLJai1WgFvt1HWVIPw==} - '@iconify/tools@4.0.4': - resolution: {integrity: sha512-hX1Z3i1Tm6JxyrDv45jNEijPpepZZfal/4leFGtUC04H9LsgRo597BOBFB9PUZsQdFGLOxVUUfv6lqU/dC+xXw==} + '@iconify/tools@4.0.5': + resolution: {integrity: sha512-l8KoA1lxlN/FFjlMd3vjfD7BtcX/QnFWtlBapILMlJSBgM5zhDYak/ldw/LkKG3258q/0YmXa48sO/QpxX7ptg==} '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} @@ -2283,6 +2297,9 @@ packages: '@iconify/utils@2.1.23': resolution: {integrity: sha512-YGNbHKM5tyDvdWZ92y2mIkrfvm5Fvhe6WJSkWu7vvOFhMtYDP0casZpoRz0XEHZCrYsR4stdGT3cZ52yp5qZdQ==} + '@iconify/utils@2.1.30': + resolution: {integrity: sha512-bY0IO5xLOlbzJBnjWLxknp6Sss3yla03sVY9VeUz9nT6dbc+EGKlLfCt+6uytJnWm5CUvTF/BNotsLWF7kI61A==} + '@iconify/vue@4.1.2': resolution: {integrity: sha512-CQnYqLiQD5LOAaXhBrmj1mdL2/NCJvwcC4jtW2Z8ukhThiFkLDkutarTOV2trfc9EXqUqRs0KqXOL9pZ/IyysA==} peerDependencies: @@ -4111,6 +4128,7 @@ packages: are-we-there-yet@2.0.0: resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} engines: {node: '>=10'} + deprecated: This package is no longer supported. arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -4279,6 +4297,9 @@ packages: axios@1.6.8: resolution: {integrity: sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==} + axios@1.7.4: + resolution: {integrity: sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==} + axobject-query@4.0.0: resolution: {integrity: sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==} @@ -4586,9 +4607,9 @@ packages: cheerio-select@2.1.0: resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} - cheerio@1.0.0-rc.12: - resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==} - engines: {node: '>= 6'} + cheerio@1.0.0: + resolution: {integrity: sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==} + engines: {node: '>=18.17'} chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} @@ -5336,6 +5357,9 @@ packages: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} + encoding-sniffer@0.2.0: + resolution: {integrity: sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==} + end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} @@ -6085,6 +6109,7 @@ packages: gauge@3.0.2: resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} engines: {node: '>=10'} + deprecated: This package is no longer supported. gaxios@6.5.0: resolution: {integrity: sha512-R9QGdv8j4/dlNoQbX3hSaK/S0rkMijqjVvW3YM06CoBdbU/VdKd159j4hePpng0KuE6Lh6JJ7UdmVGJZFcAG1w==} @@ -6194,10 +6219,12 @@ packages: glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported global-cache-dir@4.4.0: resolution: {integrity: sha512-bk0gI6IbbphRjAaCJJn5H+T/CcEck5B3a5KBO2BXSDzjFSV+API17w8GA7YPJ6IXJiasW8M0VsEIig1PCHdfOQ==} @@ -6453,8 +6480,8 @@ packages: html-whitespace-sensitive-tag-names@3.0.0: resolution: {integrity: sha512-KlClZ3/Qy5UgvpvVvDomGhnQhNWH5INE8GwvSIQ9CWt1K0zbbXrl7eN5bWaafOZgtmO3jMPwUqmrmEwinhPq1w==} - htmlparser2@8.0.2: - resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} + htmlparser2@9.1.0: + resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==} http-cache-semantics@4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} @@ -6569,6 +6596,7 @@ packages: inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -7888,6 +7916,9 @@ packages: mlly@1.6.1: resolution: {integrity: sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==} + mlly@1.7.1: + resolution: {integrity: sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==} + modern-ahocorasick@1.0.1: resolution: {integrity: sha512-yoe+JbhTClckZ67b2itRtistFKf8yPYelHLc7e5xAwtNAXxM6wJTUx2C7QeVSJFDzKT7bCIFyBVybPMKvmB9AA==} @@ -8171,6 +8202,7 @@ packages: npmlog@5.0.1: resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} + deprecated: This package is no longer supported. nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} @@ -8473,6 +8505,9 @@ packages: parse5-htmlparser2-tree-adapter@7.0.0: resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==} + parse5-parser-stream@7.1.2: + resolution: {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==} + parse5@7.1.2: resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} @@ -8609,6 +8644,9 @@ packages: pkg-types@1.1.0: resolution: {integrity: sha512-/RpmvKdxKf8uILTtoOhAgf30wYbP2Qw+L9p3Rvshx1JZVX+XQNZQFjlbmGHEGIm4CkVPlSn+NXmIM8+9oWQaSA==} + pkg-types@1.1.3: + resolution: {integrity: sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==} + posix-character-classes@0.1.1: resolution: {integrity: sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==} engines: {node: '>=0.10.0'} @@ -9238,6 +9276,7 @@ packages: rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rollup@4.16.4: @@ -9807,6 +9846,11 @@ packages: engines: {node: '>=14.0.0'} hasBin: true + svgo@3.3.2: + resolution: {integrity: sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==} + engines: {node: '>=14.0.0'} + hasBin: true + synckit@0.9.0: resolution: {integrity: sha512-7RnqIMq572L8PeEzKeBINYEJDDxpcH8JEgLwUqBd3TkofhFRbkq4QLR0u+36avGAhCRbk2nnmjcW9SE531hPDg==} engines: {node: ^14.18.0 || >=16.0.0} @@ -10302,6 +10346,10 @@ packages: resolution: {integrity: sha512-JfjKqIauur3Q6biAtHJ564e3bWa8VvT+7cSiOJHFbX4Erv6CLGDpg8z+Fmg/1OI/47RA+GI2QZaF48SSaLvyBA==} engines: {node: '>=18.17'} + undici@6.19.7: + resolution: {integrity: sha512-HR3W/bMGPSr90i8AAp2C4DM3wChFdJPLrWYpIS++LxS8K+W535qftjt+4MyjNYHeWabMj1nvtmLIi7l++iq91A==} + engines: {node: '>=18.17'} + unenv@1.9.0: resolution: {integrity: sha512-QKnFNznRxmbOF1hDgzpqrlIf6NC5sbZ2OJ+5Wl3OX8uM+LUJXbj4TXvLJCtwbPTmbMHCLIz6JLKNinNsMShK9g==} @@ -10906,6 +10954,14 @@ packages: resolution: {integrity: sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==} engines: {node: '>=6'} + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} @@ -11143,6 +11199,12 @@ packages: snapshots: + '@1password/sdk-core@0.1.0-beta.15': {} + + '@1password/sdk@0.1.1': + dependencies: + '@1password/sdk-core': 0.1.0-beta.15 + '@aashutoshrathi/word-wrap@1.2.6': {} '@achrinza/event-pubsub@5.0.9': @@ -11174,6 +11236,8 @@ snapshots: dependencies: '@jsdevtools/ez-spawn': 3.0.4 + '@antfu/utils@0.7.10': {} + '@antfu/utils@0.7.7': {} '@astrojs/check@0.7.0(typescript@5.5.2)': @@ -11317,13 +11381,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/vue@4.5.0(astro@4.11.1(@types/node@20.14.12)(less@4.2.0)(typescript@5.5.2))(rollup@4.19.0)(vite@5.3.1(@types/node@20.14.12)(less@4.2.0))(vue@3.4.30(typescript@5.5.2))': + '@astrojs/vue@4.5.0(astro@4.11.1(@types/node@20.14.12)(less@4.2.0)(typescript@5.5.2))(rollup@4.19.0)(vite@5.3.5(@types/node@20.14.12)(less@4.2.0))(vue@3.4.30(typescript@5.5.2))': dependencies: - '@vitejs/plugin-vue': 5.0.5(vite@5.3.1(@types/node@20.14.12)(less@4.2.0))(vue@3.4.30(typescript@5.5.2)) - '@vitejs/plugin-vue-jsx': 4.0.0(vite@5.3.1(@types/node@20.14.12)(less@4.2.0))(vue@3.4.30(typescript@5.5.2)) + '@vitejs/plugin-vue': 5.0.5(vite@5.3.5(@types/node@20.14.12)(less@4.2.0))(vue@3.4.30(typescript@5.5.2)) + '@vitejs/plugin-vue-jsx': 4.0.0(vite@5.3.5(@types/node@20.14.12)(less@4.2.0))(vue@3.4.30(typescript@5.5.2)) '@vue/compiler-sfc': 3.4.30 astro: 4.11.1(@types/node@20.14.12)(less@4.2.0)(typescript@5.5.2) - vite-plugin-vue-devtools: 7.3.4(rollup@4.19.0)(vite@5.3.1(@types/node@20.14.12)(less@4.2.0))(vue@3.4.30(typescript@5.5.2)) + vite-plugin-vue-devtools: 7.3.4(rollup@4.19.0)(vite@5.3.5(@types/node@20.14.12)(less@4.2.0))(vue@3.4.30(typescript@5.5.2)) vue: 3.4.30(typescript@5.5.2) transitivePeerDependencies: - '@nuxt/kit' @@ -12533,17 +12597,18 @@ snapshots: '@iconify/types': 2.0.0 pathe: 1.1.2 - '@iconify/tools@4.0.4': + '@iconify/tools@4.0.5': dependencies: '@iconify/types': 2.0.0 - '@iconify/utils': 2.1.23 + '@iconify/utils': 2.1.30 '@types/tar': 6.1.13 - axios: 1.6.8 - cheerio: 1.0.0-rc.12 + axios: 1.7.4 + cheerio: 1.0.0 + domhandler: 5.0.3 extract-zip: 2.0.1 local-pkg: 0.5.0 pathe: 1.1.2 - svgo: 3.2.0 + svgo: 3.3.2 tar: 6.2.1 transitivePeerDependencies: - debug @@ -12563,6 +12628,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@iconify/utils@2.1.30': + dependencies: + '@antfu/install-pkg': 0.1.1 + '@antfu/utils': 0.7.10 + '@iconify/types': 2.0.0 + debug: 4.3.5(supports-color@9.4.0) + kolorist: 1.8.0 + local-pkg: 0.5.0 + mlly: 1.7.1 + transitivePeerDependencies: + - supports-color + '@iconify/vue@4.1.2(vue@3.4.30(typescript@5.5.2))': dependencies: '@iconify/types': 2.0.0 @@ -14569,19 +14646,19 @@ snapshots: - encoding - supports-color - '@vitejs/plugin-vue-jsx@4.0.0(vite@5.3.1(@types/node@20.14.12)(less@4.2.0))(vue@3.4.30(typescript@5.5.2))': + '@vitejs/plugin-vue-jsx@4.0.0(vite@5.3.5(@types/node@20.14.12)(less@4.2.0))(vue@3.4.30(typescript@5.5.2))': dependencies: '@babel/core': 7.24.7 '@babel/plugin-transform-typescript': 7.24.7(@babel/core@7.24.7) '@vue/babel-plugin-jsx': 1.2.2(@babel/core@7.24.7) - vite: 5.3.1(@types/node@20.14.12)(less@4.2.0) + vite: 5.3.5(@types/node@20.14.12)(less@4.2.0) vue: 3.4.30(typescript@5.5.2) transitivePeerDependencies: - supports-color - '@vitejs/plugin-vue@5.0.5(vite@5.3.1(@types/node@20.14.12)(less@4.2.0))(vue@3.4.30(typescript@5.5.2))': + '@vitejs/plugin-vue@5.0.5(vite@5.3.5(@types/node@20.14.12)(less@4.2.0))(vue@3.4.30(typescript@5.5.2))': dependencies: - vite: 5.3.1(@types/node@20.14.12)(less@4.2.0) + vite: 5.3.5(@types/node@20.14.12)(less@4.2.0) vue: 3.4.30(typescript@5.5.2) '@vitest/expect@1.6.0': @@ -14762,14 +14839,14 @@ snapshots: '@vue/compiler-dom': 3.4.30 '@vue/shared': 3.4.30 - '@vue/devtools-core@7.3.4(vite@5.3.1(@types/node@20.14.12)(less@4.2.0))(vue@3.4.30(typescript@5.5.2))': + '@vue/devtools-core@7.3.4(vite@5.3.5(@types/node@20.14.12)(less@4.2.0))(vue@3.4.30(typescript@5.5.2))': dependencies: '@vue/devtools-kit': 7.3.4 '@vue/devtools-shared': 7.3.4 mitt: 3.0.1 nanoid: 3.3.7 pathe: 1.1.2 - vite-hot-client: 0.2.3(vite@5.3.1(@types/node@20.14.12)(less@4.2.0)) + vite-hot-client: 0.2.3(vite@5.3.5(@types/node@20.14.12)(less@4.2.0)) vue: 3.4.30(typescript@5.5.2) transitivePeerDependencies: - vite @@ -15226,7 +15303,7 @@ snapshots: astro-iconify@1.2.0: dependencies: - '@iconify/tools': 4.0.4 + '@iconify/tools': 4.0.5 node-fetch: 3.3.2 resolve-pkg: 2.0.0 svgo: 3.2.0 @@ -15447,6 +15524,14 @@ snapshots: transitivePeerDependencies: - debug + axios@1.7.4: + dependencies: + follow-redirects: 1.15.6(debug@4.3.4) + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + axobject-query@4.0.0: dependencies: dequal: 2.0.3 @@ -15821,15 +15906,19 @@ snapshots: domhandler: 5.0.3 domutils: 3.1.0 - cheerio@1.0.0-rc.12: + cheerio@1.0.0: dependencies: cheerio-select: 2.1.0 dom-serializer: 2.0.0 domhandler: 5.0.3 domutils: 3.1.0 - htmlparser2: 8.0.2 + encoding-sniffer: 0.2.0 + htmlparser2: 9.1.0 parse5: 7.1.2 parse5-htmlparser2-tree-adapter: 7.0.0 + parse5-parser-stream: 7.1.2 + undici: 6.19.7 + whatwg-mimetype: 4.0.0 chokidar@3.5.3: dependencies: @@ -16542,6 +16631,11 @@ snapshots: encodeurl@1.0.2: {} + encoding-sniffer@0.2.0: + dependencies: + iconv-lite: 0.6.3 + whatwg-encoding: 3.1.1 + end-of-stream@1.4.4: dependencies: once: 1.4.0 @@ -18253,7 +18347,7 @@ snapshots: html-whitespace-sensitive-tag-names@3.0.0: {} - htmlparser2@8.0.2: + htmlparser2@9.1.0: dependencies: domelementtype: 2.3.0 domhandler: 5.0.3 @@ -18332,7 +18426,6 @@ snapshots: iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 - optional: true icss-utils@5.1.0(postcss@8.4.38): dependencies: @@ -20146,6 +20239,13 @@ snapshots: pkg-types: 1.1.0 ufo: 1.5.3 + mlly@1.7.1: + dependencies: + acorn: 8.12.0 + pathe: 1.1.2 + pkg-types: 1.1.3 + ufo: 1.5.3 + modern-ahocorasick@1.0.1: {} modern-async@2.0.0: @@ -21082,6 +21182,10 @@ snapshots: domhandler: 5.0.3 parse5: 7.1.2 + parse5-parser-stream@7.1.2: + dependencies: + parse5: 7.1.2 + parse5@7.1.2: dependencies: entities: 4.5.0 @@ -21206,6 +21310,12 @@ snapshots: mlly: 1.6.1 pathe: 1.1.2 + pkg-types@1.1.3: + dependencies: + confbox: 0.1.7 + mlly: 1.7.1 + pathe: 1.1.2 + posix-character-classes@0.1.1: {} possible-typed-array-names@1.0.0: {} @@ -21214,14 +21324,6 @@ snapshots: dependencies: postcss: 8.4.38 - postcss-load-config@4.0.2(postcss@8.4.38)(ts-node@10.9.2(@types/node@20.14.12)(typescript@5.4.5)): - dependencies: - lilconfig: 3.1.1 - yaml: 2.4.1 - optionalDependencies: - postcss: 8.4.38 - ts-node: 10.9.2(@types/node@20.14.12)(typescript@5.4.5) - postcss-load-config@4.0.2(postcss@8.4.38)(ts-node@10.9.2(@types/node@20.14.8)(typescript@5.5.4)): dependencies: lilconfig: 3.1.1 @@ -21246,6 +21348,14 @@ snapshots: postcss: 8.4.40 ts-node: 10.9.2(@types/node@20.12.7)(typescript@5.4.5) + postcss-load-config@4.0.2(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.14.12)(typescript@5.4.5)): + dependencies: + lilconfig: 3.1.1 + yaml: 2.4.1 + optionalDependencies: + postcss: 8.4.40 + ts-node: 10.9.2(@types/node@20.14.12)(typescript@5.4.5) + postcss-load-config@4.0.2(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.14.8)(typescript@5.5.2)): dependencies: lilconfig: 3.1.1 @@ -22664,6 +22774,16 @@ snapshots: csso: 5.0.5 picocolors: 1.0.0 + svgo@3.3.2: + dependencies: + '@trysound/sax': 0.2.0 + commander: 7.2.0 + css-select: 5.1.0 + css-tree: 2.3.1 + css-what: 6.1.0 + csso: 5.0.5 + picocolors: 1.0.1 + synckit@0.9.0: dependencies: '@pkgr/core': 0.1.1 @@ -23018,7 +23138,7 @@ snapshots: tslib@2.6.2: {} - tsup@8.0.2(postcss@8.4.38)(ts-node@10.9.2(@types/node@20.14.12)(typescript@5.4.5))(typescript@5.4.5): + tsup@8.0.2(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.12.12)(typescript@5.4.5))(typescript@5.4.5): dependencies: bundle-require: 4.0.3(esbuild@0.19.12) cac: 6.7.14 @@ -23028,20 +23148,20 @@ snapshots: execa: 5.1.1 globby: 11.1.0 joycon: 3.1.1 - postcss-load-config: 4.0.2(postcss@8.4.38)(ts-node@10.9.2(@types/node@20.14.12)(typescript@5.4.5)) + postcss-load-config: 4.0.2(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.12.12)(typescript@5.4.5)) resolve-from: 5.0.0 rollup: 4.16.4 source-map: 0.8.0-beta.0 sucrase: 3.35.0 tree-kill: 1.2.2 optionalDependencies: - postcss: 8.4.38 + postcss: 8.4.40 typescript: 5.4.5 transitivePeerDependencies: - supports-color - ts-node - tsup@8.0.2(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.12.12)(typescript@5.4.5))(typescript@5.4.5): + tsup@8.0.2(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5))(typescript@5.4.5): dependencies: bundle-require: 4.0.3(esbuild@0.19.12) cac: 6.7.14 @@ -23051,7 +23171,7 @@ snapshots: execa: 5.1.1 globby: 11.1.0 joycon: 3.1.1 - postcss-load-config: 4.0.2(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.12.12)(typescript@5.4.5)) + postcss-load-config: 4.0.2(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)) resolve-from: 5.0.0 rollup: 4.16.4 source-map: 0.8.0-beta.0 @@ -23064,7 +23184,7 @@ snapshots: - supports-color - ts-node - tsup@8.0.2(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5))(typescript@5.4.5): + tsup@8.0.2(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.14.12)(typescript@5.4.5))(typescript@5.4.5): dependencies: bundle-require: 4.0.3(esbuild@0.19.12) cac: 6.7.14 @@ -23074,7 +23194,7 @@ snapshots: execa: 5.1.1 globby: 11.1.0 joycon: 3.1.1 - postcss-load-config: 4.0.2(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.12.7)(typescript@5.4.5)) + postcss-load-config: 4.0.2(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.14.12)(typescript@5.4.5)) resolve-from: 5.0.0 rollup: 4.16.4 source-map: 0.8.0-beta.0 @@ -23334,6 +23454,8 @@ snapshots: undici@6.19.2: {} + undici@6.19.7: {} + unenv@1.9.0: dependencies: consola: 3.2.3 @@ -23707,9 +23829,9 @@ snapshots: unist-util-stringify-position: 4.0.0 vfile-message: 4.0.2 - vite-hot-client@0.2.3(vite@5.3.1(@types/node@20.14.12)(less@4.2.0)): + vite-hot-client@0.2.3(vite@5.3.5(@types/node@20.14.12)(less@4.2.0)): dependencies: - vite: 5.3.1(@types/node@20.14.12)(less@4.2.0) + vite: 5.3.5(@types/node@20.14.12)(less@4.2.0) vite-node@1.5.2(@types/node@20.12.7)(less@4.2.0): dependencies: @@ -23762,7 +23884,7 @@ snapshots: - supports-color - terser - vite-plugin-inspect@0.8.4(rollup@4.19.0)(vite@5.3.1(@types/node@20.14.12)(less@4.2.0)): + vite-plugin-inspect@0.8.4(rollup@4.19.0)(vite@5.3.5(@types/node@20.14.12)(less@4.2.0)): dependencies: '@antfu/utils': 0.7.7 '@rollup/pluginutils': 5.1.0(rollup@4.19.0) @@ -23773,28 +23895,28 @@ snapshots: perfect-debounce: 1.0.0 picocolors: 1.0.0 sirv: 2.0.4 - vite: 5.3.1(@types/node@20.14.12)(less@4.2.0) + vite: 5.3.5(@types/node@20.14.12)(less@4.2.0) transitivePeerDependencies: - rollup - supports-color - vite-plugin-vue-devtools@7.3.4(rollup@4.19.0)(vite@5.3.1(@types/node@20.14.12)(less@4.2.0))(vue@3.4.30(typescript@5.5.2)): + vite-plugin-vue-devtools@7.3.4(rollup@4.19.0)(vite@5.3.5(@types/node@20.14.12)(less@4.2.0))(vue@3.4.30(typescript@5.5.2)): dependencies: - '@vue/devtools-core': 7.3.4(vite@5.3.1(@types/node@20.14.12)(less@4.2.0))(vue@3.4.30(typescript@5.5.2)) + '@vue/devtools-core': 7.3.4(vite@5.3.5(@types/node@20.14.12)(less@4.2.0))(vue@3.4.30(typescript@5.5.2)) '@vue/devtools-kit': 7.3.4 '@vue/devtools-shared': 7.3.4 execa: 8.0.1 sirv: 2.0.4 - vite: 5.3.1(@types/node@20.14.12)(less@4.2.0) - vite-plugin-inspect: 0.8.4(rollup@4.19.0)(vite@5.3.1(@types/node@20.14.12)(less@4.2.0)) - vite-plugin-vue-inspector: 5.1.2(vite@5.3.1(@types/node@20.14.12)(less@4.2.0)) + vite: 5.3.5(@types/node@20.14.12)(less@4.2.0) + vite-plugin-inspect: 0.8.4(rollup@4.19.0)(vite@5.3.5(@types/node@20.14.12)(less@4.2.0)) + vite-plugin-vue-inspector: 5.1.2(vite@5.3.5(@types/node@20.14.12)(less@4.2.0)) transitivePeerDependencies: - '@nuxt/kit' - rollup - supports-color - vue - vite-plugin-vue-inspector@5.1.2(vite@5.3.1(@types/node@20.14.12)(less@4.2.0)): + vite-plugin-vue-inspector@5.1.2(vite@5.3.5(@types/node@20.14.12)(less@4.2.0)): dependencies: '@babel/core': 7.24.7 '@babel/plugin-proposal-decorators': 7.24.1(@babel/core@7.24.7) @@ -23805,7 +23927,7 @@ snapshots: '@vue/compiler-dom': 3.4.30 kolorist: 1.8.0 magic-string: 0.30.10 - vite: 5.3.1(@types/node@20.14.12)(less@4.2.0) + vite: 5.3.5(@types/node@20.14.12)(less@4.2.0) transitivePeerDependencies: - supports-color @@ -23859,6 +23981,16 @@ snapshots: fsevents: 2.3.3 less: 4.2.0 + vite@5.3.5(@types/node@20.14.12)(less@4.2.0): + dependencies: + esbuild: 0.21.5 + postcss: 8.4.40 + rollup: 4.19.0 + optionalDependencies: + '@types/node': 20.14.12 + fsevents: 2.3.3 + less: 4.2.0 + vite@5.3.5(@types/node@20.14.8)(less@4.2.0): dependencies: esbuild: 0.21.5 @@ -24070,6 +24202,12 @@ snapshots: well-known-symbols@2.0.0: {} + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + whatwg-url@5.0.0: dependencies: tr46: 0.0.3