@cfxlabsinc/nx-dopplerWorkspace-internal Nx plugin that infers the doppler-run, doppler-serve and doppler-build targets.
createNodesV2 watches **/project.json and adds doppler-run to any project whose scripts/ directory holds at least one .ts / .mts / .mjs / .js file:
nx doppler-run <pkg> --args.script=<file> # dev config (default)
nx doppler-run <pkg> --args.script=<file> -c local # local | dev | prod
The command is doppler run -p cfx-<project> -c <config> --preserve-env -- bun {projectRoot}/scripts/{args.script}.
scripts/ is the markerThe command ends in bun {projectRoot}/scripts/{args.script}, so a project without a scripts/ directory has nothing it could ever run — the target is dead weight there.
That was the state before this plugin, and it was inverted: doppler-run lived in targetDefaults and five projects opted in with "doppler-run": {} — admin-dashboard, backoffice-api, bank-dashboard, customer-dashboard, temporal-worker. None of them had a runnable script (customer-dashboard's scripts/ held only a .gitkeep). Meanwhile the five projects that did have scripts — db, deposit-api, fed-directory-services, solana-services, wire-email-handler — had no target at all.
Inferring from the directory makes the target exist exactly where it works, and it stays correct as scripts are added or removed.
doppler-serve and doppler-buildBoth wrap an existing target — doppler run … -- nx serve <pkg> and doppler run … -- nx build <pkg> — so each is inferred only where that target exists:
nx doppler-build <pkg> # local config (default)
nx doppler-build <pkg> -c prod # local | dev | prod
An Nx plugin cannot see another plugin's inferred targets, so the presence check has three branches:
Source of build / serve |
How it is detected |
|---|---|
@cfxlabsinc/nx-lambda |
isLambdaProject() — src/lambda.ts + src/main.ts |
@cfxlabsinc/nx-open-next |
isOpenNextProject() — open-next.config.ts |
the project's own project.json |
reading targets[<name>] |
The last branch alone is not enough, and the dashboards show why: they declare serve in project.json purely to override the port, so doppler-serve was inferred there while doppler-build was not — their build is declared nowhere but in the OpenNext plugin. Importing the predicate instead of re-deriving it keeps the two plugins from drifting apart.
no-doppler tagA project tagged no-doppler still gets its doppler-* targets, but they run the inner command unwrapped — plain nx serve <pkg> instead of doppler run … -- nx serve <pkg>:
"tags": ["nextjs", "no-doppler"]
The plugin infers a dopplerProject of cfx-<project> and has no way to check that it exists — Doppler is a network call, and the graph is computed offline on every command. So the exception has to be declared, and a tag is the declaration Nx already has: schema-valid, colocated with the project it describes rather than in a central registry that drifts, and enumerable with nx show projects --with-tag no-doppler.
packages/ui-sandbox is the case it exists for. It is a credential-free harness for the packages/ui primitives — its sst.config.ts has "no VPC, no Valkey, no Aurora, no Doppler blob … nothing to connect to and nothing to protect" — but it is an OpenNext project, so isOpenNextProject infers serve / build and this plugin wrapped both in a doppler run against a project nobody ever created. nx doppler-serve ui-sandbox died on Could not find requested project 'cfx-ui-sandbox' before Next ever started.
The three configuration names (local / dev / prod) are kept as empty no-ops so the target's shape does not depend on the tag: -c dev still runs rather than failing with "configuration not found".
Not the fix for a renamed project. Several projects have a Doppler project under a name the
cfx-<project>convention does not produce —db→cfx-aurora-db,fed-directory-services→cfx-fed-directory-api,solana-services→cfx-solana-api. Those want adopplerProjectoverride in their ownproject.json; tagging them would silently drop secrets they do need.
Registered in workspace nx.json, excluding the vendored Pulumi provider SDKs (four of which ship a scripts/ directory of their own, and which the TypeScript and Biome plugins exclude the same way):
{
"plugin": "@cfxlabsinc/nx-doppler",
"options": {},
"exclude": ["packages/pulumi/sdks/*/**"]
}
The doppler-run entry in targetDefaults was removed — a targetDefault outranks an inferred target and would clobber the options set here. See @cfxlabsinc/nx-lambda.
Resolves through the workspace dependency in the root package.json:
"@cfxlabsinc/nx-doppler": "workspace:*"