Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,4 @@ 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"
6 changes: 0 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,6 @@ To create the github token:
### Upload Key
The key used by LibraryTracking. See that project for the correct value.

### Google keys
deprecated

### Version Status spreadsheet
deprecated

### Slack token
deprecated

Expand Down
13 changes: 3 additions & 10 deletions exe/analyze
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,27 @@

require "library_version_analysis"

if ARGV.count == 1
spreadsheet_id = ARGV[0]
repository = "jobber-mobile"
source = "npm"
context = nil
elsif ARGV.count == 2
# this supports legacy calls
spreadsheet_id = ""
if ARGV.count == 2
repository = ARGV[0]
source = ARGV[1]
context = nil
elsif ARGV.count == 3
# pnpm workspace support: analyze <repository> pnpm <workspace>
spreadsheet_id = ""
repository = ARGV[0]
source = ARGV[1]
context = ARGV[2]
else
puts "Usage: analyze <repository> <source> [context]"
puts " Examples:"
puts " analyze my-repo npm # analyze npm packages"
puts " analyze my-repo gemfile # analyze ruby gems"
puts " analyze my-repo pnpm # analyze all pnpm workspaces"
puts " analyze my-repo pnpm packages/ui # analyze specific workspace"

Kernel.exit(1)
end

results = LibraryVersionAnalysis::CheckVersionStatus.run(
spreadsheet_id: spreadsheet_id,
repository: repository,
source: source,
context: context
Expand Down
4 changes: 1 addition & 3 deletions lib/library_version_analysis/analyze.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ module LibraryVersionAnalysis
class Analyze

def self.go(repository, source)
spreadsheet_id = ENV["VERSION_STATUS_SPREADSHEET_ID"]

results = LibraryVersionAnalysis::CheckVersionStatus.run(spreadsheet_id: spreadsheet_id, repository: repository, source: source)
results = LibraryVersionAnalysis::CheckVersionStatus.run(repository: repository, source: source)

merged_result = {}
results.keys.each { |key| merged_result.merge!(results[key]) }
Expand Down
146 changes: 23 additions & 123 deletions lib/library_version_analysis/check_version_status.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
require "googleauth"
require "google/apis/sheets_v4"
require "pry-byebug"
require "library_version_analysis/library_tracking"
require "library_version_analysis/configuration"
Expand Down Expand Up @@ -48,15 +46,15 @@ def self.dev_output?

class CheckVersionStatus
# TODO: joint - Need to change Jobbers https://github.com/GetJobber/Jobber/blob/dea12cebf8e6c65b2cafb5318bd42c1f3bf7d7a3/lib/code_analysis/code_analyzer/online_version_analysis.rb#L6 to run three times. One for each.
def self.run(spreadsheet_id: "", repository: "", source: "", context: nil)
def self.run(repository: "", source: "", context: nil)
# check for env vars before we do anything
keys = %w(WORD_LIST_RANDOM_SEED GITHUB_READ_API_TOKEN LIBRARY_UPLOAD_URL UPLOAD_KEY)
missing_keys = keys.reject { |key| !ENV[key].nil? && !ENV[key].empty? }

raise "Missing ENV vars: #{missing_keys}" if missing_keys.any?

c = CheckVersionStatus.new
mode_results = c.go(spreadsheet_id: spreadsheet_id, repository: repository, source: source, context: context)
mode_results = c.go(repository: repository, source: source, context: context)

mode_key = "#{repository}/#{source}"

Expand Down Expand Up @@ -99,27 +97,16 @@ def obfuscate(data)
return ":#{@word_list[idx]}" # note: the colon is required in the dependency graph obfuscation
end

def go(spreadsheet_id:, repository:, source:, context: nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength

if spreadsheet_id.nil? || spreadsheet_id.empty?
@update_server = true
@update_spreadsheet = false
variant = "lt"
else
@update_server = false
@update_spreadsheet = true
variant = "legacy"
end

puts "Check Version #{variant}" if LibraryVersionAnalysis.dev_output?
def go(repository:, source:, context: nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
puts "Check Version" if LibraryVersionAnalysis.dev_output?

case source
when "npm"
meta_data, mode = go_npm(spreadsheet_id, repository, source)
meta_data, mode = go_npm(repository, source)
when "gemfile"
meta_data, mode = go_gemfile(spreadsheet_id, repository, source)
meta_data, mode = go_gemfile(repository, source)
when "pnpm"
meta_data, mode = go_pnpm(spreadsheet_id, repository, context)
meta_data, mode = go_pnpm(repository, context)
else
puts "Don't recognize source #{source}"
exit(-1)
Expand All @@ -134,31 +121,25 @@ def go(spreadsheet_id:, repository:, source:, context: nil) # rubocop:disable Me
}
end

def go_gemfile(spreadsheet_id, repository, source)
def go_gemfile(repository, source)
puts " gemfile" if LibraryVersionAnalysis.dev_output?
gemfile = Gemfile.new(repository)

meta_data, mode = get_version_summary(gemfile, "OnlineVersionData!A:Q", spreadsheet_id, repository, source)
meta_data, mode = get_version_summary(gemfile, repository, source)

return meta_data, mode
end

def go_npm(spreadsheet_id, repository, source)
def go_npm(repository, source)
puts " npm" if LibraryVersionAnalysis.dev_output?
npm = Npm.new(repository)

if repository == "jobber" # ugly legacy hack
range = "OnlineNodeVersionData!A:Q"
else
range = "MobileVersionData!A:Q"
end

meta_data, mode = get_version_summary(npm, range, spreadsheet_id, repository, source)
meta_data, mode = get_version_summary(npm, repository, source)

return meta_data, mode
end

def go_pnpm(spreadsheet_id, repository, context = nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
def go_pnpm(repository, context = nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
puts " pnpm" if LibraryVersionAnalysis.dev_output?
pnpm = Pnpm.new(repository)

Expand Down Expand Up @@ -190,19 +171,11 @@ def go_pnpm(spreadsheet_id, repository, context = nil) # rubocop:disable Metrics

all_modes[workspace_name] = mode

if @update_spreadsheet
puts " updating spreadsheet #{workspace_name}" if LibraryVersionAnalysis.dev_output?
data = spreadsheet_data(parsed_results, repository, workspace_name)
update_spreadsheet(spreadsheet_id, "PnpmVersionData!A:Q", data)
end

if @update_server
puts " updating server for #{workspace_name}" if LibraryVersionAnalysis.dev_output?
# Use workspace_name as the source for pnpm workspaces
server_payload = server_data(parsed_results, repository, workspace_name)
log_server_payload(server_payload)
LibraryTracking.upload(server_payload.to_json)
end
puts " updating server for #{workspace_name}" if LibraryVersionAnalysis.dev_output?
# Use workspace_name as the source for pnpm workspaces
server_payload = server_data(parsed_results, repository, workspace_name)
log_server_payload(server_payload)
LibraryTracking.upload(server_payload.to_json)
end

puts "All Done! Uploaded #{results_by_workspace.keys.count} workspace(s)" if LibraryVersionAnalysis.dev_output?
Expand All @@ -211,23 +184,15 @@ def go_pnpm(spreadsheet_id, repository, context = nil) # rubocop:disable Metrics
return combined_meta_data, all_modes
end

def get_version_summary(parser, range, spreadsheet_id, repository, source)
def get_version_summary(parser, repository, source)
parsed_results, meta_data = parser.get_versions(source)
mode = get_mode_summary(parsed_results, meta_data)

if @update_spreadsheet
puts " updating spreadsheet #{source}" if LibraryVersionAnalysis.dev_output?
data = spreadsheet_data(parsed_results, repository, source)
update_spreadsheet(spreadsheet_id, range, data)
end

if @update_server
puts " updating server" if LibraryVersionAnalysis.dev_output?
server_payload = server_data(parsed_results, repository, source)
log_server_payload(server_payload)
data = server_payload.to_json
LibraryTracking.upload(data)
end
puts " updating server" if LibraryVersionAnalysis.dev_output?
server_payload = server_data(parsed_results, repository, source)
log_server_payload(server_payload)
data = server_payload.to_json
LibraryTracking.upload(data)

puts "All Done!" if LibraryVersionAnalysis.dev_output?

Expand Down Expand Up @@ -288,71 +253,6 @@ def obfuscate_dependency_graph(dependency_graph)
end
end

def spreadsheet_data(results, repository, source)
header_row = %w(name owner parent source current_version current_version_date latest_version latest_version_date major minor patch age cve note cve_label cve_severity note_lookup_key)
data = [header_row]

case source
when "npm"
if repository == "jobber" # ugly legacy hack
legacy_source= "ONLINE NODE"
else
legacy_source= "MOBILE"
end
when "gemfile"
legacy_source = "ONLINE"
when "pnpm", "root", /^apps\//, /^packages\//
legacy_source = source
else
legacy_source = "UNKNOWN"
end

data << ["Updated: #{Time.now.utc}"]

results.each do |name, row|
vuln = row.vulnerabilities.nil? ? nil:row.vulnerabilities.select { |v| v.state != "FIXED" }.first
if vuln.nil?
cvss = nil
else
cvss = "#{vuln.assigned_severity}#{vuln.identifier}"
end

data << [
name,
row.owner,
row.parent,
legacy_source,
row.current_version,
row.current_version_date,
row.latest_version,
row.latest_version_date,
row.major,
row.minor,
row.patch,
row.age,
cvss,
'=IFERROR(concatenate(vlookup(indirect("Q" & row()),Notes!A:E,4,false), ":", concatenate(vlookup(indirect("Q" & row()),Notes!A:E,5,false))))',
'=IFERROR(vlookup(indirect("Q" & row()),Notes!A:E,4,false), IFERROR(trim(LEFT(INDIRECT("Q" & row()), SEARCH("[", INDIRECT("M" & row()))-1))))',
'=IFERROR(vlookup(indirect("O" & row()),\'Lookup data\'!$A$2:$B$6,2,false))',
'=IF(ISBLANK(indirect("M" & row())), indirect("A" & row()), indirect("M" & row()))',
]
end

return data
end

def update_spreadsheet(spreadsheet_id, range_name, results)
service = Google::Apis::SheetsV4::SheetsService.new
service.authorization = ::Google::Auth::ServiceAccountCredentials.make_creds(scope: "https://www.googleapis.com/auth/spreadsheets")

clear_range = Google::Apis::SheetsV4::BatchClearValuesRequest.new
clear_range.ranges = [range_name]
service.batch_clear_values(spreadsheet_id, clear_range)

value_range_object = Google::Apis::SheetsV4::ValueRange.new(range: range_name, values: results)
service.update_spreadsheet_value(spreadsheet_id, range_name, value_range_object, value_input_option: "USER_ENTERED")
end

def get_mode_summary(results, meta_data) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
mode_summary = ModeSummary.new
mode_summary.one_major = 0
Expand Down
1 change: 1 addition & 0 deletions lib/library_version_analysis/gemfile.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
require "open3"
require "library_version_analysis/ownership"
require "library_version_analysis/configuration"
require "code_ownership"
Expand Down
2 changes: 2 additions & 0 deletions lib/library_version_analysis/github.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require "date"
require "time"
require "graphql/client"
require "graphql/client/http"
require "pry-byebug"
Expand Down
2 changes: 2 additions & 0 deletions lib/library_version_analysis/library_tracking.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
require "net/http"
require "uri"
require "zlib"

module LibraryVersionAnalysis
class LibraryTracking
Expand Down
2 changes: 2 additions & 0 deletions lib/library_version_analysis/npm.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require "open3"
require "json"
require "library_version_analysis/ownership"
require "library_version_analysis/configuration"

Expand Down
2 changes: 2 additions & 0 deletions lib/library_version_analysis/pnpm.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require "open3"
require "json"
require "library_version_analysis/ownership"
require "library_version_analysis/configuration"
require "pathname"
Expand Down
2 changes: 1 addition & 1 deletion lib/library_version_analysis/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module LibraryVersionAnalysis
VERSION = "1.4.7".freeze
VERSION = "2.0.0".freeze
end
2 changes: 0 additions & 2 deletions library_version_analysis.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ Gem::Specification.new do |spec|
spec.metadata["source_code_uri"] = "https://github.com/GetJobber/library_version_analysis"
spec.metadata["changelog_uri"] = "https://github.com/GetJobber/library_version_analysis/CHANGELOG.MD"

spec.add_dependency 'google-api-client'
spec.add_dependency "googleauth"
spec.add_dependency "graphql-client", "~> 0.20"
spec.add_dependency "libyear-bundler"
spec.add_dependency "open3"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-06-07
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# remove-gsheet-support

Remove Google Sheets support from the library_version_analysis gem
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
## Context

`CheckVersionStatus` currently supports two output modes selected at runtime by the presence of a `spreadsheet_id`:

- **legacy** (`spreadsheet_id` present): write rows to a Google Sheet via `update_spreadsheet()` / `spreadsheet_data()`, authenticating with a Google service account.
- **lt** (`spreadsheet_id` empty/nil): upload a JSON payload to the Library Tracking server.

Every analysis entrypoint (`go_npm`, `go_gemfile`, `go_pnpm`, `get_version_summary`) threads `spreadsheet_id` and a sheet range through, then branches on `@update_spreadsheet` / `@update_server`. The Google Sheet output is deprecated and superseded by Library Tracking.

## Goals / Non-Goals

**Goals:**
- Make Library Tracking the gem's single upload destination.
- Delete all Google Sheets code, dependencies, and the mode-selection branching.
- Simplify the public surface: `CheckVersionStatus.run()` and the CLI no longer accept `spreadsheet_id`.

**Non-Goals:**
- Changing the Library Tracking payload shape or upload behavior.
- Decommissioning the Google service account, the sheet itself, or untracked local credential scripts.
- Broader refactors beyond what removing the gsheet path requires (e.g. the remaining "ugly legacy hack" result-key mapping is left as-is).

## Decisions

- **Remove the parameter rather than ignore it (BREAKING).** The only known caller is Jobber, which we control and are updating under JOB-171738. A clean signature is preferable to carrying a permanently-ignored argument.
- **Always upload to Library Tracking.** The `@update_spreadsheet` / `@update_server` flags and the `variant` toggle are deleted; `get_version_summary` and `go_pnpm` call `LibraryTracking.upload` unconditionally.
- **Capture surviving behavior as a new spec.** The repo has no existing specs, so rather than writing a "removal delta" against nothing, we record the post-change contract as the `library-version-upload` capability.

## Risks / Trade-offs

- **Cross-repo ordering.** The gem cannot drop `spreadsheet_id` while Jobber still passes it. Mitigation: land the Jobber change (JOB-171738) first so it stops passing the argument, then release this gem change; or release them together. The OpenSpec changes are linked via the Jira "relates to" link.
- **Lost gsheet output.** Anyone still reading the Google Sheet loses updates. Accepted: the sheet is already deprecated and replaced by Library Tracking.
- **Stale env/credentials.** `VERSION_STATUS_SPREADSHEET_ID` and `GOOGLE_*` vars become unused; leaving them set is harmless but should be cleaned up out-of-band.
Loading
Loading