switch out chokidar for @parcel/watcher in dev cli (#148924)

After the recent changes to limit the dev-cli watcher to relevant
packages, the watcher started logging tons of unnecessary changes, and
in some cases breaking based on the state of the repo. I have seen this
happen with Chokidar before, and I'm not convinced we'll be able to fix
it, so instead I decided to swap it out with `@parcel/watcher`, which is
a conceptually simpler implementation that automatically batches changes
and watches an entire directory, rather than tons of unique
directories/files.

This new implementation is conceptually simpler, and because of the
design of the `@parcel/watcher` module I was pushed to reuse the
`RepoSourceClassifier` to determine if we should restart the server
based on a specific change. This means we now have a single source of
truth for test files and the like (the classifier will tell us if a file
is a test file, regardless of where it exists in the repo).

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Spencer 2023-01-13 17:42:13 -06:00 committed by GitHub
parent a94a1b620e
commit 99013bdab8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 107 additions and 538 deletions

View file

@ -826,6 +826,7 @@
"@mapbox/vector-tile": "1.3.1",
"@octokit/rest": "^16.35.0",
"@openpgp/web-stream-tools": "^0.0.10",
"@parcel/watcher": "^2.1.0",
"@storybook/addon-a11y": "^6.5.15",
"@storybook/addon-actions": "^6.5.15",
"@storybook/addon-controls": "^6.5.15",
@ -1025,7 +1026,6 @@
"backport": "^8.9.7",
"callsites": "^3.1.0",
"chance": "1.0.18",
"chokidar": "^3.5.3",
"chromedriver": "^108.0.0",
"clean-webpack-plugin": "^3.0.0",
"compression-webpack-plugin": "^4.0.0",

View file

@ -23,7 +23,7 @@ objects.
## `Watcher`
The `Watcher` manages a [chokidar](https://github.com/paulmillr/chokidar) instance to watch the
The `Watcher` manages a [@parcel/watcher](https://github.com/parcel-bundler/watcher) instance to watch the
server files, logs about file changes observed and provides an observable to the `DevServer` via
its `serverShouldRestart$()` method.

View file

@ -35,13 +35,6 @@ const { BasePathProxyServer } = jest.requireMock('./base_path_proxy_server');
jest.mock('@kbn/ci-stats-reporter');
const { CiStatsReporter } = jest.requireMock('@kbn/ci-stats-reporter');
jest.mock('./get_server_watch_paths', () => ({
getServerWatchPaths: jest.fn(() => ({
watchPaths: ['<mock watch paths>'],
ignorePaths: ['<mock ignore paths>'],
})),
}));
const mockBasePathProxy = {
targetPort: 9999,
basePath: '/foo/bar',
@ -142,15 +135,9 @@ it('passes correct args to sub-classes', () => {
Array [
Array [
Object {
"cwd": <absolute path>,
"enabled": true,
"ignore": Array [
"<mock ignore paths>",
],
"log": <TestLog>,
"paths": Array [
"<mock watch paths>",
],
"repoRoot": <absolute path>,
},
],
]

View file

@ -31,7 +31,6 @@ import { DevServer } from './dev_server';
import { Watcher } from './watcher';
import { BasePathProxyServer } from './base_path_proxy_server';
import { shouldRedirectFromOldBasePath } from './should_redirect_from_old_base_path';
import { getServerWatchPaths } from './get_server_watch_paths';
import { CliDevConfig } from './config';
// signal that emits undefined once a termination signal has been sent
@ -114,18 +113,10 @@ export class CliDevMode {
this.basePathProxy = new BasePathProxyServer(this.log, config.http, config.dev);
}
const { watchPaths, ignorePaths } = getServerWatchPaths({
runExamples: cliArgs.runExamples,
pluginPaths: config.plugins.additionalPluginPaths,
pluginScanDirs: config.plugins.pluginSearchPaths,
});
this.watcher = new Watcher({
enabled: !!cliArgs.watch,
log: this.log,
cwd: REPO_ROOT,
paths: watchPaths,
ignore: ignorePaths,
repoRoot: REPO_ROOT,
});
this.devServer = new DevServer({

View file

@ -1,140 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import Path from 'path';
import { createAbsolutePathSerializer } from '@kbn/jest-serializers';
import { REPO_ROOT } from '@kbn/repo-info';
import type { KibanaPackageType } from '@kbn/repo-packages';
const TYPES = Object.keys(
(() => {
const asObj: { [k in KibanaPackageType]: true } = {
'functional-tests': true,
'plugin-browser': true,
'plugin-server': true,
'shared-browser': true,
'shared-common': true,
'shared-scss': true,
'shared-server': true,
'test-helper': true,
};
return asObj;
})()
);
import { getServerWatchPaths } from './get_server_watch_paths';
jest.mock('@kbn/repo-packages', () => ({
getPackages: jest.fn(),
getPluginPackagesFilter: jest.fn().mockReturnValue(() => true),
}));
const mockGetPluginPackagesFilter = jest.requireMock('@kbn/repo-packages').getPluginPackagesFilter;
const mockGetPackages = jest.requireMock('@kbn/repo-packages').getPackages;
expect.addSnapshotSerializer(createAbsolutePathSerializer());
it('produces the right watch and ignore list', () => {
mockGetPackages.mockReturnValue(
TYPES.flatMap((type) => ({
isPlugin: type.startsWith('plugin-'),
directory: Path.resolve(REPO_ROOT, 'packages', type),
manifest: {
type,
},
}))
);
const { watchPaths, ignorePaths } = getServerWatchPaths({
runExamples: false,
pluginPaths: [Path.resolve(REPO_ROOT, 'x-pack/test/plugin_functional/plugins/resolver_test')],
pluginScanDirs: [
Path.resolve(REPO_ROOT, 'src/plugins'),
Path.resolve(REPO_ROOT, 'test/plugin_functional/plugins'),
Path.resolve(REPO_ROOT, 'x-pack/plugins'),
],
});
expect(watchPaths).toMatchInlineSnapshot(`
Array [
<absolute path>/src/core,
<absolute path>/config,
<absolute path>/x-pack/test/plugin_functional/plugins/resolver_test,
<absolute path>/src/plugins,
<absolute path>/test/plugin_functional/plugins,
<absolute path>/x-pack/plugins,
<absolute path>/packages/plugin-server,
<absolute path>/packages/shared-common,
<absolute path>/packages/shared-server,
]
`);
expect(ignorePaths).toMatchInlineSnapshot(`
Array [
/\\[\\\\\\\\\\\\/\\]\\(\\\\\\.\\.\\*\\|node_modules\\|bower_components\\|target\\|public\\|__\\[a-z0-9_\\]\\+__\\|coverage\\)\\(\\[\\\\\\\\\\\\/\\]\\|\\$\\)/,
/\\\\\\.\\(test\\|spec\\)\\\\\\.\\(js\\|ts\\|tsx\\)\\$/,
/\\\\\\.\\(md\\|sh\\|txt\\)\\$/,
/debug\\\\\\.log\\$/,
<absolute path>/src/plugins/*/test/**,
<absolute path>/src/plugins/*/integration_tests/**,
<absolute path>/src/plugins/*/build/**,
<absolute path>/src/plugins/*/target/**,
<absolute path>/src/plugins/*/scripts/**,
<absolute path>/src/plugins/*/docs/**,
<absolute path>/test/plugin_functional/plugins/*/test/**,
<absolute path>/test/plugin_functional/plugins/*/integration_tests/**,
<absolute path>/test/plugin_functional/plugins/*/build/**,
<absolute path>/test/plugin_functional/plugins/*/target/**,
<absolute path>/test/plugin_functional/plugins/*/scripts/**,
<absolute path>/test/plugin_functional/plugins/*/docs/**,
<absolute path>/x-pack/plugins/*/test/**,
<absolute path>/x-pack/plugins/*/integration_tests/**,
<absolute path>/x-pack/plugins/*/build/**,
<absolute path>/x-pack/plugins/*/target/**,
<absolute path>/x-pack/plugins/*/scripts/**,
<absolute path>/x-pack/plugins/*/docs/**,
<absolute path>/x-pack/test/plugin_functional/plugins/resolver_test/test/**,
<absolute path>/x-pack/test/plugin_functional/plugins/resolver_test/integration_tests/**,
<absolute path>/x-pack/test/plugin_functional/plugins/resolver_test/build/**,
<absolute path>/x-pack/test/plugin_functional/plugins/resolver_test/target/**,
<absolute path>/x-pack/test/plugin_functional/plugins/resolver_test/scripts/**,
<absolute path>/x-pack/test/plugin_functional/plugins/resolver_test/docs/**,
<absolute path>/x-pack/plugins/screenshotting/chromium,
<absolute path>/x-pack/plugins/security_solution/cypress,
<absolute path>/x-pack/plugins/apm/scripts,
<absolute path>/x-pack/plugins/apm/ftr_e2e,
<absolute path>/x-pack/plugins/canvas/canvas_plugin_src,
<absolute path>/x-pack/plugins/cases/server/scripts,
<absolute path>/x-pack/plugins/lists/scripts,
<absolute path>/x-pack/plugins/lists/server/scripts,
<absolute path>/x-pack/plugins/security_solution/scripts,
<absolute path>/x-pack/plugins/security_solution/server/lib/detection_engine/scripts,
<absolute path>/x-pack/plugins/synthetics/e2e,
<absolute path>/x-pack/plugins/ux/e2e,
<absolute path>/x-pack/plugins/observability/e2e,
]
`);
expect(mockGetPluginPackagesFilter.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
Object {
"examples": false,
"parentDirs": Array [
<absolute path>/src/plugins,
<absolute path>/test/plugin_functional/plugins,
<absolute path>/x-pack/plugins,
],
"paths": Array [
<absolute path>/x-pack/test/plugin_functional/plugins/resolver_test,
],
},
],
]
`);
});

View file

@ -1,94 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import Path from 'path';
import { REPO_ROOT } from '@kbn/repo-info';
import { getPackages, getPluginPackagesFilter } from '@kbn/repo-packages';
interface Options {
runExamples: boolean;
pluginPaths: string[];
pluginScanDirs: string[];
}
export type WatchPaths = ReturnType<typeof getServerWatchPaths>;
export function getServerWatchPaths(opts: Options) {
const fromRoot = (p: string) => Path.resolve(REPO_ROOT, p);
const pluginInternalDirsIgnore = opts.pluginScanDirs
.map((scanDir) => Path.resolve(scanDir, '*'))
.concat(opts.pluginPaths)
.reduce(
(acc: string[], path) => [
...acc,
Path.resolve(path, 'test/**'),
Path.resolve(path, 'integration_tests/**'),
Path.resolve(path, 'build/**'),
Path.resolve(path, 'target/**'),
Path.resolve(path, 'scripts/**'),
Path.resolve(path, 'docs/**'),
],
[]
);
function getServerPkgDirs() {
const pluginFilter = getPluginPackagesFilter({
examples: opts.runExamples,
paths: opts.pluginPaths,
parentDirs: opts.pluginScanDirs,
});
return getPackages(REPO_ROOT).flatMap((p) => {
if (p.isPlugin) {
return pluginFilter(p) && p.manifest.type === 'plugin-server' ? p.directory : [];
}
return p.manifest.type === 'shared-common' || p.manifest.type === 'shared-server'
? p.directory
: [];
});
}
const watchPaths = Array.from(
new Set([
fromRoot('src/core'),
fromRoot('config'),
...opts.pluginPaths.map((path) => Path.resolve(path)),
...opts.pluginScanDirs.map((path) => Path.resolve(path)),
...getServerPkgDirs(),
])
);
const ignorePaths = [
/[\\\/](\..*|node_modules|bower_components|target|public|__[a-z0-9_]+__|coverage)([\\\/]|$)/,
/\.(test|spec)\.(js|ts|tsx)$/,
/\.(md|sh|txt)$/,
/debug\.log$/,
...pluginInternalDirsIgnore,
fromRoot('x-pack/plugins/screenshotting/chromium'),
fromRoot('x-pack/plugins/security_solution/cypress'),
fromRoot('x-pack/plugins/apm/scripts'),
fromRoot('x-pack/plugins/apm/ftr_e2e'), // prevents restarts for APM cypress tests
fromRoot('x-pack/plugins/canvas/canvas_plugin_src'), // prevents server from restarting twice for Canvas plugin changes,
fromRoot('x-pack/plugins/cases/server/scripts'),
fromRoot('x-pack/plugins/lists/scripts'),
fromRoot('x-pack/plugins/lists/server/scripts'),
fromRoot('x-pack/plugins/security_solution/scripts'),
fromRoot('x-pack/plugins/security_solution/server/lib/detection_engine/scripts'),
fromRoot('x-pack/plugins/synthetics/e2e'),
fromRoot('x-pack/plugins/ux/e2e'),
fromRoot('x-pack/plugins/observability/e2e'),
];
return {
watchPaths,
ignorePaths,
};
}

View file

@ -1,207 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EventEmitter } from 'events';
import * as Rx from 'rxjs';
import { materialize, toArray } from 'rxjs/operators';
import { TestLog } from './log';
import { Watcher, Options } from './watcher';
class MockChokidar extends EventEmitter {
close = jest.fn();
}
let mockChokidar: MockChokidar | undefined;
jest.mock('chokidar');
const chokidar = jest.requireMock('chokidar');
function isMock(mock: MockChokidar | undefined): asserts mock is MockChokidar {
expect(mock).toBeInstanceOf(MockChokidar);
}
chokidar.watch.mockImplementation(() => {
mockChokidar = new MockChokidar();
return mockChokidar;
});
const subscriptions: Rx.Subscription[] = [];
const run = (watcher: Watcher) => {
const subscription = watcher.run$.subscribe({
error(e) {
throw e;
},
});
subscriptions.push(subscription);
return subscription;
};
const log = new TestLog();
const defaultOptions: Options = {
enabled: true,
log,
paths: ['foo.js', 'bar.js'],
ignore: [/^f/],
cwd: '/app/repo',
};
afterEach(() => {
jest.clearAllMocks();
if (mockChokidar) {
mockChokidar.removeAllListeners();
mockChokidar = undefined;
}
for (const sub of subscriptions) {
sub.unsubscribe();
}
subscriptions.length = 0;
log.messages.length = 0;
});
it('completes restart streams immediately when disabled', () => {
const watcher = new Watcher({
...defaultOptions,
enabled: false,
});
const restart$ = new Rx.BehaviorSubject<void>(undefined);
subscriptions.push(watcher.serverShouldRestart$().subscribe(restart$));
run(watcher);
expect(restart$.isStopped).toBe(true);
});
it('calls chokidar.watch() with expected arguments', () => {
const watcher = new Watcher(defaultOptions);
expect(chokidar.watch).not.toHaveBeenCalled();
run(watcher);
expect(chokidar.watch.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
Array [
"foo.js",
"bar.js",
],
Object {
"cwd": "/app/repo",
"ignored": Array [
/\\^f/,
],
},
],
]
`);
});
it('closes chokidar watcher when unsubscribed', () => {
const sub = run(new Watcher(defaultOptions));
isMock(mockChokidar);
expect(mockChokidar.close).not.toHaveBeenCalled();
sub.unsubscribe();
expect(mockChokidar.close).toHaveBeenCalledTimes(1);
});
it('rethrows chokidar errors', async () => {
const watcher = new Watcher(defaultOptions);
const promise = Rx.firstValueFrom(watcher.run$.pipe(materialize(), toArray()));
isMock(mockChokidar);
mockChokidar.emit('error', new Error('foo bar'));
const notifications = await promise;
expect(notifications).toMatchInlineSnapshot(`
Array [
Notification {
"error": [Error: foo bar],
"hasValue": false,
"kind": "E",
"value": undefined,
},
]
`);
});
it('logs the count of add events after the ready event', () => {
run(new Watcher(defaultOptions));
isMock(mockChokidar);
mockChokidar.emit('add');
mockChokidar.emit('add');
mockChokidar.emit('add');
mockChokidar.emit('add');
mockChokidar.emit('ready');
expect(log.messages).toMatchInlineSnapshot(`
Array [
Object {
"args": Array [
"watching for changes",
"(4 files)",
],
"type": "good",
},
]
`);
});
it('buffers subsequent changes before logging and notifying serverShouldRestart$', async () => {
const watcher = new Watcher(defaultOptions);
const history: any[] = [];
subscriptions.push(
watcher
.serverShouldRestart$()
.pipe(materialize())
.subscribe((n) => history.push(n))
);
run(watcher);
expect(history).toMatchInlineSnapshot(`Array []`);
isMock(mockChokidar);
mockChokidar.emit('ready');
mockChokidar.emit('all', ['add', 'foo.js']);
mockChokidar.emit('all', ['add', 'bar.js']);
mockChokidar.emit('all', ['delete', 'bar.js']);
await new Promise((resolve) => setTimeout(resolve, 1000));
expect(log.messages).toMatchInlineSnapshot(`
Array [
Object {
"args": Array [
"watching for changes",
"(0 files)",
],
"type": "good",
},
Object {
"args": Array [
"restarting server",
"due to changes in
- \\"foo.js\\"
- \\"bar.js\\"",
],
"type": "warn",
},
]
`);
expect(history).toMatchInlineSnapshot(`
Array [
Notification {
"error": undefined,
"hasValue": true,
"kind": "N",
"value": undefined,
},
]
`);
});

View file

@ -7,44 +7,48 @@
*/
import * as Rx from 'rxjs';
import {
map,
tap,
takeUntil,
count,
share,
buffer,
debounceTime,
ignoreElements,
} from 'rxjs/operators';
import Chokidar from 'chokidar';
import Pw from '@parcel/watcher';
import { REPO_ROOT } from '@kbn/repo-info';
import { RepoSourceClassifier } from '@kbn/repo-source-classifier';
import { ImportResolver } from '@kbn/import-resolver';
import { makeMatcher } from '@kbn/picomatcher';
import { Log } from './log';
const packageMatcher = makeMatcher(['**/*', '!**/.*']);
/**
* Any non-package code must match this in order to trigger a restart
*/
const nonPackageMatcher = makeMatcher([
'src/**',
'!src/{dev,fixtures}/**',
'x-pack/plugins/**',
'!x-pack/plugins/screenshotting/chromium',
'!x-pack/plugins/canvas/canvas_plugin_src',
]);
export interface Options {
enabled: boolean;
log: Log;
paths: string[];
ignore: Array<string | RegExp>;
cwd: string;
repoRoot: string;
}
export class Watcher {
public readonly enabled: boolean;
private readonly log: Log;
private readonly paths: string[];
private readonly ignore: Array<string | RegExp>;
private readonly cwd: string;
private readonly repoRoot: string;
private readonly classifier: RepoSourceClassifier;
private readonly restart$ = new Rx.Subject<void>();
private readonly resolver: ImportResolver;
constructor(options: Options) {
this.enabled = !!options.enabled;
this.log = options.log;
this.paths = options.paths;
this.ignore = options.ignore;
this.cwd = options.cwd;
this.repoRoot = options.repoRoot;
this.resolver = ImportResolver.create(REPO_ROOT);
this.classifier = new RepoSourceClassifier(this.resolver);
}
run$ = new Rx.Observable((subscriber) => {
@ -54,54 +58,56 @@ export class Watcher {
return;
}
const chokidar = Chokidar.watch(this.paths, {
cwd: this.cwd,
ignored: this.ignore,
});
const fire = (repoRel: string) => {
this.log.warn(`restarting server`, `due to changes in ${repoRel}`);
this.restart$.next();
};
subscriber.add(() => {
chokidar.close();
});
Pw.subscribe(
this.repoRoot,
(error, events) => {
if (error) {
subscriber.error(error);
return;
}
const error$ = Rx.fromEvent(chokidar, 'error').pipe(
map((error) => {
throw error;
})
);
for (const event of events) {
const pkg = this.resolver.getPackageManifestForPath(event.path);
const init$ = Rx.fromEvent(chokidar, 'add').pipe(
takeUntil(Rx.fromEvent(chokidar, 'ready')),
count(),
tap((fileCount) => {
this.log.good('watching for changes', `(${fileCount} files)`);
})
);
// ignore changes in any devOnly package, these can't power the server so we can ignore them
if (pkg?.devOnly) {
return;
}
const change$ = Rx.fromEvent<[string, string]>(chokidar, 'all').pipe(
map(([, path]) => path),
share()
);
subscriber.add(
Rx.merge(
error$,
Rx.concat(
init$,
change$.pipe(
buffer(change$.pipe(debounceTime(50))),
map((changes) => {
const paths = Array.from(new Set(changes));
const prefix = paths.length > 1 ? '\n - ' : ' ';
const fileList = paths.reduce((list, file) => `${list || ''}${prefix}"${file}"`, '');
this.log.warn(`restarting server`, `due to changes in${fileList}`);
this.restart$.next();
})
)
)
)
.pipe(ignoreElements())
.subscribe(subscriber)
const result = this.classifier.classify(event.path);
if (result.type === 'common package' || result.type === 'server package') {
return packageMatcher(result.repoRel) && fire(result.repoRel);
}
if (result.type === 'non-package') {
return nonPackageMatcher(result.repoRel) && fire(result.repoRel);
}
}
},
{
// some basic high-level ignore statements. Additional filtering is done above
// before paths are passed to `fire()`, using the RepoSourceClassifier mostly
ignore: [
'**/{node_modules,target,public,coverage,__*__}/**',
'**/*.{test,spec,story,stories}.*',
'**/*.{md,sh,txt}',
'**/debug.log',
],
}
).then(
(sub) => {
this.log.good('watching server files for changes');
subscriber.add(() => {
sub.unsubscribe();
});
},
(error) => {
subscriber.error(error);
}
);
// complete state subjects when run$ completes

View file

@ -21,7 +21,9 @@
"@kbn/jest-serializers",
"@kbn/stdio-dev-helpers",
"@kbn/tooling-log",
"@kbn/repo-packages",
"@kbn/repo-source-classifier",
"@kbn/import-resolver",
"@kbn/picomatcher",
],
"exclude": [
"target/**/*",

View file

@ -107,6 +107,11 @@ export class ImportResolver {
return this.pkgIdForDir(Path.dirname(relative));
}
getPackageManifestForPath(path: string) {
const pkgId = this.getPackageIdForPath(path);
return pkgId ? this.getPkgManifest(pkgId) : undefined;
}
getAbsolutePackageDir(pkgId: string) {
const dir = this.pkgMap.get(pkgId);
return dir ? Path.resolve(this.cwd, dir) : null;

View file

@ -16,4 +16,6 @@ export interface ModuleId {
repoRel: string;
/** info about the package the source file is within, in the case the file is found within a package */
pkgInfo?: PkgInfo;
/** path segments of the dirname of this */
dirs: string[];
}

View file

@ -60,10 +60,12 @@ export class RepoPath {
}
private segs: string[] | undefined;
/** get and cache the path segments from the repo-realtive versions of this path */
/** get and cache the path segments from the repo-realtive dirname of this path */
getSegs() {
if (this.segs === undefined) {
this.segs = Path.dirname(this.getRepoRel()).split('/');
this.segs = Path.dirname(this.getRepoRel())
.split('/')
.filter((s) => s !== '.');
}
return this.segs;

View file

@ -12,6 +12,10 @@ import { ModuleType } from './module_type';
import { RANDOM_TEST_FILE_NAMES, TEST_DIR, TEST_TAG } from './config';
import { RepoPath } from './repo_path';
const STATIC_EXTS = new Set(
'json|woff|woff2|ttf|eot|svg|ico|png|jpg|gif|jpeg|html|md|txt|tmpl'.split('|').map((e) => `.${e}`)
);
export class RepoSourceClassifier {
constructor(private readonly resolver: ImportResolver) {}
@ -124,7 +128,7 @@ export class RepoSourceClassifier {
* Determine the "type" of a file
*/
private getType(path: RepoPath): ModuleType {
if (path.getExtname() === '.json') {
if (STATIC_EXTS.has(path.getExtname())) {
return 'static';
}
@ -214,6 +218,7 @@ export class RepoSourceClassifier {
type: this.getType(path),
repoRel: path.getRepoRel(),
pkgInfo: path.getPkgInfo() ?? undefined,
dirs: path.getSegs(),
};
this.ids.set(path, id);
return id;

View file

@ -5060,6 +5060,16 @@
resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.4.0.tgz#facf2c67d6063b9918d5a5e3fdf25f3a30d547b6"
integrity sha512-Hzl8soGpmyzja9w3kiFFcYJ7n5HNETpplY6cb67KR4QPlxp4FTTresO06qXHgHDhyIInmbLJXuwARjjpsKYGuQ==
"@parcel/watcher@^2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.1.0.tgz#5f32969362db4893922c526a842d8af7a8538545"
integrity sha512-8s8yYjd19pDSsBpbkOHnT6Z2+UJSuLQx61pCFM0s5wSRvKCEMDjd/cHY3/GI1szHIWbpXpsJdg3V6ISGGx9xDw==
dependencies:
is-glob "^4.0.3"
micromatch "^4.0.5"
node-addon-api "^3.2.1"
node-gyp-build "^4.3.0"
"@pmmmwh/react-refresh-webpack-plugin@^0.5.3":
version "0.5.7"
resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.7.tgz#58f8217ba70069cc6a73f5d7e05e85b458c150e2"
@ -20448,7 +20458,7 @@ node-abi@^3.3.0:
dependencies:
semver "^7.3.5"
node-addon-api@^3.0.0:
node-addon-api@^3.0.0, node-addon-api@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161"
integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==
@ -20506,7 +20516,7 @@ node-gyp-build-optional-packages@5.0.3:
resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.3.tgz#92a89d400352c44ad3975010368072b41ad66c17"
integrity sha512-k75jcVzk5wnnc/FMxsf4udAoTEUv2jY3ycfdSd3yWu6Cnd1oee6/CfZJApyscA4FJOmdoixWwiwOyf16RzD5JA==
node-gyp-build@^4.2.2, node-gyp-build@^4.2.3:
node-gyp-build@^4.2.2, node-gyp-build@^4.2.3, node-gyp-build@^4.3.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.5.0.tgz#7a64eefa0b21112f89f58379da128ac177f20e40"
integrity sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==