@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,388 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { readFileSync } from 'node:fs';
4
- import { resolve } from 'node:path';
5
- import { glob } from 'glob';
6
- import { parse as parseYaml } from 'yaml';
7
-
8
- // Import dependency graph functions from parent module
9
- import {
10
- discoverWorkspaces,
11
- buildDependencyGraph,
12
- findAffectedPackages,
13
- type WorkspacePackage,
14
- } from '../index';
15
-
16
- interface ChangedFile {
17
- path: string;
18
- status: 'modified' | 'added' | 'deleted';
19
- }
20
-
21
- interface DeploymentIndicator {
22
- file?: string;
23
- field?: string;
24
- dependency?: string;
25
- }
26
-
27
- interface PlatformDetection {
28
- platform: string;
29
- indicators: DeploymentIndicator[];
30
- }
31
-
32
- const WORKFLOW_MAPPING: Record<string, string> = {
33
- 'deploy-web.yml': 'web',
34
- 'deploy-api-origin.yml': 'api-origin',
35
- 'deploy-api-edge.yml': 'api-edge',
36
- 'release-mobile.yml': 'mobile',
37
- 'release-desktop.yml': 'desktop',
38
- 'release-cli.yml': 'cli',
39
- };
40
-
41
- // Convention-based deployment detection (order matters - most specific first)
42
- const PLATFORM_INDICATORS: PlatformDetection[] = [
43
- {
44
- platform: 'cloudflare-workers',
45
- indicators: [{ file: 'wrangler.toml' }],
46
- },
47
- {
48
- platform: 'expo',
49
- indicators: [{ file: 'app.json' }, { file: 'eas.json' }],
50
- },
51
- {
52
- platform: 'npm-package',
53
- indicators: [{ field: 'bin' }],
54
- },
55
- {
56
- platform: 'electron',
57
- indicators: [
58
- { dependency: 'electron' },
59
- { dependency: 'electron-builder' },
60
- ],
61
- },
62
- {
63
- platform: 'next.js',
64
- indicators: [
65
- { file: 'next.config.js' },
66
- { file: 'next.config.mjs' },
67
- { file: 'next.config.ts' },
68
- { dependency: 'next' },
69
- ],
70
- },
71
- {
72
- platform: 'node.js',
73
- indicators: [
74
- { field: 'start' }, // has start script
75
- ],
76
- },
77
- ];
78
-
79
- const DEFAULT_APP_DIRECTORIES = ['apps'];
80
-
81
- function isDeployableApp(pkg: WorkspacePackage): {
82
- deployable: boolean;
83
- platform?: string;
84
- } {
85
- // Check if package is in apps directory (configurable in future)
86
- const isInAppDirectory = DEFAULT_APP_DIRECTORIES.some(
87
- (dir) => pkg.path.includes(`/${dir}/`) || pkg.path.endsWith(`/${dir}`),
88
- );
89
-
90
- if (!isInAppDirectory) {
91
- return { deployable: false };
92
- }
93
-
94
- // Detect platform based on indicators
95
- for (const platformDetection of PLATFORM_INDICATORS) {
96
- const hasIndicators = platformDetection.indicators.some((indicator) => {
97
- if (indicator.file) {
98
- try {
99
- const filePath = resolve(pkg.path, indicator.file);
100
- readFileSync(filePath, 'utf-8');
101
- return true;
102
- } catch {
103
- return false;
104
- }
105
- }
106
-
107
- if (indicator.field) {
108
- const scripts = pkg.packageJson.scripts as
109
- | Record<string, string>
110
- | undefined;
111
- if (indicator.field === 'start' && scripts?.start) {
112
- return true;
113
- }
114
- const packageJsonField =
115
- pkg.packageJson[indicator.field as keyof typeof pkg.packageJson];
116
- if (indicator.field !== 'start' && packageJsonField) {
117
- return true;
118
- }
119
- }
120
-
121
- if (indicator.dependency) {
122
- return !!(
123
- pkg.dependencies[indicator.dependency] ||
124
- pkg.devDependencies[indicator.dependency]
125
- );
126
- }
127
-
128
- return false;
129
- });
130
-
131
- if (hasIndicators) {
132
- return { deployable: true, platform: platformDetection.platform };
133
- }
134
- }
135
-
136
- // If in apps directory but no specific platform detected, not deployable
137
- return { deployable: false };
138
- }
139
-
140
- async function getChangedFiles(): Promise<ChangedFile[]> {
141
- const { execSync } = await import('node:child_process');
142
-
143
- try {
144
- // Try local production first, then fall back to origin/production
145
- let baseBranch = 'production';
146
- try {
147
- execSync('git rev-parse production', {
148
- encoding: 'utf-8',
149
- stdio: 'pipe',
150
- });
151
- } catch {
152
- baseBranch = 'origin/production';
153
- }
154
-
155
- const output = execSync(`git diff --name-status ${baseBranch}...HEAD`, {
156
- encoding: 'utf-8',
157
- });
158
-
159
- return output
160
- .trim()
161
- .split('\n')
162
- .filter((line) => line)
163
- .map((line) => {
164
- const [status, path] = line.split('\t');
165
-
166
- return {
167
- path: path ?? '',
168
- status:
169
- status === 'D' ? 'deleted' : status === 'A' ? 'added' : 'modified',
170
- };
171
- })
172
- .filter((file): file is ChangedFile => file.path !== '');
173
- } catch {
174
- console.error(
175
- 'Failed to get changed files. Ensure you are on a branch with commits compared to production.',
176
- );
177
- process.exit(1);
178
- }
179
- }
180
-
181
- async function main() {
182
- try {
183
- const rootDir = resolve(process.cwd());
184
- const changedFiles = await getChangedFiles();
185
-
186
- console.log(
187
- `\n📦 Release Preview - Changed Files: ${changedFiles.length}\n`,
188
- );
189
-
190
- changedFiles.forEach((file) => {
191
- console.log(` ${file.status === 'deleted' ? '❌' : '📝'} ${file.path}`);
192
- });
193
-
194
- const packages = await discoverWorkspaces(rootDir, {
195
- fs: {
196
- readFile: (path) => {
197
- try {
198
- return Promise.resolve(readFileSync(path, 'utf-8'));
199
- } catch {
200
- return Promise.reject(new Error(`Failed to read file: ${path}`));
201
- }
202
- },
203
- exists: (path) => {
204
- try {
205
- readFileSync(path, 'utf-8');
206
- return Promise.resolve(true);
207
- } catch {
208
- return Promise.resolve(false);
209
- }
210
- },
211
- },
212
- glob: {
213
- glob: async (pattern, options) => glob(pattern, options),
214
- },
215
- yaml: {
216
- parse: (content) => parseYaml(content) as Record<string, unknown>,
217
- },
218
- });
219
-
220
- const graph = buildDependencyGraph(packages);
221
- const changedPackages = new Set<string>();
222
-
223
- for (const file of changedFiles) {
224
- if (file.status === 'deleted') continue;
225
-
226
- // Convert absolute package paths to relative for comparison
227
- const pkg = packages.find((p) => {
228
- const relativePkgPath = p.path.replace(rootDir + '/', '');
229
- return file.path.startsWith(relativePkgPath);
230
- });
231
-
232
- if (pkg) {
233
- changedPackages.add(pkg.name);
234
- }
235
- }
236
-
237
- const changedWorkflows = changedFiles.filter((f) =>
238
- f.path.startsWith('.github/workflows/'),
239
- );
240
-
241
- const workflowAffectedApps = new Set<string>();
242
- for (const workflow of changedWorkflows) {
243
- const workflowName = workflow.path.split('/').pop();
244
- if (workflowName && WORKFLOW_MAPPING[workflowName]) {
245
- workflowAffectedApps.add(WORKFLOW_MAPPING[workflowName]);
246
- }
247
- }
248
-
249
- console.log(
250
- `\n📦 Changed packages: ${Array.from(changedPackages).join(', ') || 'none'}\n`,
251
- );
252
-
253
- if (changedPackages.size === 0) {
254
- console.log('\n✨ No packages changed - no deployments needed\n');
255
- return;
256
- }
257
-
258
- const affected = findAffectedPackages(changedPackages, graph, {
259
- direction: 'upstream',
260
- respectAffectsUpstream: true,
261
- });
262
-
263
- // Find all deployable apps (affected and unaffected)
264
- const allDeployableApps = packages
265
- .map((pkg) => {
266
- const detection = isDeployableApp(pkg);
267
- return detection.deployable
268
- ? { name: pkg.name, pkg, platform: detection.platform }
269
- : null;
270
- })
271
- .filter(
272
- (
273
- item,
274
- ): item is {
275
- name: string;
276
- pkg: WorkspacePackage;
277
- platform: string | undefined;
278
- } => item !== null,
279
- );
280
-
281
- // Categorize apps by deployment type
282
- const affectedApps = allDeployableApps.filter(
283
- (app): app is NonNullable<typeof app> =>
284
- affected.has(app.name) || workflowAffectedApps.has(app.name),
285
- );
286
- const unaffectedApps = allDeployableApps.filter(
287
- (app): app is NonNullable<typeof app> =>
288
- !affected.has(app.name) && !workflowAffectedApps.has(app.name),
289
- );
290
-
291
- // Separate deploys (web-based) vs releases (installed)
292
- const getAppCategory = (platform?: string) => {
293
- switch (platform) {
294
- case 'next.js':
295
- case 'cloudflare-workers':
296
- case 'node.js':
297
- return 'deploy';
298
- case 'expo':
299
- case 'electron':
300
- case 'npm-package':
301
- return 'release';
302
- default:
303
- return 'deploy'; // default to deploy for generic
304
- }
305
- };
306
-
307
- const getAppIcon = (platform?: string) => {
308
- switch (platform) {
309
- case 'next.js':
310
- case 'cloudflare-workers':
311
- case 'node.js':
312
- return '🌐';
313
- case 'expo':
314
- return '📱';
315
- case 'electron':
316
- return '🖥️';
317
- case 'npm-package':
318
- return '⚡';
319
- default:
320
- return '🌐';
321
- }
322
- };
323
-
324
- const affectedDeploys = affectedApps.filter(
325
- (app): app is NonNullable<typeof app> =>
326
- getAppCategory(app.platform) === 'deploy',
327
- );
328
- const affectedReleases = affectedApps.filter(
329
- (app): app is NonNullable<typeof app> =>
330
- getAppCategory(app.platform) === 'release',
331
- );
332
-
333
- console.log(`\n🚀 PR Preview\n`);
334
-
335
- if (affectedApps.length === 0) {
336
- console.log('No apps affected by changes.\n');
337
- } else {
338
- if (affectedDeploys.length > 0) {
339
- console.log('📋 Apps that will be DEPLOYED:');
340
- for (const item of affectedDeploys) {
341
- const platformDisplay =
342
- item.platform === 'generic' ? '' : ` (${item.platform})`;
343
- const icon = getAppIcon(item.platform);
344
- console.log(`${icon} ${item.name}${platformDisplay}`);
345
- }
346
- console.log('');
347
- }
348
-
349
- if (affectedReleases.length > 0) {
350
- console.log('📋 Apps that will be RELEASED:');
351
- for (const item of affectedReleases) {
352
- const platformDisplay =
353
- item.platform === 'generic' ? '' : ` (${item.platform})`;
354
- const icon = getAppIcon(item.platform);
355
- console.log(`${icon} ${item.name}${platformDisplay}`);
356
- }
357
- console.log('');
358
- }
359
- }
360
-
361
- if (unaffectedApps.length > 0) {
362
- console.log(`📋 Apps that will NOT be affected:`);
363
- for (const item of unaffectedApps) {
364
- const platformDisplay =
365
- item.platform === 'generic' ? '' : ` (${item.platform})`;
366
- console.log(`⏭️ ${item.name}${platformDisplay}`);
367
- }
368
- console.log('');
369
- }
370
-
371
- console.log(`Legend:`);
372
- console.log(`🌐 = Deploy (web-based, instant updates)`);
373
- console.log(`📱 = Release (mobile app, user installs)`);
374
- console.log(`🖥️ = Release (desktop app, user installs)`);
375
- console.log(`⚡ = Release (CLI tool, user installs)`);
376
- console.log(`⏭️ = Unaffected (no changes needed)`);
377
- console.log(`📝 = File changed`);
378
- console.log(`❌ = File deleted\n`);
379
- } catch (error) {
380
- console.error(
381
- 'Error:',
382
- error instanceof Error ? error.message : String(error),
383
- );
384
- process.exit(1);
385
- }
386
- }
387
-
388
- void main();
@@ -1,287 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { existsSync, readFileSync, readdirSync } from 'node:fs';
4
- import { join, resolve } from 'node:path';
5
- import { parse as parseYaml } from 'yaml';
6
- import { validateWorkflows } from '../workflow/validator';
7
- import type { WorkflowValidationResult } from '../workflow/types';
8
-
9
- interface PackageJson {
10
- packageManager?: string;
11
- }
12
-
13
- interface PnpmValidationResult {
14
- valid: boolean;
15
- workflowIssues: { workflow: string; issue: string }[];
16
- dockerfileIssues: { dockerfile: string; issue: string }[];
17
- }
18
-
19
- function discoverPnpmWorkflows(rootDir: string): string[] {
20
- const workflowsDir = join(rootDir, '.github/workflows');
21
- if (!existsSync(workflowsDir)) {
22
- return [];
23
- }
24
-
25
- const workflows: string[] = [];
26
- const files = readdirSync(workflowsDir).filter((file) =>
27
- file.endsWith('.yml'),
28
- );
29
-
30
- for (const file of files) {
31
- const content = readFileSync(join(workflowsDir, file), 'utf-8');
32
- if (content.includes('pnpm/action-setup')) {
33
- workflows.push(file);
34
- }
35
- }
36
-
37
- return workflows;
38
- }
39
-
40
- function discoverPnpmDockerfiles(rootDir: string): string[] {
41
- const dockerfiles: string[] = [];
42
-
43
- for (const entry of readdirSync(rootDir)) {
44
- if (!entry.startsWith('Dockerfile')) {
45
- continue;
46
- }
47
-
48
- const content = readFileSync(join(rootDir, entry), 'utf-8');
49
- if (content.includes('pnpm@')) {
50
- dockerfiles.push(entry);
51
- }
52
- }
53
-
54
- const appsDir = join(rootDir, 'apps');
55
- if (!existsSync(appsDir)) {
56
- return dockerfiles;
57
- }
58
-
59
- for (const entry of readdirSync(appsDir, { withFileTypes: true })) {
60
- if (!entry.isDirectory()) {
61
- continue;
62
- }
63
-
64
- const dockerfilePath = join(appsDir, entry.name, 'Dockerfile');
65
- if (!existsSync(dockerfilePath)) {
66
- continue;
67
- }
68
-
69
- const content = readFileSync(dockerfilePath, 'utf-8');
70
- if (content.includes('pnpm@')) {
71
- dockerfiles.push(`apps/${entry.name}/Dockerfile`);
72
- }
73
- }
74
-
75
- return dockerfiles;
76
- }
77
-
78
- function getExpectedPnpmVersion(rootDir: string): string {
79
- const packageJson = JSON.parse(
80
- readFileSync(join(rootDir, 'package.json'), 'utf-8'),
81
- ) as PackageJson;
82
- const packageManager = packageJson.packageManager;
83
-
84
- if (!packageManager?.startsWith('pnpm@')) {
85
- throw new Error('packageManager field must specify pnpm version');
86
- }
87
-
88
- return packageManager.replace('pnpm@', '');
89
- }
90
-
91
- function checkWorkflowPnpmVersion(
92
- workflowFile: string,
93
- rootDir: string,
94
- ): { valid: boolean; issue?: string } {
95
- const workflowPath = join(rootDir, '.github/workflows', workflowFile);
96
- if (!existsSync(workflowPath)) {
97
- return { valid: true };
98
- }
99
-
100
- const content = readFileSync(workflowPath, 'utf-8');
101
- const workflow = parseYaml(content) as {
102
- jobs?: Record<
103
- string,
104
- { steps?: Array<{ uses?: string; with?: { version?: string | number } }> }
105
- >;
106
- };
107
-
108
- for (const job of Object.values(workflow.jobs ?? {})) {
109
- for (const step of job.steps ?? []) {
110
- if (!step.uses?.startsWith('pnpm/action-setup')) {
111
- continue;
112
- }
113
-
114
- const version = step.with?.version;
115
- if (version !== undefined && !/^\d+$/.test(String(version))) {
116
- return {
117
- valid: false,
118
- issue: `Hardcoded pnpm version '${version}' - remove 'version' key to auto-detect from packageManager`,
119
- };
120
- }
121
- }
122
- }
123
-
124
- return { valid: true };
125
- }
126
-
127
- function checkDockerfilePnpmVersion(
128
- dockerfile: string,
129
- expectedVersion: string,
130
- rootDir: string,
131
- ): { valid: boolean; issue?: string } {
132
- const dockerfilePath = join(rootDir, dockerfile);
133
- if (!existsSync(dockerfilePath)) {
134
- return { valid: true };
135
- }
136
-
137
- const content = readFileSync(dockerfilePath, 'utf-8');
138
- const matches = content.matchAll(/npm install -g pnpm@([\d.]+)/g);
139
-
140
- for (const match of matches) {
141
- if (match[1] !== expectedVersion) {
142
- return {
143
- valid: false,
144
- issue: `${dockerfile} uses pnpm@${match[1]} but package.json specifies pnpm@${expectedVersion}`,
145
- };
146
- }
147
- }
148
-
149
- return { valid: true };
150
- }
151
-
152
- function validatePnpmVersions(rootDir: string): PnpmValidationResult {
153
- const result: PnpmValidationResult = {
154
- valid: true,
155
- workflowIssues: [],
156
- dockerfileIssues: [],
157
- };
158
-
159
- for (const workflowFile of discoverPnpmWorkflows(rootDir)) {
160
- const check = checkWorkflowPnpmVersion(workflowFile, rootDir);
161
- if (!check.valid && check.issue) {
162
- result.valid = false;
163
- result.workflowIssues.push({
164
- workflow: workflowFile,
165
- issue: check.issue,
166
- });
167
- }
168
- }
169
-
170
- const expectedVersion = getExpectedPnpmVersion(rootDir);
171
- for (const dockerfile of discoverPnpmDockerfiles(rootDir)) {
172
- const check = checkDockerfilePnpmVersion(
173
- dockerfile,
174
- expectedVersion,
175
- rootDir,
176
- );
177
- if (!check.valid && check.issue) {
178
- result.valid = false;
179
- result.dockerfileIssues.push({ dockerfile, issue: check.issue });
180
- }
181
- }
182
-
183
- return result;
184
- }
185
-
186
- function printResult(result: WorkflowValidationResult): void {
187
- const icon = result.valid ? '✅' : '❌';
188
- console.log(`\n${icon} ${result.workflow} (${result.targetPackage})`);
189
-
190
- if (result.valid) {
191
- console.log(' All paths match dependencies');
192
- return;
193
- }
194
-
195
- const extraIssues = result.issues.filter(
196
- (issue) => issue.kind !== 'missing' && issue.kind !== 'unnecessary',
197
- );
198
-
199
- if (extraIssues.length > 0) {
200
- console.log(' Issues:');
201
- for (const issue of extraIssues) {
202
- console.log(` ⚠️ ${issue.message}`);
203
- }
204
- }
205
-
206
- if (result.missing.length > 0) {
207
- console.log(' Missing paths:');
208
- for (const path of result.missing) {
209
- console.log(` - ${path}`);
210
- }
211
- }
212
-
213
- if (result.unnecessary.length > 0) {
214
- console.log(' Unnecessary paths:');
215
- for (const path of result.unnecessary) {
216
- console.log(` - ${path}`);
217
- }
218
- }
219
- }
220
-
221
- async function main(): Promise<void> {
222
- const rootDir = resolve(process.cwd());
223
- let hasErrors = false;
224
-
225
- console.log(
226
- '🔍 Validating GitHub Actions workflows against dependencies...\n',
227
- );
228
- const results = await validateWorkflows(rootDir);
229
- console.log(`Found ${results.length} workflow(s) to validate\n`);
230
-
231
- if (results.length === 0) {
232
- console.log('No deploy-*.yml or release-*.yml workflows found.\n');
233
- }
234
-
235
- for (const result of results) {
236
- printResult(result);
237
- }
238
-
239
- const validCount = results.filter((result) => result.valid).length;
240
- const invalidCount = results.length - validCount;
241
-
242
- console.log('\n' + '='.repeat(60));
243
- console.log(
244
- `\n📊 Path validation: ${validCount} valid, ${invalidCount} invalid\n`,
245
- );
246
-
247
- if (invalidCount > 0) {
248
- console.log('❌ Some workflows need updates to match dependencies');
249
- console.log('\nTo fix:');
250
- console.log('1. Update workflow path filters to match missing paths');
251
- console.log('2. Remove unnecessary paths');
252
- console.log('3. Replace broad workspace wildcards with specific paths\n');
253
- hasErrors = true;
254
- } else if (results.length > 0) {
255
- console.log('✅ All workflows match their dependencies!\n');
256
- }
257
-
258
- console.log('='.repeat(60));
259
- console.log('\n🔍 Validating pnpm version consistency...\n');
260
-
261
- const pnpmResult = validatePnpmVersions(rootDir);
262
- if (!pnpmResult.valid) {
263
- console.log('❌ PNPM version issues found:\n');
264
- for (const { workflow, issue } of pnpmResult.workflowIssues) {
265
- console.log(` ⚠️ ${workflow}: ${issue}`);
266
- }
267
- for (const { issue } of pnpmResult.dockerfileIssues) {
268
- console.log(` ⚠️ ${issue}`);
269
- }
270
- console.log('\nTo fix:');
271
- console.log(
272
- '1. Remove hardcoded pnpm versions from workflows (let pnpm/action-setup auto-detect from packageManager)',
273
- );
274
- console.log(
275
- '2. Update Dockerfile pnpm versions to match package.json packageManager field\n',
276
- );
277
- hasErrors = true;
278
- } else {
279
- console.log('✅ PNPM versions are consistent!\n');
280
- }
281
-
282
- if (hasErrors) {
283
- process.exit(1);
284
- }
285
- }
286
-
287
- void main();