- 
                Notifications
    You must be signed in to change notification settings 
- Fork 13
plugins system + 1pass plugin #168
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
| 🦋 Changeset detectedLatest commit: ff543cd The changes in this PR will be included in the next version bump. This PR includes changesets to release 11 packages
 Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR | 
| # | ||
| # # @initSimpleVault(id=vault1, key=SIMPLE_VAULT_ENCRYPTION_KEY) | ||
| # | ||
| # @plugin(./node_modules/@varlock/1password-plugin/dist/plugin.js) | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we'll have an easier to way to reference installed node modules rather than using a path like this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it could be something like @plugin(node_modules:@varlock/1password-plugin)
or it could just be the default when no other protocol is provided @plugin(@varlock/1password-plugin)
- local paths will start with ./or../or/
- registries (npm/jsr/?) will be npm:packageName@1.2.3
| # | ||
| # @plugin(./node_modules/@varlock/1password-plugin/dist/plugin.js) | ||
| # # @plugin(https://varlock-plugins-registry.dmno.workers.dev/varlock-1password-plugin-0.0.0.tgz) | ||
| # @initOpVault(id=v1, token=$OP_TOKEN, enableCliAuth=true) | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
most plugins will probably introduce a root decorator that will be responsible for initialization - which usually means setting options, and wiring up auth to env items. In many cases you may want to create multiple instances of a plugin like this.
Some plugins may not need one? Or could optionally rely on defaults if no init decorator was used
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think maybe they should all have it for consistency's sake, and the case where there's some sort of update which adds parameters to a previously "empty" init
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think forcing the user to have an init call could make sense in the case where we standardize the init call (something like @initPlugin). Otherwise it's up to the plugin to do whatever it wants, which could be no init decorator, or several decorators involved in setting things up correctly.
If we do that, the question is whether you allow the plugin to introduce any behavior at all before calling init.
|  | ||
| OP_VAULT_NAME=example | ||
|  | ||
| OP1=op(v1, "op://dev test/${OP_VAULT_NAME}/credential") | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Example of new custom resolver. It is referencing the vault ids in the initOpVault calls. Nothing special or formalized involved though... plugins can track their own state and do whatever they want.
They can use their module singleton scope to do so. In the future, we'll likely add more hooks, so they could for example initialize or reset their state as part of the overall loading process
In this 1pass example, the vault IDs are optional, with id on both the init call and items defaulting to _default if not specified.
Note that we'll get schema errors before value resolution if ids are no good, because we require the ids to be static strings, so are evaluated early.
|  | ||
| # some description | ||
| # @required=true | ||
| # @docs(https://example1.com) | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@docsUrl is now deprecated in favor of a @docs() function, which can be called multiple times. It also now supports both a URL and optional label. Multiple docs links across all definitions will all be combined together and included in generated types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
makes sense to me. I think since there are multiple deprecations in this release we should probably consider a proper minor (0.1.0?) release
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So far my thinking has been that we bump up to 0.1.0 (or maybe 1.0.0?) when we reach a point where we are ready to guarantee stability, and otherwise just keep rolling with it. But open to other ideas. Should also probably write something about our versioning strategy somewhere in the docs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this feels like at minimum a minor version to me. we're definitely not adhering to semver currently but normally you'd announce the deprecation in a minor version and remove it in a major
| # @defaultRequired=infer @defaultSensitive=false | ||
| # @generateTypes(lang=ts, path=env.d.ts) | ||
| # | ||
| # @currentEnv=$APP_ENV | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@envFlag=APP_ENV is now deprecated (although still works) in favor of @currentEnv=$APP_ENV
While this now uses the unified resolvers to reference another item, we add some limitations - it must be set to ref(SOME_VAR) (or the unexpanded equivalent), and SOME_VAR must be an item that is defined in the same file.
Doesn't change the DX in this case very much, but I do think as we have more of these that unifying the resolvers will make the overall DX much better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is the new name to give us time to deprecate envFlag?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could leave the name the same and still make it work, but I thought the decorator naming didnt really make as much sense. Like with @envFlag youre saying - the name of the env flag item is "APP_ENV". Whereas "$APP_ENV" really represents the value that it resolves to, so you're saying the @currentEnv is what $APP_ENV will resolve to (eg "dev")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah makes sense to me, probably good for us to draft some guidelines about these types of deprecations though
| }) {} | ||
| }) { | ||
| if (this.data.value) { | ||
| const expanded = expand(this.data.value!); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
expansion now happens automatically within the parser library, rather than needing to be called explicitly
| "scripts": { | ||
| "dev": "pnpm run copy-wasm && tsup --watch", | ||
| "build": "tsup && pnpm run copy-wasm", | ||
| "copy-wasm": "mkdir -p dist && cp node_modules/@1password/sdk-core/nodejs/core_bg.wasm ./dist", | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1pass internal lib uses wasm and we need to copy it into our final dist folder, since we dont have any dependencies being loaded.
This is what nudged me in the direction of plugins usually being a folder with a package.json, rather than defaulting to a single file. We may still want to support the single file case for when a user wants to just do a little custom stuff, but any published plugins should be a full folder.
| const PLUGIN_VERSION = plugin.version; | ||
| const OP_ICON = 'simple-icons:1password'; | ||
|  | ||
| plugin.name = '1pass'; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
plugin is injected globally... its a bit awkward, but the best way I've found so far to inject in memory objects into dynamically imported code. Otherwise we'd end up re-bundling copies of our classes and functions into the plugin, and instanceof checks would not work.
| @@ -0,0 +1,55 @@ | |||
| { | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ignore this whole thing for now. Need to figure out how we will deal with CLIs and plugins before this plugin can be useful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe cherry pick it into another branch/PR?
| code: 'BAD_VAULT_REFERENCE', | ||
| extraMetadata: { badVaultId: vaultNameOrId }, | ||
| tip: [ | ||
| 'By not using a service account token, you are relying on your local 1password cli installation and authentication.', | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 'By not using a service account token, you are relying on your local 1password cli installation and authentication.', | |
| 'By not using a service account token, you are relying on your local 1Password CLI installation and authentication.', | 
| const fieldNameOrId = matches?.[2]?.replace('.', '/') || 'unknown'; | ||
|  | ||
| // const vaultNameOrId = errMessage.substring(1, errMessage.substring(1).indexOf('"') + 1); | ||
| return new ResolutionError(`1password vault "${vaultId}" item "${itemId}" does not have field "${fieldNameOrId}"`, { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| return new ResolutionError(`1password vault "${vaultId}" item "${itemId}" does not have field "${fieldNameOrId}"`, { | |
| return new ResolutionError(`1Password vault "${vaultId}" item "${itemId}" does not have field "${fieldNameOrId}"`, { | 
| if (!errMessage) { | ||
| return 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.', | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 'By not using a service account token, you are relying on your local 1password cli installation and authentication.', | |
| 'By not using a service account token, you are relying on your local 1Password CLI installation and authentication.', | 
| return 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', | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| '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', | |
| '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', | 
| 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', | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 'Try running `op whoami` to make sure the cli is connected to the correct account', | |
| 'Try running `op whoami` to make sure the CLI is connected to the correct account', | 
| } else if ((err as any).code === 'ENOENT') { | ||
| return 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.', | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 'By not using a service account token, you are relying on your local 1password cli installation for ambient auth.', | |
| 'By not using a service account token, you are relying on your local 1Password CLI installation for ambient auth.', | 
| return 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/', | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 'But your local 1password cli (`op`) was not found. Install it here - https://developer.1password.com/docs/cli/get-started/', | |
| 'But your local 1Password CLI (`op`) was not found. Install it here - https://developer.1password.com/docs/cli/get-started/', | 
| ], | ||
| }); | ||
| } else { | ||
| return new ResolutionError(`Problem invoking 1password cli: ${(err as any).message}`); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| return new ResolutionError(`Problem invoking 1password cli: ${(err as any).message}`); | |
| return new ResolutionError(`Problem invoking 1Password CLI: ${(err as any).message}`); | 
|  | ||
| private async executeReadBatch() { | ||
| const opClient = await this.opClientPromise; | ||
| if (!opClient) throw new Error('Expected op sdk to be initialized'); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if (!opClient) throw new Error('Expected op sdk to be initialized'); | |
| if (!opClient) throw new Error('Expected op SDK to be initialized'); | 
| let commonErr; | ||
| // 1pass sdk throws strings as errors... | ||
| if (typeof err === 'string') { | ||
| commonErr = new ResolutionError(`1password SDK error - ${err}`); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| commonErr = new ResolutionError(`1password SDK error - ${err}`); | |
| commonErr = new ResolutionError(`1Password SDK error - ${err}`); | 
| typeDescription: 'Service account token used to authenticate with the [1Password CLI](https://developer.1password.com/docs/cli/get-started/) and [SDKs](https://developer.1password.com/docs/sdks/)', | ||
| icon: OP_ICON, | ||
| docs: [ | ||
| '1password service accounts', | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| '1password service accounts', | |
| '1Password service accounts', | 
| astro-example
 example-repo
 example-repo-init-sample-test
 example-repo-init-test
 plugin-test
 @env-spec/parser
 varlock
 @varlock/astro-integration
 @varlock/nextjs-integration
 @varlock/vite-integration
 @varlock/1password-plugin
 commit:  | 
e6158f4    to
    895ea05      
    Compare
  
    | Deploying with   | 
| Status | Name | Latest Commit | Updated (UTC) | 
|---|---|---|---|
| ❌ Deployment failed View logs | varlock-docs-mcp | 0691896 | Oct 30 2025, 11:25 PM | 
| Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub. 
 | 
60d1dd8    to
    eac5574      
    Compare
  
    
A ton going on here. Ideally would have broken this up more, but it was in figuring out the DX for plugin authoring that a lot of this became clear, so authoring the plugin, while building the plugin system, and the general changes around decorators, all together was how it was actually built.
@plugindecorator. These can be loaded from local files, installed node modules, https, or from npm@required=eq($SOME_VAR, 'foo')