@ontrails/warden 1.0.0-beta.46 → 1.0.0-beta.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/package.json +10 -10
- package/src/cli.ts +97 -62
- package/src/rules/retired-vocabulary.ts +10 -0
- package/src/workspaces.ts +71 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @ontrails/warden
|
|
2
2
|
|
|
3
|
+
## 1.0.0-beta.47
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`ea2f296`](https://github.com/outfitter-dev/trails/commit/ea2f296e8119cf79bd03aeca3f0aa804f2fbcdd6): Classify adjacent package-route variants in the authored Warden AST regression
|
|
8
|
+
fixtures as explicit preserves so the completed transition remains auditable.
|
|
9
|
+
- [`90d394c`](https://github.com/outfitter-dev/trails/commit/90d394c005fdf6b898ba7052d0b56755af0f4954): Derive nested worktree, repository, and submodule collection boundaries in the
|
|
10
|
+
shared Source walker. Regrade and Warden now observe one directly targeted
|
|
11
|
+
working tree per run, and Regrade audit summaries expose boundary skip counts.
|
|
12
|
+
|
|
3
13
|
## 1.0.0-beta.46
|
|
4
14
|
|
|
5
15
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ontrails/warden",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.47",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/outfitter-dev/trails.git",
|
|
@@ -32,20 +32,20 @@
|
|
|
32
32
|
"clean": "rm -rf dist *.tsbuildinfo"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@ontrails/adapter-kit": "^1.0.0-beta.
|
|
36
|
-
"@ontrails/cli": "^1.0.0-beta.
|
|
37
|
-
"@ontrails/config": "^1.0.0-beta.
|
|
38
|
-
"@ontrails/permits": "^1.0.0-beta.
|
|
39
|
-
"@ontrails/source": "^1.0.0-beta.
|
|
40
|
-
"@ontrails/store": "^1.0.0-beta.
|
|
35
|
+
"@ontrails/adapter-kit": "^1.0.0-beta.47",
|
|
36
|
+
"@ontrails/cli": "^1.0.0-beta.47",
|
|
37
|
+
"@ontrails/config": "^1.0.0-beta.47",
|
|
38
|
+
"@ontrails/permits": "^1.0.0-beta.47",
|
|
39
|
+
"@ontrails/source": "^1.0.0-beta.47",
|
|
40
|
+
"@ontrails/store": "^1.0.0-beta.47",
|
|
41
41
|
"oxc-resolver": "11.19.1",
|
|
42
42
|
"zod": "^4.3.5"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@ontrails/testing": "^1.0.0-beta.
|
|
45
|
+
"@ontrails/testing": "^1.0.0-beta.47"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
|
-
"@ontrails/core": "^1.0.0-beta.
|
|
49
|
-
"@ontrails/topography": "^1.0.0-beta.
|
|
48
|
+
"@ontrails/core": "^1.0.0-beta.47",
|
|
49
|
+
"@ontrails/topography": "^1.0.0-beta.47"
|
|
50
50
|
}
|
|
51
51
|
}
|
package/src/cli.ts
CHANGED
|
@@ -63,7 +63,12 @@ import {
|
|
|
63
63
|
collectCrudTableIds as collectCrudTableIdsFromAst,
|
|
64
64
|
collectReconcileTableIds as collectReconcileTableIdsFromAst,
|
|
65
65
|
} from './rules/source/stores.js';
|
|
66
|
-
import {
|
|
66
|
+
import {
|
|
67
|
+
collectSourceTree,
|
|
68
|
+
findTrailDefinitions,
|
|
69
|
+
parse,
|
|
70
|
+
} from '@ontrails/source';
|
|
71
|
+
import type { SourceTreeCollection } from '@ontrails/source';
|
|
67
72
|
import { collectFileCrudCoverage } from './rules/incomplete-crud.js';
|
|
68
73
|
import { wardenRules, wardenTopoRules } from './rules/index.js';
|
|
69
74
|
import { getWardenRuleMetadata } from './rules/metadata.js';
|
|
@@ -238,29 +243,28 @@ export interface WardenReport {
|
|
|
238
243
|
* surfaces that should not contribute most committed-source diagnostics.
|
|
239
244
|
*/
|
|
240
245
|
const collectFilesMatching = (
|
|
241
|
-
|
|
246
|
+
collection: NonNullable<ReturnType<typeof collectSourceTree>>,
|
|
242
247
|
pattern: string,
|
|
243
248
|
dot = false
|
|
244
249
|
): readonly string[] => {
|
|
245
250
|
const glob = new Bun.Glob(pattern);
|
|
246
|
-
let matches: IterableIterator<string>;
|
|
247
|
-
try {
|
|
248
|
-
matches = glob.scanSync({ cwd: dir, dot, onlyFiles: true });
|
|
249
|
-
} catch {
|
|
250
|
-
return [];
|
|
251
|
-
}
|
|
252
|
-
|
|
253
251
|
const files: string[] = [];
|
|
254
|
-
for (const
|
|
255
|
-
if (
|
|
256
|
-
|
|
252
|
+
for (const file of collection.files) {
|
|
253
|
+
if (
|
|
254
|
+
(dot ||
|
|
255
|
+
!file.path.split('/').some((segment) => segment.startsWith('.'))) &&
|
|
256
|
+
glob.match(file.path) &&
|
|
257
|
+
isWardenSourceScanTarget(file.path)
|
|
258
|
+
) {
|
|
259
|
+
files.push(file.absolutePath);
|
|
257
260
|
}
|
|
258
261
|
}
|
|
259
262
|
return files;
|
|
260
263
|
};
|
|
261
264
|
|
|
262
|
-
const collectTsFiles = (
|
|
263
|
-
|
|
265
|
+
const collectTsFiles = (
|
|
266
|
+
collection: NonNullable<ReturnType<typeof collectSourceTree>>
|
|
267
|
+
): readonly string[] => collectFilesMatching(collection, '**/*.ts');
|
|
264
268
|
|
|
265
269
|
const draftModeIncludesFile = (
|
|
266
270
|
filePath: string,
|
|
@@ -309,41 +313,42 @@ const filterSourceFilesByScope = (
|
|
|
309
313
|
|
|
310
314
|
const EMPTY_WARDEN_SCOPE: WardenScope = { exclude: [] };
|
|
311
315
|
|
|
312
|
-
const collectDevPermitTestFiles = (
|
|
316
|
+
const collectDevPermitTestFiles = (
|
|
317
|
+
collection: NonNullable<ReturnType<typeof collectSourceTree>>
|
|
318
|
+
): readonly string[] => {
|
|
313
319
|
const glob = new Bun.Glob('**/*.ts');
|
|
314
|
-
let matches: IterableIterator<string>;
|
|
315
|
-
try {
|
|
316
|
-
matches = glob.scanSync({ cwd: dir, onlyFiles: true });
|
|
317
|
-
} catch {
|
|
318
|
-
return [];
|
|
319
|
-
}
|
|
320
|
-
|
|
321
320
|
const files: string[] = [];
|
|
322
|
-
for (const
|
|
323
|
-
if (
|
|
324
|
-
|
|
321
|
+
for (const file of collection.files) {
|
|
322
|
+
if (
|
|
323
|
+
!file.path.split('/').some((segment) => segment.startsWith('.')) &&
|
|
324
|
+
glob.match(file.path) &&
|
|
325
|
+
isWardenDevPermitTestScanTarget(file.path)
|
|
326
|
+
) {
|
|
327
|
+
files.push(file.absolutePath);
|
|
325
328
|
}
|
|
326
329
|
}
|
|
327
330
|
return files;
|
|
328
331
|
};
|
|
329
332
|
|
|
330
|
-
const collectTextScanFiles = (
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
...collectFilesMatching(
|
|
334
|
-
...collectFilesMatching(
|
|
335
|
-
...collectFilesMatching(
|
|
336
|
-
...collectFilesMatching(
|
|
337
|
-
...collectFilesMatching(
|
|
338
|
-
...collectFilesMatching(
|
|
339
|
-
...collectFilesMatching(
|
|
340
|
-
...collectFilesMatching(
|
|
341
|
-
...collectFilesMatching(
|
|
342
|
-
...collectFilesMatching(
|
|
343
|
-
...collectFilesMatching(
|
|
344
|
-
...collectFilesMatching(
|
|
345
|
-
...collectFilesMatching(
|
|
346
|
-
...collectFilesMatching(
|
|
333
|
+
const collectTextScanFiles = (
|
|
334
|
+
collection: NonNullable<ReturnType<typeof collectSourceTree>>
|
|
335
|
+
): readonly string[] => [
|
|
336
|
+
...collectFilesMatching(collection, '**/*.sh', true),
|
|
337
|
+
...collectFilesMatching(collection, '**/*.bash', true),
|
|
338
|
+
...collectFilesMatching(collection, '**/*.zsh', true),
|
|
339
|
+
...collectFilesMatching(collection, '**/*.yml', true),
|
|
340
|
+
...collectFilesMatching(collection, '**/*.yaml', true),
|
|
341
|
+
...collectFilesMatching(collection, 'plugin/**/*.json', true),
|
|
342
|
+
...collectFilesMatching(collection, 'plugin/**/*.jsonc', true),
|
|
343
|
+
...collectFilesMatching(collection, 'plugin/**/*.toml', true),
|
|
344
|
+
...collectFilesMatching(collection, 'plugin/**/*.cjs', true),
|
|
345
|
+
...collectFilesMatching(collection, 'plugin/**/*.mjs', true),
|
|
346
|
+
...collectFilesMatching(collection, 'scripts/**/*.json', true),
|
|
347
|
+
...collectFilesMatching(collection, 'scripts/**/*.jsonc', true),
|
|
348
|
+
...collectFilesMatching(collection, 'scripts/**/*.toml', true),
|
|
349
|
+
...collectFilesMatching(collection, 'scripts/**/*.cjs', true),
|
|
350
|
+
...collectFilesMatching(collection, 'scripts/**/*.mjs', true),
|
|
351
|
+
...collectFilesMatching(collection, '**/package.json', true),
|
|
347
352
|
];
|
|
348
353
|
|
|
349
354
|
const isDocumentationScanTarget = (match: string): boolean => {
|
|
@@ -367,19 +372,19 @@ const isDocumentationScanTarget = (match: string): boolean => {
|
|
|
367
372
|
);
|
|
368
373
|
};
|
|
369
374
|
|
|
370
|
-
const collectDocumentationFiles = (
|
|
375
|
+
const collectDocumentationFiles = (
|
|
376
|
+
collection: NonNullable<ReturnType<typeof collectSourceTree>>
|
|
377
|
+
): readonly string[] => {
|
|
371
378
|
const glob = new Bun.Glob('**/*.md');
|
|
372
|
-
let matches: IterableIterator<string>;
|
|
373
|
-
try {
|
|
374
|
-
matches = glob.scanSync({ cwd: dir, onlyFiles: true });
|
|
375
|
-
} catch {
|
|
376
|
-
return [];
|
|
377
|
-
}
|
|
378
|
-
|
|
379
379
|
const files: string[] = [];
|
|
380
|
-
for (const
|
|
381
|
-
if (
|
|
382
|
-
|
|
380
|
+
for (const file of collection.files) {
|
|
381
|
+
if (
|
|
382
|
+
!file.path.split('/').some((segment) => segment.startsWith('.')) &&
|
|
383
|
+
glob.match(file.path) &&
|
|
384
|
+
isWardenSourceScanTarget(file.path) &&
|
|
385
|
+
isDocumentationScanTarget(file.path)
|
|
386
|
+
) {
|
|
387
|
+
files.push(file.absolutePath);
|
|
383
388
|
}
|
|
384
389
|
}
|
|
385
390
|
return files;
|
|
@@ -692,12 +697,34 @@ const collectReconcileTableIds = (
|
|
|
692
697
|
}
|
|
693
698
|
};
|
|
694
699
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
700
|
+
interface LoadedSourceFiles {
|
|
701
|
+
readonly collection: SourceTreeCollection | null;
|
|
702
|
+
readonly sourceFiles: readonly SourceFile[];
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const loadSourceFiles = async (rootDir: string): Promise<LoadedSourceFiles> => {
|
|
698
706
|
const sourceFiles: SourceFile[] = [];
|
|
707
|
+
const collection = collectSourceTree(rootDir, {
|
|
708
|
+
classify: (entry) => {
|
|
709
|
+
if (
|
|
710
|
+
entry.kind === 'directory' &&
|
|
711
|
+
['.git', 'dist', 'node_modules'].includes(entry.name)
|
|
712
|
+
) {
|
|
713
|
+
return { action: 'skip', reason: 'ignored-directory' };
|
|
714
|
+
}
|
|
715
|
+
if (entry.kind === 'directory') {
|
|
716
|
+
return { action: 'recurse' };
|
|
717
|
+
}
|
|
718
|
+
return entry.kind === 'file'
|
|
719
|
+
? { action: 'collect' }
|
|
720
|
+
: { action: 'skip', reason: 'unsupported-entry' };
|
|
721
|
+
},
|
|
722
|
+
});
|
|
723
|
+
if (!collection) {
|
|
724
|
+
return { collection, sourceFiles };
|
|
725
|
+
}
|
|
699
726
|
|
|
700
|
-
for (const filePath of collectTsFiles(
|
|
727
|
+
for (const filePath of collectTsFiles(collection)) {
|
|
701
728
|
try {
|
|
702
729
|
sourceFiles.push({
|
|
703
730
|
filePath,
|
|
@@ -709,7 +736,7 @@ const loadSourceFiles = async (
|
|
|
709
736
|
}
|
|
710
737
|
}
|
|
711
738
|
|
|
712
|
-
for (const filePath of collectTextScanFiles(
|
|
739
|
+
for (const filePath of collectTextScanFiles(collection)) {
|
|
713
740
|
try {
|
|
714
741
|
sourceFiles.push({
|
|
715
742
|
filePath,
|
|
@@ -721,7 +748,7 @@ const loadSourceFiles = async (
|
|
|
721
748
|
}
|
|
722
749
|
}
|
|
723
750
|
|
|
724
|
-
for (const filePath of collectDocumentationFiles(
|
|
751
|
+
for (const filePath of collectDocumentationFiles(collection)) {
|
|
725
752
|
try {
|
|
726
753
|
sourceFiles.push({
|
|
727
754
|
filePath,
|
|
@@ -733,7 +760,7 @@ const loadSourceFiles = async (
|
|
|
733
760
|
}
|
|
734
761
|
}
|
|
735
762
|
|
|
736
|
-
for (const filePath of collectDevPermitTestFiles(
|
|
763
|
+
for (const filePath of collectDevPermitTestFiles(collection)) {
|
|
737
764
|
try {
|
|
738
765
|
sourceFiles.push({
|
|
739
766
|
filePath,
|
|
@@ -745,7 +772,7 @@ const loadSourceFiles = async (
|
|
|
745
772
|
}
|
|
746
773
|
}
|
|
747
774
|
|
|
748
|
-
return sourceFiles;
|
|
775
|
+
return { collection, sourceFiles };
|
|
749
776
|
};
|
|
750
777
|
|
|
751
778
|
const collectTopoKnownIds = (
|
|
@@ -983,6 +1010,7 @@ const collectAuthoredMcpSurfaceBindingSets = (
|
|
|
983
1010
|
|
|
984
1011
|
const buildProjectContext = (
|
|
985
1012
|
sourceFiles: readonly SourceFile[],
|
|
1013
|
+
collection: SourceTreeCollection | null,
|
|
986
1014
|
rootDir: string,
|
|
987
1015
|
appTopos: readonly Topo[] = [],
|
|
988
1016
|
scope: WardenScope = EMPTY_WARDEN_SCOPE,
|
|
@@ -1003,6 +1031,11 @@ const buildProjectContext = (
|
|
|
1003
1031
|
(sourceFile) => sourceFile.kind === 'documentation'
|
|
1004
1032
|
);
|
|
1005
1033
|
context.publicWorkspaces = collectPublicWorkspaces(rootDir, {
|
|
1034
|
+
collectedPackageJsonPaths: new Set(
|
|
1035
|
+
collection?.files
|
|
1036
|
+
.filter((file) => file.path.endsWith('package.json'))
|
|
1037
|
+
.map((file) => file.path)
|
|
1038
|
+
),
|
|
1006
1039
|
exclude: scope.exclude,
|
|
1007
1040
|
});
|
|
1008
1041
|
context.governedVocabularyHistoryRequired =
|
|
@@ -1366,13 +1399,15 @@ const lintFiles = async (
|
|
|
1366
1399
|
};
|
|
1367
1400
|
}
|
|
1368
1401
|
|
|
1402
|
+
const loaded = await loadSourceFiles(rootDir);
|
|
1369
1403
|
const sourceFiles = filterSourceFilesByScope(
|
|
1370
|
-
filterSourceFilesByDraftMode(
|
|
1404
|
+
filterSourceFilesByDraftMode(loaded.sourceFiles, drafts),
|
|
1371
1405
|
rootDir,
|
|
1372
1406
|
scope
|
|
1373
1407
|
);
|
|
1374
1408
|
const context = buildProjectContext(
|
|
1375
1409
|
sourceFiles,
|
|
1410
|
+
loaded.collection,
|
|
1376
1411
|
rootDir,
|
|
1377
1412
|
topoTargets.map((target) => target.topo),
|
|
1378
1413
|
scope,
|
|
@@ -744,6 +744,16 @@ export const governedVocabularyTransitions =
|
|
|
744
744
|
reason:
|
|
745
745
|
'Preserve transition regression fixtures and the intentional negative resolver assertion after the public route is retired.',
|
|
746
746
|
},
|
|
747
|
+
{
|
|
748
|
+
paths: [
|
|
749
|
+
'apps/trails/src/__tests__/regrade.test.ts',
|
|
750
|
+
'packages/regrade/src/downstream/__tests__/ast-rewrite.test.ts',
|
|
751
|
+
'packages/regrade/src/downstream/__tests__/vocabulary.test.ts',
|
|
752
|
+
],
|
|
753
|
+
pattern: '^@ontrails/warden/ast(?:\\.js|\\.utils|/utils)$',
|
|
754
|
+
reason:
|
|
755
|
+
'Preserve the adjacent package-route variants used by the authored Warden AST transition regression fixtures.',
|
|
756
|
+
},
|
|
747
757
|
],
|
|
748
758
|
reviewForms: [],
|
|
749
759
|
safeRewriteForms: {
|
package/src/workspaces.ts
CHANGED
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
* Workspace-manifest discovery for Warden project-aware rules.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { realpathSync } from 'node:fs';
|
|
6
|
-
import { dirname, join, relative, resolve } from 'node:path';
|
|
5
|
+
import { readFileSync, realpathSync } from 'node:fs';
|
|
6
|
+
import { dirname, join, posix, relative, resolve } from 'node:path';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
listWorkspacePackages,
|
|
10
|
+
listWorkspacePatterns,
|
|
11
|
+
matchesAnyPathGlob,
|
|
12
|
+
} from '@ontrails/core';
|
|
9
13
|
|
|
10
14
|
export interface WardenPublicWorkspace {
|
|
11
15
|
readonly name: string;
|
|
@@ -18,6 +22,8 @@ export interface WardenPublicWorkspace {
|
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
export interface WardenWorkspaceCollectionOptions {
|
|
25
|
+
/** Root-relative package manifests observed by the owning source collection. */
|
|
26
|
+
readonly collectedPackageJsonPaths?: ReadonlySet<string> | undefined;
|
|
21
27
|
readonly exclude?: readonly string[];
|
|
22
28
|
}
|
|
23
29
|
|
|
@@ -27,6 +33,7 @@ interface PackageManifest {
|
|
|
27
33
|
readonly files?: unknown;
|
|
28
34
|
readonly name?: unknown;
|
|
29
35
|
readonly private?: unknown;
|
|
36
|
+
readonly workspaces?: unknown;
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
const normalizePath = (path: string): string => path.replaceAll('\\', '/');
|
|
@@ -42,6 +49,60 @@ const normalizeRealPath = (path: string): string => {
|
|
|
42
49
|
const rootRelativePath = (rootDir: string, path: string): string =>
|
|
43
50
|
normalizePath(relative(rootDir, path));
|
|
44
51
|
|
|
52
|
+
const readPackageManifest = (path: string): PackageManifest | undefined => {
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(readFileSync(path, 'utf8')) as PackageManifest;
|
|
55
|
+
} catch {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const matchesWorkspacePattern = (
|
|
61
|
+
workspacePath: string,
|
|
62
|
+
pattern: string
|
|
63
|
+
): boolean => {
|
|
64
|
+
const normalizedPattern = normalizePath(pattern).replace(/^\.\//, '');
|
|
65
|
+
return normalizedPattern.endsWith('/*')
|
|
66
|
+
? posix.dirname(workspacePath) === normalizedPattern.slice(0, -2)
|
|
67
|
+
: workspacePath === normalizedPattern;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
interface WorkspaceManifestCandidate {
|
|
71
|
+
readonly manifest: PackageManifest;
|
|
72
|
+
readonly packageJsonPath: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const collectedWorkspaceManifests = (
|
|
76
|
+
rootDir: string,
|
|
77
|
+
packageJsonPaths: ReadonlySet<string>
|
|
78
|
+
): readonly WorkspaceManifestCandidate[] => {
|
|
79
|
+
if (!packageJsonPaths.has('package.json')) {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
const rootManifest = readPackageManifest(join(rootDir, 'package.json'));
|
|
83
|
+
const patterns = listWorkspacePatterns(rootManifest);
|
|
84
|
+
|
|
85
|
+
return [...packageJsonPaths]
|
|
86
|
+
.filter((path) => path !== 'package.json' && path.endsWith('/package.json'))
|
|
87
|
+
.toSorted()
|
|
88
|
+
.flatMap((path): readonly WorkspaceManifestCandidate[] => {
|
|
89
|
+
const normalizedPath = normalizePath(path).replace(/^\.\//, '');
|
|
90
|
+
const workspacePath = posix.dirname(normalizedPath);
|
|
91
|
+
if (
|
|
92
|
+
posix.isAbsolute(normalizedPath) ||
|
|
93
|
+
normalizedPath.startsWith('../') ||
|
|
94
|
+
!patterns.some((pattern) =>
|
|
95
|
+
matchesWorkspacePattern(workspacePath, pattern)
|
|
96
|
+
)
|
|
97
|
+
) {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
const packageJsonPath = resolve(rootDir, normalizedPath);
|
|
101
|
+
const manifest = readPackageManifest(packageJsonPath);
|
|
102
|
+
return manifest ? [{ manifest, packageJsonPath }] : [];
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
|
|
45
106
|
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
46
107
|
Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
47
108
|
|
|
@@ -172,10 +233,14 @@ export const collectPublicWorkspaces = (
|
|
|
172
233
|
): ReadonlyMap<string, WardenPublicWorkspace> => {
|
|
173
234
|
const normalizedRoot = normalizeRealPath(rootDir);
|
|
174
235
|
const workspaces = new Map<string, WardenPublicWorkspace>();
|
|
236
|
+
const candidates = options.collectedPackageJsonPaths
|
|
237
|
+
? collectedWorkspaceManifests(
|
|
238
|
+
normalizedRoot,
|
|
239
|
+
options.collectedPackageJsonPaths
|
|
240
|
+
)
|
|
241
|
+
: listWorkspacePackages<PackageManifest>(normalizedRoot);
|
|
175
242
|
|
|
176
|
-
for (const workspacePackage of
|
|
177
|
-
normalizedRoot
|
|
178
|
-
)) {
|
|
243
|
+
for (const workspacePackage of candidates) {
|
|
179
244
|
const workspace = publicWorkspaceFromManifest(
|
|
180
245
|
workspacePackage.packageJsonPath,
|
|
181
246
|
workspacePackage.manifest
|