From e73c260482af54b339415277599735aab5e2246b Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Thu, 5 Feb 2026 18:07:56 +0900 Subject: [PATCH 1/7] chore: add some debug output to the resolver --- .../check-parser/package-files/resolver.ts | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/packages/cli/src/services/check-parser/package-files/resolver.ts b/packages/cli/src/services/check-parser/package-files/resolver.ts index 28b1737e5..28d95e7c3 100644 --- a/packages/cli/src/services/check-parser/package-files/resolver.ts +++ b/packages/cli/src/services/check-parser/package-files/resolver.ts @@ -1,5 +1,7 @@ import path from 'node:path' +import Debug from 'debug' + import { SourceFile } from './source-file' import { PackageJsonFile } from './package-json-file' import { TSConfigFile } from './tsconfig-json-file' @@ -12,6 +14,8 @@ import { LookupContext } from './lookup' import { lineage, LineageOptions } from './walk' import { Package, Workspace } from './workspace' +const debug = Debug('checkly:cli:services:check-parser:resolver') + class PackageFilesCache { #sourceFileCache = new FileLoader(SourceFile.loadFromFilePath) @@ -351,6 +355,8 @@ export class PackageFilesResolver { filePath: string, dependencies: RawDependency[], ): Promise { + debug(`Resolving dependencies for %s`, filePath) + const resolved: Dependencies = { external: [], missing: [], @@ -388,6 +394,9 @@ export class PackageFilesResolver { }) if (packageJson) { + debug('Found workspace root package.json file %s', + packageJson.meta.filePath, + ) resolved.local.push({ kind: 'workspace-root-package-json-file', importPath: filePath, @@ -397,6 +406,9 @@ export class PackageFilesResolver { } if (tsconfigJson) { + debug('Found workspace root tsconfig.json file %s', + tsconfigJson.meta.filePath, + ) resolved.local.push({ kind: 'workspace-root-tsconfig-file', importPath: filePath, @@ -406,6 +418,9 @@ export class PackageFilesResolver { } if (jsconfigJson) { + debug('Found workspace root jsconfig.json file %s', + jsconfigJson.meta.filePath, + ) resolved.local.push({ kind: 'workspace-root-tsconfig-file', importPath: filePath, @@ -419,6 +434,9 @@ export class PackageFilesResolver { this.workspace.lockfile.ok(), ) if (lockfile !== undefined) { + debug('Found workspace root lockfile %s', + lockfile.meta.filePath, + ) resolved.local.push({ kind: 'workspace-root-lockfile', importPath: filePath, @@ -432,6 +450,9 @@ export class PackageFilesResolver { this.workspace.configFile.ok(), ) if (configFile !== undefined) { + debug('Found workspace root config file %s', + configFile.meta.filePath, + ) resolved.local.push({ kind: 'workspace-root-config-file', importPath: filePath, @@ -453,6 +474,9 @@ export class PackageFilesResolver { // here is not necessary in restricted mode. if (!this.restricted) { if (packageJson) { + debug('Found nearest package.json file %s', + packageJson.meta.filePath, + ) resolved.local.push({ kind: 'nearest-package-json-file', importPath: filePath, @@ -462,6 +486,9 @@ export class PackageFilesResolver { } if (tsconfigJson) { + debug('Found nearest tsconfig.json file %s', + tsconfigJson.meta.filePath, + ) resolved.local.push({ kind: 'nearest-tsconfig-file', importPath: filePath, @@ -471,6 +498,9 @@ export class PackageFilesResolver { } if (jsconfigJson) { + debug('Found nearest jsconfig.json file %s', + jsconfigJson.meta.filePath, + ) resolved.local.push({ kind: 'nearest-tsconfig-file', importPath: filePath, @@ -487,6 +517,7 @@ export class PackageFilesResolver { resolve: for (const { importPath, source } of dependencies) { if (isBuiltinPath(importPath)) { + debug('Found built-in %s', importPath) resolved.external.push({ importPath, }) @@ -500,6 +531,7 @@ export class PackageFilesResolver { const resolvedFiles = await this.resolveSourceFile(sourceFile, context) let found = false for (const resolvedFile of resolvedFiles) { + debug('Found local file %s', resolvedFile.meta.filePath) resolved.local.push({ kind: 'relative-path', importPath, @@ -511,6 +543,7 @@ export class PackageFilesResolver { continue resolve } } + debug('Failed to find local file %s', importPath) resolved.missing.push({ importPath, filePath: relativeDepPath, @@ -533,6 +566,9 @@ export class PackageFilesResolver { const resolvedFiles = await this.resolveSourceFile(sourceFile, context) for (const resolvedFile of resolvedFiles) { configJson.registerRelatedSourceFile(resolvedFile) + debug('Found tsconfig paths resolved file %s', + resolvedFile.meta.filePath, + ) resolved.local.push({ kind: 'supporting-tsconfig-resolved-path', importPath, @@ -543,6 +579,9 @@ export class PackageFilesResolver { target, }, }) + debug('Found supporting tsconfig.json file %s (paths)', + configJson.meta.filePath, + ) resolved.local.push({ kind: 'supporting-tsconfig-file', importPath, @@ -571,12 +610,18 @@ export class PackageFilesResolver { let found = false for (const resolvedFile of resolvedFiles) { configJson.registerRelatedSourceFile(resolvedFile) + debug('Found tsconfig baseUrl relative file %s', + resolvedFile.meta.filePath, + ) resolved.local.push({ kind: 'supporting-tsconfig-baseurl-relative-path', importPath, sourceFile: resolvedFile, configFile: configJson, }) + debug('Found supporting tsconfig file %s (baseUrl)', + configJson.meta.filePath, + ) resolved.local.push({ kind: 'supporting-tsconfig-file', importPath, @@ -593,6 +638,10 @@ export class PackageFilesResolver { } if (isImportsPath(importPath)) { + debug('Found local imports path %s', + importPath, + ) + // TODO continue resolve } @@ -609,6 +658,7 @@ export class PackageFilesResolver { }) let found = false for (const resolvedFile of resolvedFiles) { + debug('Found workspace neighbor file %s', resolvedFile.meta.filePath) resolved.local.push({ kind: 'workspace-neighbor', neighbor, @@ -618,6 +668,7 @@ export class PackageFilesResolver { found = true } if (found) { + debug('Found workspace neighbor reference %s', neighbor.path) usedNeighbors.add(neighbor) continue resolve } @@ -625,6 +676,7 @@ export class PackageFilesResolver { } } + debug(`Found external dependency %s`, importPath) resolved.external.push({ importPath, }) @@ -641,6 +693,7 @@ export class PackageFilesResolver { for (const dep of Object.keys(combinedDependencies)) { const neighbor = this.workspace.memberByName(dep) if (neighbor) { + debug('Found workspace neighbor requirement %s', neighbor.path) requiredNeighbors.add(neighbor) } } From 5a3a3df519100dcd5296523234596472c349e8cc Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Mon, 16 Feb 2026 05:31:56 +0900 Subject: [PATCH 2/7] feat: add a fixture for complex workspace relations (to be used later) --- .../depended-on-by-main-and-root/index.js | 1 + .../depended-on-by-main-and-root/package.json | 13 ++++++ .../apps/depended-on-by-main/index.js | 1 + .../apps/depended-on-by-main/package.json | 16 +++++++ .../index.js | 1 + .../package.json | 13 ++++++ .../index.js | 1 + .../package.json | 13 ++++++ .../apps/depended-on-by-root/index.js | 1 + .../apps/depended-on-by-root/package.json | 16 +++++++ .../apps/main/checkly.config.ts | 28 +++++++++++ .../apps/main/index.js | 1 + .../apps/main/package.json | 17 +++++++ .../apps/main/playwright.config.js | 5 ++ .../apps/main/tests/foo.spec.js | 2 + .../apps/not-depended-on/index.js | 1 + .../apps/not-depended-on/package.json | 13 ++++++ .../index.js | 2 + .../package.json | 19 ++++++++ .../pnpm-lock.yaml | 46 +++++++++++++++++++ .../pnpm-workspace.yaml | 2 + 21 files changed, 212 insertions(+) create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main-and-root/index.js create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main-and-root/package.json create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main/index.js create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main/package.json create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-main/index.js create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-main/package.json create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-root/index.js create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-root/package.json create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-root/index.js create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-root/package.json create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/main/checkly.config.ts create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/main/index.js create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/main/package.json create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/main/playwright.config.js create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/main/tests/foo.spec.js create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/not-depended-on/index.js create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/not-depended-on/package.json create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/index.js create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/package.json create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/pnpm-lock.yaml create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/pnpm-workspace.yaml diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main-and-root/index.js b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main-and-root/index.js new file mode 100644 index 000000000..aaf3bc0dd --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main-and-root/index.js @@ -0,0 +1 @@ +console.log('Hello from depended-on-by-main-and-root') diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main-and-root/package.json b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main-and-root/package.json new file mode 100644 index 000000000..238103e0b --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main-and-root/package.json @@ -0,0 +1,13 @@ +{ + "name": "depended-on-by-main-and-root", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "packageManager": "pnpm@10.28.1" +} diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main/index.js b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main/index.js new file mode 100644 index 000000000..b25438033 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main/index.js @@ -0,0 +1 @@ +console.log('Hello from depended-on-by-main') diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main/package.json b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main/package.json new file mode 100644 index 000000000..c7c3eff39 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main/package.json @@ -0,0 +1,16 @@ +{ + "name": "depended-on-by-main", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "packageManager": "pnpm@10.28.1", + "dependencies": { + "depended-on-by-neighbor-depended-on-by-main": "workspace:*" + } +} \ No newline at end of file diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-main/index.js b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-main/index.js new file mode 100644 index 000000000..4ad7a33d2 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-main/index.js @@ -0,0 +1 @@ +console.log('depended-on-by-neighbor-depended-on-by-main') diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-main/package.json b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-main/package.json new file mode 100644 index 000000000..b2c28d714 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-main/package.json @@ -0,0 +1,13 @@ +{ + "name": "depended-on-by-neighbor-depended-on-by-main", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "packageManager": "pnpm@10.28.1" +} diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-root/index.js b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-root/index.js new file mode 100644 index 000000000..864fe6b4c --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-root/index.js @@ -0,0 +1 @@ +console.log('depended-on-by-neighbor-depended-on-by-root') diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-root/package.json b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-root/package.json new file mode 100644 index 000000000..2b05e7cc4 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-root/package.json @@ -0,0 +1,13 @@ +{ + "name": "depended-on-by-neighbor-depended-on-by-root", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "packageManager": "pnpm@10.28.1" +} diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-root/index.js b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-root/index.js new file mode 100644 index 000000000..a72269b3b --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-root/index.js @@ -0,0 +1 @@ +console.log('Hello from depended-on-by-root') diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-root/package.json b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-root/package.json new file mode 100644 index 000000000..3dfae92e3 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/depended-on-by-root/package.json @@ -0,0 +1,16 @@ +{ + "name": "depended-on-by-root", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "packageManager": "pnpm@10.28.1", + "dependencies": { + "depended-on-by-neighbor-depended-on-by-root": "workspace:*" + } +} \ No newline at end of file diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/main/checkly.config.ts b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/main/checkly.config.ts new file mode 100644 index 000000000..47be22312 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/main/checkly.config.ts @@ -0,0 +1,28 @@ +import { defineConfig } from 'checkly' + +const config = defineConfig({ + logicalId: 'main', + projectName: 'main', + checks: { + playwrightConfigPath: './playwright.config.js', + playwrightChecks: [ + { + logicalId: 'main', + name: 'main', + frequency: 10, + locations: [ + 'us-east-1', + ], + }, + ], + frequency: 10, + locations: [ + 'us-east-1', + ], + }, + cli: { + runLocation: 'us-east-1', + }, +}) + +export default config \ No newline at end of file diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/main/index.js b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/main/index.js new file mode 100644 index 000000000..f854a2373 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/main/index.js @@ -0,0 +1 @@ +console.log('Hello from main') diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/main/package.json b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/main/package.json new file mode 100644 index 000000000..fe65a85e6 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/main/package.json @@ -0,0 +1,17 @@ +{ + "name": "main", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "packageManager": "pnpm@10.28.1", + "dependencies": { + "depended-on-by-main": "workspace:*", + "depended-on-by-main-and-root": "workspace:*" + } +} diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/main/playwright.config.js b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/main/playwright.config.js new file mode 100644 index 000000000..fbc10111b --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/main/playwright.config.js @@ -0,0 +1,5 @@ +const { defineConfig } = require('@playwright/test') + +module.exports = defineConfig({ + testDir: './tests' +}) diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/main/tests/foo.spec.js b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/main/tests/foo.spec.js new file mode 100644 index 000000000..69dd72cba --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/main/tests/foo.spec.js @@ -0,0 +1,2 @@ +require('depended-on-by-main') +require('depended-on-by-main-and-root') diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/not-depended-on/index.js b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/not-depended-on/index.js new file mode 100644 index 000000000..b58ed54c4 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/not-depended-on/index.js @@ -0,0 +1 @@ +console.log('Hello from not-depended-on') diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/not-depended-on/package.json b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/not-depended-on/package.json new file mode 100644 index 000000000..7faace776 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/apps/not-depended-on/package.json @@ -0,0 +1,13 @@ +{ + "name": "not-depended-on", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "packageManager": "pnpm@10.28.1" +} diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/index.js b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/index.js new file mode 100644 index 000000000..90284b563 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/index.js @@ -0,0 +1,2 @@ +require('depended-on-by-root') +require('depended-on-by-main-and-root') diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/package.json b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/package.json new file mode 100644 index 000000000..758275b0b --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/package.json @@ -0,0 +1,19 @@ +{ + "name": "test-pnpm-workspace-dependency-in-root-package", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "packageManager": "pnpm@10.28.1", + "dependencies": { + "depended-on-by-root": "workspace:*" + }, + "devDependencies": { + "depended-on-by-main-and-root": "workspace:*" + } +} diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/pnpm-lock.yaml b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/pnpm-lock.yaml new file mode 100644 index 000000000..fc4d52e52 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/pnpm-lock.yaml @@ -0,0 +1,46 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + depended-on-by-root: + specifier: workspace:* + version: link:apps/depended-on-by-root + devDependencies: + depended-on-by-main-and-root: + specifier: workspace:* + version: link:apps/depended-on-by-main-and-root + + apps/depended-on-by-main: + dependencies: + depended-on-by-neighbor-depended-on-by-main: + specifier: workspace:* + version: link:../depended-on-by-neighbor-depended-on-by-main + + apps/depended-on-by-main-and-root: {} + + apps/depended-on-by-neighbor-depended-on-by-main: {} + + apps/depended-on-by-neighbor-depended-on-by-root: {} + + apps/depended-on-by-root: + dependencies: + depended-on-by-neighbor-depended-on-by-root: + specifier: workspace:* + version: link:../depended-on-by-neighbor-depended-on-by-root + + apps/main: + dependencies: + depended-on-by-main: + specifier: workspace:* + version: link:../depended-on-by-main + depended-on-by-main-and-root: + specifier: workspace:* + version: link:../depended-on-by-main-and-root + + apps/not-depended-on: {} diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/pnpm-workspace.yaml b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/pnpm-workspace.yaml new file mode 100644 index 000000000..8ab3e17a0 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/pnpm-depend-on-workspace-package-in-root-package/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'apps/*' From e9305d4b7032c35a72671101ccc7003a60ca6165 Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Mon, 16 Feb 2026 05:32:50 +0900 Subject: [PATCH 3/7] fix: support workspace root depending on other packages in the workspace --- .../cli/src/services/check-parser/parser.ts | 233 +++++++++++------- 1 file changed, 145 insertions(+), 88 deletions(-) diff --git a/packages/cli/src/services/check-parser/parser.ts b/packages/cli/src/services/check-parser/parser.ts index c415e03cc..74319959c 100644 --- a/packages/cli/src/services/check-parser/parser.ts +++ b/packages/cli/src/services/check-parser/parser.ts @@ -42,8 +42,6 @@ function isLegacySupportedFileExtension (value: string): value is LegacySupporte } } -const PACKAGE_EXTENSION = `${path.sep}package.json` - const supportedBuiltinModules = [ 'node:assert', 'node:buffer', @@ -143,18 +141,23 @@ export type File = | VirtualFile | PhysicalFile +const FILEOP_PARSE = 1 +const FILEOP_RESOLVE = 2 + export class Parser { supportedModules: Set checkUnsupportedModules: boolean workspace?: Workspace resolver: PackageFilesResolver restricted: boolean - cache = new Map() + #resolveCache = new Map() + // TODO: pass a npm matrix of supported npm modules // Maybe pass a cache so we don't have to fetch files separately all the time constructor (options: ParserOptions) { @@ -180,37 +183,52 @@ export class Parser { return false } - private async validateFile ( + private validateEntrypoint ( filePath: string, - ): Promise { - debug(`Validating file ${filePath}`) - const extension = path.extname(filePath) - if (!this.isProcessableExtension(extension)) { + ): void { + const ops = this.determineFileOps(filePath) + if ((ops & FILEOP_PARSE) === 0) { throw new Error(`Unsupported file extension for ${filePath}`) } + } + + private async readFile ( + filePath: string, + ): Promise { + debug(`Reading file ${filePath}`) try { const content = await fs.readFile(filePath, { encoding: 'utf-8' }) return { filePath, content } } catch (err) { - debug(`Failed to validate file ${filePath}: ${err}`) + debug(`Failed to read file ${filePath}: ${err}`) throw new DependencyParseError(filePath, [filePath], [], []) } } - private isProcessableExtension (extension: string): boolean { + private determineFileOps (filePath: string): number { + const extension = path.extname(filePath) + if (this.restricted) { - return isLegacySupportedFileExtension(extension) + if (isLegacySupportedFileExtension(extension)) { + return FILEOP_RESOLVE | FILEOP_PARSE + } + + return 0 } - if (isCoreExtension(extension) && extension !== '.json') { - return true + if (isCoreExtension(extension)) { + if (extension === '.json') { + return FILEOP_RESOLVE + } + + return FILEOP_RESOLVE | FILEOP_PARSE } if (isTSExtension(extension)) { - return true + return FILEOP_RESOLVE | FILEOP_PARSE } - return false + return 0 } async getFilesAndDependencies ( @@ -225,47 +243,51 @@ export class Parser { const missingFiles = new Set() const resultFileSet = new Set() const neighbors = new NeighborDependencyCollector() - for (const file of files) { - if (resultFileSet.has(file)) { + for (const filePath of files) { + if (resultFileSet.has(filePath)) { continue } - const extension = path.extname(file) - if (!this.isProcessableExtension(extension)) { - resultFileSet.add(file) - continue - } - const item = await this.validateFile(file) - const cache = this.cache.get(item.filePath) - const { module, error } = cache !== undefined - ? cache - : Parser.parseDependencies(item.filePath, item.content) + const ops = this.determineFileOps(filePath) - if (error) { - this.cache.set(item.filePath, { module, error }) - errors.add(item.filePath) + // Not necessary - just a minor optimization. + if (ops === 0) { + resultFileSet.add(filePath) continue } - const resolvedDependencies = cache?.resolvedDependencies - ?? await this.resolver.resolveDependenciesForFilePath(item.filePath, module.dependencies) - for (const dep of resolvedDependencies.missing) { - missingFiles.add(dep.filePath) + let dependencies: RawDependency[] = [] + if (ops & FILEOP_PARSE) { + const { module, error } = await this.parseFile(filePath) + + if (error) { + errors.add(filePath) + continue + } + + dependencies = module.dependencies } - neighbors.markDepended(...resolvedDependencies.neighbors.depends) - neighbors.markReferenced(...resolvedDependencies.neighbors.references) + if (ops & FILEOP_RESOLVE) { + const resolvedDependencies = await this.resolveDependencies(filePath, dependencies) - this.cache.set(item.filePath, { module, resolvedDependencies }) + for (const dep of resolvedDependencies.missing) { + missingFiles.add(dep.filePath) + } - for (const dep of resolvedDependencies.local) { - if (resultFileSet.has(dep.sourceFile.meta.filePath)) { - continue + neighbors.markDepended(...resolvedDependencies.neighbors.depends) + neighbors.markReferenced(...resolvedDependencies.neighbors.references) + + for (const dep of resolvedDependencies.local) { + if (resultFileSet.has(dep.sourceFile.meta.filePath)) { + continue + } + const filePath = dep.sourceFile.meta.filePath + files.add(filePath) } - const filePath = dep.sourceFile.meta.filePath - files.add(filePath) } - resultFileSet.add(item.filePath) + + resultFileSet.add(filePath) } if (missingFiles.size) { @@ -296,6 +318,41 @@ export class Parser { } } + private async parseFile ( + filePath: string, + ): Promise<{ module: Module, error?: any }> { + const cache = this.#parseCache.get(filePath) + if (!cache) { + const { content } = await this.readFile(filePath) + return this.parseFileContent(filePath, content) + } + return cache + } + + private parseFileContent ( + filePath: string, + content: string, + ): { module: Module, error?: any } { + let cache = this.#parseCache.get(filePath) + if (!cache) { + cache = Parser.parseDependencies(filePath, content) + this.#parseCache.set(filePath, cache) + } + return cache + } + + private async resolveDependencies ( + filePath: string, + dependencies: RawDependency[], + ): Promise { + let cache = this.#resolveCache.get(filePath) + if (!cache) { + cache = await this.resolver.resolveDependenciesForFilePath(filePath, dependencies) + this.#resolveCache.set(filePath, cache) + } + return cache + } + private async collectFiles (cache: Map, testDir: string, ignoredFiles: string[]) { let files = cache.get(testDir) if (!files) { @@ -371,7 +428,9 @@ export class Parser { } async parse (entrypoint: string) { - const { content } = await this.validateFile(entrypoint) + this.validateEntrypoint(entrypoint) + + const { content } = await this.readFile(entrypoint) /* * The importing of files forms a directed graph. @@ -383,63 +442,61 @@ export class Parser { const neighbors = new NeighborDependencyCollector() const bfsQueue: [FileEntry] = [{ filePath: entrypoint, content }] while (bfsQueue.length > 0) { - // Since we just checked the length, shift() will never return undefined. - // We can add a not-null assertion operator (!). - + // Since we just checked the length, shift() will never return undefined. + // We can add a not-null assertion operator (!). const item = bfsQueue.shift()! - if (item.filePath.endsWith(PACKAGE_EXTENSION)) { - // Holds info about the main file and doesn't need to be parsed - continue - } + const ops = this.determineFileOps(item.filePath) - // This cache is only useful when there are multiple entrypoints with - // common files, as we make sure to not add the same file twice to - // bfsQueue. - const cache = this.cache.get(item.filePath) - const { module, error } = cache !== undefined - ? cache - : Parser.parseDependencies(item.filePath, item.content) - - if (error) { - this.cache.set(item.filePath, { module, error }) - collector.addParsingError(item.filePath, error.message) + // Not necessary - just a minor optimization. + if (ops === 0) { continue } - const resolvedDependencies = cache?.resolvedDependencies - ?? await this.resolver.resolveDependenciesForFilePath(item.filePath, module.dependencies) + let dependencies: RawDependency[] = [] + if (ops & FILEOP_PARSE) { + const { module, error } = this.parseFileContent(item.filePath, item.content) - this.cache.set(item.filePath, { module, resolvedDependencies }) + if (error) { + collector.addParsingError(item.filePath, error.message) + continue + } - if (this.checkUnsupportedModules) { - const unsupportedDependencies = resolvedDependencies.external.flatMap(dep => { - if (!this.supportsModule(dep.importPath)) { - return [dep.importPath] - } else { - return [] + dependencies = module.dependencies + } + + if (ops & FILEOP_RESOLVE) { + const resolvedDependencies = await this.resolveDependencies(item.filePath, dependencies) + + if (this.checkUnsupportedModules) { + const unsupportedDependencies = resolvedDependencies.external.flatMap(dep => { + if (!this.supportsModule(dep.importPath)) { + return [dep.importPath] + } else { + return [] + } + }) + if (unsupportedDependencies.length) { + collector.addUnsupportedNpmDependencies(item.filePath, unsupportedDependencies) } - }) - if (unsupportedDependencies.length) { - collector.addUnsupportedNpmDependencies(item.filePath, unsupportedDependencies) } - } - for (const dep of resolvedDependencies.missing) { - collector.addMissingFile(dep.filePath) - } + for (const dep of resolvedDependencies.missing) { + collector.addMissingFile(dep.filePath) + } - for (const dep of resolvedDependencies.local) { - const filePath = dep.sourceFile.meta.filePath - if (collector.hasDependency(filePath)) { - continue + for (const dep of resolvedDependencies.local) { + const filePath = dep.sourceFile.meta.filePath + if (collector.hasDependency(filePath)) { + continue + } + collector.addDependency(filePath, dep.sourceFile.contents) + bfsQueue.push({ filePath, content: dep.sourceFile.contents }) } - collector.addDependency(filePath, dep.sourceFile.contents) - bfsQueue.push({ filePath, content: dep.sourceFile.contents }) - } - neighbors.markDepended(...resolvedDependencies.neighbors.depends) - neighbors.markReferenced(...resolvedDependencies.neighbors.references) + neighbors.markDepended(...resolvedDependencies.neighbors.depends) + neighbors.markReferenced(...resolvedDependencies.neighbors.references) + } } for (const pkg of neighbors.unreferencedDependedPackages()) { From d893a105477b957049f65d2664091511ebabdfa9 Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Thu, 19 Feb 2026 04:19:59 +0900 Subject: [PATCH 4/7] feat(tests): add basic tests for pnpm and npm workspaces --- .../depended-on-by-main-and-root/index.js | 1 + .../depended-on-by-main-and-root/package.json | 12 ++ .../apps/depended-on-by-main/index.js | 1 + .../apps/depended-on-by-main/package.json | 15 +++ .../index.js | 1 + .../package.json | 12 ++ .../index.js | 1 + .../package.json | 12 ++ .../apps/depended-on-by-root/index.js | 1 + .../apps/depended-on-by-root/package.json | 15 +++ .../apps/main/checkly.config.ts | 28 +++++ .../apps/main/index.js | 1 + .../apps/main/package.json | 16 +++ .../apps/main/playwright.config.js | 5 + .../apps/main/tests/foo.spec.js | 2 + .../apps/not-depended-on/index.js | 1 + .../apps/not-depended-on/package.json | 12 ++ .../index.js | 2 + .../package-lock.json | 88 +++++++++++++++ .../package.json | 21 ++++ .../__tests__/check-parser.spec.ts | 103 ++++++++++++++++++ .../src/services/check-parser/faux-package.ts | 7 +- packages/cli/src/testing/fixture-sandbox.ts | 9 +- 23 files changed, 361 insertions(+), 5 deletions(-) create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main-and-root/index.js create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main-and-root/package.json create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main/index.js create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main/package.json create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-main/index.js create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-main/package.json create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-root/index.js create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-root/package.json create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-root/index.js create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-root/package.json create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/main/checkly.config.ts create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/main/index.js create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/main/package.json create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/main/playwright.config.js create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/main/tests/foo.spec.js create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/not-depended-on/index.js create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/not-depended-on/package.json create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/index.js create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/package-lock.json create mode 100644 packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/package.json diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main-and-root/index.js b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main-and-root/index.js new file mode 100644 index 000000000..aaf3bc0dd --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main-and-root/index.js @@ -0,0 +1 @@ +console.log('Hello from depended-on-by-main-and-root') diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main-and-root/package.json b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main-and-root/package.json new file mode 100644 index 000000000..de6821998 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main-and-root/package.json @@ -0,0 +1,12 @@ +{ + "name": "depended-on-by-main-and-root", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} \ No newline at end of file diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main/index.js b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main/index.js new file mode 100644 index 000000000..b25438033 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main/index.js @@ -0,0 +1 @@ +console.log('Hello from depended-on-by-main') diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main/package.json b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main/package.json new file mode 100644 index 000000000..c99b3e886 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-main/package.json @@ -0,0 +1,15 @@ +{ + "name": "depended-on-by-main", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "depended-on-by-neighbor-depended-on-by-main": "*" + } +} \ No newline at end of file diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-main/index.js b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-main/index.js new file mode 100644 index 000000000..4ad7a33d2 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-main/index.js @@ -0,0 +1 @@ +console.log('depended-on-by-neighbor-depended-on-by-main') diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-main/package.json b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-main/package.json new file mode 100644 index 000000000..7e8ff30b6 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-main/package.json @@ -0,0 +1,12 @@ +{ + "name": "depended-on-by-neighbor-depended-on-by-main", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} \ No newline at end of file diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-root/index.js b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-root/index.js new file mode 100644 index 000000000..864fe6b4c --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-root/index.js @@ -0,0 +1 @@ +console.log('depended-on-by-neighbor-depended-on-by-root') diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-root/package.json b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-root/package.json new file mode 100644 index 000000000..9d90e14a4 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-neighbor-depended-on-by-root/package.json @@ -0,0 +1,12 @@ +{ + "name": "depended-on-by-neighbor-depended-on-by-root", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} \ No newline at end of file diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-root/index.js b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-root/index.js new file mode 100644 index 000000000..a72269b3b --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-root/index.js @@ -0,0 +1 @@ +console.log('Hello from depended-on-by-root') diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-root/package.json b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-root/package.json new file mode 100644 index 000000000..076e3b6ec --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/depended-on-by-root/package.json @@ -0,0 +1,15 @@ +{ + "name": "depended-on-by-root", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "depended-on-by-neighbor-depended-on-by-root": "*" + } +} \ No newline at end of file diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/main/checkly.config.ts b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/main/checkly.config.ts new file mode 100644 index 000000000..47be22312 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/main/checkly.config.ts @@ -0,0 +1,28 @@ +import { defineConfig } from 'checkly' + +const config = defineConfig({ + logicalId: 'main', + projectName: 'main', + checks: { + playwrightConfigPath: './playwright.config.js', + playwrightChecks: [ + { + logicalId: 'main', + name: 'main', + frequency: 10, + locations: [ + 'us-east-1', + ], + }, + ], + frequency: 10, + locations: [ + 'us-east-1', + ], + }, + cli: { + runLocation: 'us-east-1', + }, +}) + +export default config \ No newline at end of file diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/main/index.js b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/main/index.js new file mode 100644 index 000000000..f854a2373 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/main/index.js @@ -0,0 +1 @@ +console.log('Hello from main') diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/main/package.json b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/main/package.json new file mode 100644 index 000000000..a9895ce7e --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/main/package.json @@ -0,0 +1,16 @@ +{ + "name": "main", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "depended-on-by-main": "*", + "depended-on-by-main-and-root": "*" + } +} \ No newline at end of file diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/main/playwright.config.js b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/main/playwright.config.js new file mode 100644 index 000000000..fbc10111b --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/main/playwright.config.js @@ -0,0 +1,5 @@ +const { defineConfig } = require('@playwright/test') + +module.exports = defineConfig({ + testDir: './tests' +}) diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/main/tests/foo.spec.js b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/main/tests/foo.spec.js new file mode 100644 index 000000000..69dd72cba --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/main/tests/foo.spec.js @@ -0,0 +1,2 @@ +require('depended-on-by-main') +require('depended-on-by-main-and-root') diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/not-depended-on/index.js b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/not-depended-on/index.js new file mode 100644 index 000000000..b58ed54c4 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/not-depended-on/index.js @@ -0,0 +1 @@ +console.log('Hello from not-depended-on') diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/not-depended-on/package.json b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/not-depended-on/package.json new file mode 100644 index 000000000..f45d07923 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/apps/not-depended-on/package.json @@ -0,0 +1,12 @@ +{ + "name": "not-depended-on", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} \ No newline at end of file diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/index.js b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/index.js new file mode 100644 index 000000000..90284b563 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/index.js @@ -0,0 +1,2 @@ +require('depended-on-by-root') +require('depended-on-by-main-and-root') diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/package-lock.json b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/package-lock.json new file mode 100644 index 000000000..0f471f562 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/package-lock.json @@ -0,0 +1,88 @@ +{ + "name": "npm-depend-on-workspace-package-in-root-package", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "npm-depend-on-workspace-package-in-root-package", + "version": "1.0.0", + "license": "ISC", + "workspaces": [ + "apps/*" + ], + "dependencies": { + "depended-on-by-root": "*" + }, + "devDependencies": { + "depended-on-by-main-and-root": "*" + } + }, + "apps/depended-on-by-main": { + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "depended-on-by-neighbor-depended-on-by-main": "*" + } + }, + "apps/depended-on-by-main-and-root": { + "version": "1.0.0", + "license": "ISC" + }, + "apps/depended-on-by-neighbor-depended-on-by-main": { + "version": "1.0.0", + "license": "ISC" + }, + "apps/depended-on-by-neighbor-depended-on-by-root": { + "version": "1.0.0", + "license": "ISC" + }, + "apps/depended-on-by-root": { + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "depended-on-by-neighbor-depended-on-by-root": "*" + } + }, + "apps/main": { + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "depended-on-by-main": "*", + "depended-on-by-main-and-root": "*" + } + }, + "apps/not-depended-on": { + "version": "1.0.0", + "license": "ISC" + }, + "node_modules/depended-on-by-main": { + "resolved": "apps/depended-on-by-main", + "link": true + }, + "node_modules/depended-on-by-main-and-root": { + "resolved": "apps/depended-on-by-main-and-root", + "link": true + }, + "node_modules/depended-on-by-neighbor-depended-on-by-main": { + "resolved": "apps/depended-on-by-neighbor-depended-on-by-main", + "link": true + }, + "node_modules/depended-on-by-neighbor-depended-on-by-root": { + "resolved": "apps/depended-on-by-neighbor-depended-on-by-root", + "link": true + }, + "node_modules/depended-on-by-root": { + "resolved": "apps/depended-on-by-root", + "link": true + }, + "node_modules/main": { + "resolved": "apps/main", + "link": true + }, + "node_modules/not-depended-on": { + "resolved": "apps/not-depended-on", + "link": true + } + } +} diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/package.json b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/package.json new file mode 100644 index 000000000..7d1c49f41 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/npm-depend-on-workspace-package-in-root-package/package.json @@ -0,0 +1,21 @@ +{ + "name": "npm-depend-on-workspace-package-in-root-package", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "depended-on-by-root": "*" + }, + "devDependencies": { + "depended-on-by-main-and-root": "*" + }, + "workspaces": [ + "apps/*" + ] +} \ No newline at end of file diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser.spec.ts b/packages/cli/src/services/check-parser/__tests__/check-parser.spec.ts index a7ddce9f7..94cbaa0b5 100644 --- a/packages/cli/src/services/check-parser/__tests__/check-parser.spec.ts +++ b/packages/cli/src/services/check-parser/__tests__/check-parser.spec.ts @@ -1,9 +1,13 @@ +import fs from 'node:fs' import path from 'node:path' import { describe, it, expect, beforeAll, afterAll } from 'vitest' import { Parser } from '../parser' import { FixtureSandbox } from '../../../testing/fixture-sandbox' +import { NpmDetector, PNpmDetector } from '../package-files/package-manager' +import { FAUX_PACKAGE_DESCRIPTION } from '../faux-package' +import { Workspace } from '../package-files/workspace' const defaultNpmModules = [ 'timers', 'tls', 'url', 'util', 'zlib', '@faker-js/faker', '@opentelemetry/api', '@opentelemetry/sd-trace-base', @@ -28,6 +32,105 @@ describe('dependency-parser - parser()', () => { }) describe('unrestricted mode', () => { + describe('workspaces', () => { + const realFileEntry = (filePath: string) => { + return { + filePath, + content: fs.readFileSync(filePath, 'utf8'), + } + } + + const fauxFileEntry = (filePath: string) => { + return { + filePath, + content: expect.stringContaining(FAUX_PACKAGE_DESCRIPTION), + } + } + + describe('pnpm workspaces', () => { + describe('pnpm-depend-on-workspace-package-in-root-package', () => { + const toAbsolutePath = (...filepath: string[]) => fixt.abspath( + 'pnpm-depend-on-workspace-package-in-root-package', + ...filepath, + ) + + const packageManager = new PNpmDetector() + + let workspace: Workspace | undefined + beforeAll(async () => { + workspace = await packageManager.lookupWorkspace(toAbsolutePath('.')) + expect(workspace).toBeDefined() + }) + + it('should find all dependencies', async () => { + const parser = new Parser({ + supportedNpmModules: defaultNpmModules, + restricted: false, + workspace, + }) + const { dependencies } = await parser.parse(toAbsolutePath('apps/main/tests/foo.spec.js')) + const got = dependencies.sort((a, b) => a.filePath.localeCompare(b.filePath)) + const want = [ + realFileEntry(toAbsolutePath('apps/depended-on-by-main-and-root/index.js')), + realFileEntry(toAbsolutePath('apps/depended-on-by-main-and-root/package.json')), + realFileEntry(toAbsolutePath('apps/depended-on-by-main/index.js')), + realFileEntry(toAbsolutePath('apps/depended-on-by-main/package.json')), + fauxFileEntry(toAbsolutePath('apps/depended-on-by-neighbor-depended-on-by-main/package.json')), + fauxFileEntry(toAbsolutePath('apps/depended-on-by-root/package.json')), + realFileEntry(toAbsolutePath('apps/main/package.json')), + realFileEntry(toAbsolutePath('package.json')), + realFileEntry(toAbsolutePath('pnpm-lock.yaml')), + realFileEntry(toAbsolutePath('pnpm-workspace.yaml')), + ] + // Only check paths first, makes missing files easier to see. + expect(got.map(({ filePath }) => filePath)).toEqual(want.map(({ filePath }) => filePath)) + expect(got).toEqual(want) + }) + }) + }) + + describe('npm workspaces', () => { + describe('npm-depend-on-workspace-package-in-root-package', () => { + const toAbsolutePath = (...filepath: string[]) => fixt.abspath( + 'npm-depend-on-workspace-package-in-root-package', + ...filepath, + ) + + const packageManager = new NpmDetector() + + let workspace: Workspace | undefined + beforeAll(async () => { + workspace = await packageManager.lookupWorkspace(toAbsolutePath('.')) + expect(workspace).toBeDefined() + }) + + it('should find all dependencies', async () => { + const parser = new Parser({ + supportedNpmModules: defaultNpmModules, + restricted: false, + workspace, + }) + const { dependencies } = await parser.parse(toAbsolutePath('apps/main/tests/foo.spec.js')) + const got = dependencies.sort((a, b) => a.filePath.localeCompare(b.filePath)) + const want = [ + realFileEntry(toAbsolutePath('apps/depended-on-by-main-and-root/index.js')), + realFileEntry(toAbsolutePath('apps/depended-on-by-main-and-root/package.json')), + realFileEntry(toAbsolutePath('apps/depended-on-by-main/index.js')), + realFileEntry(toAbsolutePath('apps/depended-on-by-main/package.json')), + fauxFileEntry(toAbsolutePath('apps/depended-on-by-neighbor-depended-on-by-main/package.json')), + fauxFileEntry(toAbsolutePath('apps/depended-on-by-root/package.json')), + realFileEntry(toAbsolutePath('apps/main/package.json')), + realFileEntry(toAbsolutePath('package-lock.json')), + realFileEntry(toAbsolutePath('package.json')), + ] + // Only check paths first, makes missing files easier to see. + expect(got.map(({ filePath }) => filePath)).toEqual(want.map(({ filePath }) => filePath)) + expect(got).toEqual(want) + }) + }) + }) + }) + it('should handle JS file with no dependencies', async () => { const parser = new Parser({ supportedNpmModules: defaultNpmModules, diff --git a/packages/cli/src/services/check-parser/faux-package.ts b/packages/cli/src/services/check-parser/faux-package.ts index d43365a4f..fb43963c7 100644 --- a/packages/cli/src/services/check-parser/faux-package.ts +++ b/packages/cli/src/services/check-parser/faux-package.ts @@ -1,8 +1,9 @@ import { Package } from './package-files/workspace' import { VirtualFile } from './parser' -const DESCRIPTION = `This is a placeholder for an otherwise unused package ` - + `that Checkly determined to be needed during the installation step.` +export const FAUX_PACKAGE_DESCRIPTION = `This is a placeholder for an ` + + `otherwise unused package that Checkly determined to be needed during ` + + `the installation step.` export function createFauxPackageFiles (pkg: Package): VirtualFile[] { return [{ @@ -12,7 +13,7 @@ export function createFauxPackageFiles (pkg: Package): VirtualFile[] { { name: pkg.name, version: '0.0.0', - description: DESCRIPTION, + description: FAUX_PACKAGE_DESCRIPTION, private: true, }, undefined, diff --git a/packages/cli/src/testing/fixture-sandbox.ts b/packages/cli/src/testing/fixture-sandbox.ts index f3aa42431..db87d55ab 100644 --- a/packages/cli/src/testing/fixture-sandbox.ts +++ b/packages/cli/src/testing/fixture-sandbox.ts @@ -82,7 +82,10 @@ export class FixtureSandbox { const root = maybeRoot ? await fs.mkdir(maybeRoot, { recursive: true }).then(() => maybeRoot) - : await fs.mkdtemp(path.join(tmpdir(), 'fixture-sandbox-')) + // tmpdir() on macOS usually returns a path starting with /var which is + // a symlink. Resolve the path so that we don't run into path mismatch + // issues. + : await fs.realpath(await fs.mkdtemp(path.join(tmpdir(), 'fixture-sandbox-'))) debug(`Using root ${root}`) @@ -181,7 +184,9 @@ export class FixtureSandbox { } abspath (...to: string[]): string { - return path.join(this.#root, ...to) + // Split segments by / so that we can define paths in posix style and + // still expect the results to work on Windows. + return path.join(this.#root, ...to.flatMap(segment => segment.split('/'))) } } From b5fd8cccb11680f5b62b7df34d801687e63d6d0f Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Thu, 19 Feb 2026 04:42:01 +0900 Subject: [PATCH 5/7] fix(ci): make pnpm available for tests --- .github/workflows/test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f31f39c84..c97b55929 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,6 +34,9 @@ jobs: node-version: '20.x' cache: "npm" cache-dependency-path: package-lock.json + - uses: pnpm/action-setup@v4 + with: + version: 10 - run: npm config set fund false && npm set audit false - run: npm ci - run: npm run prepack From b8b9ebe4fe2b2a832fc430433ee532c57d380c0d Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Thu, 19 Feb 2026 05:03:51 +0900 Subject: [PATCH 6/7] fix(tests): in unrestricted workspace mode, we don't need to check supported deps --- .../src/services/check-parser/__tests__/check-parser.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser.spec.ts b/packages/cli/src/services/check-parser/__tests__/check-parser.spec.ts index 94cbaa0b5..b11c66688 100644 --- a/packages/cli/src/services/check-parser/__tests__/check-parser.spec.ts +++ b/packages/cli/src/services/check-parser/__tests__/check-parser.spec.ts @@ -64,7 +64,7 @@ describe('dependency-parser - parser()', () => { it('should find all dependencies', async () => { const parser = new Parser({ - supportedNpmModules: defaultNpmModules, + checkUnsupportedModules: false, restricted: false, workspace, }) @@ -106,7 +106,7 @@ describe('dependency-parser - parser()', () => { it('should find all dependencies', async () => { const parser = new Parser({ - supportedNpmModules: defaultNpmModules, + checkUnsupportedModules: false, restricted: false, workspace, }) From 3b5e2159972a8e301052fe0dbcee3ad0bdf527b7 Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Thu, 19 Feb 2026 06:33:18 +0900 Subject: [PATCH 7/7] fix: workspace glob pattern must not use windows paths --- .../src/services/check-parser/package-files/workspace.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/services/check-parser/package-files/workspace.ts b/packages/cli/src/services/check-parser/package-files/workspace.ts index 45c6d3f71..ae24027a8 100644 --- a/packages/cli/src/services/check-parser/package-files/workspace.ts +++ b/packages/cli/src/services/check-parser/package-files/workspace.ts @@ -1,5 +1,3 @@ -import path from 'node:path' - import { glob } from 'glob' import { PackageJsonFile } from './package-json-file' @@ -126,9 +124,10 @@ export class Workspace { root: string, patterns: string[], ): Promise { - const lookup = patterns.map(pattern => - path.join(pattern, PackageJsonFile.FILENAME), - ) + const lookup = patterns.map(pattern => { + const joiner = pattern.endsWith('/') ? '' : '/' + return [pattern, PackageJsonFile.FILENAME].join(joiner) + }) const results = await glob(lookup, { cwd: root,