Skip to content

lc dev: backlog fixes, dep modernization, CI + release - #3

Merged
dp9u0 merged 27 commits into
mainfrom
dev
Aug 31, 2026
Merged

lc dev: backlog fixes, dep modernization, CI + release#3
dp9u0 merged 27 commits into
mainfrom
dev

Conversation

@dp9u0

@dp9u0 dp9u0 commented Aug 30, 2026

Copy link
Copy Markdown
Owner

Summary

Batch of work accumulated on dev since the last merge to main:

Bug fixes (from upstream backlog triage + local incidents)

  • problems.json cache: 24h TTL + purge on every login method (stale-cache mass-desync incident)
  • checkError: cloudflare 403/429 no longer misreported as session expired
  • parseCookie crash on invalid cookie; log.js toString crash on null args
  • getUserInfo -> userStatus query (fixes premium/locked + "login as undefined")
  • stat -c ERR_OUT_OF_RANGE on modern node; -v/-vv silently exiting (yargs argv snapshot)
  • output columns sized to data (4-digit ids, long names)

Features

  • lc show -d daily challenge; official hints in show + generated files
  • problem URL accepted as keyword (show/test/submit)
  • tag filtering actually works now (-t stack), fetched via graphql
  • plain-text problem descriptions in lc show
  • non-zero exit codes on failures; solved markers in lc cache

Dependency modernization

  • dropped: sqlite3 (unused), lodash, wordwrap, mkdirp, prompt (-> readline helper)
  • request (deprecated) -> axios-backed adapter (lib/http.js)
  • eslint 5 -> 10 (flat config), mocha 11, nock 14, cheerio 1.2, underscore 1.13.8
  • audit: 55 findings (2 critical) -> 10, none on the runtime path

Engineering

  • security: user.json chmod 0600 (holds a live session cookie)
  • CI: lint+test on node 20/22/24; release workflow builds @yao-pkg/pkg binaries + checksums and publishes a GitHub Release on merge to main
  • test suite: 171 -> 185 (new command-layer coverage)

Merging this to main will trigger the first binary release (v3.0.0 tag).

🤖 Generated with Claude Code

dp9u0 and others added 27 commits August 29, 2026 18:49
The problems cache embeds per-user solve state and was trusted forever,
so a months-old snapshot reads as mass "not solved" desync. Refresh it
when a problemsMeta marker is missing or older than 24h; local
updateProblem patches do not renew the TTL. On refresh failure serve
the stale list with a warning instead of failing outright.

Co-Authored-By: Claude <noreply@anthropic.com>
Only password login (plugin.login) went through the cache plugin's
purge hook; cookie/github/linkedin logins resolved straight to the
leetcode plugin via the prototype chain, leaving the previous
account's problems.json behind. Wrap all four entry points with the
same purge. retry.relogin is left alone on purpose: it re-authenticates
the same saved user, so the identity cannot change.

Use module-level `plugin` refs instead of `this` since commands/user.js
invokes github/linkedin login detached from the plugin object.

Co-Authored-By: Claude <noreply@anthropic.com>
Fixed-width columns broke as content grew past the assumed width:
stat bars went ragged once the ac/total counts hit 4 digits, list's
60-col name field overflows for 61 titles (up to 79 chars, shifting
the level column), and cache's size/created columns shift on cache
names longer than 60. Derive the widths from the data being printed
instead of hardcoding them.

Known residual: titles containing zero-width/narrow unicode chars
(e.g. U+200C, U+2011) still drift 1-2 cols because sprintf's len()
counts every non-ascii char as 2 columns; fixing that needs a proper
east-asian-width table.

Co-Authored-By: Claude <noreply@anthropic.com>
checkError mapped every 401/403 to "session expired, please login
again", but leetcode now signals auth failures with 401 (REST) or
200 + empty user fields (GraphQL / problem list). A 403/429 is
cloudflare blocking or rate limiting the client with a perfectly
valid session, so report it as BLOCKED instead.

This also stops the autologin machinery from firing doomed relogins
on transient 403s: retry only engages on the EXPIRED object.

Co-Authored-By: Claude <noreply@anthropic.com>
Fixes the "Cannot read property 'toString' of undefined" crash when a
caller passes a missing value to any log method.

Co-Authored-By: Claude <noreply@anthropic.com>
parseCookie took (cookie, body, cb) while cookieLogin called it with
(cookie, cb), so on an invalid cookie the error callback landed on the
unused `body` param and the caller crashed dereferencing the undefined
return value instead of showing "invalid cookie?". Make parseCookie a
pure function returning null on failure and have both call sites check.

Co-Authored-By: Claude <noreply@anthropic.com>
The `user { isCurrentUserPremium }` query returns http 400 on
leetcode.cn, so user.paid was never set there (locked problems then
fail to load even for premium users, upstream skygragon#228/skygragon#207). Query
`userStatus { username isPremium }` instead, which works on both
sites. On fetch failure keep the (still valid) login but warn instead
of silently saving a user without name, and fall back to the login
label in the success message instead of printing "login as undefined".

Also pins the login tests' user-info call to a nock instead of relying
on a real network request happening to fail.

Co-Authored-By: Claude <noreply@anthropic.com>
Cookie/third-party logins store no password, so an EXPIRED-triggered
relogin ran the dead password form flow twice per request and only
produced "invalid password?" noise. Warn and skip instead, pointing
the user at a manual re-login.

Co-Authored-By: Claude <noreply@anthropic.com>
The month header position is fractional whenever the month's day-1 is
not a whole number of weeks away, and modern node throws
ERR_OUT_OF_RANGE on the non-integer Buffer.write offset instead of
silently coercing it. `leetcode stat -c` crashed on any recent node.

Co-Authored-By: Claude <noreply@anthropic.com>
Ported from leetcode-tools#60 with the graphql query replaced: the
PR's currentDailyCodingChallenge field no longer exists on
leetcode.com, activeDailyCodingChallengeQuestion does.

Co-Authored-By: Claude <noreply@anthropic.com>
Fetch the `hints` field on question detail and print it under the
description (plain terminal lines instead of the upstream PR's raw
<details> HTML), and emit a Hint section in the detailed template.
Per-problem caches saved before this change lack the field, so extend
the existing staleness check to treat missing hints as too old.

Co-Authored-By: Claude <noreply@anthropic.com>
Extract the slug from urls like https://leetcode.com/problems/two-sum/
before the usual lookup, so show/test/submit all accept pasted links.
Also default hints to an empty array in exportProblem so the detailed
template renders for problems (and older caches) without hints.

Co-Authored-By: Claude <noreply@anthropic.com>
Removals (all verified unreferenced or trivially replaced):
- sqlite3: zero require sites, 15 audit findings, native build cost
- lodash: only startCase was used; moved to helper.startCase
- wordwrap: abandoned; replaced with a local greedy wrap in core.js
  (exportProblem's detailed tests pin the exact output)
- mkdirp: superseded by fs.mkdirSync(recursive)
- pkg: vercel pkg is deprecated and pkg4 cannot target node >= 20;
  single-binary builds belong to @yao-pkg/pkg if wanted later

Bumps: underscore 1.13.8 (fixes a prototype/recursion advisory),
cheerio 1.2.0, nconf 0.13.0, mocha 11, nock 14, nyc 18, chai 6,
rewire 9.

Also fixes a test isolation bug exposed by the mocha bump:
test_file.js set process.env.HOME/USERPROFILE without restoring them,
poisoning every suite loaded afterwards (config.init then tried to
write into /home/skygragon). With that fixed the full suite passes
for the first time: 167 passing, 0 failing.

Audit: 55 findings (2 critical) -> 18 (1 critical, all in the
eslint/request chains slated for a separate upgrade).

Co-Authored-By: Claude <noreply@anthropic.com>
prompt (and its winston dependency chain) was the source of the
padLevels circular-dependency warning on modern node. helper.readInput
covers the four call sites (login, cookie, third-party, github 2FA)
with secret muting for hidden fields.

Co-Authored-By: Claude <noreply@anthropic.com>
Replace the archived eslint-config-google bridge with a flat config
generated from the old stack's effective ruleset (eslint --print-config
on eslint 5, active rules only). Most google style rules were already
off in practice, so the 59-rule inline set reproduces lint behavior
exactly: 0 errors, same warning classes.

Co-Authored-By: Claude <noreply@anthropic.com>
yargs snapshots process.argv at require time and exits silently when
the leading option is unknown, so `leetcode -v <cmd>` / `-vv <cmd>`
produced no output at all (the flags predate this branch's work — a
latent yargs 12 -> 17 migration gap). initLogLevel consumes them
first; they are now removed from argv before yargs is required.

Co-Authored-By: Claude <noreply@anthropic.com>
request has been unmaintained since 2020 and accounts for the last
runtime audit findings (including a critical via form-data). lib/http
reimplements the request call surface the codebase relies on —
cb(e, resp, body), resp.statusCode / resp.request.uri.href, opts.json
/ opts.form, defaults({jar}) for the github/linkedin login flows,
response streaming for plugin downloads and a -vv trace hook — with
redirects followed manually so cookies propagate across hops and
error statuses stay checkError's job. All 167 tests pass unchanged
against the adapter and live commands hit leetcode.com successfully.

Co-Authored-By: Claude <noreply@anthropic.com>
The description was printed as raw HTML (lists, code tags, entities).
Decode it for the terminal the same way exportProblem does for
generated files, leaving the cached raw HTML untouched.

Co-Authored-By: Claude <noreply@anthropic.com>
The REST problem list carries no tags, so `-t stack` and friends
silently matched nothing. Fetch the whole tag map from the paged
problemsetQuestionListV2 graphql endpoint after the category list and
merge it into the problems before caching. A tag-fetch failure keeps
the list usable with a warning. problemsMeta gains a version field so
existing caches refresh once and pick tags up.

Co-Authored-By: Claude <noreply@anthropic.com>
log.fail, failing tests, rejected submissions and failed downloads now
set process.exitCode = 1 (instead of always exiting 0), so scripts can
detect failures; batch commands still run to completion. Also print a
blank line after the test command's stdout block.

Co-Authored-By: Claude <noreply@anthropic.com>
`leetcode cache` now cross-references the problems list and shows a
check mark on per-problem caches whose problem is solved. The
solution.discuss plugin follows leetcode's move from /discuss/ to
/solutions/ for community solution pages.

Co-Authored-By: Claude <noreply@anthropic.com>
The emitWarning filter in bin/leetcode silenced prompt's winston
circular-dependency warning; prompt is gone now. Plugin dependency
installation shells out to pnpm, matching this checkout and avoiding a
hard npm dependency.

Co-Authored-By: Claude <noreply@anthropic.com>
- chmod 0600 on user.json: it holds a live session cookie and was
  world-readable (existing files fixed too, via saveUser path)
- drop the dead "pkg" config block and the unused solved variable in
  the cache listing; stop linting the vendored company tag list
- remove the stale skygragon-era docs/ folder
- password login now reports that leetcode.com rejects it and points
  at cookie login instead of a misleading "invalid password?"; the
  session-expired message names the actual remedy (leetcode user -c)
- mechanical lint cleanup: unused catch bindings/imports, a missing
  semicolon, .parse() instead of the bare .argv expression; warnings
  drop from 31 to 17, all remaining ones are deliberate

Co-Authored-By: Claude <noreply@anthropic.com>
Node 20/22/24 matrix, pnpm with frozen lockfile, running the same
`pnpm test` entry point locally used (lint + mocha across lib, command
and plugin suites).

Co-Authored-By: Claude <noreply@anthropic.com>
New test/commands suite (list, stat, show, user, cache, submission)
exercising handlers with stubbed core + temp-dir caches: filter
semantics and the -s summary, stat bars and graph legend, plain-text
desc/hints rendering, code generation, the solved marker in cache
listings, submission download + error exit codes. Assertions strip
ANSI so colored output stays testable. `pnpm test` now runs lib,
commands and plugin suites.

Co-Authored-By: Claude <noreply@anthropic.com>
A push to main builds @yao-pkg/pkg binaries (linux/mac x64+arm64,
win x64), attaches sha256 checksums and publishes a GitHub Release
tagged from the package version; re-pushing without a version bump is
a no-op. pkg config restored with arm64 targets added; the local
macos-arm64 build was verified end to end (version/stat/show all work
from the snapshot). ci.yml now follows the main-based flow.

Co-Authored-By: Claude <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 30, 2026 14:37

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

It introduces at least one confirmed runtime-breaking bug in the new HTTP shim integration (and other issues) that should be fixed before merge.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR is a large “dev backlog” merge that modernizes the project’s dependencies and CI/release pipeline while adding several LeetCode feature fixes (daily challenge, tags, hints, cache TTL/purging) and replacing the deprecated request dependency with an axios-backed adapter.

Changes:

  • Replaced request (and other legacy deps) with an axios-backed HTTP shim and updated plugins/commands accordingly.
  • Added/extended CLI functionality and resiliency (daily challenge, hints/tag support, better cache semantics, non-zero exit codes on failures).
  • Modernized engineering workflow (ESLint flat config, expanded unit tests, CI matrix, automated release workflow + pkg targets).
File summaries
File Description
test/test_plugin.js Updates expected install command string for plugin deps.
test/test_log.js Adds coverage for null/undefined log args and exit code behavior.
test/test_file.js Prevents env var leakage across test suites.
test/test_core.js Adds tests for URL keywords and daily challenge resolution.
test/plugins/test_retry.js Adds coverage for skipping relogin without saved password.
test/plugins/test_leetcode.js Adds extensive coverage for cookie login, error mapping, tags, hints, daily challenge.
test/plugins/test_cache.js Adds coverage for problems cache TTL/meta + purge-on-login behavior.
test/mock/find-the-difference.json.20171216 Updates fixture to include hints and normalized JSON formatting.
test/commands/test_user.js Adds command-layer tests for user output/logout/error exit codes.
test/commands/test_submission.js Adds command-layer tests for submission download and error exit codes.
test/commands/test_stat.js Adds command-layer tests for progress/graph rendering.
test/commands/test_show.js Adds command-layer tests for plain-text rendering + hints + codegen.
test/commands/test_list.js Adds command-layer tests for listing/filtering and warnings.
test/commands/test_cache.js Adds command-layer tests for solved markers and deletion.
templates/detailed.tpl Emits hints into generated detailed templates.
package.json Updates dependencies/devDependencies and expands test script paths; pkg targets/output.
lib/session.js Adds BLOCKED error and chmod hardening for persisted session cookie file.
lib/plugins/solution.discuss.js Switches HTTP client to new adapter and updates solution URL path.
lib/plugins/retry.js Avoids burning retries when no saved password is available.
lib/plugins/leetcode.js Migrates off request, adds tags fetch, daily challenge, hints, improved error mapping, cookie parsing changes.
lib/plugins/leetcode.cn.js Migrates off request to new adapter.
lib/plugins/cache.js Adds problems TTL/meta versioning, stale fallback, and purges caches on all login methods.
lib/plugin.js Uses new HTTP adapter; changes plugin dep install command; refactors download/copy logic.
lib/log.js Sets process.exitCode on failures; makes logging tolerant of null/undefined args.
lib/http.js Adds axios-backed drop-in request shim with redirect and jar support.
lib/helper.js Adds problemsMeta key, startCase helper, and readInput prompt replacement.
lib/file.js Replaces mkdirp with fs.mkdirSync(..., {recursive:true}).
lib/core.js Accepts full problem URLs as keywords; adds daily challenge flow; replaces wordwrap dependency.
lib/commands/version.js Uses optional catch binding cleanup.
lib/commands/user.js Replaces prompt with readline-based input and avoids “login as undefined”.
lib/commands/test.js Replaces lodash.startCase; sets exit code on failed tests.
lib/commands/submit.js Replaces lodash.startCase; sets exit code on failed submits.
lib/commands/submission.js Sets exit code when per-problem task fails.
lib/commands/stat.js Fixes width alignment + Buffer.write out-of-range issue.
lib/commands/show.js Adds daily option, plain-text description output, and hint printing.
lib/commands/session.js Removes prompt dependency (cleanup).
lib/commands/list.js Makes output columns dynamic to data widths.
lib/commands/config.js Uses optional catch binding cleanup.
lib/commands/cache.js Widens columns dynamically and adds solved markers for per-problem caches.
lib/cli.js Routes TRACE HTTP debugging to new adapter; fixes yargs -v/-vv handling.
eslint.config.js Adds ESLint flat config equivalent to previous ruleset.
docs/releases.md Removes legacy Jekyll release notes page content.
docs/install.md Removes legacy Jekyll installation page content.
docs/index.html Removes legacy Jekyll docs landing page content.
docs/demo.html Removes legacy Jekyll demo page content.
docs/commands.md Removes legacy Jekyll command docs content.
docs/advanced.md Removes legacy Jekyll advanced tips content.
docs/_config.yml Removes Jekyll theme config.
bin/leetcode Removes process.emitWarning suppression wrapper.
1.js Adds a sample solution file (likely accidental/temporary).
.github/workflows/release.yml Adds release workflow to build pkg binaries and publish GitHub Releases.
.github/workflows/ci.yml Adds CI workflow for lint/test on Node 20/22/24.
.eslintrc.js Removes legacy ESLint config in favor of flat config.
Review details

Suppressed comments (1)

lib/plugin.js:73

  • Switching plugin dependency installation to pnpm add introduces a new runtime requirement: end users who installed leetcode-cli via npm (or run the packaged binaries) are unlikely to have pnpm available on PATH, so plugin installs may start failing. Consider using npm install --save (always present with Node) or adding a fallback/detection layer.
  • Files reviewed: 52/57 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread lib/http.js
Comment on lines +61 to +67
function shim(resp, finalUrl) {
return {
statusCode: resp.status,
headers: Object.assign({}, resp.headers),
request: {uri: {href: finalUrl}}
};
}
Comment thread lib/session.js
Comment on lines 31 to 34
cache.set(h.KEYS.user, _user);
// the file holds a live session cookie, keep it private
fs.chmodSync(file.cacheFile(h.KEYS.user), 0o600);
};
Comment thread lib/commands/cache.js
Comment on lines +54 to +58
// per-problem caches get a solved marker from the problems list
const problems = name === '' ? cache.get(h.KEYS.problems) : null;

log.info(chalk.gray(sprintf(' %-' + width + 's %8s %s', 'Cache', 'Size', 'Created')));
log.info(chalk.gray('-'.repeat(width + 26)));
@dp9u0
dp9u0 merged commit ca186f5 into main Aug 31, 2026
1 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants