Implement jscpd server#746
Conversation
WalkthroughAdds a new Express-based JSCPD Server app: CLI entry, HTTP API (POST /api/check, GET /api/stats, GET /api/health, root), per-request ephemeral isolation, detector orchestration, store selection, request validation/middleware, types, tests, and docs. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Express as Express Server
participant Middleware
participant Router
participant Service as JscpdServerService
participant Detector
participant Store as EphemeralHybridStore
participant Response
Client->>Express: POST /api/check { code, format }
Express->>Middleware: validateCheckRequest(req)
Middleware-->>Router: next()
Router->>Service: checkSnippet(request)
Service->>Store: create ephemeral store (seed from source)
Service->>Detector: detectClones(opts, ephemeralStore)
Detector->>Store: read frames (ephemeral -> source fallback)
Store-->>Detector: frames
Detector->>Detector: locate clones
Detector-->>Service: clones
Service->>Service: filter & map clones, calc stats
Service-->>Response: CheckSnippetResponse
Response->>Client: 200 { duplications, statistics }
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 📜 Recent review detailsConfiguration used: Organization UI Review profile: CHILL Plan: Pro ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Reviewing your PR now. Feedback on the way! |
| } | ||
|
|
||
| close(): void { | ||
| this.ephemeralStore.close(); |
There was a problem hiding this comment.
The close() method doesn't await the ephemeral store's close operation. If ephemeralStore.close() returns a Promise (which some store implementations might), this could lead to incomplete cleanup and potential resource leaks. The method should be async and await the close operation to ensure proper cleanup.
Fix it with Roo Code or mention @roomote and request a fix.
There was a problem hiding this comment.
Actionable comments posted: 11
🤖 Fix all issues with AI agents
In @apps/jscpd-server/README.md:
- Around line 453-464: The README shows an inconsistent CLI invocation: it
documents `jscpd server /path/to/project` but the project exposes the CLI
entrypoint as `jscpd-server`; update the example and any other occurrences to
use the correct command `jscpd-server` (or vice versa if the actual binary
should be `jscpd`) so they match; search for the strings `jscpd server` and
`jscpd-server` (e.g., the "Starting the Server" section and the example under
"Start server with LevelDB persistence") and make them consistent.
- Around line 586-589: The CI example uses the wrong executable name; update the
GitHub Actions step under the "Start jscpd server" job to call the correct
binary `jscpd-server` (replace `jscpd server . --port 3000 &` with `jscpd-server
. --port 3000 &`) and scan the README for other occurrences of `jscpd server` to
make them consistent with the `jscpd-server` command used elsewhere.
In @apps/jscpd-server/src/server/constants.ts:
- Around line 1-3: The module currently calls readPackageJson() at import time
which can throw and crash startup; change constants.ts to call readPackageJson()
inside a try/catch (or create a small helper like getPackageJsonSafe or
getPackageVersion) and fall back to a safe default version string (e.g. "0.0.0"
or undefined) when the file is missing or parsing fails, then export the same
constant names (e.g. packageJson or PACKAGE_VERSION) so callers aren’t broken
but the import no longer throws.
In @apps/jscpd-server/src/server/middleware.ts:
- Around line 65-80: The errorHandler currently logs the full error object which
may include sensitive user-submitted code; change the logging to only emit safe
fields (e.g., err.name and err.message) and avoid printing err.stack or the
entire err object. In the errorHandler function, replace console.error('Error:',
err) with a focused log such as console.error('Error:', err.name, '-',
err.message) or use the app's logger to record only those fields, and keep
constructing the ErrorResponse (error, message, statusCode) unchanged so the
response behavior remains the same.
In @apps/jscpd-server/src/server/service.ts:
- Around line 206-218: The close() method currently cleans up many fields but
omits resetting the Statistic reference; update the async close() implementation
(method close in the service class) to set this.statistic = null as part of
teardown (place it alongside the other null assignments such as this.store,
this.options, this.tokenizer, this.detector) so the Statistic object can be
garbage-collected.
- Around line 65-68: Remove the unused instance field this.detector and its
instantiation via new Detector(...) in the constructor/initializer; instead rely
on context.detector (used by initialize()) and the ephemeralDetector created in
checkSnippet(). Also update any null-guard that checks this.detector (e.g., the
guard referenced around line 156) to check context.detector or otherwise handle
the absence, and delete the this.detector property declaration so no unused
member remains.
In @apps/jscpd-server/src/setup/ignore.ts:
- Line 15: The current call to ignore.map(...) discards the mapped array so
leading '!' negation markers are not removed; update the code to reassign the
result back to ignore (e.g., ignore = ignore.map(...)) and use replace(/^!/, '')
instead of replace('!', '') so only a leading negation marker is stripped while
preserving other '!' characters.
In @apps/jscpd-server/src/setup/reporters.ts:
- Around line 35-36: The code unguardedly calls options.reporters.forEach which
will throw if options.reporters is undefined; replace the @ts-ignore with a safe
guard or default (e.g., use (options.reporters ?? []) or conditional check)
before iterating so that Reporter processing logic (the forEach over
options.reporters and any handling of reporter strings) only runs when reporters
is an array; update references to the reporter iteration site
(options.reporters.forEach(...)) accordingly and remove the @ts-ignore.
In @apps/jscpd-server/tsconfig.json:
- Around line 21-28: The JSON in the tsconfig has an extraneous trailing comma
after the "ts-node" object which breaks parsing; remove the comma immediately
following the closing brace of the "ts-node" block (the comma after the object
that contains "compilerOptions": { "module": "commonjs" }) so the file becomes
valid JSON.
In @packages/finder/src/files.ts:
- Line 50: The current fallback of '0' causes all non-empty files to be skipped;
change the logic around shouldSkip so that when getOption('maxSize', options) is
falsy you use a very large numeric limit instead of '0' (e.g.,
Number.MAX_SAFE_INTEGER) and compare raw byte counts (use bytes.parse on the
option when present, otherwise use the large numeric limit) so that
bytes.parse(stats.size) > maxBytesOnly skips files correctly; update any
associated debug log to reflect the actual maxSize used (option value or the
large-limit sentinel) and reference the shouldSkip variable, bytes.parse and
getOption('maxSize', options) when making the change.
🧹 Nitpick comments (18)
apps/jscpd-server/nodemon.json (1)
1-7: Consider optimizing the development workflow.The current configuration runs
pnpm buildon every file change, which may be slow during active development. For faster iteration, consider usingts-nodeor a similar tool to execute the TypeScript directly without a full build step.Additionally, the
ignoreRoot: []property can be removed as an empty array serves no purpose.⚡ Suggested optimization for faster development
{ "$schema": "https://json.schemastore.org/nodemon.json", "watch": ["./src/**"], - "ignoreRoot": [], "ext": "ts,js", - "exec": "pnpm build" + "exec": "tsx bin/jscpd-server.ts" }Note: This assumes you have
tsxinstalled. Alternatively, you could usets-nodeor keep the current approach if full builds are acceptable during development.apps/jscpd-server/package.json (1)
10-10: Consider using.d.tsextension for broader tooling compatibility.While
.d.mtsis valid for ESM declaration files, the more conventional approach is to use.d.tswith"type": "module"in package.json. Some older TypeScript tooling may not recognize.d.mtsfiles.Also applies to: 13-13
apps/jscpd-server/src/setup/store.ts (2)
7-12: Enhance error logging for better debugging.The catch block doesn't log the actual error details, making it difficult to diagnose why the store failed to load. Consider logging the error and adding a semicolon for consistency.
📝 Proposed improvements to error handling
try { const store = require(packageName).default; return new store(); } catch (e) { - console.error(red('store name ' + storeName + ' not installed.')) + console.error(red(`Store "${storeName}" not installed or failed to load.`)); + console.error(red('Error details:'), e); }
4-15: Consider logging the fallback to MemoryStore.After the error is logged, the function silently falls back to
MemoryStore. While this provides graceful degradation, explicitly logging the fallback would make the behavior more transparent to users.export function getStore(storeName: string | undefined): IStore<IMapFrame> { if (storeName) { const packageName = '@jscpd/' + storeName + '-store'; try { const store = require(packageName).default; return new store(); } catch (e) { console.error(red(`Store "${storeName}" not installed or failed to load.`)); console.error(red('Error details:'), e); console.log('Falling back to in-memory store...'); } } return new MemoryStore<IMapFrame>(); }apps/jscpd-server/src/setup/cli-utils.ts (1)
6-24: Improve error message with attempted paths.The error message on Line 23 doesn't indicate which paths were attempted, making it harder to debug when package.json cannot be found.
📝 Proposed improvement to error message
for (const path of possiblePaths) { try { return readJSONSync(path); } catch (e) { // Continue to next path } } - throw new Error('Could not find package.json'); + throw new Error( + `Could not find package.json. Attempted paths:\n${possiblePaths.map(p => ` - ${p}`).join('\n')}` + );apps/jscpd-server/bin/jscpd-server.ts (1)
3-12: Reconsider re-exporting from a bin script with side effects.The IIFE on lines 3-10 executes immediately when this file is loaded, meaning any module that imports from this bin script will trigger the server startup as a side effect. The
export * from '../src'at line 12 makes this file importable, which could lead to unintended behavior.If the intent is to expose the API for programmatic use, consumers should import directly from
../src. Consider removing the re-export or restructuring so that the side-effect-free exports and the CLI entry point are separate.♻️ Suggested fix
import { runServer } from "../src"; (async () => { try { await runServer(process.argv, process.exit) } catch(e) { console.error(e); process.exit(1); } })() - -export * from '../src'apps/jscpd-server/README.md (1)
593-603: Shell script example may fail with special characters.The
jq -Rs .approach for escaping file contents can still fail if the file contains characters that break the outer JSON structure or if the file is binary. Consider noting this limitation or using a more robust approach.apps/jscpd-server/src/setup/ignore.ts (1)
9-12: Usepath.joinand replace deprecatedsubstr.Two minor improvements:
- Line 9: Use
path.join(process.cwd(), '.gitignore')for cross-platform path handling- Line 12:
substris deprecated; useslice(-1)orsubstring♻️ Suggested improvements
+import {join} from "path"; import {existsSync} from "fs"; const gitignoreToGlob = require('gitignore-to-glob'); export function initIgnore(options: IOptions): string[] { const ignore: string[] = options.ignore || []; - if (options.gitignore && existsSync(process.cwd() + '/.gitignore')) { - let gitignorePatterns: string[] = gitignoreToGlob(process.cwd() + '/.gitignore') || []; + if (options.gitignore && existsSync(join(process.cwd(), '.gitignore'))) { + let gitignorePatterns: string[] = gitignoreToGlob(join(process.cwd(), '.gitignore')) || []; gitignorePatterns = gitignorePatterns.map((pattern) => - pattern.substr(pattern.length - 1) === '/' ? `${pattern}**/*` : pattern, + pattern.slice(-1) === '/' ? `${pattern}**/*` : pattern, );apps/jscpd-server/src/setup/reporters.ts (1)
43-50: Consider renaming shadowed catch variable.The inner
catch (e)shadows the outercatch (e). While this works correctly due to block scoping, using distinct names like_outerErrandinnerErr(or_efor ignored errors) would improve readability.apps/jscpd-server/src/server/routes.ts (1)
58-65: Consider adding error handling for consistency.While
getState()is unlikely to throw, wrapping this in a try-catch would maintain consistency with other endpoints and guard against future changes.♻️ Suggested improvement
router.get('/health', (_req: Request, res: Response) => { + try { const state = service.getState(); res.json({ status: state.isScanning ? 'initializing' : 'ready', workingDirectory: state.workingDirectory, lastScanTime: state.lastScanTime, }); + } catch (err) { + handleRouteError(res, err, 'HealthError', HTTP_STATUS.INTERNAL_SERVER_ERROR); + } });apps/jscpd-server/src/server/server.ts (1)
31-40: Content-Type header override may cause issues with non-JSON responses.The middleware sets
Content-Type: application/jsonfor all responses. This could cause issues if you later add endpoints that return other content types (e.g., HTML, plain text, or files).💡 Consideration
If all endpoints are intended to return JSON, this is fine. Otherwise, consider setting the Content-Type per-response or only for
/apiroutes:- this.app.use((_req, res, next) => { - res.header("Content-Type", "application/json"); - next(); - }); + // Content-Type is automatically set by res.json()apps/jscpd-server/src/setup/options.ts (4)
1-1: Avoid using@ts-nocheckin production code.Disabling TypeScript checks defeats the purpose of type safety. Instead, address the specific type issues with targeted
@ts-ignorecomments or proper type definitions for theCommandobject properties.Consider creating a typed interface for CLI options
interface CliOptions { minTokens?: string; minLines?: string; maxLines?: string; maxSize?: string; // ... other CLI properties } const convertCliToOptions = (cli: Command & CliOptions): Partial<IOptions> => { // ... }
13-15: Use explicit radix withparseInt.Always pass radix
10toparseIntfor decimal parsing to avoid unexpected behavior with strings starting with0.Proposed fix
- minTokens: cli.minTokens ? parseInt(cli.minTokens) : undefined, - minLines: cli.minLines ? parseInt(cli.minLines) : undefined, - maxLines: cli.maxLines ? parseInt(cli.maxLines) : undefined, + minTokens: cli.minTokens ? parseInt(cli.minTokens, 10) : undefined, + minLines: cli.minLines ? parseInt(cli.minLines, 10) : undefined, + maxLines: cli.maxLines ? parseInt(cli.maxLines, 10) : undefined,
26-26: Redundant assignment:formatis assigned twice.Line 26 assigns
cli.formatdirectly, but lines 46-48 always overwrite it withcli.format.split(',')whencli.formatis truthy. Remove the redundant assignment on line 26.Proposed fix
output: cli.output, - format: cli.format, formatsExts: parseFormatsExtensions(cli.formatsExts),Also applies to: 46-48
84-84: Usepath.joinfor cross-platform path construction.String concatenation with
/may cause issues on Windows.Proposed fix
- const config = resolve(process.cwd() + '/package.json'); + const config = resolve(process.cwd(), 'package.json');apps/jscpd-server/__tests__/server.test.ts (2)
180-204: Test usesDate.now()which may produce flaky behavior.The unique code generation relies on
Date.now()in template literals, but these are evaluated once when the test runs. If the test executes quickly,Date.now()values could theoretically collide across test runs or be cached. Consider using a counter or UUID for more reliable uniqueness.
322-350: Good lifecycle tests, but consider adding error case for double-start.The test for multiple
stop()calls is good. Consider also adding a test that verifies behavior when callingstart()twice on the same server instance (e.g., port already in use error handling).packages/finder/src/files.ts (1)
49-50: Replace@ts-expect-errorwith a safer type assertion.Using
@ts-expect-errorto suppress TypeScript errors is risky as it can hide legitimate type errors in the future. Consider using a non-null assertion or type guard instead.♻️ Proposed fix using non-null assertion
- // @ts-expect-error - stats is checked above, but DTS build doesn't recognize control flow - const shouldSkip = bytes.parse(stats.size) > bytes.parse(getOption('maxSize', options) || '0'); + const shouldSkip = bytes.parse(stats!.size) > bytes.parse(getOption('maxSize', options) || '0');Or use a type guard for better safety:
if (!stats) { return true; } - // @ts-expect-error - stats is checked above, but DTS build doesn't recognize control flow - const shouldSkip = bytes.parse(stats.size) > bytes.parse(getOption('maxSize', options) || '0'); + const size = stats.size; + const shouldSkip = bytes.parse(size) > bytes.parse(getOption('maxSize', options) || '0');
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (34)
apps/jscpd-server/README.mdapps/jscpd-server/__tests__/server.test.tsapps/jscpd-server/bin/jscpd-serverapps/jscpd-server/bin/jscpd-server.tsapps/jscpd-server/nodemon.jsonapps/jscpd-server/package.jsonapps/jscpd-server/src/detect.tsapps/jscpd-server/src/index.tsapps/jscpd-server/src/server/constants.tsapps/jscpd-server/src/server/ephemeral-store.tsapps/jscpd-server/src/server/index.tsapps/jscpd-server/src/server/middleware.tsapps/jscpd-server/src/server/routes.tsapps/jscpd-server/src/server/server.tsapps/jscpd-server/src/server/service.tsapps/jscpd-server/src/server/types/index.tsapps/jscpd-server/src/server/types/requests.tsapps/jscpd-server/src/server/types/responses.tsapps/jscpd-server/src/server/types/state.tsapps/jscpd-server/src/setup/cli-utils.tsapps/jscpd-server/src/setup/hooks.tsapps/jscpd-server/src/setup/ignore.tsapps/jscpd-server/src/setup/index.tsapps/jscpd-server/src/setup/options.tsapps/jscpd-server/src/setup/reporters.tsapps/jscpd-server/src/setup/store.tsapps/jscpd-server/src/setup/subscribers.tsapps/jscpd-server/tsconfig.build.jsonapps/jscpd-server/tsconfig.jsonapps/jscpd-server/tsup.config.tsapps/jscpd/package.jsonpackage.jsonpackages/finder/src/files.tspnpm-workspace.yaml
🧰 Additional context used
🧬 Code graph analysis (14)
apps/jscpd-server/src/setup/hooks.ts (3)
packages/core/src/interfaces/options.interface.ts (1)
IOptions(1-37)packages/finder/src/in-files-detector.ts (1)
InFilesDetector(18-113)packages/finder/src/hooks/blamer.ts (1)
BlamerHook(6-37)
apps/jscpd-server/src/setup/subscribers.ts (4)
packages/core/src/interfaces/options.interface.ts (1)
IOptions(1-37)packages/finder/src/in-files-detector.ts (1)
InFilesDetector(18-113)packages/finder/src/subscribers/verbose.ts (1)
VerboseSubscriber(4-32)packages/finder/src/subscribers/progress.ts (1)
ProgressSubscriber(4-14)
apps/jscpd-server/src/server/middleware.ts (2)
apps/jscpd-server/src/server/types/responses.ts (1)
ErrorResponse(37-41)apps/jscpd-server/src/server/constants.ts (2)
HTTP_STATUS(29-35)ERROR_MESSAGES(11-21)
apps/jscpd-server/src/server/types/responses.ts (1)
packages/core/src/interfaces/statistic.interface.ts (1)
IStatistic(19-23)
apps/jscpd-server/src/setup/cli-utils.ts (1)
packages/core/src/options.ts (1)
getOption(34-37)
apps/jscpd-server/src/server/constants.ts (1)
apps/jscpd-server/src/setup/cli-utils.ts (1)
readPackageJson(6-24)
apps/jscpd-server/__tests__/server.test.ts (1)
apps/jscpd-server/src/server/server.ts (1)
JscpdServer(15-110)
apps/jscpd-server/src/server/routes.ts (5)
apps/jscpd-server/src/server/constants.ts (1)
HTTP_STATUS(29-35)apps/jscpd-server/src/server/types/responses.ts (1)
ErrorResponse(37-41)apps/jscpd-server/src/server/service.ts (1)
JscpdServerService(27-219)apps/jscpd-server/src/server/middleware.ts (1)
validateCheckRequest(44-63)apps/jscpd-server/src/server/types/requests.ts (1)
CheckSnippetRequest(1-4)
packages/finder/src/files.ts (1)
packages/core/src/options.ts (1)
getOption(34-37)
apps/jscpd-server/src/server/types/state.ts (1)
packages/core/src/interfaces/statistic.interface.ts (1)
IStatistic(19-23)
apps/jscpd-server/src/server/ephemeral-store.ts (1)
packages/core/src/interfaces/map-frame.interface.ts (1)
IMapFrame(3-10)
apps/jscpd-server/src/detect.ts (5)
packages/core/src/interfaces/options.interface.ts (1)
IOptions(1-37)packages/finder/src/in-files-detector.ts (1)
InFilesDetector(18-113)packages/tokenizer/src/formats.ts (1)
getSupportedFormats(467-469)apps/jscpd-server/src/setup/store.ts (1)
getStore(4-15)packages/finder/src/files.ts (1)
getFilesToDetect(80-113)
apps/jscpd-server/bin/jscpd-server.ts (1)
apps/jscpd-server/src/index.ts (1)
runServer(35-66)
apps/jscpd-server/src/setup/ignore.ts (1)
packages/core/src/interfaces/options.interface.ts (1)
IOptions(1-37)
🔇 Additional comments (46)
pnpm-workspace.yaml (2)
2-3: LGTM: Cosmetic formatting change.Removing quotes from package patterns is a valid stylistic choice. Both formats work identically in YAML.
5-7: Remove non-existent packages fromonlyBuiltDependenciesconfiguration.The field
onlyBuiltDependenciesis valid pnpm configuration for whitelisting packages allowed to run lifecycle scripts. However, neitherclassic-levelnoresbuildappear as dependencies in any workspace packages (package.jsonfiles acrossapps/*andpackages/*). These entries should either be:
- Removed if no longer needed
- Added as actual workspace dependencies if intentional
package.json (2)
32-32: Verify dependency placement of @vitest/coverage-v8.The
@vitest/coverage-v8package is listed underdependenciesrather thandevDependencies, which is unusual for a test coverage tool. In monorepo root package.json files, dependencies are typically avoided unless they need to be shared across all workspaces at runtime.Please confirm this placement is intentional. If it's only needed for testing, consider moving it to
devDependencies.
20-23: All tooling versions are available on npm.The updated devDependencies (@changesets/cli@2.29.8, commitizen@4.3.1, cz-conventional-changelog@3.3.0, turbo@1.13.4) are all valid and accessible from the npm registry.
apps/jscpd-server/tsup.config.ts (1)
1-9: LGTM!The tsup configuration is well-structured with appropriate settings for a server package that provides both a CLI tool and a library API. Dual ESM/CJS output, source maps, and code splitting are all correctly configured.
apps/jscpd-server/src/setup/hooks.ts (1)
4-9: LGTM!The hook registration logic is correct and follows the expected pattern from the @jscpd/finder package. The unconditional registration of
FragmentsHookand conditional registration ofBlamerHookbased on theoptions.blameflag is appropriate.apps/jscpd/package.json (1)
58-58: The version@types/node@24.10.0is valid and published. No issues found.apps/jscpd-server/package.json (2)
18-20: LGTM! Binary configuration follows npm conventions.The bin entry correctly points to the executable file. Ensure the target file has a proper shebang (
#!/usr/bin/env node) and execute permissions.
49-49: Commander 5.1.0 has no known security vulnerabilities and is compatible with the project.The version is confirmed to have zero recorded security issues according to Snyk and npm. While commander is significantly outdated (current version is 14.0.2), the project's caret constraint maintains stability within the v5.x line, and no action is required.
apps/jscpd-server/src/server/ephemeral-store.ts (2)
32-34: Verify that not closing sourceStore is intentional.The
close()method only closes the ephemeral store. IfsourceStoreis a shared resource across multiple instances, this is correct. However, if each instance owns itssourceStore, not closing it could lead to resource leaks (e.g., open file handles, database connections).Consider adding a comment documenting this behavior, or verify that
sourceStorelifecycle is managed elsewhere:close(): void { // Only close ephemeral store; sourceStore is shared and managed externally this.ephemeralStore.close(); }
9-18: LGTM! Clean hybrid store implementation.The class structure and namespace delegation are well-designed. The pattern of layering an ephemeral store over a source store is appropriate for the use case described in the documentation.
apps/jscpd-server/src/setup/cli-utils.ts (2)
26-28: LGTM! Helper functions are clean and concise.Both
createBaseCommandandgetWorkingDirectoryare well-implemented with appropriate fallback behavior.Also applies to: 61-63
30-59: Code is safe; allgetOption()calls return defined values.All five option keys (
minLines,minTokens,maxLines,maxSize,mode) have defined defaults ingetDefaultOptions()at packages/core/src/options.ts:5-31. ThegetOption()function always falls back to these defaults, ensuring the string concatenations on lines 38, 42, 44, 47, and 51 will never receive undefined or null values.apps/jscpd-server/src/server/types/responses.ts (1)
1-59: LGTM! Well-structured API response types.The type definitions are clean, comprehensive, and follow TypeScript best practices:
- Clear separation of concerns with distinct interfaces
- Appropriate use of optional fields (e.g.,
fragment?)- Type-safe literal unions (e.g.,
'initializing' | 'ready')- Proper reuse of imported types from
@jscpd/coreThese interfaces provide a solid contract for the REST API.
apps/jscpd-server/src/server/index.ts (1)
1-6: LGTM!Standard barrel file pattern for consolidating the server module's public exports.
apps/jscpd-server/tsconfig.build.json (1)
1-11: LGTM!Clean build configuration that appropriately extends the parent config and sets up compilation output.
apps/jscpd-server/bin/jscpd-server (1)
1-3: LGTM!Standard CLI wrapper that correctly delegates to the compiled output.
apps/jscpd-server/src/setup/index.ts (1)
1-3: LGTM!Clean barrel file for setup utilities.
apps/jscpd-server/tsconfig.json (1)
1-20: LGTM!Well-configured TypeScript setup with appropriate strict mode flags for a Node.js 20 project.
apps/jscpd-server/src/server/types/index.ts (1)
1-3: LGTM!Clean barrel export pattern that consolidates the type definitions for the server API surface.
apps/jscpd-server/src/server/types/requests.ts (1)
1-4: LGTM!The request interface is clean and matches the documented API contract for the
/api/checkendpoint.apps/jscpd-server/src/setup/subscribers.ts (1)
4-12: LGTM!The subscriber registration logic correctly applies
VerboseSubscriberwhen verbose mode is enabled andProgressSubscriberwhen silent mode is not active. This centralizes the subscriber hookup logic cleanly.apps/jscpd-server/src/server/types/state.ts (1)
1-8: LGTM!The
ServerStateinterface cleanly captures the server's runtime state, supporting the health check endpoint's ability to report initialization status and scan timestamps.apps/jscpd-server/src/index.ts (2)
12-33: LGTM!The CLI initialization is well-structured with proper option definitions. The
packageJson: anytype is acceptable for this use case since the JSON structure is read dynamically.
35-66: LGTM!The
runServerfunction has solid error handling:
- Port validation correctly rejects NaN and out-of-range values
- Dynamic import allows lazy loading of server dependencies
- Errors are logged and exit callback is invoked appropriately
- Returns
nullon failure for graceful handling by callersapps/jscpd-server/src/detect.ts (2)
22-43: LGTM!The
createBaseDetectorContextfunction properly merges defaults, initializes all required components, and returns a well-typed context object. The use of MD5 for content hashing is appropriate for duplicate detection purposes (non-cryptographic use case).
45-64: LGTM!The
detectClonesfunction is well-structured with proper separation between silent and verbose modes. The timing instrumentation is appropriately guarded by thesilentoption.apps/jscpd-server/src/server/routes.ts (3)
7-23: LGTM!The
handleRouteErrorhelper is well-designed, properly handling both Error instances and string/unknown error types with a consistentErrorResponsestructure.
28-36: LGTM!The
/checkendpoint properly validates input via middleware, handles async errors, and returns structured responses.
38-56: LGTM!The
/statsendpoint correctly handles the "not ready" state with a 503 response and has proper error handling for unexpected failures.apps/jscpd-server/src/server/middleware.ts (3)
5-42: LGTM!The validation helpers are well-designed with a clean
FieldValidationinterface and reusablevalidateFieldfunction that properly handles required fields, type checking, and empty string validation.
44-63: LGTM!The
validateCheckRequestmiddleware is well-structured, validating both required fields (codeandformat) and returning early on validation failures.
82-89: LGTM!The
notFoundHandlerprovides a clear, consistent 404 response with useful route information for debugging.apps/jscpd-server/src/server/constants.ts (1)
5-35: LGTM!The constants are well-organized with proper use of
as constfor type narrowing. The centralized configuration makes maintenance easier.apps/jscpd-server/src/server/server.ts (4)
15-29: LGTM!The
JscpdServerclass is well-structured with proper separation of concerns. The constructor correctly initializes the service and Express app, then sets up middleware, routes, and error handlers in the correct order.
65-85: LGTM!The
start()method properly initializes the service before starting the HTTP server and handles both synchronous and asynchronous errors appropriately via the Promise pattern.
87-101: LGTM!The
stop()method implements graceful shutdown correctly, closing the HTTP server first then cleaning up the service. It also handles the case where the server wasn't started (line 100).
112-125: LGTM!The
startServerhelper function provides a clean public API for starting the server programmatically.apps/jscpd-server/src/setup/options.ts (1)
95-137: LGTM!The configuration merging logic correctly applies precedence (CLI > stored config > package.json > defaults), and the post-processing for reporters, mode, formats, and ignore rules is well-structured.
apps/jscpd-server/__tests__/server.test.ts (2)
10-25: Setup correctly initializes service without starting HTTP server.The pattern of calling
getService().initialize()directly (instead ofserver.start()) is appropriate for supertest-based testing, as it avoids binding to a real port while still initializing the service state. This is a valid approach.
547-701: Excellent isolation and concurrency test coverage.These tests thoroughly validate that:
- Snippet tokens don't contaminate the project store
- Concurrent requests don't cross-contaminate each other
- Sequential requests produce consistent results
The assertions checking for
<snippet>paths incodebaseLocation.fileare particularly valuable for ensuring proper isolation.apps/jscpd-server/src/server/service.ts (4)
77-82: Snippet counter is safe but consider edge cases.The
snippetCounter++is safe in Node.js's single-threaded event loop. However, after many requests (Number.MAX_SAFE_INTEGER), the counter could overflow. For a long-running server, consider resetting onclose()or using a more robust ID scheme.The counter is reset in
close()(line 215), which handles service restarts appropriately.
151-193: Solid ephemeral store pattern for request isolation.The try/finally ensures
ephemeralStore.close()is always called, preventing resource leaks. The ephemeral hybrid store approach correctly prevents snippet tokens from contaminating the shared project store.
23-25: LGTM!The percentage calculation correctly handles the zero-division edge case and uses integer rounding for precision.
139-149: No issue found - the code is correct.The
namespace()method mutates the store in-place and returnsvoid, so discarding the return value is the proper usage pattern. No changes needed.packages/finder/src/files.ts (1)
46-48: The guard for undefined stats is defensive and likely unnecessary given fast-glob's guarantees.Since
fast-globis configured withstats: true, it guarantees that thestatsfield will always be present as anfs.Statsobject. Theif (!stats) { return true; }guard appears to be defensive programming for an edge case that shouldn't occur in normal operation. If this guard is needed, it suggests either:
- The
stats: trueconfiguration is not being respected, or- The guard is masking a TypeScript type-checking issue (as evidenced by the
@ts-expect-errorcomment)Consider removing this guard and instead addressing the TypeScript type narrowing issue directly, or document why this defensive guard is necessary if there are edge cases where
fast-globmight returnundefinedstats despite the configuration.
| # Start server with LevelDB persistence | ||
| jscpd server /path/to/project --store leveldb --port 3000 | ||
|
|
||
| # On first start, the server will: | ||
| # 1. Scan the codebase | ||
| # 2. Store results in .jscpd/ directory | ||
| # 3. Accept requests | ||
|
|
||
| # On subsequent restarts, the server will: | ||
| # 1. Load cached data from .jscpd/ directory | ||
| # 2. Accept requests immediately (no re-scan needed) | ||
| ``` |
There was a problem hiding this comment.
Inconsistent command name in documentation.
The command here uses jscpd server but the CLI entry point established in this PR is jscpd-server (as shown in the "Starting the Server" section, lines 13-25). This inconsistency could confuse users.
📝 Suggested fix
-jscpd server /path/to/project --store leveldb --port 3000
+jscpd-server /path/to/project --store leveldb --port 3000📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Start server with LevelDB persistence | |
| jscpd server /path/to/project --store leveldb --port 3000 | |
| # On first start, the server will: | |
| # 1. Scan the codebase | |
| # 2. Store results in .jscpd/ directory | |
| # 3. Accept requests | |
| # On subsequent restarts, the server will: | |
| # 1. Load cached data from .jscpd/ directory | |
| # 2. Accept requests immediately (no re-scan needed) | |
| ``` | |
| # Start server with LevelDB persistence | |
| jscpd-server /path/to/project --store leveldb --port 3000 | |
| # On first start, the server will: | |
| # 1. Scan the codebase | |
| # 2. Store results in .jscpd/ directory | |
| # 3. Accept requests | |
| # On subsequent restarts, the server will: | |
| # 1. Load cached data from .jscpd/ directory | |
| # 2. Accept requests immediately (no re-scan needed) |
🤖 Prompt for AI Agents
In @apps/jscpd-server/README.md around lines 453 - 464, The README shows an
inconsistent CLI invocation: it documents `jscpd server /path/to/project` but
the project exposes the CLI entrypoint as `jscpd-server`; update the example and
any other occurrences to use the correct command `jscpd-server` (or vice versa
if the actual binary should be `jscpd`) so they match; search for the strings
`jscpd server` and `jscpd-server` (e.g., the "Starting the Server" section and
the example under "Start server with LevelDB persistence") and make them
consistent.
| - name: Start jscpd server | ||
| run: | | ||
| jscpd server . --port 3000 & | ||
| sleep 10 # Wait for server to initialize |
There was a problem hiding this comment.
Same command inconsistency in CI/CD example.
The GitHub Actions example uses jscpd server instead of jscpd-server.
📝 Suggested fix
- name: Start jscpd server
run: |
- jscpd server . --port 3000 &
+ jscpd-server . --port 3000 &
sleep 10 # Wait for server to initialize📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - name: Start jscpd server | |
| run: | | |
| jscpd server . --port 3000 & | |
| sleep 10 # Wait for server to initialize | |
| - name: Start jscpd server | |
| run: | | |
| jscpd-server . --port 3000 & | |
| sleep 10 # Wait for server to initialize |
🤖 Prompt for AI Agents
In @apps/jscpd-server/README.md around lines 586 - 589, The CI example uses the
wrong executable name; update the GitHub Actions step under the "Start jscpd
server" job to call the correct binary `jscpd-server` (replace `jscpd server .
--port 3000 &` with `jscpd-server . --port 3000 &`) and scan the README for
other occurrences of `jscpd server` to make them consistent with the
`jscpd-server` command used elsewhere.
| import { readPackageJson } from "../setup"; | ||
|
|
||
| const packageJson = readPackageJson(); |
There was a problem hiding this comment.
Module load failure if package.json is not found.
readPackageJson() is called at module load time. If it throws (package.json not found), the entire module fails to import, which will crash the application during startup. Consider handling this gracefully with a fallback version.
♻️ Suggested defensive approach
import { readPackageJson } from "../setup";
-const packageJson = readPackageJson();
+let packageVersion = 'unknown';
+try {
+ packageVersion = readPackageJson().version;
+} catch {
+ console.warn('Could not read package.json for version info');
+}
// ...
export const API_INFO = {
NAME: "jscpd-server",
- VERSION: packageJson.version,
+ VERSION: packageVersion,
DOCUMENTATION_URL: "https://github.com/kucherenko/jscpd",
} as const;Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In @apps/jscpd-server/src/server/constants.ts around lines 1 - 3, The module
currently calls readPackageJson() at import time which can throw and crash
startup; change constants.ts to call readPackageJson() inside a try/catch (or
create a small helper like getPackageJsonSafe or getPackageVersion) and fall
back to a safe default version string (e.g. "0.0.0" or undefined) when the file
is missing or parsing fails, then export the same constant names (e.g.
packageJson or PACKAGE_VERSION) so callers aren’t broken but the import no
longer throws.
| async get(key: string): Promise<IMapFrame> { | ||
| try { | ||
| return await this.ephemeralStore.get(key); | ||
| } catch { | ||
| return this.sourceStore.get(key); | ||
| } | ||
| } |
There was a problem hiding this comment.
Overly broad error handling masks legitimate errors.
The empty catch block on Line 23 swallows all errors from ephemeralStore.get(), not just "not found" errors. This could hide legitimate issues like permission errors, corruption, or connection failures. Additionally, sourceStore.get() on Line 24 can also throw but isn't handled.
🛡️ Proposed fix to handle only "not found" errors
If IStore.get() throws a specific error type for missing keys, catch only that error:
async get(key: string): Promise<IMapFrame> {
try {
return await this.ephemeralStore.get(key);
- } catch {
- return this.sourceStore.get(key);
+ } catch (error) {
+ // Only fallback for "not found" errors; propagate others
+ if (isNotFoundError(error)) {
+ return this.sourceStore.get(key);
+ }
+ throw error;
}
}Alternatively, if the store implementation returns undefined or null for missing keys instead of throwing:
async get(key: string): Promise<IMapFrame> {
- try {
- return await this.ephemeralStore.get(key);
- } catch {
- return this.sourceStore.get(key);
- }
+ const ephemeralResult = await this.ephemeralStore.get(key);
+ if (ephemeralResult) {
+ return ephemeralResult;
+ }
+ return this.sourceStore.get(key);
}Committable suggestion skipped: line range outside the PR's diff.
| export function errorHandler( | ||
| err: Error, | ||
| _req: Request, | ||
| res: Response, | ||
| _next: NextFunction | ||
| ): void { | ||
| console.error('Error:', err); | ||
|
|
||
| const error: ErrorResponse = { | ||
| error: err.name || 'InternalServerError', | ||
| message: err.message || 'An unexpected error occurred', | ||
| statusCode: HTTP_STATUS.INTERNAL_SERVER_ERROR, | ||
| }; | ||
|
|
||
| res.status(error.statusCode).json(error); | ||
| } |
There was a problem hiding this comment.
Avoid logging potentially sensitive code snippets in errors.
The error handler logs the full error object. If errors contain user-submitted code snippets (e.g., from validation failures or processing errors), this could inadvertently log sensitive content. Consider logging only error name and message rather than the full error object.
♻️ Suggested improvement
console.error('Error:', err);
+ // Consider: console.error('Error:', err.name, err.message);
+ // to avoid logging potentially sensitive stack traces or data📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export function errorHandler( | |
| err: Error, | |
| _req: Request, | |
| res: Response, | |
| _next: NextFunction | |
| ): void { | |
| console.error('Error:', err); | |
| const error: ErrorResponse = { | |
| error: err.name || 'InternalServerError', | |
| message: err.message || 'An unexpected error occurred', | |
| statusCode: HTTP_STATUS.INTERNAL_SERVER_ERROR, | |
| }; | |
| res.status(error.statusCode).json(error); | |
| } | |
| export function errorHandler( | |
| err: Error, | |
| _req: Request, | |
| res: Response, | |
| _next: NextFunction | |
| ): void { | |
| console.error('Error:', err.name, err.message); | |
| const error: ErrorResponse = { | |
| error: err.name || 'InternalServerError', | |
| message: err.message || 'An unexpected error occurred', | |
| statusCode: HTTP_STATUS.INTERNAL_SERVER_ERROR, | |
| }; | |
| res.status(error.statusCode).json(error); | |
| } |
🤖 Prompt for AI Agents
In @apps/jscpd-server/src/server/middleware.ts around lines 65 - 80, The
errorHandler currently logs the full error object which may include sensitive
user-submitted code; change the logging to only emit safe fields (e.g., err.name
and err.message) and avoid printing err.stack or the entire err object. In the
errorHandler function, replace console.error('Error:', err) with a focused log
such as console.error('Error:', err.name, '-', err.message) or use the app's
logger to record only those fields, and keep constructing the ErrorResponse
(error, message, statusCode) unchanged so the response behavior remains the
same.
| async close(): Promise<void> { | ||
| if (this.store) { | ||
| await this.store.close(); | ||
| } | ||
|
|
||
| this.store = null; | ||
| this.options = null; | ||
| this.tokenizer = null; | ||
| this.detector = null; | ||
| this.snippetCounter = 0; | ||
| this.state.statistics = null; | ||
| this.state.lastScanTime = null; | ||
| } |
There was a problem hiding this comment.
this.statistic field not reset in close().
The statistic field (line 31) holds a reference to a Statistic object but is not set to null during cleanup, potentially preventing garbage collection.
Proposed fix
async close(): Promise<void> {
if (this.store) {
await this.store.close();
}
this.store = null;
this.options = null;
+ this.statistic = null;
this.tokenizer = null;
this.detector = null;
this.snippetCounter = 0;
this.state.statistics = null;
this.state.lastScanTime = null;
}🤖 Prompt for AI Agents
In @apps/jscpd-server/src/server/service.ts around lines 206 - 218, The close()
method currently cleans up many fields but omits resetting the Statistic
reference; update the async close() implementation (method close in the service
class) to set this.statistic = null as part of teardown (place it alongside the
other null assignments such as this.store, this.options, this.tokenizer,
this.detector) so the Statistic object can be garbage-collected.
| pattern.substr(pattern.length - 1) === '/' ? `${pattern}**/*` : pattern, | ||
| ); | ||
| ignore.push(...gitignorePatterns); | ||
| ignore.map((pattern) => pattern.replace('!', '')); |
There was a problem hiding this comment.
Bug: map result is discarded, negation markers are not removed.
The ignore.map(...) call returns a new array with patterns stripped of !, but the result is never assigned. The original ignore array remains unchanged.
🐛 Proposed fix
- ignore.map((pattern) => pattern.replace('!', ''));
+ const cleanedIgnore = ignore.map((pattern) => pattern.replace(/^!/, ''));
+ return cleanedIgnore;
}
- return ignore;
+ return ignore;Or more concisely, reassign the result:
- ignore.map((pattern) => pattern.replace('!', ''));
+ return ignore.map((pattern) => pattern.replace(/^!/, ''));Note: Also changed replace('!', '') to replace(/^!/, '') to only strip leading negation markers.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ignore.map((pattern) => pattern.replace('!', '')); | |
| return ignore.map((pattern) => pattern.replace(/^!/, '')); |
🤖 Prompt for AI Agents
In @apps/jscpd-server/src/setup/ignore.ts at line 15, The current call to
ignore.map(...) discards the mapped array so leading '!' negation markers are
not removed; update the code to reassign the result back to ignore (e.g., ignore
= ignore.map(...)) and use replace(/^!/, '') instead of replace('!', '') so only
a leading negation marker is stripped while preserving other '!' characters.
| // @ts-ignore | ||
| options.reporters.forEach((reporter: string) => { |
There was a problem hiding this comment.
Guard against undefined options.reporters.
The @ts-ignore comment suppresses the type error, but if options.reporters is undefined, calling .forEach will throw a runtime error. Add a guard or default to an empty array.
🐛 Proposed fix
- // @ts-ignore
- options.reporters.forEach((reporter: string) => {
+ const reporterList = options.reporters ?? [];
+ reporterList.forEach((reporter: string) => {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // @ts-ignore | |
| options.reporters.forEach((reporter: string) => { | |
| const reporterList = options.reporters ?? []; | |
| reporterList.forEach((reporter: string) => { |
🤖 Prompt for AI Agents
In @apps/jscpd-server/src/setup/reporters.ts around lines 35 - 36, The code
unguardedly calls options.reporters.forEach which will throw if
options.reporters is undefined; replace the @ts-ignore with a safe guard or
default (e.g., use (options.reporters ?? []) or conditional check) before
iterating so that Reporter processing logic (the forEach over options.reporters
and any handling of reporter strings) only runs when reporters is an array;
update references to the reporter iteration site
(options.reporters.forEach(...)) accordingly and remove the @ts-ignore.
| "ts-node": { | ||
| // these options are overrides used only by ts-node | ||
| // same as the --compilerOptions flag and the TS_NODE_COMPILER_OPTIONS environment variable | ||
| "compilerOptions": { | ||
| "module": "commonjs" | ||
| } | ||
| }, | ||
| } |
There was a problem hiding this comment.
Remove the trailing comma on line 28.
Line 28 contains a trailing comma after the closing brace, which is invalid JSON syntax and will cause parsing errors.
🐛 Proposed fix
"ts-node": {
// these options are overrides used only by ts-node
// same as the --compilerOptions flag and the TS_NODE_COMPILER_OPTIONS environment variable
"compilerOptions": {
"module": "commonjs"
}
- },
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "ts-node": { | |
| // these options are overrides used only by ts-node | |
| // same as the --compilerOptions flag and the TS_NODE_COMPILER_OPTIONS environment variable | |
| "compilerOptions": { | |
| "module": "commonjs" | |
| } | |
| }, | |
| } | |
| "ts-node": { | |
| // these options are overrides used only by ts-node | |
| // same as the --compilerOptions flag and the TS_NODE_COMPILER_OPTIONS environment variable | |
| "compilerOptions": { | |
| "module": "commonjs" | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
In @apps/jscpd-server/tsconfig.json around lines 21 - 28, The JSON in the
tsconfig has an extraneous trailing comma after the "ts-node" object which
breaks parsing; remove the comma immediately following the closing brace of the
"ts-node" block (the comma after the object that contains "compilerOptions": {
"module": "commonjs" }) so the file becomes valid JSON.
| return true; | ||
| } | ||
| // @ts-expect-error - stats is checked above, but DTS build doesn't recognize control flow | ||
| const shouldSkip = bytes.parse(stats.size) > bytes.parse(getOption('maxSize', options) || '0'); |
There was a problem hiding this comment.
Critical: Wrong fallback value for maxSize will filter out all files.
The fallback value '0' is incorrect. When getOption('maxSize', options) returns a falsy value, bytes.parse('0') returns 0, making the comparison stats.size > 0. This would skip all non-empty files, effectively breaking the file detection.
If maxSize is not specified, the intent should be to have no size limit. Use a very large fallback value instead.
🐛 Proposed fix using a large fallback value
- const shouldSkip = bytes.parse(stats!.size) > bytes.parse(getOption('maxSize', options) || '0');
+ const shouldSkip = bytes.parse(stats!.size) > bytes.parse(getOption('maxSize', options) || '999GB');Also update the debug log for consistency:
if (options.debug && shouldSkip) {
- console.log(`File ${path} skipped! Size more then limit (${bytes(stats.size)} > ${getOption('maxSize', options)})`);
+ console.log(`File ${path} skipped! Size more then limit (${bytes(stats.size)} > ${getOption('maxSize', options) || '999GB'})`);
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In @packages/finder/src/files.ts at line 50, The current fallback of '0' causes
all non-empty files to be skipped; change the logic around shouldSkip so that
when getOption('maxSize', options) is falsy you use a very large numeric limit
instead of '0' (e.g., Number.MAX_SAFE_INTEGER) and compare raw byte counts (use
bytes.parse on the option when present, otherwise use the large numeric limit)
so that bytes.parse(stats.size) > maxBytesOnly skips files correctly; update any
associated debug log to reflect the actual maxSize used (option value or the
large-limit sentinel) and reference the shouldSkip variable, bytes.parse and
getOption('maxSize', options) when making the change.
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/jscpd/package.json (2)
44-55: Move@jscpd/badge-reportertooptionalDependencies.The badge-reporter package is not statically imported in the codebase and is only loaded dynamically when explicitly requested by users (via the reporter loading mechanism at
src/init/reporters.tslines 40–51). Unlike@jscpd/html-reporterandjscpd-sarif-reporter, which are hardcoded into the reporters registry and statically imported, badge-reporter is an optional plugin and should not be a required dependency. Moving it tooptionalDependenciesprevents unnecessary installations in minimal environments and CI pipelines.
56-66:@types/node@^24.10.0conflicts with@tsconfig/node20—align to Node 20.
@types/node@24.10.0targets Node.js v24, but@tsconfig/node20targets Node.js v20. Either downgrade@types/nodeto^20.x.x(the v20 series) or remove/update@tsconfig/node20to match the intended target. Explicitly declare Node engine support inenginesfield to clarify intent.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (1)
apps/jscpd/package.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Rooview
Feature related to #740 and #743
Currently,
jscpdoperates primarily as a CLI tool. There is no built-in HTTP server or RESTful API for integrating code duplication detection into other services or workflows that require a persistent service or real-time checks.This PR introduces a new package
@jscpd/server(located inapps/jscpd-server) which implements a RESTful API server for code duplication detection.Key Features:
POST /api/check: Check a code snippet for duplications against the codebase.GET /api/stats: Retrieve project statistics (duplicated lines, files, formats, etc.).GET /api/health: Health check endpoint.GET /api/info: Server version and API information.apps/jscpd-server/README.md.packages/finderdependencies.This change is
Summary by CodeRabbit
New Features
Documentation
Tests
Chores
✏️ Tip: You can customize this high-level summary in your review settings.