Conversation
pkg compiles node from source for targets without a cached binary,
and cross-compiling arm64 from an x64 host fails in openssl's
arm_arch.h ("unsupported ARM architecture"). Build each OS on its own
runner (ubuntu/macos/windows), then assemble all artifacts in a
release job and publish. Also bump pkg/node20 targets to node22 and
drop node 20 from the ci matrix — Actions deprecated the runner.
Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
The release workflow still risks cross-target builds and non-portable checksum generation, which can break the release pipeline on macOS/Windows and/or reintroduce the original cross-compilation failure.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR updates the GitHub Actions workflows to build release artifacts per OS runner (instead of attempting cross-target builds from a single runner) and updates packaging/CI Node versions to newer baselines.
Changes:
- Update
pkgtargets from Node 20 to Node 22. - Split release workflow into a per-OS build matrix that uploads artifacts, plus a final job that creates the GitHub Release from downloaded artifacts.
- Drop Node 20 from the CI test matrix.
File summaries
| File | Description |
|---|---|
package.json |
Bumps pkg build targets to Node 22. |
.github/workflows/release.yml |
Adds OS-matrix build job, artifact upload/download, and release assembly step. |
.github/workflows/ci.yml |
Removes Node 20 from the test matrix. |
Review details
Suppressed comments (1)
.github/workflows/release.yml:40
pnpm exec pkg . --out-path distwill use thepkg.targetslist from package.json, which can trigger cross-compilation and defeats the "native arch" intent of this job. Use the per-job target from the matrix (or otherwise constrain targets) so each runner only builds one native binary.
- name: build standalone binaries (native arch)
run: pnpm exec pkg . --out-path dist
- Files reviewed: 3/3 changed files
- Comments generated: 4
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| include: | ||
| - os: ubuntu-latest | ||
| - os: macos-latest | ||
| - os: windows-latest |
| - name: checksums | ||
| shell: bash | ||
| run: | | ||
| cd dist | ||
| for f in *; do sha256sum "$f" > "$f.sha256"; done | ||
|
|
| "targets": [ | ||
| "node20-linux-x64", | ||
| "node20-linux-arm64", | ||
| "node20-macos-x64", | ||
| "node20-macos-arm64", | ||
| "node20-win-x64" | ||
| "node22-linux-x64", | ||
| "node22-linux-arm64", | ||
| "node22-macos-x64", | ||
| "node22-macos-arm64", | ||
| "node22-win-x64" |
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| node: [20, 22, 24] | ||
| node: [22, 24] |
Previously the only way to bypass the 24h TTL was deleting problems.json by hand. `leetcode cache -r` drops the cached list (and its meta) and re-fetches immediately, printing how many questions came back. Co-Authored-By: Claude <noreply@anthropic.com>
`stat -c` previously rendered only this machine's lc-submit log (stat.json). Fetch matchedUser.userCalendar.submissionCalendar from graphql and fill the heatmap from the real per-day submission counts, falling back to the local log with a warning when the fetch fails. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
The release workflow and new CLI paths contain reliability issues (portable checksums, pkg target selection vs “native arch” intent, and unsafe calendar parsing/user access) that could break releases or crash the CLI.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (4)
test/commands/test_cache.js:91
- This test currently only asserts
process.exitCodeon refresh failure; if the intent is to preserve the existing cached problems list when refresh fails, add an assertion that the old cache remains available.
it('should fail loudly if refresh fails', function(done) {
cache.set('problems', [{fid: 1}]);
cmd.__set__('core', {getProblems: (t, cb) => cb('refresh error')});
cmd.handler({refresh: true, dontTranslate: true});
const text = out.join('\n');
assert.include(text, 'refresh error');
assert.equal(process.exitCode, 1);
done();
});
test/commands/test_stat.js:90
- Same as above: the timeout isn't needed for this failure-path test when
getCalendaris stubbed synchronously.
cmd.handler({lock: true, cal: true});
setTimeout(() => {
const text = stripAnsi(out.join('\n'));
assert.include(text, 'using local log');
done();
}, 10);
});
.github/workflows/release.yml:47
- The checksum step uses
sha256sum, which is not available by default on macOS runners (and can vary on Windows even under bash). Using Node'scryptomakes this portable across all runners already in the matrix.
- name: checksums
shell: bash
run: |
cd dist
for f in *; do sha256sum "$f" > "$f.sha256"; done
.github/workflows/release.yml:40
pnpm exec pkg . --out-path distwill build all targets listed inpackage.json'spkg.targets. That includes cross-arch targets (e.g. linux-arm64 from ubuntu x64, macos-x64 from an arm64 macOS runner), which contradicts the 'native arch only' comment and can reintroduce the OpenSSL cross-compile failure you’re trying to avoid. Consider explicitly setting--targetsper matrix entry (and/or adding native arm64/x64 runners) so each job builds only what its runner can build reliably.
# pkg compiles node from source for any target without a cached
# binary, which cannot be cross-compiled (arm64 from an x64 host
# fails in openssl's arm_arch.h). Building the native arch only
# avoids that: the host arch is inferred from the runner and pkg
# picks the matching node* target from the config.
- name: build standalone binaries (native arch)
run: pnpm exec pkg . --out-path dist
- Files reviewed: 11/11 changed files
- Comments generated: 8
- Review effort level: Lite
| opts.body = { | ||
| query: 'query cal($username: String!) { matchedUser(username: $username) { userCalendar { submissionCalendar } } }', | ||
| variables: {username: session.getUser().name} | ||
| }; |
| const cal = body.data.matchedUser.userCalendar; | ||
| if (!cal) return cb('failed to load submission calendar!'); | ||
| try { |
| // server-side submission calendar: {unixDay: submissionCount} | ||
| plugin.getCalendar = function(cb) { | ||
| log.debug('running leetcode.getCalendar'); |
| if (argv.refresh) { | ||
| // drop the problems cache so the next fetch is a fresh one | ||
| cache.del(h.KEYS.problems); | ||
| cache.del(h.KEYS.problemsMeta); | ||
| core.getProblems(!argv.dontTranslate, function(e, problems) { | ||
| if (e) return log.fail(e); | ||
| log.info('Problems list refreshed (' + problems.length + ' questions).'); | ||
| }); | ||
| return; | ||
| } |
| const NEW = [{fid: 1, name: 'new'}, {fid: 2, name: 'two'}]; | ||
| cmd.__set__('core', {getProblems: (t, cb) => cb(null, NEW)}); | ||
|
|
||
| cmd.handler({refresh: true, dontTranslate: true}); | ||
|
|
||
| const text = out.join('\n'); | ||
| assert.include(text, 'refreshed (2 questions)'); | ||
| // the problems cache was dropped so the next fetch re-pulls | ||
| assert.equal(cache.get('problems'), null); | ||
| assert.equal(cache.get('problemsMeta'), null); | ||
| done(); | ||
| }); |
| // graph[i] = submission count i days ago; prefers the server calendar | ||
| // and falls back to the local lc-submit log | ||
| const graph = []; |
| cmd.handler({lock: true, cal: true}); | ||
| // async getCalendar -> handler prints inside callback | ||
| setTimeout(() => { | ||
| const text = stripAnsi(out.join('\n')); | ||
| assert.include(text, 'Sun'); | ||
| assert.include(text, 'Mon'); | ||
| assert.match(text, /▣/, 'submission cells rendered'); | ||
| done(); | ||
| }, 10); | ||
| }); |
| uses: actions/download-artifact@v4 | ||
| with: | ||
| path: dist-all | ||
| merge-multiple: true | ||
|
|
Drop .travis.yml and Dockerfile (CI moved to GitHub Actions), the old bin/pkg* packaging scripts and bin/install (replaced by pkg + release workflow), a stray 1.js debug file, and the stale npm package-lock.json (project uses pnpm). Update .npmignore for the renamed eslint config and fix the issue template to reference pnpm and the main branch.
Delete the company plugin (outdated 2017 hardcoded company/tag mappings) and solution.discuss (its --solution command was removed, leaving getSolution without callers). Drop the win7/ascii icon themes and make icon fallback to 'default' on all platforms. Update plugin/list examples and the affected tests.
langToCommentStyle('all') crashed with 'Cannot read properties of
undefined (reading style)' when submission ran without -l (default
'all' is not a real lang). Return an empty style so the export
degrades gracefully instead of throwing.
leetcode.com's getProblemOfToday queries activeDailyCodingChallengeQuestion which returns http 400 on leetcode.cn. Override it in the cn plugin with the todayRecord query and add a dedicated test file.
Drop the github/linkedin login options from user command, the githubLogin/linkedinLogin implementations, their config URLs, and the cache plugin wrappers. The HTML-scraping flows are long broken against current github/linkedin pages and unused; cookie login remains the supported path.
getUser looked up a list literally named 'Favorite', which leetcode.cn never provides (users name lists freely), so user.hash stayed undefined and starProblem sent favoriteIdHash: undefined -> graphql 400. Fall back to the first private favorite when 'Favorite' is absent; leetcode.com behavior is unchanged.
Remove the githubLogin/linkedinLogin methods and the now-orphaned requestLeetcodeAndSave helper. HTML form scraping of github/linkedin login pages is long broken; cookie login is the supported path.
file.meta split the @lc comment line on spaces, so 'id=LCR 064' yielded id='LCR' and dropped the numeric part, breaking test/push for all LCP/LCR/LCS problems. Append following bare tokens (until the next key=value) to a value, keeping 'LCR 064' intact while plain numeric ids and tab-separated pairs still parse.
There was a problem hiding this comment.
🟡 Changes recommended
The updated release workflow has concrete correctness/portability issues (pkg target selection vs multi-target config, and non-portable checksum tooling) that can break releases/CI.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (4)
.github/workflows/release.yml:40
- The build job runs
pnpm exec pkg .without--targets, butpackage.jsondefines a multi-targetpkg.targetslist (linux x64+arm64, macOS x64+arm64, win x64).pkgwill attempt to build every configured target on each runner, reintroducing cross-arch builds (e.g. linux-arm64 on ubuntu-latest x64) despite the “native arch only” comment, and can fail when a target requires a from-source Node build.
# pkg compiles node from source for any target without a cached
# binary, which cannot be cross-compiled (arm64 from an x64 host
# fails in openssl's arm_arch.h). Building the native arch only
# avoids that: the host arch is inferred from the runner and pkg
# picks the matching node* target from the config.
- name: build standalone binaries (native arch)
run: pnpm exec pkg . --out-path dist
.github/workflows/release.yml:47
- The checksum step uses
sha256sum, which is available on Ubuntu but typically not present on macOS runners (and can vary on Windows even under bash). This can cause the build matrix to fail before artifacts are uploaded.
- name: checksums
shell: bash
run: |
cd dist
for f in *; do sha256sum "$f" > "$f.sha256"; done
lib/plugins/leetcode.js:496
getCalendarassumessession.getUser().nameis always present and thatbody.data.matchedUser.userCalendarexists. If the username wasn't persisted (e.g.getUserInfofailed) or GraphQL returns{errors: ...}/ an unexpected body shape, this will throw a TypeError instead of returning a helpful error to the caller.
const opts = plugin.makeOpts(config.sys.urls.graphql);
opts.headers.Origin = config.sys.urls.base;
opts.headers.Referer = config.sys.urls.base;
opts.json = true;
opts.body = {
query: 'query cal($username: String!) { matchedUser(username: $username) { userCalendar { submissionCalendar } } }',
variables: {username: session.getUser().name}
};
const spin = h.spin('Retrieving submission calendar');
request.post(opts, function(e, resp, body) {
spin.stop();
e = plugin.checkError(e, resp, 200);
if (e) return cb(e);
const cal = body.data.matchedUser.userCalendar;
if (!cal) return cb('failed to load submission calendar!');
try {
test/commands/test_stat.js:90
- These tests wait on a fixed
setTimeout(..., 10)even though the stubbedgetCalendarcallback is invoked synchronously; this adds unnecessary delay and can introduce flakiness on slow CI. Prefer asserting immediately aftercmd.handler(...)(or usesetImmediate/process.nextTickif you want to model async).
- Files reviewed: 35/41 changed files
- Comments generated: 1
- Review effort level: Lite
|
|
||
| - run: pnpm install --frozen-lockfile | ||
|
|
||
| - name: build standalone binaries | ||
| # pkg compiles node from source for any target without a cached | ||
| # binary, which cannot be cross-compiled (arm64 from an x64 host | ||
| # fails in openssl's arm_arch.h). Building the native arch only | ||
| # avoids that: the host arch is inferred from the runner and pkg | ||
| # picks the matching node* target from the config. | ||
| - name: build standalone binaries (native arch) |
Fix the first release run: pkg tried to build linux-arm64 from an x64 runner and cross-compiling failed in openssl. Now builds each OS natively (ubuntu/macos/windows), uploads artifacts, and a release job assembles them into one GitHub Release. Also bumps pkg targets to node22 and drops node20 from the ci matrix (runner deprecated). Validated with actionlint.