mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-09 16:02:36 +01:00
fix(deps): let Dependabot update the sandbox providers (#19489)
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Sandbox providers (`packages/plugins/sandbox-providers/*`) are standalone published packages. `pnpm-workspace.yaml` keeps them out of the root workspace on purpose. > - `.github/dependabot.yml` watches only `/`, so Dependabot never saw the providers. > - Because of this, the Kubernetes provider stayed on zod 3 (GHSA-mrhx-c7xc-m3w5) after the rest of the repo moved to zod `^4.6.6`. #18839 fixed it by hand. > - We do not want to keep these versions current by hand in many places. > - This pull request lets Dependabot update the providers, and keeps each provider standalone. > - The benefit is that a provider dependency that falls behind gets a pull request that can pass CI. ## Linked Issues or Issue Description Refs #18839 (adds `scripts/typecheck-sandbox-providers` and fixes Kubernetes zod by hand). Refs #18998 (a Dependabot pull request for the Pi distribution that fails CI by design). **What existing behavior does this improve?** Dependabot version updates. They skip every package that the root pnpm workspace excludes. **Subsystem affected** CI and dependency automation: `.github/dependabot.yml`, `scripts/typecheck-sandbox-providers`. **Current behavior** Dependabot does not update the sandbox providers. A provider can stay on an old or unsafe version, and nobody gets a pull request. **Proposed behavior** Dependabot updates the providers each week. Each pull request changes the provider manifests and the lockfiles that CI installs frozen, so the pull request can pass CI. **Reason and benefit** We stop having to find and fix old provider dependencies by hand. ## What Changed - `.github/dependabot.yml`: add an npm entry for `/scripts/typecheck-sandbox-providers`. Dependabot treats this workspace as one unit. One pull request per dependency changes every provider that uses the dependency and the shared `pnpm-lock.yaml` together. So the entry needs no `groups`. One entry per provider would open a separate pull request for each provider, and each one would fail the frozen install in CI. - `.github/dependabot.yml`: add an npm entry for `/packages/plugins/sandbox-providers/daytona`. Daytona is the only provider with its own lockfile, and the release build and the Daytona end-to-end job install it frozen. Each lockfile has exactly one Dependabot entry, so every pull request changes a manifest and its lockfile together. - Take Daytona out of the shared typecheck workspace. This pull request does not commit `scripts/typecheck-sandbox-providers/pnpm-lock.yaml`: since #19522, only the lockfile refresh workflow writes it. After merge, the bot removes Daytona's packages from it (no other version changes; checked with pnpm 9.15.4). Until then CI resolves it in place. `scripts/typecheck-test-programs.mjs` now installs Daytona from its own lockfile (`LOCKED_PROVIDERS`, with `--ignore-workspace`). A stale lockfile is resolved in place for the run, for both installs. - `.github/dependabot.yml`: add a comment that tells why the Pi and Kimi runtime distributions have no entry (see Risks). - `scripts/typecheck-sandbox-providers/pnpm-workspace.yaml`: list each provider by its full path. Dependabot does not expand a workspace glob that starts with `../`, so with the glob it saw no providers. - `scripts/typecheck-test-programs.test.mjs`: add tests. They check that the workspace list has no glob and matches `SANDBOX_PROVIDERS` without `LOCKED_PROVIDERS`, that each locked provider has its lockfile, that a locked provider installs frozen with `--ignore-workspace`, and that a stale locked-provider lockfile is resolved in place. - Add `.npmrc` (`auto-install-peers=true`) in `scripts/typecheck-sandbox-providers` and in the Daytona provider. Without it, Dependabot reads the root `.npmrc` (`false`). That rewrites the lockfile settings, and the frozen install then fails. npm and pnpm never publish `.npmrc`. - `scripts/typecheck-sandbox-providers/package.json`: add `"packageManager": "pnpm@9.15.4"`, the version CI uses. Without it, Dependabot guesses pnpm 10. - `doc/DEVELOPING.md`: say that contributors do not refresh or commit Daytona's lockfile either, and that a new provider goes in both lists. No provider moves into the root workspace. There is no pnpm catalog. The root `pnpm-lock.yaml` does not change. ## Verification I ran the real Dependabot updater on this branch with the Dependabot CLI (`dependabot update ... --local`, updater image `dependabot-updater-npm@sha256:c5a9451c…`): - Before the change, the workspace entry loaded only `cloudflare/bridge-template` (the one path without a glob). It skipped all the other providers. - After the change, `--dep typescript` gave one pull request. It changed the 9 non-Daytona provider `package.json` files and the shared `pnpm-lock.yaml`, and did not touch Daytona. - `--dep wrangler` changed only the wrangler closure in the shared lockfile. Before the `.npmrc` fix, the same run changed `autoInstallPeers` to `false` and 757 lockfile lines. - The Daytona entry (`--dep @daytonaio/sdk`) changed Daytona's `package.json` and its own lockfile in one pull request, and kept `autoInstallPeers: true`. It keeps the exact pin exact: `0.203.0` becomes `0.227.3`. - Both frozen installs (shared workspace, and Daytona with `--ignore-workspace`) pass with pnpm 9.15.4. - Dependabot installed `pnpm@9.15.4` from the new `packageManager` field. Other checks: - `.github/dependabot.yml` passes the SchemaStore `dependabot-2.0` schema (Ajv). A wrong key fails the same check. - With pnpm 9.15.4, `install --resolution-only --ignore-scripts --no-frozen-lockfile` in `scripts/typecheck-sandbox-providers` (the lockfile bot's command) gives exactly the lockfile that passed the frozen installs earlier in this pull request. - The lockfile rule from #19522 allows this pull request's diff: it changes no lock content. - `node --test scripts/typecheck-test-programs.test.mjs`: the new test and the provider tests pass. Two other tests need a root `node_modules` (`tsc`), which my worktree did not have. CI runs them. These checks cannot run before merge. GitHub reads `dependabot.yml` only from the default branch. After merge, open Insights → Dependency graph → Dependabot and make sure that the two new entries show no errors and run on Monday. ## Risks - A dependency in both a non-Daytona provider and Daytona (for example `typescript`) gets two pull requests: one from each entry. Each one passes alone, and they can merge in any order. - The typecheck job now typechecks Daytona's tests against Daytona's own lockfile instead of the shared one. Its versions are a little older (for example vitest 4.1.11), so a type difference can show up there. - Majors of shared dev tools (for example TypeScript 7, Vitest 5) come as one pull request across all providers. It can need code changes before it passes. That is the same as the root entry. - Pi and Kimi get no version updates. Every direct dependency there is a pinned provider release, and the pins feed the provider's ACPX `commandDigest`. The Runner checks compare the Pi lock with `PI_DISTRIBUTION_PINS`, and the Kimi build compares the lock SHA-256 with a pin. So any Dependabot change there fails CI (see #18998), and a real upgrade starts saved sessions fresh. GitHub security alerts still cover both lockfiles. - The Pi zod `3.25.76` pin is still affected by GHSA-mrhx-c7xc-m3w5 (open alert). It is deliberate: `pi-acp@0.0.33` has `zod ^3.25.0` as a direct dependency, not a peer. A fix needs a `pi-acp` release that allows zod 4. This pull request does not change it. - `packages/plugins/examples/plugin-orchestration-smoke-example` is also outside the root workspace. It uses `workspace:*` dependencies, so it cannot install alone, and this pull request does not cover it. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude Code with tool use (shell, file edits, local Docker runs of the Dependabot CLI). ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
65aee998a3
commit
7dfdc97b9f
8 files changed
+208
-32
No files matched your search
@@ -30,6 +30,75 @@ updates:
|
||||
update-types:
|
||||
- "version-update:semver-major"
|
||||
|
||||
# Sandbox providers (packages/plugins/sandbox-providers/*). pnpm-workspace.yaml
|
||||
# keeps them out of the root workspace on purpose, so the "/" entry above never
|
||||
# sees them. Each provider is a standalone published package with its own
|
||||
# version ranges.
|
||||
#
|
||||
# Each lockfile has exactly one entry, so every pull request changes a
|
||||
# manifest and the lockfile that installs it together, and CI can pass.
|
||||
#
|
||||
# scripts/typecheck-sandbox-providers is a small pnpm workspace that lists every
|
||||
# provider without its own lockfile, and holds one lockfile for them. CI
|
||||
# installs that lockfile frozen. Dependabot updates this workspace as one unit:
|
||||
# one pull request per dependency changes every provider that uses the
|
||||
# dependency, and the shared lockfile, together. So this entry needs no
|
||||
# `groups`. One entry per provider would open a separate pull request for each
|
||||
# provider for the same package, and each one would fail CI because the shared
|
||||
# lockfile would stay old.
|
||||
#
|
||||
# Dependabot does not expand a workspace glob that starts with ../, so that
|
||||
# workspace lists each provider by its full path. A test checks the list.
|
||||
#
|
||||
# Providers are published libraries, so Dependabot's default versioning
|
||||
# strategy keeps a range when a new version fits it. It widens the range when a
|
||||
# release falls outside it, for example zod 3 to zod 4.
|
||||
- package-ecosystem: npm
|
||||
directory: "/scripts/typecheck-sandbox-providers"
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: "06:00"
|
||||
open-pull-requests-limit: 10
|
||||
labels:
|
||||
- "dependencies"
|
||||
ignore:
|
||||
# Same reason as the "/" entry.
|
||||
- dependency-name: "@types/node"
|
||||
update-types:
|
||||
- "version-update:semver-major"
|
||||
|
||||
# Daytona is the only provider with its own lockfile. The release build and the
|
||||
# Daytona end-to-end job install it frozen, so the shared workspace above leaves
|
||||
# Daytona out, and scripts/typecheck-test-programs.mjs installs Daytona from
|
||||
# this lockfile (LOCKED_PROVIDERS). This entry is the only one that changes
|
||||
# Daytona's package.json, and it changes this lockfile in the same pull request.
|
||||
- package-ecosystem: npm
|
||||
directory: "/packages/plugins/sandbox-providers/daytona"
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: "06:00"
|
||||
open-pull-requests-limit: 5
|
||||
labels:
|
||||
- "dependencies"
|
||||
ignore:
|
||||
# Same reason as the "/" entry.
|
||||
- dependency-name: "@types/node"
|
||||
update-types:
|
||||
- "version-update:semver-major"
|
||||
|
||||
# No entry for packages/paperclip-runner/scripts/pi-distribution or
|
||||
# kimi-distribution. Every direct dependency there is a pinned provider
|
||||
# release, and the pins feed each provider's ACPX commandDigest. The Runner
|
||||
# checks compare the Pi lockfile with PI_DISTRIBUTION_PINS, and the Kimi build
|
||||
# compares the SHA-256 of its package-lock.json with a pin. So a Dependabot
|
||||
# change to either lockfile fails CI (for example #18998), and a real upgrade
|
||||
# also starts saved sessions fresh. Change these by hand (see
|
||||
# rules/acpx-profile-identity.mdc). pi-acp@0.0.33 needs zod ^3.25.0 as a
|
||||
# direct dependency, so the Pi zod pin (3.25.76) cannot move to zod 4 until
|
||||
# pi-acp allows it. GitHub security alerts still cover both lockfiles.
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/"
|
||||
schedule:
|
||||
|
||||
@@ -1368,6 +1368,19 @@ Do not add a lockfile inside a provider.
|
||||
exists, which changes what a release resolves. Daytona already has one; the
|
||||
release build resolves it in place when it is stale.
|
||||
|
||||
Daytona is the one exception: it commits its own lockfile, so the workspace
|
||||
leaves it out and the script installs it from that lockfile (`LOCKED_PROVIDERS`).
|
||||
The same rule applies to it: do not refresh or commit that lockfile. The script
|
||||
resolves a stale one in place for the run, and the lockfile refresh workflow
|
||||
commits the new one after merge.
|
||||
|
||||
Dependabot updates the providers through this workspace (see
|
||||
`.github/dependabot.yml`), and Daytona through a second entry. Dependabot does
|
||||
not expand a workspace glob that starts with `../`, so
|
||||
`scripts/typecheck-sandbox-providers/pnpm-workspace.yaml` lists each provider by
|
||||
its full path. When you add a provider, add it there and to `SANDBOX_PROVIDERS`
|
||||
in `scripts/typecheck-test-programs.mjs`. A test checks that the two lists match.
|
||||
|
||||
#### Sandbox provider tests
|
||||
|
||||
`pnpm test:run` does not reach the sandbox providers, because they are outside the
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# pnpm reads this file for installs in this directory. Without it, Dependabot
|
||||
# uses the root .npmrc (auto-install-peers=false). That rewrites this lockfile,
|
||||
# which was made with pnpm's default (true), and frozen installs fail.
|
||||
# npm and pnpm never publish .npmrc, so this file does not ship.
|
||||
auto-install-peers=true
|
||||
@@ -0,0 +1,4 @@
|
||||
# pnpm reads this file for installs in this workspace. Without it, Dependabot
|
||||
# uses the root .npmrc (auto-install-peers=false). That rewrites this lockfile,
|
||||
# which was made with pnpm's default (true), and the frozen install in CI fails.
|
||||
auto-install-peers=true
|
||||
@@ -2,6 +2,7 @@
|
||||
"name": "@paperclipai/typecheck-sandbox-providers",
|
||||
"private": true,
|
||||
"description": "Installs sandbox provider dependencies for scripts/typecheck-test-programs.mjs. See doc/DEVELOPING.md.",
|
||||
"packageManager": "pnpm@9.15.4",
|
||||
"engines": {
|
||||
"node": ">=24.11.0"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,21 @@
|
||||
# commit a lockfile: scripts/build-standalone-public-packages.mjs uses a frozen
|
||||
# install when one exists. This workspace pins their dependencies in one lockfile
|
||||
# for scripts/typecheck-test-programs.mjs, without changing how they are released.
|
||||
#
|
||||
# List each provider by its full path. Do not use a glob such as
|
||||
# ../../packages/plugins/sandbox-providers/*. Dependabot updates this workspace
|
||||
# (see .github/dependabot.yml), and it does not expand a glob that starts with
|
||||
# ../, so it would not see the providers. Daytona is not listed: it has its own
|
||||
# lockfile and its own Dependabot entry, and scripts/typecheck-test-programs.mjs
|
||||
# installs it from that lockfile (LOCKED_PROVIDERS). A test checks that this list
|
||||
# matches SANDBOX_PROVIDERS without LOCKED_PROVIDERS.
|
||||
packages:
|
||||
- ../../packages/plugins/sandbox-providers/*
|
||||
- ../../packages/plugins/sandbox-providers/boxd
|
||||
- ../../packages/plugins/sandbox-providers/cloudflare
|
||||
- ../../packages/plugins/sandbox-providers/cloudflare/bridge-template
|
||||
- ../../packages/plugins/sandbox-providers/createos
|
||||
- ../../packages/plugins/sandbox-providers/e2b
|
||||
- ../../packages/plugins/sandbox-providers/exe-dev
|
||||
- ../../packages/plugins/sandbox-providers/kubernetes
|
||||
- ../../packages/plugins/sandbox-providers/modal
|
||||
- ../../packages/plugins/sandbox-providers/novita
|
||||
@@ -6,7 +6,8 @@
|
||||
// in PROGRAMS reads them without emitting. Sandbox providers are outside the
|
||||
// workspace, so the root install has none of their dependencies. They install from
|
||||
// typecheck-sandbox-providers/pnpm-lock.yaml here, because their SDK types are part
|
||||
// of what a test checks.
|
||||
// of what a test checks. A provider with its own lockfile (LOCKED_PROVIDERS) installs
|
||||
// from that lockfile instead, so each lockfile has one owner and one Dependabot entry.
|
||||
//
|
||||
// The script then fails if a tracked test file is in no program. tsc decides that,
|
||||
// not a glob: `include` silently drops foo.test.tsx when foo.test.ts exists.
|
||||
@@ -37,6 +38,10 @@ export const SANDBOX_PROVIDERS = [
|
||||
"packages/plugins/sandbox-providers/novita",
|
||||
];
|
||||
|
||||
// Providers that commit their own pnpm-lock.yaml. The release build and the Daytona
|
||||
// end-to-end job install it frozen, so the shared workspace leaves them out.
|
||||
export const LOCKED_PROVIDERS = ["packages/plugins/sandbox-providers/daytona"];
|
||||
|
||||
export const PROGRAMS = [
|
||||
"packages/db/tsconfig.test.json",
|
||||
"packages/kv-demo-mcp-server/tsconfig.test.json",
|
||||
@@ -60,10 +65,12 @@ const PREPARE_STEPS = [
|
||||
];
|
||||
// Replacing a provider's standalone node_modules would otherwise ask for confirmation.
|
||||
const INSTALL_ARGS = ["install", "--frozen-lockfile", "--ignore-scripts", "--prefer-offline", "--config.confirmModulesPurge=false"];
|
||||
const LOCKED_INSTALL_ARGS = ["install", "--ignore-workspace", ...INSTALL_ARGS.slice(1)];
|
||||
// Contributors do not commit provider lockfiles: the lockfile refresh workflow regenerates them after
|
||||
// merge, as it does the root pnpm-lock.yaml. Until then a changed manifest leaves the lockfile stale,
|
||||
// so resolve it in place, like the root install in CI.
|
||||
export const RESOLVE_ARGS = ["install", "--resolution-only", "--ignore-scripts", "--no-frozen-lockfile"];
|
||||
const LOCKED_RESOLVE_ARGS = ["install", "--ignore-workspace", ...RESOLVE_ARGS.slice(1)];
|
||||
|
||||
export function findUncheckedTests({ trackedFiles, checkedFiles, checkedElsewhere = CHECKED_ELSEWHERE }) {
|
||||
const checked = new Set(checkedFiles);
|
||||
@@ -168,21 +175,27 @@ export function typecheckTestPrograms({
|
||||
const status = pnpm(args, { cwd: root, env });
|
||||
if (status !== 0) return status;
|
||||
}
|
||||
const workspace = path.join(root, PROVIDER_WORKSPACE);
|
||||
let installed = pnpm(INSTALL_ARGS, { cwd: workspace, env });
|
||||
if (installed !== 0) {
|
||||
log(
|
||||
`typecheck:test-programs: ${PROVIDER_WORKSPACE}/pnpm-lock.yaml does not match the provider manifests. ` +
|
||||
`Resolving it in place. Do not commit it: the lockfile refresh workflow updates it after merge.`,
|
||||
);
|
||||
installed = pnpm(RESOLVE_ARGS, { cwd: workspace, env });
|
||||
if (installed === 0) installed = pnpm(INSTALL_ARGS, { cwd: workspace, env });
|
||||
const install = (directory, frozenArgs, resolveArgs) => {
|
||||
const cwd = path.join(root, directory);
|
||||
let status = pnpm(frozenArgs, { cwd, env });
|
||||
if (status !== 0) {
|
||||
log(
|
||||
`typecheck:test-programs: ${directory}/pnpm-lock.yaml does not match its manifests. ` +
|
||||
`Resolving it in place. Do not commit it: the lockfile refresh workflow updates it after merge.`,
|
||||
);
|
||||
status = pnpm(resolveArgs, { cwd, env });
|
||||
if (status === 0) status = pnpm(frozenArgs, { cwd, env });
|
||||
}
|
||||
if (status !== 0) logError(`typecheck:test-programs: could not install the sandbox providers in ${directory}`);
|
||||
return status;
|
||||
};
|
||||
const installed = install(PROVIDER_WORKSPACE, INSTALL_ARGS, RESOLVE_ARGS);
|
||||
if (installed !== 0) return installed;
|
||||
for (const provider of LOCKED_PROVIDERS) {
|
||||
const status = install(provider, LOCKED_INSTALL_ARGS, LOCKED_RESOLVE_ARGS);
|
||||
if (status !== 0) return status;
|
||||
}
|
||||
if (installed !== 0) {
|
||||
logError(`typecheck:test-programs: could not install the sandbox providers in ${PROVIDER_WORKSPACE}`);
|
||||
return installed;
|
||||
}
|
||||
// The install removes the SDK link from each provider's node_modules.
|
||||
// The installs remove the SDK link from each provider's node_modules.
|
||||
linkPlugins();
|
||||
|
||||
let exitCode = 0;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { execFileSync, spawnSync } from "node:child_process";
|
||||
import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { test } from "node:test";
|
||||
@@ -8,6 +8,7 @@ import { fileURLToPath } from "node:url";
|
||||
import {
|
||||
checkCoverage,
|
||||
findUncheckedTests,
|
||||
LOCKED_PROVIDERS,
|
||||
PROGRAMS,
|
||||
PROVIDER_WORKSPACE,
|
||||
RESOLVE_ARGS,
|
||||
@@ -63,6 +64,22 @@ test("every program exists, and the provider list matches the tracked provider p
|
||||
assert.deepEqual(manifests.map((manifest) => path.dirname(manifest)).sort(), [...SANDBOX_PROVIDERS].sort());
|
||||
});
|
||||
|
||||
test("the provider workspace lists every provider without its own lockfile by full path, so Dependabot sees them", () => {
|
||||
const workspace = readFileSync(path.join(root, PROVIDER_WORKSPACE, "pnpm-workspace.yaml"), "utf8");
|
||||
const listed = [...workspace.matchAll(/^\s+-\s+(\S+)\s*$/gm)].map((match) => match[1]);
|
||||
assert.ok(listed.every((entry) => !entry.includes("*")), "Dependabot does not expand a glob that starts with ../");
|
||||
const providers = listed.map((entry) => path.posix.normalize(path.posix.join(PROVIDER_WORKSPACE, entry)));
|
||||
const unlocked = SANDBOX_PROVIDERS.filter((provider) => !LOCKED_PROVIDERS.includes(provider));
|
||||
assert.deepEqual(providers.sort(), unlocked.sort());
|
||||
});
|
||||
|
||||
test("each provider with its own lockfile is a provider and has that lockfile", () => {
|
||||
for (const provider of LOCKED_PROVIDERS) {
|
||||
assert.ok(SANDBOX_PROVIDERS.includes(provider), provider);
|
||||
assert.ok(existsSync(path.join(root, provider, "pnpm-lock.yaml")), provider);
|
||||
}
|
||||
});
|
||||
|
||||
test("no provider commits a lockfile, except the one Daytona already has", () => {
|
||||
const tracked = execFileSync("git", ["ls-files", "packages/plugins/sandbox-providers"], { cwd: root, encoding: "utf8" });
|
||||
assert.deepEqual(
|
||||
@@ -105,26 +122,65 @@ test("providers install frozen without scripts, then relink the SDK", () => {
|
||||
assert.equal(calls.linked, 1);
|
||||
});
|
||||
|
||||
test("a stale provider lockfile is resolved in place, then installed frozen", () => {
|
||||
const workspace = path.join("/repo", PROVIDER_WORKSPACE);
|
||||
let frozenAttempts = 0;
|
||||
const { options, calls } = harness({
|
||||
pnpm: (args, { cwd }) => {
|
||||
calls.pnpm.push({ args, cwd });
|
||||
if (cwd !== workspace || !args.includes("--frozen-lockfile")) return 0;
|
||||
frozenAttempts += 1;
|
||||
return frozenAttempts === 1 ? 1 : 0;
|
||||
},
|
||||
});
|
||||
test("a locked provider installs frozen from its own lockfile, outside any workspace", () => {
|
||||
const { options, calls } = harness();
|
||||
assert.equal(typecheckTestPrograms(options), 0);
|
||||
assert.deepEqual(
|
||||
calls.pnpm.filter(({ cwd }) => cwd === workspace).map(({ args }) => args.includes("--frozen-lockfile") ? "frozen" : args),
|
||||
["frozen", RESOLVE_ARGS, "frozen"],
|
||||
);
|
||||
for (const provider of LOCKED_PROVIDERS) {
|
||||
const install = calls.pnpm.find(({ cwd }) => cwd === path.join("/repo", provider));
|
||||
assert.ok(install.args.includes("--ignore-workspace"));
|
||||
assert.ok(install.args.includes("--frozen-lockfile"));
|
||||
assert.ok(install.args.includes("--ignore-scripts"));
|
||||
}
|
||||
});
|
||||
|
||||
// Each directory's first frozen install fails; resolution and the second frozen install pass.
|
||||
const staleOnce = (directory, calls) => {
|
||||
const cwd = path.join("/repo", directory);
|
||||
let frozenAttempts = 0;
|
||||
return (args, options) => {
|
||||
calls.pnpm.push({ args, cwd: options.cwd });
|
||||
if (options.cwd !== cwd || !args.includes("--frozen-lockfile")) return 0;
|
||||
frozenAttempts += 1;
|
||||
return frozenAttempts === 1 ? 1 : 0;
|
||||
};
|
||||
};
|
||||
const installSteps = (calls, directory) =>
|
||||
calls.pnpm
|
||||
.filter(({ cwd }) => cwd === path.join("/repo", directory))
|
||||
.map(({ args }) => (args.includes("--frozen-lockfile") ? "frozen" : args));
|
||||
|
||||
test("a stale provider lockfile is resolved in place, then installed frozen", () => {
|
||||
const { options, calls } = harness();
|
||||
options.pnpm = staleOnce(PROVIDER_WORKSPACE, calls);
|
||||
assert.equal(typecheckTestPrograms(options), 0);
|
||||
assert.deepEqual(installSteps(calls, PROVIDER_WORKSPACE), ["frozen", RESOLVE_ARGS, "frozen"]);
|
||||
assert.ok(RESOLVE_ARGS.includes("--resolution-only") && RESOLVE_ARGS.includes("--ignore-scripts"));
|
||||
assert.equal(calls.tsc.length, 2);
|
||||
});
|
||||
|
||||
test("a stale locked-provider lockfile is resolved in place, outside any workspace", () => {
|
||||
const [provider] = LOCKED_PROVIDERS;
|
||||
const { options, calls } = harness();
|
||||
options.pnpm = staleOnce(provider, calls);
|
||||
assert.equal(typecheckTestPrograms(options), 0);
|
||||
const [, resolve] = installSteps(calls, provider);
|
||||
assert.deepEqual(installSteps(calls, provider), ["frozen", resolve, "frozen"]);
|
||||
for (const flag of ["--ignore-workspace", "--resolution-only", "--ignore-scripts", "--no-frozen-lockfile"]) {
|
||||
assert.ok(resolve.includes(flag), flag);
|
||||
}
|
||||
assert.equal(calls.tsc.length, 2);
|
||||
});
|
||||
|
||||
test("a locked provider install that still fails stops before any program runs", () => {
|
||||
const [provider] = LOCKED_PROVIDERS;
|
||||
const { options, calls } = harness({
|
||||
pnpm: (args, { cwd }) => (cwd === path.join("/repo", provider) ? 1 : 0),
|
||||
});
|
||||
assert.equal(typecheckTestPrograms(options), 1);
|
||||
assert.equal(calls.tsc.length, 0);
|
||||
assert.ok(calls.errors[0].includes(provider), calls.errors[0]);
|
||||
});
|
||||
|
||||
test("a provider install that still fails stops before any program runs", () => {
|
||||
const { options, calls } = harness({
|
||||
pnpm: (args, { cwd }) => (cwd === path.join("/repo", PROVIDER_WORKSPACE) ? 1 : 0),
|
||||
|
||||
Reference in new issue
Block a user