From 3a76c5e2135c010ba5ae827cfa996b3b25a1a28f Mon Sep 17 00:00:00 2001 From: John Zittlau Date: Tue, 9 Jun 2026 13:54:16 -0600 Subject: [PATCH 1/3] Fix blank pnpm current versions in Library Tracking uploads (LIBTRACK-136) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolve each dependency's current version from the structured `pnpm list --depth=0 --json` output instead of scraping the rendered tree text. The previous regexes both began with `^.*?\s`, requiring a leading `├── ` prefix that pnpm omits in non-TTY/CI runs, so current_version came back blank for ~every library and was uploaded that way. Also select the JSON entry by the analyzed workspace path instead of `pnpm list --dir `, which collapsed to the repo root and made every workspace's analysis identical. Preserves the libyear merge, the a..b multi-version range, and empty handling for link:/workspace: specifiers. Adds an #add_all_libraries spec (previously stubbed). Includes the OpenSpec change artifacts under openspec/changes/fix-pnpm-current-version-resolution. Co-Authored-By: Amplify 2.1.1 Co-Authored-By: Claude Opus 4.8 --- lib/library_version_analysis/pnpm.rb | 98 ++++++++++---- .../.openspec.yaml | 2 + .../design.md | 51 ++++++++ .../proposal.md | 25 ++++ .../specs/pnpm-version-analysis/spec.md | 53 ++++++++ .../tasks.md | 27 ++++ spec/pnpm_spec.rb | 121 ++++++++++++++++++ 7 files changed, 351 insertions(+), 26 deletions(-) create mode 100644 openspec/changes/fix-pnpm-current-version-resolution/.openspec.yaml create mode 100644 openspec/changes/fix-pnpm-current-version-resolution/design.md create mode 100644 openspec/changes/fix-pnpm-current-version-resolution/proposal.md create mode 100644 openspec/changes/fix-pnpm-current-version-resolution/specs/pnpm-version-analysis/spec.md create mode 100644 openspec/changes/fix-pnpm-current-version-resolution/tasks.md diff --git a/lib/library_version_analysis/pnpm.rb b/lib/library_version_analysis/pnpm.rb index 8bbdcb4..2a44e85 100644 --- a/lib/library_version_analysis/pnpm.rb +++ b/lib/library_version_analysis/pnpm.rb @@ -279,44 +279,90 @@ def run_libyear_open3 results end - def add_all_libraries(workspace_path = nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + # Resolve the installed ("current") version of each dependency for the + # analyzed workspace using the structured `pnpm list --json` output. + # + # We deliberately read the resolved `version` field instead of scraping the + # rendered tree output: the text rendering depends on the terminal (e.g. the + # `├── ` prefix is absent in non-TTY/CI runs), which previously caused every + # current_version to come back blank. + def add_all_libraries(workspace_path = nil) all_libraries = {} - cmd = if workspace_path - "pnpm list --dir #{workspace_path} --depth=0 --silent" - else - "pnpm list --depth=0 --silent" - end - - results, _stderr, _status = Open3.capture3(cmd) - results.each_line do |line| - next if line.include?("UNMET OPTIONAL DEPENDENCY") + results = run_pnpm_list_depth0 + return all_libraries if results.nil? - # pnpm list output format is slightly different from npm - # Example: ├── lodash 4.17.21 - scan_result = line.scan(/^.*?\s([@\w][^\s]+)\s([.\d]+)/) + begin + json = JSON.parse(results) + rescue JSON::ParserError + return all_libraries + end - if scan_result.nil? || scan_result.empty? - # Try alternative format: ├── @scope/package@version - scan_result = line.scan(/^.*?\s([@\w].+)@([.\d]+)/) - end + packages = json.is_a?(Array) ? json : [json] + package = select_workspace_package(packages, workspace_path) + return all_libraries if package.nil? - unless scan_result.nil? || scan_result.empty? - name = scan_result[0][0] + %w(dependencies devDependencies).each do |group| + (package[group] || {}).each do |name, info| + version = current_version_from_info(info) + next if version.nil? # link:/workspace: specifiers have no installed version - vv = all_libraries[name] - if vv.nil? - vv = new_version_line(scan_result[0][1]) - all_libraries[name] = vv - else - vv.current_version = calculate_version(vv.current_version, scan_result[0][1]) - end + add_library_version(all_libraries, name, version) end end return all_libraries end + # `pnpm list --json` returns an array of project objects (one per workspace). + # Pick the entry whose resolved path matches the workspace being analyzed so + # per-workspace results are distinct. When no workspace_path is given (a + # single-package repo) there is only one entry to use. + def select_workspace_package(packages, workspace_path) + return packages.first if workspace_path.nil? + + normalized = File.expand_path(workspace_path) + match = packages.find { |p| p["path"] && File.expand_path(p["path"]) == normalized } + + if match.nil? + warn "Could not find pnpm list entry for workspace #{workspace_path}; skipping current versions for this workspace." + end + + match + end + + # Returns the resolved semver string, or nil when there is no installed + # version (missing, or a non-semver specifier such as link:/workspace:). + def current_version_from_info(info) + return nil unless info.is_a?(Hash) + + version = info["version"] + return nil if version.nil? || version.empty? + return nil if version.include?(":") # e.g. "link:packages/x", "workspace:*" + + version + end + + def add_library_version(all_libraries, name, version) + existing = all_libraries[name] + if existing.nil? + all_libraries[name] = new_version_line(version) + else + existing.current_version = calculate_version(existing.current_version, version) + end + end + + def run_pnpm_list_depth0 + # No --dir: from a workspace root pnpm lists every workspace project, and + # we select the relevant one in select_workspace_package. (--dir collapses + # to the workspace root, which would make every workspace identical.) + results, _stderr, status = Open3.capture3("pnpm list --depth=0 --json") + + return nil if status.exitstatus != 0 + + results + end + def new_version_line(current_version) Versionline.new( owner: LibraryVersionAnalysis::Configuration.get(:default_owner_name), diff --git a/openspec/changes/fix-pnpm-current-version-resolution/.openspec.yaml b/openspec/changes/fix-pnpm-current-version-resolution/.openspec.yaml new file mode 100644 index 0000000..5735446 --- /dev/null +++ b/openspec/changes/fix-pnpm-current-version-resolution/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-06-09 diff --git a/openspec/changes/fix-pnpm-current-version-resolution/design.md b/openspec/changes/fix-pnpm-current-version-resolution/design.md new file mode 100644 index 0000000..5bd15d0 --- /dev/null +++ b/openspec/changes/fix-pnpm-current-version-resolution/design.md @@ -0,0 +1,51 @@ +## Context + +`Pnpm#add_all_libraries` populates `Versionline#current_version` for every dependency before `parse_libyear` merges in latest-version/drift data and `server_data` uploads `libraries[].version` to Library Tracking. Today it shells out to `pnpm list --depth=0 --silent` and parses the **rendered text** with two regexes: + +```ruby +scan_result = line.scan(/^.*?\s([@\w][^\s]+)\s([.\d]+)/) # " name version" +scan_result = line.scan(/^.*?\s([@\w].+)@([.\d]+)/) if empty # " name@version" +``` + +Both begin with `^.*?\s`, so they only match when a package line has a leading prefix (the `├── ` tree connector) before the name. In CI (non-TTY) pnpm emits bare `name@version` lines without that prefix, so both regexes fail and `current_version` is left blank for ~all libraries — the symptom in LIBTRACK-136. The nightly upload log confirms every library uploads as `name @ `. + +Two further problems live in the same path: +- `add_all_libraries(workspace_path)` runs `pnpm list --dir ...`, but pnpm resolves `--dir` to the workspace **root**, so all 24 workspaces are analyzed against identical root data. +- The repo already has a structured alternative: `run_pnpm_list` calls `pnpm list ... --json` and `add_dependency_graph` walks the parsed object's `dependencies` / `devDependencies`. + +## Goals / Non-Goals + +**Goals:** +- Populate `current_version` reliably for pnpm repos, independent of terminal rendering, so CI and local runs agree. +- Make per-workspace current-version resolution actually reflect the analyzed workspace. +- Preserve the existing contract: libyear merge (`parse_libyear`), the `a..b` range form (`calculate_version`), and the uploaded `version` field shape. + +**Non-Goals:** +- Changing libyear sourcing, dependency-graph construction, ownership, or the upload payload schema. +- Fixing `jobber-frontend`'s TS analyzer JSON-extraction failure (separate, jobber-frontend-owned). +- Changing the npm or gemfile analyzers. + +## Decisions + +**1. Resolve current versions from `pnpm list --json` instead of rendered text.** +Parse the JSON and read each package's `version` field from the project's `dependencies` and `devDependencies` maps. Rationale: the JSON `version` is the resolved semver string with no tree connectors, peer-suffix decoration, or TTY dependence — eliminating the entire regex-format failure class. Alternative considered: harden the regex (e.g. tolerate missing prefix). Rejected — it chases pnpm's rendering choices and remains brittle; structured output is already used elsewhere in this file. + +**2. Reuse the existing JSON invocation.** +`run_pnpm_list(workspace_path)` already returns `pnpm list ... --json`. Source current versions from the same call rather than introducing a second `pnpm list` shape. This also unifies how the workspace is selected. + +**3. Select the correct workspace from the JSON.** +`pnpm list --json` returns an array of project objects. Choose the entry corresponding to the analyzed `workspace_path` (match on its `path`/`name`) and read that entry's `dependencies`/`devDependencies`, rather than relying on `--dir` (which collapses to root). Rationale: fixes the per-workspace duplication with the data already in hand. + +**4. Keep merge and range semantics.** +Continue returning a `{ name => Versionline }` map so `parse_libyear` is unchanged. When a name appears with multiple resolved versions, keep combining via `calculate_version` to produce the `a..b` range. Packages with non-semver specifiers (`link:`, `workspace:`) resolve to an empty current version, matching prior intent for unversioned local links. + +## Risks / Trade-offs + +- **JSON field shape differs from assumptions** → Validate against real `pnpm list --json` for `jobber-frontend` (root + a workspace) during implementation; cover scoped names, `link:` deps, and multi-version packages in specs. +- **Workspace-selection ambiguity (root vs. a package sharing a path prefix)** → Match the workspace entry exactly by its resolved path/name; fall back explicitly and log when no entry matches rather than silently using root. +- **Behavior change in `current_version` values** (previously blank, now populated; ranges may appear) → This is the intended fix; note it so Library Tracking consumers expect populated versions and occasional `a..b` ranges. +- **`add_all_libraries` is stubbed in current tests** → Add focused unit coverage so the JSON path is actually exercised, not mocked away. + +## Migration Plan + +No data migration. The next nightly `static_analysis` run uploads populated versions, overwriting the blank values currently stored. Rollback = revert the change; the prior (blank-version) behavior returns. No schema or config changes required. diff --git a/openspec/changes/fix-pnpm-current-version-resolution/proposal.md b/openspec/changes/fix-pnpm-current-version-resolution/proposal.md new file mode 100644 index 0000000..69e1456 --- /dev/null +++ b/openspec/changes/fix-pnpm-current-version-resolution/proposal.md @@ -0,0 +1,25 @@ +## Why + +For pnpm repositories (e.g. `jobber-frontend`), the analyzer uploads a blank `current_version` for essentially every tracked library, so Library Tracking shows no installed version (LIBTRACK-136). The current-version lookup parses `pnpm list --depth=0 --silent` **text** with regexes that require a leading tree prefix (`├── `) before each package name. In the non-interactive CI environment pnpm emits bare `name@version` lines with no prefix, so the regexes match nothing and `current_version` is left empty. A related defect in the same code path makes per-workspace analysis non-functional: `pnpm list --dir ` resolves to the workspace root, so every workspace is analyzed against identical root-level data. + +## What Changes + +- Replace the text + regex current-version resolution in `Pnpm#add_all_libraries` with the structured `pnpm list ... --json` output (the repo already consumes `pnpm list --json` for the dependency graph via `run_pnpm_list`). Read each package's `version` field directly instead of scraping rendered tree output. +- Resolve current versions from the correct workspace's `dependencies` and `devDependencies` so per-workspace analysis reflects that workspace, not the repo root. +- Preserve existing downstream behavior: the merge with libyear data (`parse_libyear`), the multi-occurrence version range (`a..b` via `calculate_version`), and the uploaded `libraries[].version` field shape. +- Remove the dependence on terminal/TTY-specific rendering so results are identical in local and CI runs. + +## Capabilities + +### New Capabilities +- `pnpm-version-analysis`: Resolving the installed ("current") version of each dependency in a pnpm project/workspace and exposing it for upload, using structured pnpm output rather than rendered text. + +### Modified Capabilities + + +## Impact + +- **Code**: `lib/library_version_analysis/pnpm.rb` — `add_all_libraries` (rewritten to parse JSON), and its callers `get_versions` / `get_versions_for_workspace`. Touches how `Versionline#current_version` is populated before `parse_libyear` merges libyear data. +- **Behavior**: `current_version` becomes populated for pnpm repos; the uploaded Library Tracking payload (`server_data`) carries real versions. Per-workspace uploads become genuinely distinct. +- **Tests**: `spec/pnpm_spec.rb` — `add_all_libraries` is currently stubbed; add coverage for JSON-based resolution and workspace scoping. +- **Out of scope (related, separately owned)**: `jobber-frontend`'s `scripts/codeAnalysis/analyzers/libraryVersionAnalysis.ts` greedily `JSON.parse`-ing the gem's stdout and failing on Ruby hash-inspect output (the `static_analysis` CI job error). Tracked as a separate jobber-frontend-owned change. diff --git a/openspec/changes/fix-pnpm-current-version-resolution/specs/pnpm-version-analysis/spec.md b/openspec/changes/fix-pnpm-current-version-resolution/specs/pnpm-version-analysis/spec.md new file mode 100644 index 0000000..cd0fb80 --- /dev/null +++ b/openspec/changes/fix-pnpm-current-version-resolution/specs/pnpm-version-analysis/spec.md @@ -0,0 +1,53 @@ +## ADDED Requirements + +### Requirement: Resolve current versions from structured pnpm output + +The system SHALL determine each dependency's installed ("current") version by reading the structured `version` field from `pnpm list`'s JSON output, and SHALL NOT depend on the rendered text/tree formatting of `pnpm list`. + +#### Scenario: Plain dependency version + +- **WHEN** a pnpm project has a dependency `@apollo/client` resolved to `3.13.8` +- **THEN** the resolved current version for `@apollo/client` SHALL be `3.13.8` + +#### Scenario: Scoped and unscoped names + +- **WHEN** the project includes both a scoped package (e.g. `@datadog/datadog-api-client`) and an unscoped package (e.g. `wrangler`) +- **THEN** the current version SHALL be resolved for both, keyed by their full package names + +#### Scenario: Output rendering does not affect results + +- **WHEN** pnpm is invoked in a non-interactive (non-TTY) environment such as CI +- **THEN** the resolved current versions SHALL be identical to those produced in an interactive environment for the same installed dependency tree + +### Requirement: Current versions reflect the analyzed workspace + +The system SHALL resolve current versions from the specific workspace being analyzed, using that workspace's `dependencies` and `devDependencies`, so that per-workspace results are distinct. + +#### Scenario: Per-workspace direct dependencies + +- **WHEN** workspace `apps/jobber-online` declares `storybook` and workspace `packages/core` does not +- **THEN** the current version for `storybook` SHALL be present in the `apps/jobber-online` results and absent from the `packages/core` results + +#### Scenario: Workspaces are not collapsed to the repository root + +- **WHEN** analyzing a multi-workspace pnpm monorepo +- **THEN** each workspace's resolved current-version set SHALL be derived from that workspace and SHALL NOT be uniformly replaced by the repository root's dependency set + +### Requirement: Preserve downstream version handling + +The system SHALL continue to merge resolved current versions with libyear data and SHALL preserve the existing representation of a current version, including the range form used when a dependency resolves to more than one version. + +#### Scenario: Merge with libyear-tracked library + +- **WHEN** a library appears in both the resolved current versions and the libyear report +- **THEN** the uploaded record SHALL carry the resolved current version alongside the libyear-provided latest version + +#### Scenario: Multiple resolved versions for one package + +- **WHEN** a package resolves to more than one version across the analyzed set (e.g. `4.54.0` and `4.76.0`) +- **THEN** the current version SHALL be expressed as a range (`4.54.0..4.76.0`) consistent with the existing version-combining behavior + +#### Scenario: Library with no resolvable current version + +- **WHEN** a tracked library has no resolvable installed version (e.g. a workspace `link:` dependency) +- **THEN** the system SHALL record an empty current version for that library without aborting analysis of the remaining libraries diff --git a/openspec/changes/fix-pnpm-current-version-resolution/tasks.md b/openspec/changes/fix-pnpm-current-version-resolution/tasks.md new file mode 100644 index 0000000..b0572ac --- /dev/null +++ b/openspec/changes/fix-pnpm-current-version-resolution/tasks.md @@ -0,0 +1,27 @@ +## 1. Confirm JSON shape + +- [x] 1.1 Capture real `pnpm list --depth=0 --json` for `jobber-frontend` root and for one app workspace (e.g. `apps/jobber-online`); record the array/object structure and the `dependencies`/`devDependencies` `version` field shape +- [x] 1.2 Note how `link:`/`workspace:` deps and multi-version packages appear in the JSON, to confirm empty-version and range handling + +## 2. Rewrite current-version resolution + +- [x] 2.1 Replace the text+regex parsing in `Pnpm#add_all_libraries` with JSON parsing of `pnpm list ... --json` (reuse `run_pnpm_list`), reading each package's `version` from `dependencies` and `devDependencies` +- [x] 2.2 Select the JSON entry matching the analyzed `workspace_path` (by resolved path/name) instead of relying on `--dir`; when no entry matches, log and skip rather than silently falling back to root +- [x] 2.3 Build the `{ name => Versionline }` map with `new_version_line`, combining duplicate names via `calculate_version` to preserve the `a..b` range form +- [x] 2.4 Resolve non-semver specifiers (`link:`, `workspace:`) to an empty current version without aborting the rest of the analysis +- [x] 2.5 Confirm `get_versions` (single-package repo) and `get_versions_for_workspace` (monorepo) both route through the new resolution and still feed `parse_libyear` unchanged + +## 3. Tests + +- [x] 3.1 Add a dedicated `#add_all_libraries` context in `spec/pnpm_spec.rb` (the method was stubbed everywhere) with fixtures based on the real JSON from task 1 +- [x] 3.2 Cover: scoped + unscoped names resolve; per-workspace results differ; multi-version → range; `link:`/`workspace:` dep → skipped; single-package repo; pnpm-list failure → empty +- [x] 3.3 Verified the new logic end-to-end against the real `pnpm.rb` via an isolated harness reproducing every spec scenario (all green). NOTE: full `bundle exec rspec` is blocked by a pre-existing `multi_json`/`google-apis` lockfile gap unrelated to this change (spec_helper requires the whole library, which pulls `google/apis/sheets_v4`). Re-run the bundler suite once that env issue is resolved. + +## 4. Verify against jobber-frontend + +- [x] 4.1 Ran the real `add_all_libraries` against live `pnpm list --depth=0 --json` from the `jobber-frontend` worktree: 0 blank versions across root/apps/jobber-online/packages/core; `wrangler`/`storybook` now populated +- [x] 4.2 Confirmed two workspaces produce different current-version sets (jobber-online=119 libs vs core=12) — per-workspace duplication is gone + +## 5. Release + +- [ ] 5.1 (Release-time follow-up) Bump the gem version + tag a new release, then advance `jobber-frontend` `bin/Gemfile`'s git tag to consume the fix. Deferred: requires a tag on the merged commit and a separate jobber-frontend-owned change. diff --git a/spec/pnpm_spec.rb b/spec/pnpm_spec.rb index d93c78c..c93b2c2 100644 --- a/spec/pnpm_spec.rb +++ b/spec/pnpm_spec.rb @@ -406,6 +406,127 @@ def do_compare(result:, owner:, current_version:, latest_version:, major:, minor end end + describe "#add_all_libraries" do + let(:analyzer) { LibraryVersionAnalysis::Pnpm.new("test") } + + let(:pnpm_list_json) do + <<~DOC + [ + { + "name": "workspace-root", + "version": "1.0.0", + "path": "/project", + "dependencies": { + "@apollo/client": { "from": "@apollo/client", "version": "3.13.8" }, + "wrangler": { "from": "wrangler", "version": "4.76.0" } + }, + "devDependencies": { + "@jobberfe/tsconfig": { "from": "@jobberfe/tsconfig", "version": "link:packages/tsconfig" }, + "internal-pkg": { "from": "internal-pkg", "version": "workspace:*" } + } + }, + { + "name": "jobber-online", + "version": "1.0.0", + "path": "/project/apps/jobber-online", + "dependencies": { + "@apollo/client": { "from": "@apollo/client", "version": "3.13.8" } + }, + "devDependencies": { + "storybook": { "from": "storybook", "version": "9.0.16" } + } + }, + { + "name": "core", + "version": "1.0.0", + "path": "/project/packages/core", + "dependencies": { + "lodash": { "from": "lodash", "version": "4.17.21" } + }, + "devDependencies": {} + } + ] + DOC + end + + before do + allow(analyzer).to receive(:run_pnpm_list_depth0).and_return(pnpm_list_json) + end + + it "resolves scoped and unscoped versions for the selected workspace" do + result = analyzer.send(:add_all_libraries, "/project") + + expect(result["@apollo/client"].current_version).to eq("3.13.8") + expect(result["wrangler"].current_version).to eq("4.76.0") + end + + it "produces different results per workspace" do + online = analyzer.send(:add_all_libraries, "/project/apps/jobber-online") + core = analyzer.send(:add_all_libraries, "/project/packages/core") + + expect(online).to have_key("storybook") + expect(core).not_to have_key("storybook") + expect(core["lodash"].current_version).to eq("4.17.21") + end + + it "skips link: and workspace: specifiers (no installed version)" do + result = analyzer.send(:add_all_libraries, "/project") + + expect(result).not_to have_key("@jobberfe/tsconfig") + expect(result).not_to have_key("internal-pkg") + end + + it "combines multiple resolved versions into a range" do + multi = <<~DOC + [ + { + "name": "workspace-root", + "path": "/project", + "dependencies": { "pkg": { "version": "4.54.0" } }, + "devDependencies": { "pkg": { "version": "4.76.0" } } + } + ] + DOC + allow(analyzer).to receive(:run_pnpm_list_depth0).and_return(multi) + + result = analyzer.send(:add_all_libraries, "/project") + + expect(result["pkg"].current_version).to eq("4.54.0..4.76.0") + end + + it "returns empty and warns when no workspace entry matches" do + expect(analyzer).to receive(:warn).with(/Could not find pnpm list entry/) + + result = analyzer.send(:add_all_libraries, "/project/apps/does-not-exist") + + expect(result).to be_empty + end + + it "uses the only entry for a single-package repo (no workspace_path)" do + single = <<~DOC + [ + { + "name": "solo", + "path": "/project", + "dependencies": { "lodash": { "version": "4.17.21" } }, + "devDependencies": {} + } + ] + DOC + allow(analyzer).to receive(:run_pnpm_list_depth0).and_return(single) + + result = analyzer.send(:add_all_libraries) + + expect(result["lodash"].current_version).to eq("4.17.21") + end + + it "returns empty on pnpm list failure" do + allow(analyzer).to receive(:run_pnpm_list_depth0).and_return(nil) + + expect(analyzer.send(:add_all_libraries, "/project")).to be_empty + end + end + describe "#calculate_version" do let(:analyzer) { LibraryVersionAnalysis::Pnpm.new("test") } From 86a20a9e58487ec365b4e27e7fd9c23123bdaace Mon Sep 17 00:00:00 2001 From: John Zittlau Date: Tue, 9 Jun 2026 14:01:40 -0600 Subject: [PATCH 2/3] Declare multi_json so the rspec suite loads in CI representable (pulled in via google-api-client) requires "multi_json" at runtime but does not declare it as a dependency, so bundler omitted it and `require "google/apis/sheets_v4"` failed with "multi_json is not part of the bundle", aborting spec_helper load. Add multi_json to the Gemfile. `bundle exec rspec` now passes (116 examples, 0 failures), including the new #add_all_libraries specs. (Gemfile.lock is gitignored for this gem; CI regenerates it from the Gemfile.) Co-Authored-By: Amplify 2.1.1 Co-Authored-By: Claude Opus 4.8 --- Gemfile | 5 +++++ .../changes/fix-pnpm-current-version-resolution/tasks.md | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 4083683..8aeadbf 100644 --- a/Gemfile +++ b/Gemfile @@ -7,4 +7,9 @@ gem "rspec", "~> 3.0" gem "graphql", "~> 2.4.8" gem "graphql-client", "~> 0.18" +# representable (pulled in via google-api-client) requires "multi_json" at +# runtime but does not declare it as a dependency, so bundler omits it and +# loading google/apis/sheets_v4 fails with "multi_json is not part of the bundle". +gem "multi_json" + plugin "bundler-why" diff --git a/openspec/changes/fix-pnpm-current-version-resolution/tasks.md b/openspec/changes/fix-pnpm-current-version-resolution/tasks.md index b0572ac..0fdd8c5 100644 --- a/openspec/changes/fix-pnpm-current-version-resolution/tasks.md +++ b/openspec/changes/fix-pnpm-current-version-resolution/tasks.md @@ -15,7 +15,7 @@ - [x] 3.1 Add a dedicated `#add_all_libraries` context in `spec/pnpm_spec.rb` (the method was stubbed everywhere) with fixtures based on the real JSON from task 1 - [x] 3.2 Cover: scoped + unscoped names resolve; per-workspace results differ; multi-version → range; `link:`/`workspace:` dep → skipped; single-package repo; pnpm-list failure → empty -- [x] 3.3 Verified the new logic end-to-end against the real `pnpm.rb` via an isolated harness reproducing every spec scenario (all green). NOTE: full `bundle exec rspec` is blocked by a pre-existing `multi_json`/`google-apis` lockfile gap unrelated to this change (spec_helper requires the whole library, which pulls `google/apis/sheets_v4`). Re-run the bundler suite once that env issue is resolved. +- [x] 3.3 `bundle exec rspec` passes (116 examples, 0 failures), including the new `#add_all_libraries` specs. Also fixed a pre-existing suite-load failure by declaring `multi_json` in the Gemfile (representable pulls it in via google-api-client but does not declare it, so bundler omitted it and `require "google/apis/sheets_v4"` failed). ## 4. Verify against jobber-frontend From d037cbd0b6441d61b3d1668c1bfafc0180929b85 Mon Sep 17 00:00:00 2001 From: John Zittlau Date: Tue, 9 Jun 2026 14:15:26 -0600 Subject: [PATCH 3/3] Archive fix-pnpm-current-version-resolution; promote capability spec Move the completed OpenSpec change to the archive and promote its delta into a living spec at openspec/specs/pnpm-version-analysis/spec.md (3 requirements). The deferred release task (5.1) remains as a follow-up. Co-Authored-By: Amplify 2.1.1 Co-Authored-By: Claude Opus 4.8 --- .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/pnpm-version-analysis/spec.md | 0 .../tasks.md | 0 openspec/specs/pnpm-version-analysis/spec.md | 57 +++++++++++++++++++ 6 files changed, 57 insertions(+) rename openspec/changes/{fix-pnpm-current-version-resolution => archive/2026-06-09-fix-pnpm-current-version-resolution}/.openspec.yaml (100%) rename openspec/changes/{fix-pnpm-current-version-resolution => archive/2026-06-09-fix-pnpm-current-version-resolution}/design.md (100%) rename openspec/changes/{fix-pnpm-current-version-resolution => archive/2026-06-09-fix-pnpm-current-version-resolution}/proposal.md (100%) rename openspec/changes/{fix-pnpm-current-version-resolution => archive/2026-06-09-fix-pnpm-current-version-resolution}/specs/pnpm-version-analysis/spec.md (100%) rename openspec/changes/{fix-pnpm-current-version-resolution => archive/2026-06-09-fix-pnpm-current-version-resolution}/tasks.md (100%) create mode 100644 openspec/specs/pnpm-version-analysis/spec.md diff --git a/openspec/changes/fix-pnpm-current-version-resolution/.openspec.yaml b/openspec/changes/archive/2026-06-09-fix-pnpm-current-version-resolution/.openspec.yaml similarity index 100% rename from openspec/changes/fix-pnpm-current-version-resolution/.openspec.yaml rename to openspec/changes/archive/2026-06-09-fix-pnpm-current-version-resolution/.openspec.yaml diff --git a/openspec/changes/fix-pnpm-current-version-resolution/design.md b/openspec/changes/archive/2026-06-09-fix-pnpm-current-version-resolution/design.md similarity index 100% rename from openspec/changes/fix-pnpm-current-version-resolution/design.md rename to openspec/changes/archive/2026-06-09-fix-pnpm-current-version-resolution/design.md diff --git a/openspec/changes/fix-pnpm-current-version-resolution/proposal.md b/openspec/changes/archive/2026-06-09-fix-pnpm-current-version-resolution/proposal.md similarity index 100% rename from openspec/changes/fix-pnpm-current-version-resolution/proposal.md rename to openspec/changes/archive/2026-06-09-fix-pnpm-current-version-resolution/proposal.md diff --git a/openspec/changes/fix-pnpm-current-version-resolution/specs/pnpm-version-analysis/spec.md b/openspec/changes/archive/2026-06-09-fix-pnpm-current-version-resolution/specs/pnpm-version-analysis/spec.md similarity index 100% rename from openspec/changes/fix-pnpm-current-version-resolution/specs/pnpm-version-analysis/spec.md rename to openspec/changes/archive/2026-06-09-fix-pnpm-current-version-resolution/specs/pnpm-version-analysis/spec.md diff --git a/openspec/changes/fix-pnpm-current-version-resolution/tasks.md b/openspec/changes/archive/2026-06-09-fix-pnpm-current-version-resolution/tasks.md similarity index 100% rename from openspec/changes/fix-pnpm-current-version-resolution/tasks.md rename to openspec/changes/archive/2026-06-09-fix-pnpm-current-version-resolution/tasks.md diff --git a/openspec/specs/pnpm-version-analysis/spec.md b/openspec/specs/pnpm-version-analysis/spec.md new file mode 100644 index 0000000..d6a1c77 --- /dev/null +++ b/openspec/specs/pnpm-version-analysis/spec.md @@ -0,0 +1,57 @@ +# pnpm-version-analysis Specification + +## Purpose +TBD - created by archiving change fix-pnpm-current-version-resolution. Update Purpose after archive. +## Requirements +### Requirement: Resolve current versions from structured pnpm output + +The system SHALL determine each dependency's installed ("current") version by reading the structured `version` field from `pnpm list`'s JSON output, and SHALL NOT depend on the rendered text/tree formatting of `pnpm list`. + +#### Scenario: Plain dependency version + +- **WHEN** a pnpm project has a dependency `@apollo/client` resolved to `3.13.8` +- **THEN** the resolved current version for `@apollo/client` SHALL be `3.13.8` + +#### Scenario: Scoped and unscoped names + +- **WHEN** the project includes both a scoped package (e.g. `@datadog/datadog-api-client`) and an unscoped package (e.g. `wrangler`) +- **THEN** the current version SHALL be resolved for both, keyed by their full package names + +#### Scenario: Output rendering does not affect results + +- **WHEN** pnpm is invoked in a non-interactive (non-TTY) environment such as CI +- **THEN** the resolved current versions SHALL be identical to those produced in an interactive environment for the same installed dependency tree + +### Requirement: Current versions reflect the analyzed workspace + +The system SHALL resolve current versions from the specific workspace being analyzed, using that workspace's `dependencies` and `devDependencies`, so that per-workspace results are distinct. + +#### Scenario: Per-workspace direct dependencies + +- **WHEN** workspace `apps/jobber-online` declares `storybook` and workspace `packages/core` does not +- **THEN** the current version for `storybook` SHALL be present in the `apps/jobber-online` results and absent from the `packages/core` results + +#### Scenario: Workspaces are not collapsed to the repository root + +- **WHEN** analyzing a multi-workspace pnpm monorepo +- **THEN** each workspace's resolved current-version set SHALL be derived from that workspace and SHALL NOT be uniformly replaced by the repository root's dependency set + +### Requirement: Preserve downstream version handling + +The system SHALL continue to merge resolved current versions with libyear data and SHALL preserve the existing representation of a current version, including the range form used when a dependency resolves to more than one version. + +#### Scenario: Merge with libyear-tracked library + +- **WHEN** a library appears in both the resolved current versions and the libyear report +- **THEN** the uploaded record SHALL carry the resolved current version alongside the libyear-provided latest version + +#### Scenario: Multiple resolved versions for one package + +- **WHEN** a package resolves to more than one version across the analyzed set (e.g. `4.54.0` and `4.76.0`) +- **THEN** the current version SHALL be expressed as a range (`4.54.0..4.76.0`) consistent with the existing version-combining behavior + +#### Scenario: Library with no resolvable current version + +- **WHEN** a tracked library has no resolvable installed version (e.g. a workspace `link:` dependency) +- **THEN** the system SHALL record an empty current version for that library without aborting analysis of the remaining libraries +