From 0ed4a6f33cd91126736cd1c480ede71d7f7a0571 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Tue, 10 Feb 2026 19:24:29 -0800 Subject: [PATCH] Replace inline Python with Node for lockfile patching Add flatpak/fix-lockfile.mjs (vendored from npm-package-lock-add-resolved, MIT) to add missing resolved/integrity fields to package-lock.json. Use it in generate-sources.sh, and use a simpler inline Node one-liner in the manifest for the offline build-time patch. --- flatpak/app.yaak.Yaak.yml | 18 +++++--- flatpak/fix-lockfile.mjs | 73 +++++++++++++++++++++++++++++++ flatpak/generate-sources.sh | 85 +++++++++++-------------------------- 3 files changed, 108 insertions(+), 68 deletions(-) create mode 100644 flatpak/fix-lockfile.mjs diff --git a/flatpak/app.yaak.Yaak.yml b/flatpak/app.yaak.Yaak.yml index b0797566..d5f6119c 100644 --- a/flatpak/app.yaak.Yaak.yml +++ b/flatpak/app.yaak.Yaak.yml @@ -61,14 +61,18 @@ modules: - install -Dm755 protoc-bin/protoc crates-tauri/yaak-app/vendored/protoc/yaakprotoc - mkdir -p crates-tauri/yaak-app/vendored/protoc/include && cp -r protoc-bin/google crates-tauri/yaak-app/vendored/protoc/include/google - # Patch lockfile: add resolved URLs for nested workspace deps + # Patch lockfile: add resolved URLs for nested workspace deps that npm + # omits (see https://github.com/npm/cli/issues/4460) - >- - python3 -c "import json; - p='package-lock.json';f=open(p);d=json.load(f);f.close(); - [info.update({'resolved':'https://registry.npmjs.org/'+n.split('/node_modules/')[-1]+'/-/'+n.split('/')[-1]+'-'+info['version']+'.tgz'}) - for n,info in d.get('packages',{}).items() - if '/node_modules/' in n and 'resolved' not in info and not info.get('link') and info.get('version')]; - f=open(p,'w');json.dump(d,f);f.close()" + node -e "const fs=require('fs'); + const p='package-lock.json'; + const d=JSON.parse(fs.readFileSync(p,'utf-8')); + for(const[n,info]of Object.entries(d.packages||{})){ + if(!n||info.link||info.resolved||!n.includes('node_modules/')||!info.version)continue; + const pkg=n.split('node_modules/').pop(); + const base=pkg.split('/').pop(); + info.resolved='https://registry.npmjs.org/'+pkg+'/-/'+base+'-'+info.version+'.tgz'; + }fs.writeFileSync(p,JSON.stringify(d,null,2));" # Install npm dependencies offline - npm ci --offline diff --git a/flatpak/fix-lockfile.mjs b/flatpak/fix-lockfile.mjs new file mode 100644 index 00000000..aa3826de --- /dev/null +++ b/flatpak/fix-lockfile.mjs @@ -0,0 +1,73 @@ +#!/usr/bin/env node + +// Adds missing `resolved` and `integrity` fields to npm package-lock.json. +// +// npm sometimes omits these fields for nested dependencies inside workspace +// packages. This breaks offline installs and tools like flatpak-node-generator +// that need explicit tarball URLs for every package. +// +// Based on https://github.com/grant-dennison/npm-package-lock-add-resolved +// (MIT License, Copyright (c) 2024 Grant Dennison) + +import { readFile, writeFile } from "node:fs/promises"; +import { get } from "node:https"; + +const lockfilePath = process.argv[2] || "package-lock.json"; + +function fetchJson(url) { + return new Promise((resolve, reject) => { + get(url, (res) => { + let data = ""; + res.on("data", (chunk) => { + data += chunk; + }); + res.on("end", () => { + if (res.statusCode === 200) { + resolve(JSON.parse(data)); + } else { + reject(`${url} returned ${res.statusCode} ${res.statusMessage}`); + } + }); + res.on("error", reject); + }).on("error", reject); + }); +} + +async function fillResolved(name, p) { + const version = p.version.replace(/^.*@/, ""); + console.log(`Retrieving metadata for ${name}@${version}`); + const metadataUrl = `https://registry.npmjs.com/${name}/${version}`; + const metadata = await fetchJson(metadataUrl); + p.resolved = metadata.dist.tarball; + p.integrity = metadata.dist.integrity; +} + +let changesMade = false; + +async function fillAllResolved(packages) { + for (const packagePath in packages) { + if (packagePath === "") continue; + const p = packages[packagePath]; + if (!p.inBundle && !p.bundled && (!p.resolved || !p.integrity)) { + const packageName = + p.name || + /^npm:(.+?)@.+$/.exec(p.version)?.[1] || + packagePath.replace(/^.*node_modules\/(?=.+?$)/, ""); + await fillResolved(packageName, p); + changesMade = true; + } + } +} + +const oldContents = await readFile(lockfilePath, "utf-8"); +const packageLock = JSON.parse(oldContents); + +await fillAllResolved(packageLock.packages ?? []); + +if (changesMade) { + const newContents = JSON.stringify(packageLock, null, 2) + "\n"; + await writeFile(lockfilePath, newContents); + console.log(`Updated ${lockfilePath}`); +} else { + console.log("No changes needed."); +} diff --git a/flatpak/generate-sources.sh b/flatpak/generate-sources.sh index 07686009..e8fc8d70 100755 --- a/flatpak/generate-sources.sh +++ b/flatpak/generate-sources.sh @@ -44,72 +44,35 @@ echo " Done: flatpak/cargo-sources.json" echo "Generating node-sources.json..." -# flatpak-node-generator doesn't handle npm workspace packages (local paths -# without "resolved" or "link" fields). Strip them from a temp copy of the -# lockfile before running the generator. +# npm sometimes omits `resolved` and `integrity` fields from the lockfile for +# nested dependencies inside workspace packages. flatpak-node-generator needs +# these fields to know which tarballs to download. We fix the lockfile in a temp +# copy before running the generator. +# +# We also strip workspace link entries (no download needed for local packages). TMPDIR=$(mktemp -d) trap 'rm -rf "$TMPDIR"' EXIT -python3 -c " -import json, sys, shutil -with open(sys.argv[1]) as f: - data = json.load(f) -packages = data.get('packages', {}) -to_remove = [] -needs_resolve = [] -for name, info in packages.items(): - if not name: - continue - if info.get('link'): - continue - if 'resolved' in info: - continue - # No 'resolved' and not a link — local workspace package or nested dep. - if '/node_modules/' in name: - # Nested node_modules entry inside a workspace package — needs - # resolved URL and integrity for flatpak-node-generator. - needs_resolve.append((name, info)) - else: - to_remove.append(name) +cp "$REPO_ROOT/package-lock.json" "$TMPDIR/package-lock.json" +cp "$REPO_ROOT/package.json" "$TMPDIR/package.json" -# Fetch missing resolved/integrity from the npm registry -import urllib.request -_packument_cache = {} -for name, info in needs_resolve: - pkg = name.split('/node_modules/')[-1] - version = info.get('version', '') - if not version: - to_remove.append(name) - continue - if pkg not in _packument_cache: - url = f'https://registry.npmjs.org/{pkg}' - try: - req = urllib.request.Request(url, headers={'Accept': 'application/json'}) - with urllib.request.urlopen(req) as resp: - _packument_cache[pkg] = json.loads(resp.read()) - except Exception as e: - print(f'Warning: failed to fetch {url}: {e}', file=sys.stderr) - to_remove.append(name) - continue - packument = _packument_cache[pkg] - ver_info = packument.get('versions', {}).get(version, {}) - dist = ver_info.get('dist', {}) - if dist.get('tarball') and dist.get('integrity'): - info['resolved'] = dist['tarball'] - info['integrity'] = dist['integrity'] - print(f'Resolved {pkg}@{version}', file=sys.stderr) - else: - print(f'Warning: no dist info for {pkg}@{version}, removing', file=sys.stderr) - to_remove.append(name) +# Add missing resolved/integrity fields +node "$SCRIPT_DIR/fix-lockfile.mjs" "$TMPDIR/package-lock.json" -for name in to_remove: - del packages[name] -with open(sys.argv[2], 'w') as f: - json.dump(data, f, indent=2) -# Copy package.json so the generator can read the root entry -shutil.copy2(sys.argv[3], sys.argv[4]) -print(f'Stripped {len(to_remove)} local workspace packages from lockfile', file=sys.stderr) -" "$REPO_ROOT/package-lock.json" "$TMPDIR/package-lock.json" "$REPO_ROOT/package.json" "$TMPDIR/package.json" +# Strip workspace link entries (flatpak-node-generator doesn't handle them) +node -e " + const fs = require('fs'); + const p = process.argv[1]; + const data = JSON.parse(fs.readFileSync(p, 'utf-8')); + const packages = data.packages || {}; + for (const [name, info] of Object.entries(packages)) { + if (!name) continue; + if (info.link || (!info.resolved && !name.includes('node_modules/'))) { + delete packages[name]; + } + } + fs.writeFileSync(p, JSON.stringify(data, null, 2)); +" "$TMPDIR/package-lock.json" flatpak-node-generator --no-requests-cache -o "$SCRIPT_DIR/node-sources.json" npm "$TMPDIR/package-lock.json" echo " Done: flatpak/node-sources.json"