Skip to content

Commit 81b21c0

Browse files
authored
fix(vscode): stop forcing color when the project config disables it (#33)
* fix(vscode): stop forcing color when the project config disables it The master hard-coded FORCE_COLOR=1 into the rstest worker env (as the upstream extension does), so a project whose config sets process.env.NO_COLOR = '1' at load time hit Node's "'NO_COLOR' env is ignored" warning in every pool process. Mirror the CLI's getForceColorEnv semantics instead (adaptation #9): the master injects FORCE_COLOR=1 into the composed spawn env only when the user set neither color standard (marking the injection), and the worker retracts the marked injection right after config load — the CLI's own decision point — when the config turned color off. A user-set FORCE_COLOR beside a config-set NO_COLOR still warns, exactly as the bare CLI does. Also apply the review cleanups from the same pass: versionOk now retires a root's not-installed latch too (dropping the paired installed() calls), versionMismatch gains the same re-raise dedupe as notInstalled, the missing-package verdict rides on RslintResolutionError instead of a partial code table, missingDependencyCauseOf moves to shared/ for the lint/fmt config loaders (#30), stackCommandTitle loses its single-value verb parameter and now also feeds the status-bar hover, the unused NormalizedConfigResult.reason discriminant is dropped, the lint core-selection failure wording is spelled once, and the worker spawn reuses one resolved rstest path and one IPC send callback. * fix(vscode): address review findings on quiet classification, latches, color marker Three review findings, each verified before fixing: - The missing-cwd spawn refusal threw a plain Error, so callers re-logged the already-warned stale-project state as an error with a stack. The guard now throws ReportedRstestResolutionError (which gained an optional message), and the four catch sites above RstestApi share one logUnlessReported helper next to the class instead of re-deciding. - The re-raise dedupe in versionMismatch/notInstalled short-circuited before the package-state restatement, so a crash latched between two identical verdicts survived a retry that aborted before spawning. Both observations now fold into one #observePackageState that restates first and skips only the repaint. - retractForceColorIfDisabled left RSTACK_FORCE_COLOR_INJECTED in the env on the no-NO_COLOR path; the marker is now removed once the decision is complete, so pool processes and user test code never observe it. * fix(vscode): keep the spawn-race cwd classification quiet through pending RPCs The delete-after-check handler logged the stale-project warning but closed the worker with birpc's default error, so pending RPCs rejected with a bare '[birpc] rpc is closed' and the callers' catches re-logged the failure. birpc's $close(customError) rejects pending calls with the given error; the cwd-gone branch now passes a ReportedRstestResolutionError carrying the same message, so logUnlessReported stays quiet — the same contract the pre-spawn guard already follows. The branch's race window (cwd deleted between the guard and spawn, with no 'spawn' event timing guarantee) has no deterministic unit test; the classification is covered by types and the existing spawn-failure suite. * fix(vscode): retract only the injected FORCE_COLOR value A config that assigns both FORCE_COLOR and NO_COLOR at load time owns the FORCE_COLOR value; the bare CLI, deciding after config load, leaves both intact. The retraction now removes FORCE_COLOR only while it still holds the injected '1', so pools keep a config-set force-color preference.
1 parent 01c1b97 commit 81b21c0

26 files changed

Lines changed: 530 additions & 263 deletions

packages/vscode/AGENTS.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ One extension replacing the standalone `rstack.rslint` and `rstack.rstest` exten
55
## The copies are intentional
66

77
- `stacks/lint` and `stacks/test` are deliberate near-verbatim copies of the upstream extensions, kept close to upstream so changes can be synced by diffing. Do NOT deduplicate or refactor across the two stacks — the duplication is the point; consolidation is a later, explicit phase.
8-
- The copies diverge from upstream in exactly eight ways (the "adaptations" below). When syncing upstream, preserve them. A ninth divergence is either a bug or must be added to this list.
8+
- The copies diverge from upstream in exactly nine ways (the "adaptations" below). When syncing upstream, preserve them. A tenth divergence is either a bug or must be added to this list.
99
- **Tracked upstream state.** `stacks/lint` is synced to web-infra-dev/rslint `packages/vscode-extension` at **39536fd6** (#1617 — per-document core resolution, `CoreResolver` + `RuntimeManager`, `corePath`, PnP removed) and **892482e0** (#1630`configPath` on `rslint/configRefresh`). `CoreResolver.ts` / `RuntimeManager.ts` / `WorkspaceDocumentRouter.ts` / `Rslint.ts` are the files to diff when syncing further; record the new commits here when you do.
1010

11-
## The eight adaptations
11+
## The nine adaptations
1212

1313
1. **Shell activation** — stacks never self-activate; `register()` returns fast and never blocks on starting a server/worker.
1414
2. **Namespace** — everything user-visible is `rstack.*`. Legacy `rslint.*` / `rstest.*` settings and command ids are not read, aliased or migrated (breaking old settings and keybindings was an accepted cost).
@@ -18,6 +18,7 @@ One extension replacing the standalone `rstack.rslint` and `rstack.rstest` exten
1818
6. **Node runtime selection** (lint, test, fmt) — the Node a project-loading child process runs on is a **User Node runtime** chosen by the extension against one uniform floor, never assumed from PATH; the recovery path is the user's own shell, and the dividing line is the **load bound** (terms in CONTEXT.md; the full rule and rationale in `docs/adr/0001-node-runtime-selection.md`). All three callers — the lint worker, the rstest worker and the `rs fmt --lsp` server — take the decision from the one shared module (`shared/nodeResolution.ts`) and share one escape hatch, the resource-scoped `rstack.nodeExecutable` (`shared/nodeExecutableSetting.ts`); each appends its own consequence to the shared preflight message.
1919
7. **Lint worker and Rstack bridge** — the extension host is only Rslint's language client. One vscode-free, editor-shipped lint worker per **Lint runtime** (one Rslint core inside one workspace folder — CONTEXT.md) runs on the User Node runtime, owns the Go LSP plus all five reverse requests, and derives the binary/config/plugin pieces from one explicit `@rslint/core` directory. Upstream's `CoreResolver` loads that core in the extension host; ours only walks to the directory (`fs.stat` + `package.json` + semver) and hands the path to the worker, and its `CoreInstallation` therefore carries paths, not module factories; upstream's installation cache goes with the module loading it memoized (`clear()` is a no-op kept for the `RuntimeManager` contract). A bridged folder passes only rstack's published `dist/rslintConfig.js` shim; neither the extension nor the worker re-implements Rstack config semantics. Because protocol 2 locks `configPath` per process, the shim is part of the runtime key (`folder + core identity + shim`), which upstream — having no bridge — keys on the core alone. Why: `docs/adr/0003-lint-through-editor-worker.md`.
2020
8. **Self-documenting Rslint diagnostics** — client-side providers parse Inline directives into per-rule hover, DocumentLink and underline-decoration affordances (the hover renders `Rslint(rule-id)`, the shape VS Code gives the published diagnostics), and the router enriches today's `[rule-id] message` diagnostics with a derived Rule docs link. No rule metadata or network lookup is bundled (ADR 0004). The hover provider yields whenever the owning language client's resolved capabilities advertise `hoverProvider`; an optional `Rslint.onClosed` hook identity-safely prunes the controller's capability mirror; the diagnostic synthesis is removed once upstream publishes `code` / `codeDescription` natively.
21+
9. **Color env parity with the CLI** (test) — upstream hard-codes `FORCE_COLOR: '1'` into the worker's spawn env; ours mirrors the CLI's `getForceColorEnv` (rstest `packages/core/src/utils/logger.ts`) instead (`stacks/test/shared/colorEnv.ts`): the master injects `FORCE_COLOR=1` into the composed spawn env only when neither `FORCE_COLOR` nor `NO_COLOR` is already set (marking the injection with `RSTACK_FORCE_COLOR_INJECTED`), and the worker retracts the marked injection right after config load if the config set `NO_COLOR` — the CLI's own decision point. Otherwise a project whose config sets `process.env.NO_COLOR` (rstack-cli does) hits Node's "'NO_COLOR' env is ignored" warning in every pool process. A user-set `FORCE_COLOR` beside a config-set `NO_COLOR` still warns, exactly as the bare CLI does.
2122

2223
## Rules
2324

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import path from 'node:path';
2+
import { findPackageJsonUncached } from './packageResolve';
3+
4+
/**
5+
* The classifier behind the "config imports a package that is not installed"
6+
* verdict of the uniform not-installed policy (AGENTS.md). Nothing in it is
7+
* Rstest-specific — it reads Node's loader errors — and lint/fmt will need
8+
* the same verdict where their configs load (#30), which is why it lives in
9+
* `shared/` beside the walk-up it uses rather than in one stack.
10+
*
11+
* Returns the one-line cause when a config evaluation failed on a package
12+
* that is not installed, or `undefined` for a real error. Gated on the
13+
* error's `code` — Node's own classification (`ERR_MODULE_NOT_FOUND` for
14+
* ESM, `MODULE_NOT_FOUND` for CJS) — but the code alone is too broad: a
15+
* typo'd relative import fails with the same codes, and installing
16+
* dependencies cannot fix it, so only a bare specifier — a package name,
17+
* read from the message since CJS carries no structured one — counts, and
18+
* anything unrecognized fails towards the full error report. The check has
19+
* to run in the process where the error is thrown: an IPC channel back to
20+
* the extension host (`serialization: 'advanced'`) drops the `code`, so the
21+
* verdict travels as data (e.g. `NormalizedConfigResult`). Only the first
22+
* line comes back: the rest of a CJS message is the require stack, and the
23+
* not-installed state is one warn line without one.
24+
*/
25+
export function missingDependencyCauseOf(
26+
error: unknown,
27+
resolveFrom: string,
28+
): string | undefined {
29+
if (!(error instanceof Error)) return undefined;
30+
const { code } = error as NodeJS.ErrnoException;
31+
if (code !== 'ERR_MODULE_NOT_FOUND' && code !== 'MODULE_NOT_FOUND') {
32+
return undefined;
33+
}
34+
const [firstLine] = error.message.split('\n', 1);
35+
const specifier = /^Cannot find (?:package|module) '([^']+)'/.exec(
36+
firstLine,
37+
)?.[1];
38+
if (
39+
specifier === undefined ||
40+
specifier.startsWith('.') ||
41+
specifier.startsWith('file:') ||
42+
path.isAbsolute(specifier)
43+
) {
44+
return undefined;
45+
}
46+
// `installed-package/missing-subpath` wears the same bare shape, but the
47+
// package itself is there — installing dependencies cannot fix it either,
48+
// so a subpath is checked against the physical `node_modules` with the
49+
// same uncached walk-up every stack resolves packages with.
50+
const packageName = specifier.startsWith('@')
51+
? specifier.split('/').slice(0, 2).join('/')
52+
: specifier.split('/', 1)[0];
53+
if (
54+
packageName !== specifier &&
55+
findPackageJsonUncached(packageName, resolveFrom) !== undefined
56+
) {
57+
return undefined;
58+
}
59+
return firstLine;
60+
}

packages/vscode/src/shared/notInstalled.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
* where it has to be named (ADR 0002).
2020
*/
2121
const restartHint = (stack: StackId): string =>
22-
`then run "${COMMAND_CATEGORY}: ${stackCommandTitle(stack, 'restart')}" if this status stays`;
22+
`then run "${COMMAND_CATEGORY}: ${stackCommandTitle(stack)}" if this status stays`;
2323

2424
/** The `disabled` reason for a package the stack needs and cannot find. */
2525
export const formatNotInstalledStatus = (

packages/vscode/src/stacks/lint/RuntimeManager.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,19 @@ function documentKey(document: TextDocument): string {
9191
return document.uri.toString();
9292
}
9393

94+
/**
95+
* The one wording for a document whose core selection failed, shared with
96+
* the `onDocumentFailure` hook (`index.ts`), which owns the report but must
97+
* not drift from the hook-less fallback below.
98+
*/
99+
export function formatCoreSelectionFailure(
100+
documentUri: string,
101+
keeping?: string,
102+
): string {
103+
const suffix = keeping ? ` (keeping ${keeping} active)` : '';
104+
return `Could not select an Rslint core for ${documentUri}${suffix}`;
105+
}
106+
94107
function cancellationError(key: string): Error {
95108
const error = new Error(`Rslint runtime ${JSON.stringify(key)} was released`);
96109
error.name = 'AbortError';
@@ -418,9 +431,8 @@ export class RuntimeManager {
418431
});
419432
return;
420433
}
421-
const suffix = keeping ? ` (keeping ${keeping} active)` : '';
422434
this.logger.error(
423-
`Could not select an Rslint core for ${document.uri}${suffix}`,
435+
formatCoreSelectionFailure(document.uri.toString(), keeping),
424436
error,
425437
);
426438
}

packages/vscode/src/stacks/lint/index.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { Logger } from './logger';
1212
import { Rslint } from './Rslint';
1313
import type { RslintMode } from './resolution';
1414
import { registerRuleDocumentationProviders } from './ruleDocumentationProviders';
15-
import { RuntimeManager } from './RuntimeManager';
15+
import { formatCoreSelectionFailure, RuntimeManager } from './RuntimeManager';
1616
import {
1717
aggregateFolderStates,
1818
attributeToCore,
@@ -223,9 +223,8 @@ class RslintController implements StackController {
223223
),
224224
);
225225
} else {
226-
const suffix = keeping ? ` (keeping ${keeping} active)` : '';
227226
logger.error(
228-
`Could not select an Rslint core for ${document.uri}${suffix}`,
227+
formatCoreSelectionFailure(document.uri.toString(), keeping),
229228
error,
230229
);
231230
}

packages/vscode/src/stacks/lint/resolution.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,23 @@ export type RslintResolutionErrorCode =
2020
'missing-rstack' | 'missing-core' | 'invalid-package' | 'missing-shim';
2121

2222
export class RslintResolutionError extends Error {
23+
/**
24+
* The package whose absence caused this failure — set only by the
25+
* not-installed throws (`resolveInstalledPackage`), the state the three
26+
* stacks report uniformly (AGENTS.md). `missing-shim` and
27+
* `invalid-package` leave it unset on purpose: there the package is
28+
* present and the fix is an upgrade or the setting, not an install.
29+
*/
30+
readonly missingPackage?: 'rstack' | '@rslint/core';
31+
2332
constructor(
2433
readonly code: RslintResolutionErrorCode,
2534
message: string,
26-
options?: { cause?: unknown },
35+
options?: { cause?: unknown; missingPackage?: 'rstack' | '@rslint/core' },
2736
) {
28-
super(message, options);
37+
super(message, { cause: options?.cause });
2938
this.name = 'RslintResolutionError';
39+
this.missingPackage = options?.missingPackage;
3040
}
3141
}
3242

@@ -101,6 +111,7 @@ function resolveInstalledPackage(
101111
throw new RslintResolutionError(
102112
code,
103113
`Could not resolve ${packageName} from ${searchRoot}`,
114+
{ missingPackage: packageName },
104115
);
105116
}
106117
return readPackageLocation(packageName, packageJsonPath);

packages/vscode/src/stacks/lint/status.ts

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
import type { StackState } from '../../types';
22
import { formatNotInstalledStatus } from '../../shared/notInstalled';
33
import type { SupportedPackage } from '../../shared/versionCheck';
4-
import {
5-
RslintResolutionError,
6-
type RslintResolutionErrorCode,
7-
} from './resolution';
4+
import { RslintResolutionError } from './resolution';
85

96
export class RslintVersionMismatchError extends Error {
107
constructor(message: string) {
@@ -16,27 +13,15 @@ export class RslintVersionMismatchError extends Error {
1613
/**
1714
* The package whose absence makes this failure the not-installed state the
1815
* three stacks report uniformly (AGENTS.md) — a `disabled` status and a
19-
* one-line warning, never a crash or a stack trace. `missing-rstack` is a
20-
* bridged folder without `rstack`; `missing-core` is a native folder on a
21-
* fresh clone (a bridged folder reaches it only when rstack's own dependency
22-
* is gone — an interrupted install, where installing is still the fix).
23-
* `missing-shim` stays an error: the package is there, its version is wrong.
24-
* A misconfigured `corePath` is `invalid-package`, also an error: the fix is
25-
* the setting, not an install.
16+
* one-line warning, never a crash or a stack trace. The verdict is the throw
17+
* site's, carried on the error (`RslintResolutionError.missingPackage`), so
18+
* a new resolution failure cannot silently fall through to `crashed` here by
19+
* missing a mapping.
2620
*/
27-
const NOT_INSTALLED_PACKAGE: Partial<
28-
Record<RslintResolutionErrorCode, SupportedPackage>
29-
> = {
30-
'missing-rstack': 'rstack',
31-
'missing-core': '@rslint/core',
32-
};
33-
3421
export const missingPackageOf = (
3522
error: unknown,
3623
): SupportedPackage | undefined =>
37-
error instanceof RslintResolutionError
38-
? NOT_INSTALLED_PACKAGE[error.code]
39-
: undefined;
24+
error instanceof RslintResolutionError ? error.missingPackage : undefined;
4025

4126
export const statusForRslintStartFailure = (error: unknown): StackState => {
4227
if (error instanceof RslintVersionMismatchError) {

packages/vscode/src/stacks/test/bridge.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ import { status } from './status';
4343
/** Relative to the `rstack` package root. Same file `rs test` injects. */
4444
const SHIM_RELATIVE_PATH = path.join('dist', 'rstestConfig.js');
4545

46+
// Both arguments are literals (`CORE_NOT_INSTALLED_STATUS` in master.ts is
47+
// the same shape), so the status is one string instead of one per refresh
48+
// pass.
49+
const RSTACK_NOT_INSTALLED_STATUS = formatNotInstalledStatus(
50+
'rstest',
51+
'rstack',
52+
);
53+
4654
export type RstackShim = {
4755
/** Absolute path of `<rstack>/dist/rstestConfig.js`. */
4856
readonly configFilePath: string;
@@ -89,10 +97,7 @@ export function resolveRstackShim(
8997
// status stays until this directory resolves or stops being a candidate.
9098
// A stale mismatch from a previously-present install is retired by the
9199
// latch itself (a package-state observation restates its root).
92-
status.notInstalled(
93-
formatNotInstalledStatus('rstest', 'rstack'),
94-
configDir,
95-
);
100+
status.notInstalled(RSTACK_NOT_INSTALLED_STATUS, configDir);
96101
return undefined;
97102
}
98103

@@ -130,10 +135,9 @@ export function resolveRstackShim(
130135
packageDirectory,
131136
version,
132137
});
133-
// At most one of the two latches can be live (a package-state observation
134-
// restates its root), so this repaints once, whichever failure the
135-
// previous pass observed.
138+
// One recovery observation: a passed version check proves the package is
139+
// installed, so `versionOk` retires whichever of the two latches the
140+
// previous pass left.
136141
status.versionOk(configDir);
137-
status.installed(configDir);
138142
return { configFilePath, packageDirectory, version };
139143
}
Lines changed: 20 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,40 @@
11
/**
2-
* Classifying and reporting failed Rstest resolutions — the host-side helpers
3-
* for a `@rstest/core` that cannot be resolved, and the worker-side
4-
* classifier for a config whose own import failed
5-
* (`missingDependencyCauseOf`).
2+
* Classifying and reporting failed Rstest worker setups — the host-side
3+
* helpers for a `@rstest/core` that cannot be resolved, and the
4+
* already-reported marker for any setup failure whose actionable state was
5+
* logged where it was observed. (The worker-side classifier for a config
6+
* whose own import failed is `shared/missingDependency.ts`.)
67
*
78
* Every message here replaces Node's own `MODULE_NOT_FOUND` text, which
8-
* embeds a multi-line require stack and says nothing about what to do; the
9-
* classifier also touches the filesystem once, to check whether a failing
10-
* subpath's package really is absent. An
9+
* embeds a multi-line require stack and says nothing about what to do. An
1110
* uninstalled core is the normal state of a freshly cloned repository and is
1211
* resolved for every config file without the user asking, so it is only
1312
* logged; a `rstestPackagePath` that does not resolve is a setting the user
1413
* has to fix, so it is notified.
1514
*/
1615

17-
import path from 'node:path';
18-
import { findPackageJsonUncached } from '../../shared/packageResolve';
16+
import { logger } from './logger';
1917

2018
/**
21-
* Resolution failed after the actionable error was already logged or shown.
22-
* Callers still reject so project initialization stops, but must not report the
23-
* same failure again.
19+
* The worker could not be set up, and the actionable state was already logged
20+
* or shown — a core that did not resolve, or a spawn refused because the
21+
* project directory is gone. Callers still reject so the operation stops, but
22+
* must not report the same failure again: catch sites log through
23+
* `logUnlessReported` below instead of re-deciding.
2424
*/
2525
export class ReportedRstestResolutionError extends Error {
26-
constructor() {
27-
super('Failed to resolve rstest path');
26+
constructor(message = 'Failed to resolve rstest path') {
27+
super(message);
2828
this.name = 'ReportedRstestResolutionError';
2929
}
3030
}
3131

32+
/** The catch-site half of the contract above. */
33+
export function logUnlessReported(message: string, error: unknown): void {
34+
if (error instanceof ReportedRstestResolutionError) return;
35+
logger.error(message, error);
36+
}
37+
3238
// Whether `specifier` itself is what could not be found. `MODULE_NOT_FOUND`
3339
// alone is too broad: a package that is installed but whose entry file is gone
3440
// (an interrupted install, or a workspace link that has not been built) throws
@@ -52,53 +58,3 @@ export function formatConfiguredCoreNotFoundMessage(
5258
): string {
5359
return `Cannot find "@rstest/core" at the configured "rstack.rstest.rstestPackagePath": ${configuredPackagePath}. Update the setting to point at an installed "@rstest/core" package.json.`;
5460
}
55-
56-
// The one-line cause when a config evaluation failed on a package that is
57-
// not installed, or `undefined` for a real error. Gated on the error's
58-
// `code` — Node's own classification (`ERR_MODULE_NOT_FOUND` for ESM,
59-
// `MODULE_NOT_FOUND` for CJS) — but the code alone is too broad: a typo'd
60-
// relative import fails with the same codes, and installing dependencies
61-
// cannot fix it, so only a bare specifier — a package name, read from the
62-
// message since CJS carries no structured one — counts, and anything
63-
// unrecognized fails towards the full error report. The check has to run in
64-
// the worker, where the error is thrown: the IPC channel back to the
65-
// extension host (`serialization: 'advanced'`) drops the `code`, so the
66-
// verdict travels as data (`NormalizedConfigResult`). Only the first line
67-
// comes back: the rest of a CJS message is the require stack, and the
68-
// not-installed state is one warn line without one.
69-
export function missingDependencyCauseOf(
70-
error: unknown,
71-
resolveFrom: string,
72-
): string | undefined {
73-
if (!(error instanceof Error)) return undefined;
74-
const { code } = error as NodeJS.ErrnoException;
75-
if (code !== 'ERR_MODULE_NOT_FOUND' && code !== 'MODULE_NOT_FOUND') {
76-
return undefined;
77-
}
78-
const [firstLine] = error.message.split('\n', 1);
79-
const specifier = /^Cannot find (?:package|module) '([^']+)'/.exec(
80-
firstLine,
81-
)?.[1];
82-
if (
83-
specifier === undefined ||
84-
specifier.startsWith('.') ||
85-
specifier.startsWith('file:') ||
86-
path.isAbsolute(specifier)
87-
) {
88-
return undefined;
89-
}
90-
// `installed-package/missing-subpath` wears the same bare shape, but the
91-
// package itself is there — installing dependencies cannot fix it either,
92-
// so a subpath is checked against the physical `node_modules` with the
93-
// same uncached walk-up every stack resolves packages with.
94-
const packageName = specifier.startsWith('@')
95-
? specifier.split('/').slice(0, 2).join('/')
96-
: specifier.split('/', 1)[0];
97-
if (
98-
packageName !== specifier &&
99-
findPackageJsonUncached(packageName, resolveFrom) !== undefined
100-
) {
101-
return undefined;
102-
}
103-
return firstLine;
104-
}

0 commit comments

Comments
 (0)