Skip to content

Conversation

@theoephraim
Copy link
Member

@theoephraim theoephraim commented Oct 14, 2025

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.


  • creates a new plugin system, which can load plugins using a new @plugin decorator. These can be loaded from local files, installed node modules, https, or from npm
  • plugins will be able to register new root decorators, item decorators, data types, and resolver functions
  • plugins will be installed globally in the graph, no notion of limiting scope
  • formalizes decorators, and unifies their arguments with the existing value resolver system
    • this is because many plugins will now be referencing existing config items, so we want it to be the same as referencing items within values
    • this also enables arbitrary logic for required/sensitive/etc -- for example @required=eq($SOME_VAR, 'foo')
    • have added a few utility resolvers (if, eq) and more to come
  • first version of a 1password plugin, along with docs

@changeset-bot
Copy link

changeset-bot bot commented Oct 14, 2025

🦋 Changeset detected

Latest commit: ff543cd

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 11 packages
Name Type
@varlock/1password-plugin Major
@env-spec/parser Patch
varlock Minor
plugin-test Patch
@varlock/astro-integration Major
@varlock/nextjs-integration Major
@varlock/vite-integration Major
astro-example Patch
example-repo Patch
example-repo-init-sample-test Patch
example-repo-init-test Patch

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)
Copy link
Member Author

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

Copy link
Member Author

@theoephraim theoephraim Oct 15, 2025

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)
Copy link
Member Author

@theoephraim theoephraim Oct 14, 2025

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

Copy link
Member

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

Copy link
Member Author

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")
Copy link
Member Author

@theoephraim theoephraim Oct 14, 2025

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)
Copy link
Member Author

@theoephraim theoephraim Oct 14, 2025

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.

Copy link
Member

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

Copy link
Member Author

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?

Copy link
Member

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
Copy link
Member Author

@theoephraim theoephraim Oct 14, 2025

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.

Copy link
Member

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?

Copy link
Member Author

@theoephraim theoephraim Oct 16, 2025

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")

Copy link
Member

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!);
Copy link
Member Author

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",
Copy link
Member Author

@theoephraim theoephraim Oct 14, 2025

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';
Copy link
Member Author

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 @@
{
Copy link
Member Author

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.

Copy link
Member

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.',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'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}"`, {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'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',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'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',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'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.',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'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/',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'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}`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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}`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'1password service accounts',
'1Password service accounts',

@pkg-pr-new
Copy link

pkg-pr-new bot commented Oct 30, 2025

Open in StackBlitz

astro-example

npm i https://pkg.pr.new/dmno-dev/varlock/astro-example@168

example-repo

npm i https://pkg.pr.new/dmno-dev/varlock/example-repo@168

example-repo-init-sample-test

npm i https://pkg.pr.new/dmno-dev/varlock/example-repo-init-sample-test@168

example-repo-init-test

npm i https://pkg.pr.new/dmno-dev/varlock/example-repo-init-test@168

plugin-test

npm i https://pkg.pr.new/dmno-dev/varlock/plugin-test@168

@env-spec/parser

npm i https://pkg.pr.new/dmno-dev/varlock/@env-spec/parser@168

varlock

npm i https://pkg.pr.new/dmno-dev/varlock@168

@varlock/astro-integration

npm i https://pkg.pr.new/dmno-dev/varlock/@varlock/astro-integration@168

@varlock/nextjs-integration

npm i https://pkg.pr.new/dmno-dev/varlock/@varlock/nextjs-integration@168

@varlock/vite-integration

npm i https://pkg.pr.new/dmno-dev/varlock/@varlock/vite-integration@168

@varlock/1password-plugin

npm i https://pkg.pr.new/dmno-dev/varlock/@varlock/1password-plugin@168

commit: ff543cd

@theoephraim theoephraim force-pushed the plugins branch 2 times, most recently from e6158f4 to 895ea05 Compare October 30, 2025 07:46
@theoephraim theoephraim marked this pull request as ready for review October 30, 2025 07:48
@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Oct 30, 2025

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
varlock-docs-mcp 0691896 Oct 30 2025, 11:25 PM

@socket-security
Copy link

socket-security bot commented Oct 30, 2025

@socket-security
Copy link

socket-security bot commented Oct 31, 2025

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.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm vite is 91.0% likely obfuscated

Confidence: 0.91

Location: Package overview

From: examples/vanilla-vite-example/package.jsonnpm/vite@7.1.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/vite@7.1.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@theoephraim theoephraim force-pushed the plugins branch 2 times, most recently from 60d1dd8 to eac5574 Compare October 31, 2025 17:14
@theoephraim theoephraim merged commit 9161687 into main Oct 31, 2025
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants