@crossplatformai/dependency-graph 0.9.4 → 0.11.0-next.0

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.
Files changed (64) hide show
  1. package/dist/cli/index.d.ts +3 -0
  2. package/dist/cli/index.d.ts.map +1 -0
  3. package/dist/cli/pr-preview.d.ts +5 -0
  4. package/dist/cli/pr-preview.d.ts.map +1 -0
  5. package/dist/cli/validate-workflows.d.ts +3 -0
  6. package/dist/cli/validate-workflows.d.ts.map +1 -0
  7. package/dist/graph/analysis.d.ts +6 -0
  8. package/dist/graph/analysis.d.ts.map +1 -0
  9. package/dist/graph/builder.d.ts +3 -0
  10. package/dist/graph/builder.d.ts.map +1 -0
  11. package/dist/graph/traversal.d.ts +5 -0
  12. package/dist/graph/traversal.d.ts.map +1 -0
  13. package/dist/graph/types.d.ts +47 -0
  14. package/dist/graph/types.d.ts.map +1 -0
  15. package/dist/index-cli.js +1172 -0
  16. package/dist/index-cli.js.map +1 -0
  17. package/dist/index.d.ts +14 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +791 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/types/clients.d.ts +15 -0
  22. package/dist/types/clients.d.ts.map +1 -0
  23. package/dist/workflow/discovery.d.ts +4 -0
  24. package/dist/workflow/discovery.d.ts.map +1 -0
  25. package/dist/workflow/expected-paths.d.ts +13 -0
  26. package/dist/workflow/expected-paths.d.ts.map +1 -0
  27. package/dist/workflow/impact.d.ts +5 -0
  28. package/dist/workflow/impact.d.ts.map +1 -0
  29. package/dist/workflow/parser.d.ts +3 -0
  30. package/dist/workflow/parser.d.ts.map +1 -0
  31. package/dist/workflow/policy.d.ts +3 -0
  32. package/dist/workflow/policy.d.ts.map +1 -0
  33. package/dist/workflow/types.d.ts +41 -0
  34. package/dist/workflow/types.d.ts.map +1 -0
  35. package/dist/workflow/validator.d.ts +3 -0
  36. package/dist/workflow/validator.d.ts.map +1 -0
  37. package/dist/workspace/discovery.d.ts +12 -0
  38. package/dist/workspace/discovery.d.ts.map +1 -0
  39. package/dist/workspace/file-mapping.d.ts +4 -0
  40. package/dist/workspace/file-mapping.d.ts.map +1 -0
  41. package/dist/workspace/package-map.d.ts +12 -0
  42. package/dist/workspace/package-map.d.ts.map +1 -0
  43. package/package.json +33 -28
  44. package/src/cli/pr-preview.ts +0 -388
  45. package/src/cli/validate-workflows.ts +0 -287
  46. package/src/graph/analysis.ts +0 -147
  47. package/src/graph/builder.ts +0 -52
  48. package/src/graph/traversal.ts +0 -132
  49. package/src/graph/types.ts +0 -50
  50. package/src/index.test.ts +0 -94
  51. package/src/index.ts +0 -51
  52. package/src/types/clients.ts +0 -19
  53. package/src/workflow/discovery.ts +0 -58
  54. package/src/workflow/expected-paths.ts +0 -112
  55. package/src/workflow/parser.test.ts +0 -54
  56. package/src/workflow/parser.ts +0 -48
  57. package/src/workflow/policy.ts +0 -13
  58. package/src/workflow/types.ts +0 -42
  59. package/src/workflow/validator.test.ts +0 -214
  60. package/src/workflow/validator.ts +0 -230
  61. package/src/workspace/discovery.ts +0 -94
  62. package/src/workspace/file-mapping.ts +0 -35
  63. package/src/workspace/package-map.test.ts +0 -95
  64. package/src/workspace/package-map.ts +0 -74
@@ -1,112 +0,0 @@
1
- import type { WorkspacePackage } from '../graph/types';
2
- import type { WorkflowTarget, WorkflowValidationPolicy } from './types';
3
-
4
- interface ExpectedPathsOptions {
5
- workflowTarget: WorkflowTarget;
6
- packages: WorkspacePackage[];
7
- packageMap: Map<string, { workflowPath: string | null }>;
8
- policy: WorkflowValidationPolicy;
9
- }
10
-
11
- function uniqueSorted(values: string[]): string[] {
12
- return Array.from(new Set(values)).sort();
13
- }
14
-
15
- function resolveWorkspaceDependency(
16
- dependencyName: string,
17
- packages: WorkspacePackage[],
18
- ): WorkspacePackage | undefined {
19
- let match = packages.find((pkg) => pkg.name === dependencyName);
20
- if (match) {
21
- return match;
22
- }
23
-
24
- match = packages.find((pkg) => pkg.name === `@repo/${dependencyName}`);
25
- if (match) {
26
- return match;
27
- }
28
-
29
- const nameWithoutRepo = dependencyName.replace(/^@repo\//, '');
30
- return packages.find((pkg) => pkg.name === nameWithoutRepo);
31
- }
32
-
33
- function collectWorkspaceDependencyNames(
34
- pkg: WorkspacePackage,
35
- packages: WorkspacePackage[],
36
- visited: Set<string>,
37
- includeDevDependencies: boolean,
38
- includeDevDependenciesTransitively: boolean,
39
- ): Set<string> {
40
- const collected = new Set<string>();
41
- const dependencyEntries = Object.entries(pkg.dependencies);
42
- const devDependencyEntries = includeDevDependencies
43
- ? Object.entries(pkg.devDependencies)
44
- : [];
45
-
46
- for (const [dependencyName] of [
47
- ...dependencyEntries,
48
- ...devDependencyEntries,
49
- ]) {
50
- const dependency = resolveWorkspaceDependency(dependencyName, packages);
51
- if (!dependency || visited.has(dependency.name)) {
52
- continue;
53
- }
54
-
55
- visited.add(dependency.name);
56
- collected.add(dependency.name);
57
-
58
- const nestedDependencies = collectWorkspaceDependencyNames(
59
- dependency,
60
- packages,
61
- visited,
62
- includeDevDependenciesTransitively,
63
- includeDevDependenciesTransitively,
64
- );
65
-
66
- for (const nestedDependency of nestedDependencies) {
67
- collected.add(nestedDependency);
68
- }
69
- }
70
-
71
- return collected;
72
- }
73
-
74
- export function getExpectedWorkflowPaths({
75
- workflowTarget,
76
- packages,
77
- packageMap,
78
- policy,
79
- }: ExpectedPathsOptions): string[] {
80
- const targetPackageName = workflowTarget.targetPackage;
81
- if (!targetPackageName) {
82
- return [];
83
- }
84
-
85
- const targetPackage = packages.find((pkg) => pkg.name === targetPackageName);
86
- if (!targetPackage) {
87
- return [];
88
- }
89
-
90
- const dependencyNames = collectWorkspaceDependencyNames(
91
- targetPackage,
92
- packages,
93
- new Set<string>(),
94
- policy.includeDevDependenciesForRootPackage,
95
- policy.includeDevDependenciesTransitively,
96
- );
97
-
98
- const expectedPaths: string[] = [];
99
- const targetPackagePath = packageMap.get(targetPackageName)?.workflowPath;
100
- if (targetPackagePath) {
101
- expectedPaths.push(`${targetPackagePath}/**`);
102
- }
103
-
104
- for (const dependencyName of dependencyNames) {
105
- const dependencyPath = packageMap.get(dependencyName)?.workflowPath;
106
- if (dependencyPath) {
107
- expectedPaths.push(`${dependencyPath}/**`);
108
- }
109
- }
110
-
111
- return uniqueSorted(expectedPaths);
112
- }
@@ -1,54 +0,0 @@
1
- import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
2
- import { tmpdir } from 'node:os';
3
- import { join } from 'node:path';
4
- import { afterEach, describe, expect, it } from 'vitest';
5
- import { parseWorkflowFile } from './parser';
6
-
7
- const tempDirs: string[] = [];
8
-
9
- function createTempRepo(): string {
10
- const rootDir = mkdtempSync(join(tmpdir(), 'dependency-graph-parser-'));
11
- tempDirs.push(rootDir);
12
- mkdirSync(join(rootDir, '.github/workflows'), { recursive: true });
13
- return rootDir;
14
- }
15
-
16
- afterEach(() => {
17
- for (const dir of tempDirs.splice(0)) {
18
- try {
19
- rmSync(dir, { recursive: true, force: true });
20
- } catch {
21
- // Best effort cleanup.
22
- }
23
- }
24
- });
25
-
26
- describe('parseWorkflowFile', () => {
27
- it('unions push and pull request path filters', () => {
28
- const rootDir = createTempRepo();
29
- writeFileSync(
30
- join(rootDir, '.github/workflows/deploy-web.yml'),
31
- `name: Deploy Web
32
- on:
33
- push:
34
- paths:
35
- - 'package.json'
36
- - 'apps/web/**'
37
- pull_request:
38
- paths:
39
- - 'apps/shared/**'
40
- - 'apps/web/**'
41
- `,
42
- );
43
-
44
- const parsed = parseWorkflowFile('deploy-web.yml', rootDir);
45
-
46
- expect(parsed.pushPaths).toEqual(['apps/web/**', 'package.json']);
47
- expect(parsed.pullRequestPaths).toEqual(['apps/shared/**', 'apps/web/**']);
48
- expect(parsed.paths).toEqual([
49
- 'apps/shared/**',
50
- 'apps/web/**',
51
- 'package.json',
52
- ]);
53
- });
54
- });
@@ -1,48 +0,0 @@
1
- import { existsSync, readFileSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { parse as parseYaml } from 'yaml';
4
- import type { ParsedWorkflow } from './types';
5
-
6
- interface WorkflowConfig {
7
- name?: string;
8
- on?: {
9
- push?: {
10
- paths?: string[];
11
- };
12
- pull_request?: {
13
- paths?: string[];
14
- };
15
- };
16
- }
17
-
18
- function uniqueSorted(values: string[]): string[] {
19
- return Array.from(new Set(values)).sort();
20
- }
21
-
22
- export function parseWorkflowFile(
23
- workflowFile: string,
24
- rootDir: string,
25
- ): ParsedWorkflow {
26
- const workflowPath = join(rootDir, '.github/workflows', workflowFile);
27
-
28
- if (!existsSync(workflowPath)) {
29
- throw new Error(`Workflow file not found: ${workflowFile}`);
30
- }
31
-
32
- const content = readFileSync(workflowPath, 'utf-8');
33
- const workflow = parseYaml(content) as WorkflowConfig;
34
- const pushPaths = workflow.on?.push?.paths ?? [];
35
- const pullRequestPaths = workflow.on?.pull_request?.paths ?? [];
36
-
37
- const parsedWorkflow: ParsedWorkflow = {
38
- pushPaths: uniqueSorted(pushPaths),
39
- pullRequestPaths: uniqueSorted(pullRequestPaths),
40
- paths: uniqueSorted([...pushPaths, ...pullRequestPaths]),
41
- };
42
-
43
- if (workflow.name) {
44
- parsedWorkflow.name = workflow.name;
45
- }
46
-
47
- return parsedWorkflow;
48
- }
@@ -1,13 +0,0 @@
1
- import type { WorkflowValidationPolicy } from './types';
2
-
3
- export const defaultWorkflowValidationPolicy: WorkflowValidationPolicy = {
4
- workflowFilePatterns: ['deploy-*.yml', 'release-*.yml'],
5
- allowedRootPaths: [
6
- 'package.json',
7
- 'pnpm-lock.yaml',
8
- 'pnpm-workspace.yaml',
9
- 'turbo.json',
10
- ],
11
- includeDevDependenciesForRootPackage: true,
12
- includeDevDependenciesTransitively: false,
13
- };
@@ -1,42 +0,0 @@
1
- export interface WorkflowValidationPolicy {
2
- workflowFilePatterns: string[];
3
- allowedRootPaths: string[];
4
- includeDevDependenciesForRootPackage: boolean;
5
- includeDevDependenciesTransitively: boolean;
6
- }
7
-
8
- export interface WorkflowValidationIssue {
9
- kind:
10
- | 'missing'
11
- | 'unnecessary'
12
- | 'broad-wildcard'
13
- | 'parse-error'
14
- | 'config-error';
15
- message: string;
16
- path?: string;
17
- }
18
-
19
- export interface WorkflowValidationResult {
20
- workflow: string;
21
- targetPackage: string;
22
- valid: boolean;
23
- expectedPaths: string[];
24
- actualPaths: string[];
25
- missing: string[];
26
- unnecessary: string[];
27
- issues: WorkflowValidationIssue[];
28
- }
29
-
30
- export interface ParsedWorkflow {
31
- name?: string;
32
- pushPaths: string[];
33
- pullRequestPaths: string[];
34
- paths: string[];
35
- }
36
-
37
- export interface WorkflowTarget {
38
- workflowFile: string;
39
- workflowPath: string;
40
- targetSlug: string;
41
- targetPackage: string | null;
42
- }
@@ -1,214 +0,0 @@
1
- import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
2
- import { tmpdir } from 'node:os';
3
- import { join } from 'node:path';
4
- import { afterEach, describe, expect, it } from 'vitest';
5
- import { validateWorkflows } from './validator';
6
-
7
- const tempDirs: string[] = [];
8
-
9
- function createTempRepo(): string {
10
- const tempBaseDir = mkdtempSync(
11
- join(tmpdir(), 'dependency-graph-validator-'),
12
- );
13
- tempDirs.push(tempBaseDir);
14
- const rootDir = join(tempBaseDir, 'repo');
15
- mkdirSync(join(rootDir, '.github/workflows'), { recursive: true });
16
- return rootDir;
17
- }
18
-
19
- function writeJson(path: string, content: unknown): void {
20
- writeFileSync(path, JSON.stringify(content, null, 2));
21
- }
22
-
23
- function writeWorkspacePackage(
24
- rootDir: string,
25
- relativeDir: string,
26
- packageJson: Record<string, unknown>,
27
- ): void {
28
- const packageDir = join(rootDir, relativeDir);
29
- mkdirSync(packageDir, { recursive: true });
30
- writeJson(join(packageDir, 'package.json'), packageJson);
31
- }
32
-
33
- afterEach(() => {
34
- for (const dir of tempDirs.splice(0)) {
35
- rmSync(dir, { recursive: true, force: true });
36
- }
37
- });
38
-
39
- describe('validateWorkflows', () => {
40
- it('uses pnpm-workspace packages including ui and ignores non-workspace paths', async () => {
41
- const rootDir = createTempRepo();
42
-
43
- writeJson(join(rootDir, 'package.json'), {
44
- name: 'fixture',
45
- packageManager: 'pnpm@10.28.0',
46
- });
47
-
48
- writeFileSync(
49
- join(rootDir, 'pnpm-workspace.yaml'),
50
- `packages:\n - 'apps/*'\n - 'ui/*'\n`,
51
- );
52
-
53
- writeWorkspacePackage(rootDir, 'apps/web', {
54
- name: 'web',
55
- dependencies: {
56
- '@repo/shared': 'workspace:*',
57
- },
58
- devDependencies: {},
59
- });
60
- writeWorkspacePackage(rootDir, 'apps/shared', {
61
- name: '@repo/shared',
62
- dependencies: {
63
- '@repo/components': 'workspace:*',
64
- },
65
- devDependencies: {},
66
- });
67
- writeWorkspacePackage(rootDir, 'ui/components', {
68
- name: '@repo/components',
69
- dependencies: {},
70
- devDependencies: {},
71
- });
72
-
73
- mkdirSync(join(rootDir, 'scripts'), { recursive: true });
74
- writeFileSync(join(rootDir, 'scripts/run-next.mjs'), 'export {}\n');
75
-
76
- writeFileSync(
77
- join(rootDir, '.github/workflows/deploy-web.yml'),
78
- `name: Deploy Web
79
- on:
80
- pull_request:
81
- paths:
82
- - 'apps/web/**'
83
- - 'apps/shared/**'
84
- - 'ui/components/**'
85
- - 'scripts/run-next.mjs'
86
- - '.github/workflows/deploy-web.yml'
87
- `,
88
- );
89
-
90
- const [result] = await validateWorkflows(rootDir);
91
-
92
- expect(result?.valid).toBe(true);
93
- expect(result?.expectedPaths).toEqual([
94
- 'apps/shared/**',
95
- 'apps/web/**',
96
- 'ui/components/**',
97
- ]);
98
- expect(result?.actualPaths).toEqual([
99
- 'apps/shared/**',
100
- 'apps/web/**',
101
- 'ui/components/**',
102
- ]);
103
- });
104
-
105
- it('maps external workspaces to local mirrors when validating workflows', async () => {
106
- const rootDir = createTempRepo();
107
-
108
- writeJson(join(rootDir, 'package.json'), {
109
- name: 'fixture',
110
- packageManager: 'pnpm@10.28.0',
111
- });
112
-
113
- writeFileSync(
114
- join(rootDir, 'pnpm-workspace.yaml'),
115
- `packages:\n - 'apps/*'\n - '../crossplatform.ai/plugins/*'\n`,
116
- );
117
-
118
- writeWorkspacePackage(rootDir, 'apps/web', {
119
- name: 'web',
120
- dependencies: {
121
- '@repo/design-system': 'workspace:*',
122
- },
123
- devDependencies: {},
124
- });
125
-
126
- mkdirSync(join(rootDir, 'plugins/design-system'), { recursive: true });
127
- writeWorkspacePackage(
128
- rootDir,
129
- '../crossplatform.ai/plugins/design-system',
130
- {
131
- name: '@repo/design-system',
132
- dependencies: {},
133
- devDependencies: {},
134
- },
135
- );
136
-
137
- writeFileSync(
138
- join(rootDir, '.github/workflows/deploy-web.yml'),
139
- `name: Deploy Web
140
- on:
141
- pull_request:
142
- paths:
143
- - 'apps/web/**'
144
- - 'plugins/design-system/**'
145
- - '.github/workflows/deploy-web.yml'
146
- `,
147
- );
148
-
149
- const [result] = await validateWorkflows(rootDir);
150
-
151
- expect(result?.valid).toBe(true);
152
- expect(result?.expectedPaths).toEqual([
153
- 'apps/web/**',
154
- 'plugins/design-system/**',
155
- ]);
156
- });
157
-
158
- it('flags stale package paths and broad workspace wildcards', async () => {
159
- const rootDir = createTempRepo();
160
-
161
- writeJson(join(rootDir, 'package.json'), {
162
- name: 'fixture',
163
- packageManager: 'pnpm@10.28.0',
164
- });
165
-
166
- writeFileSync(
167
- join(rootDir, 'pnpm-workspace.yaml'),
168
- `packages:\n - 'apps/*'\n - 'ui/*'\n`,
169
- );
170
-
171
- writeWorkspacePackage(rootDir, 'apps/web', {
172
- name: 'web',
173
- dependencies: {
174
- '@repo/shared': 'workspace:*',
175
- },
176
- devDependencies: {},
177
- });
178
- writeWorkspacePackage(rootDir, 'apps/shared', {
179
- name: '@repo/shared',
180
- dependencies: {
181
- '@repo/components': 'workspace:*',
182
- },
183
- devDependencies: {},
184
- });
185
- writeWorkspacePackage(rootDir, 'ui/components', {
186
- name: '@repo/components',
187
- dependencies: {},
188
- devDependencies: {},
189
- });
190
-
191
- writeFileSync(
192
- join(rootDir, '.github/workflows/deploy-web.yml'),
193
- `name: Deploy Web
194
- on:
195
- pull_request:
196
- paths:
197
- - 'apps/web/**'
198
- - 'apps/shared/**'
199
- - 'packages/components/**'
200
- - 'ui/**'
201
- - '.github/workflows/deploy-web.yml'
202
- `,
203
- );
204
-
205
- const [result] = await validateWorkflows(rootDir);
206
-
207
- expect(result?.valid).toBe(false);
208
- expect(result?.missing).toContain('ui/components/**');
209
- expect(result?.unnecessary).toEqual(['ui/**']);
210
- expect(
211
- result?.issues.some((issue) => issue.kind === 'broad-wildcard'),
212
- ).toBe(true);
213
- });
214
- });
@@ -1,230 +0,0 @@
1
- import { existsSync } from 'node:fs';
2
- import { readFile } from 'node:fs/promises';
3
- import { glob } from 'glob';
4
- import { parse as parseYaml } from 'yaml';
5
- import { discoverWorkspaces } from '../workspace/discovery';
6
- import { buildPackageMap } from '../workspace/package-map';
7
- import type {
8
- WorkflowValidationIssue,
9
- WorkflowValidationPolicy,
10
- WorkflowValidationResult,
11
- } from './types';
12
- import { discoverWorkflowTargets } from './discovery';
13
- import { getExpectedWorkflowPaths } from './expected-paths';
14
- import { parseWorkflowFile } from './parser';
15
- import { defaultWorkflowValidationPolicy } from './policy';
16
-
17
- function uniqueSorted(values: string[]): string[] {
18
- return Array.from(new Set(values)).sort();
19
- }
20
-
21
- function buildBroadWildcards(workspaceRoots: Set<string>): Set<string> {
22
- const wildcards = new Set<string>();
23
-
24
- for (const root of workspaceRoots) {
25
- wildcards.add(`${root}/*`);
26
- wildcards.add(`${root}/**`);
27
- }
28
-
29
- return wildcards;
30
- }
31
-
32
- function splitActualPaths(
33
- paths: string[],
34
- workspaceRoots: Set<string>,
35
- allowedRootPaths: string[],
36
- workflowPath: string,
37
- ): { workspacePaths: string[]; ignoredPaths: string[] } {
38
- const workspacePaths: string[] = [];
39
- const ignoredPaths: string[] = [];
40
-
41
- for (const path of paths) {
42
- if (path === workflowPath || allowedRootPaths.includes(path)) {
43
- ignoredPaths.push(path);
44
- continue;
45
- }
46
-
47
- const [rootSegment] = path.split('/');
48
- if (rootSegment && workspaceRoots.has(rootSegment)) {
49
- workspacePaths.push(path);
50
- continue;
51
- }
52
-
53
- ignoredPaths.push(path);
54
- }
55
-
56
- return {
57
- workspacePaths: uniqueSorted(workspacePaths),
58
- ignoredPaths: uniqueSorted(ignoredPaths),
59
- };
60
- }
61
-
62
- function isCoveredByExpectedPath(
63
- actualPath: string,
64
- expectedPaths: string[],
65
- ): boolean {
66
- return expectedPaths.some((expectedPath) => {
67
- if (expectedPath === actualPath) {
68
- return true;
69
- }
70
-
71
- if (!expectedPath.endsWith('/**')) {
72
- return false;
73
- }
74
-
75
- const expectedPrefix = expectedPath.slice(0, -3);
76
- return actualPath.startsWith(`${expectedPrefix}/`);
77
- });
78
- }
79
-
80
- function validateWorkflowResult(
81
- workflow: string,
82
- targetPackage: string,
83
- expectedPaths: string[],
84
- actualPaths: string[],
85
- broadWildcards: Set<string>,
86
- ): WorkflowValidationResult {
87
- const issues: WorkflowValidationIssue[] = [];
88
- const missing = expectedPaths.filter((path) => !actualPaths.includes(path));
89
- const unnecessary = actualPaths.filter(
90
- (path) => !isCoveredByExpectedPath(path, expectedPaths),
91
- );
92
-
93
- for (const path of missing) {
94
- issues.push({
95
- kind: 'missing',
96
- path,
97
- message: `Missing path '${path}'`,
98
- });
99
- }
100
-
101
- for (const path of unnecessary) {
102
- issues.push({
103
- kind: 'unnecessary',
104
- path,
105
- message: `Unnecessary path '${path}'`,
106
- });
107
- }
108
-
109
- for (const path of actualPaths) {
110
- if (broadWildcards.has(path)) {
111
- issues.push({
112
- kind: 'broad-wildcard',
113
- path,
114
- message: `Uses broad wildcard '${path}' which triggers on all workspace changes under that root`,
115
- });
116
- }
117
- }
118
-
119
- return {
120
- workflow,
121
- targetPackage,
122
- valid: issues.length === 0,
123
- expectedPaths,
124
- actualPaths,
125
- missing,
126
- unnecessary,
127
- issues,
128
- };
129
- }
130
-
131
- export async function validateWorkflows(
132
- rootDir: string,
133
- policyOverrides?: Partial<WorkflowValidationPolicy>,
134
- ): Promise<WorkflowValidationResult[]> {
135
- const discoveredPackages = await discoverWorkspaces(rootDir, {
136
- fs: {
137
- readFile: (path, encoding) => readFile(path, encoding),
138
- exists: (path) => Promise.resolve(existsSync(path)),
139
- },
140
- glob: {
141
- glob: (pattern, options) => glob(pattern, options),
142
- },
143
- yaml: {
144
- parse: (content): unknown => parseYaml(content),
145
- },
146
- });
147
-
148
- const policy: WorkflowValidationPolicy = {
149
- ...defaultWorkflowValidationPolicy,
150
- ...policyOverrides,
151
- allowedRootPaths: uniqueSorted([
152
- ...defaultWorkflowValidationPolicy.allowedRootPaths,
153
- ...(policyOverrides?.allowedRootPaths ?? []),
154
- ]),
155
- };
156
-
157
- const { packageMap, workspaceRoots } = buildPackageMap(
158
- discoveredPackages,
159
- rootDir,
160
- );
161
- const workflowTargets = discoverWorkflowTargets(
162
- rootDir,
163
- discoveredPackages,
164
- policy,
165
- );
166
- const broadWildcards = buildBroadWildcards(workspaceRoots);
167
-
168
- return workflowTargets.map((workflowTarget) => {
169
- if (!workflowTarget.targetPackage) {
170
- return {
171
- workflow: workflowTarget.workflowFile,
172
- targetPackage: workflowTarget.targetSlug,
173
- valid: false,
174
- expectedPaths: [],
175
- actualPaths: [],
176
- missing: [],
177
- unnecessary: [],
178
- issues: [
179
- {
180
- kind: 'config-error',
181
- message: `Could not resolve workflow target package for '${workflowTarget.workflowFile}'`,
182
- },
183
- ],
184
- };
185
- }
186
-
187
- try {
188
- const parsedWorkflow = parseWorkflowFile(
189
- workflowTarget.workflowFile,
190
- rootDir,
191
- );
192
- const { workspacePaths: actualPaths } = splitActualPaths(
193
- parsedWorkflow.paths,
194
- workspaceRoots,
195
- policy.allowedRootPaths,
196
- workflowTarget.workflowPath,
197
- );
198
- const expectedPaths = getExpectedWorkflowPaths({
199
- workflowTarget,
200
- packages: discoveredPackages,
201
- packageMap,
202
- policy,
203
- });
204
-
205
- return validateWorkflowResult(
206
- workflowTarget.workflowFile,
207
- workflowTarget.targetPackage,
208
- expectedPaths,
209
- actualPaths,
210
- broadWildcards,
211
- );
212
- } catch (error) {
213
- return {
214
- workflow: workflowTarget.workflowFile,
215
- targetPackage: workflowTarget.targetPackage,
216
- valid: false,
217
- expectedPaths: [],
218
- actualPaths: [],
219
- missing: [],
220
- unnecessary: [],
221
- issues: [
222
- {
223
- kind: 'parse-error',
224
- message: error instanceof Error ? error.message : String(error),
225
- },
226
- ],
227
- };
228
- }
229
- });
230
- }