diff --git a/package-lock.json b/package-lock.json index b8ed104b8..01d4973d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14703,6 +14703,7 @@ "chalk": "^4.1.2", "ci-info": "^4.3.1", "conf": "^10.2.0", + "debug": "^4.4.3", "dotenv": "^16.5.0", "execa": "^9.6.1", "git-repo-info": "^2.1.1", @@ -14731,6 +14732,7 @@ "@playwright/test": "^1.57.0", "@types/archiver": "6.0.3", "@types/config": "^3.3.5", + "@types/debug": "^4.1.12", "@types/glob": "^8.1.0", "@types/luxon": "^3.7.1", "@types/node": "^22.14.1", @@ -19141,6 +19143,7 @@ "@playwright/test": "^1.57.0", "@types/archiver": "6.0.3", "@types/config": "^3.3.5", + "@types/debug": "^4.1.12", "@types/glob": "^8.1.0", "@types/luxon": "^3.7.1", "@types/node": "^22.14.1", @@ -19159,6 +19162,7 @@ "conf": "^10.2.0", "config": "^3.3.12", "cross-env": "^7.0.3", + "debug": "^4.4.3", "dotenv": "^16.5.0", "execa": "^9.6.1", "git-repo-info": "^2.1.1", diff --git a/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/package.json b/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/package.json new file mode 100644 index 000000000..3fe8b6266 --- /dev/null +++ b/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/package.json @@ -0,0 +1,13 @@ +{ + "name": "workspace-basic-pnpm", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "packageManager": "pnpm@10.7.1" +} diff --git a/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/packages/a/.gitignore b/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/packages/a/.gitignore new file mode 100644 index 000000000..335bd46df --- /dev/null +++ b/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/packages/a/.gitignore @@ -0,0 +1,8 @@ + +# Playwright +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ +/playwright/.auth/ diff --git a/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/packages/a/checkly.config.ts b/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/packages/a/checkly.config.ts new file mode 100644 index 000000000..b7e754594 --- /dev/null +++ b/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/packages/a/checkly.config.ts @@ -0,0 +1,29 @@ +import { defineConfig } from 'checkly' + +const config = defineConfig({ + logicalId: 'my-app', + projectName: 'my-app', + checks: { + playwrightConfigPath: './playwright.config.ts', + playwrightChecks: [ + { + logicalId: 'my-app-tests', + name: 'my-app-tests', + frequency: 10, + locations: [ + 'us-east-1', + ], + installCommand: 'pnpm i', + }, + ], + frequency: 10, + locations: [ + 'us-east-1', + ], + }, + cli: { + runLocation: 'us-east-1', + }, +}) + +export default config diff --git a/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/packages/a/package.json b/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/packages/a/package.json new file mode 100644 index 000000000..f49ce9a01 --- /dev/null +++ b/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/packages/a/package.json @@ -0,0 +1,17 @@ +{ + "name": "a", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": {}, + "keywords": [], + "author": "", + "license": "ISC", + "packageManager": "pnpm@10.7.1", + "devDependencies": { + "@playwright/test": "^1.57.0", + "@types/node": "^25.0.3", + "checkly": "^6.9.8", + "jiti": "^2.6.1" + } +} diff --git a/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/packages/a/playwright.config.ts b/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/packages/a/playwright.config.ts new file mode 100644 index 000000000..6dfc0d9bc --- /dev/null +++ b/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/packages/a/playwright.config.ts @@ -0,0 +1,79 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('')`. */ + // baseURL: 'http://localhost:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://localhost:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/packages/a/tests/example.spec.ts b/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/packages/a/tests/example.spec.ts new file mode 100644 index 000000000..54a906a4e --- /dev/null +++ b/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/packages/a/tests/example.spec.ts @@ -0,0 +1,18 @@ +import { test, expect } from '@playwright/test'; + +test('has title', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Playwright/); +}); + +test('get started link', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Click the get started link. + await page.getByRole('link', { name: 'Get started' }).click(); + + // Expects page to have a heading with the name of Installation. + await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); +}); diff --git a/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/packages/b/package.json b/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/packages/b/package.json new file mode 100644 index 000000000..bd3575ae3 --- /dev/null +++ b/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/packages/b/package.json @@ -0,0 +1,13 @@ +{ + "name": "b", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "packageManager": "pnpm@10.7.1" +} diff --git a/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/packages/c/package.json b/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/packages/c/package.json new file mode 100644 index 000000000..d56469a5b --- /dev/null +++ b/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/packages/c/package.json @@ -0,0 +1,13 @@ +{ + "name": "c", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "packageManager": "pnpm@10.7.1" +} diff --git a/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/pnpm-lock.yaml b/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/pnpm-lock.yaml new file mode 100644 index 000000000..f58eaddd2 --- /dev/null +++ b/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/pnpm-lock.yaml @@ -0,0 +1,2561 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: {} + + packages/a: + devDependencies: + '@playwright/test': + specifier: ^1.57.0 + version: 1.57.0 + '@types/node': + specifier: ^25.0.3 + version: 25.0.3 + checkly: + specifier: ^6.9.8 + version: 6.9.8(@types/node@25.0.3)(jiti@2.6.1)(typescript@5.9.3) + jiti: + specifier: ^2.6.1 + version: 2.6.1 + + packages/b: {} + + packages/c: {} + +packages: + + '@babel/runtime@7.28.4': + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + engines: {node: '>=6.9.0'} + + '@inquirer/ansi@1.0.2': + resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} + engines: {node: '>=18'} + + '@inquirer/checkbox@4.3.2': + resolution: {integrity: sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/confirm@5.1.21': + resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@10.3.2': + resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/editor@4.2.23': + resolution: {integrity: sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/expand@4.0.23': + resolution: {integrity: sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/external-editor@1.0.3': + resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@1.0.15': + resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} + engines: {node: '>=18'} + + '@inquirer/input@4.3.1': + resolution: {integrity: sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/number@3.0.23': + resolution: {integrity: sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/password@4.0.23': + resolution: {integrity: sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/prompts@7.10.1': + resolution: {integrity: sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/rawlist@4.1.11': + resolution: {integrity: sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/search@3.2.2': + resolution: {integrity: sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/select@4.4.2': + resolution: {integrity: sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/type@3.0.10': + resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@oclif/core@4.8.0': + resolution: {integrity: sha512-jteNUQKgJHLHFbbz806aGZqf+RJJ7t4gwF4MYa8fCwCxQ8/klJNWc0MvaJiBebk7Mc+J39mdlsB4XraaCKznFw==} + engines: {node: '>=18.0.0'} + + '@oclif/plugin-help@6.2.36': + resolution: {integrity: sha512-NBQIg5hEMhvdbi4mSrdqRGl5XJ0bqTAHq6vDCCCDXUcfVtdk3ZJbSxtRVWyVvo9E28vwqu6MZyHOJylevqcHbA==} + engines: {node: '>=18.0.0'} + + '@oclif/plugin-not-found@3.2.73': + resolution: {integrity: sha512-2bQieTGI9XNFe9hKmXQjJmHV5rZw+yn7Rud1+C5uLEo8GaT89KZbiLTJgL35tGILahy/cB6+WAs812wjw7TK6w==} + engines: {node: '>=18.0.0'} + + '@oclif/plugin-plugins@5.4.54': + resolution: {integrity: sha512-yzdukEfvvyXx31AhN+YhxLhuQdx2SrZDcRtPl5CNkuqh/uNSB2BuA3xpurdv2qotpaw/Z9InRl+Sa9bLp/4aLA==} + engines: {node: '>=18.0.0'} + + '@oclif/plugin-warn-if-update-available@3.1.53': + resolution: {integrity: sha512-ALxKMNFFJQJV1Z2OMVTV+q7EbKHhnTAPcTgkgHeXCNdW5nFExoXuwusZLS4Zv2o83j9UoDx1R/CSX7QZVgEHTA==} + engines: {node: '>=18.0.0'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@playwright/test@1.57.0': + resolution: {integrity: sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==} + engines: {node: '>=18'} + hasBin: true + + '@pnpm/config.env-replace@1.1.0': + resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} + engines: {node: '>=12.22.0'} + + '@pnpm/network.ca-file@1.0.2': + resolution: {integrity: sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==} + engines: {node: '>=12.22.0'} + + '@pnpm/npm-conf@2.3.1': + resolution: {integrity: sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==} + engines: {node: '>=12'} + + '@sec-ant/readable-stream@0.4.1': + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + + '@sindresorhus/merge-streams@4.0.0': + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + engines: {node: '>=18'} + + '@types/node@25.0.3': + resolution: {integrity: sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==} + + '@types/readable-stream@4.0.23': + resolution: {integrity: sha512-wwXrtQvbMHxCbBgjHaMGEmImFTQxxpfMOR/ZoQnXxB1woqkUbdLGFDgauo00Py9IudiaqSeiBiulSV9i6XIPig==} + + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + + '@typescript-eslint/project-service@8.52.0': + resolution: {integrity: sha512-xD0MfdSdEmeFa3OmVqonHi+Cciab96ls1UhIF/qX/O/gPu5KXD0bY9lu33jj04fjzrXHcuvjBcBC+D3SNSadaw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/tsconfig-utils@8.52.0': + resolution: {integrity: sha512-jl+8fzr/SdzdxWJznq5nvoI7qn2tNYV/ZBAEcaFMVXf+K6jmXvAFrgo/+5rxgnL152f//pDEAYAhhBAZGrVfwg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.52.0': + resolution: {integrity: sha512-LWQV1V4q9V4cT4H5JCIx3481iIFxH1UkVk+ZkGGAV1ZGcjGI9IoFOfg3O6ywz8QqCDEp7Inlg6kovMofsNRaGg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.52.0': + resolution: {integrity: sha512-XP3LClsCc0FsTK5/frGjolyADTh3QmsLp6nKd476xNI9CsSsLnmn4f0jrzNoAulmxlmNIpeXuHYeEQv61Q6qeQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.52.0': + resolution: {integrity: sha512-ink3/Zofus34nmBsPjow63FP5M7IGff0RKAgqR6+CFpdk22M7aLwC9gOcLGYqr7MczLPzZVERW9hRog3O4n1sQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv-formats@2.1.1: + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + ansis@3.17.0: + resolution: {integrity: sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==} + engines: {node: '>=14'} + + archiver-utils@5.0.2: + resolution: {integrity: sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==} + engines: {node: '>= 14'} + + archiver@7.0.1: + resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==} + engines: {node: '>= 14'} + + ast-types@0.16.1: + resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} + engines: {node: '>=4'} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + atomically@1.7.0: + resolution: {integrity: sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==} + engines: {node: '>=10.12.0'} + + axios@1.13.2: + resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==} + + b4a@1.7.3: + resolution: {integrity: sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==} + peerDependencies: + react-native-b4a: '*' + peerDependenciesMeta: + react-native-b4a: + optional: true + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + bare-events@2.8.2: + resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==} + peerDependencies: + bare-abort-controller: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + bl@6.1.6: + resolution: {integrity: sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + broker-factory@3.1.11: + resolution: {integrity: sha512-ex4RuEI0AJOdaIcXe1lu9EqRAVkoYvdcvwLvNcE5UZQzYNqzPY+z0frnlxT4+cUwNVpE//9MwGx4lKiLH+pEcw==} + + buffer-crc32@1.0.0: + resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} + engines: {node: '>=8.0.0'} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chardet@2.1.1: + resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + + checkly@6.9.8: + resolution: {integrity: sha512-7CzBfjp7kVx9Rh+K6a8DLUSsIT84yV8uicZYRjxbGsETdpnawYOopt5RkfxCV73eClECAJoGIHlRrMt9dDSb5A==} + engines: {node: ^18.19.0 || >=20.5.0} + hasBin: true + peerDependencies: + jiti: '>=2' + peerDependenciesMeta: + jiti: + optional: true + + ci-info@4.3.1: + resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==} + engines: {node: '>=8'} + + clean-stack@3.0.1: + resolution: {integrity: sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg==} + engines: {node: '>=10'} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commist@3.2.0: + resolution: {integrity: sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw==} + + compress-commons@6.0.2: + resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==} + engines: {node: '>= 14'} + + concat-stream@2.0.0: + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} + + conf@10.2.0: + resolution: {integrity: sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==} + engines: {node: '>=12'} + + config-chain@1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + + crc32-stream@6.0.0: + resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==} + engines: {node: '>= 14'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + debounce-fn@4.0.0: + resolution: {integrity: sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==} + engines: {node: '>=10'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dot-prop@6.0.1: + resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} + engines: {node: '>=10'} + + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + ejs@3.1.10: + resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} + engines: {node: '>=0.10.0'} + hasBin: true + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + events-universal@1.0.1: + resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + execa@9.6.1: + resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} + engines: {node: ^18.19.0 || >=20.5.0} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + + fast-levenshtein@3.0.0: + resolution: {integrity: sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==} + + fast-unique-numbers@9.0.24: + resolution: {integrity: sha512-Dv0BYn4waOWse94j16rsZ5w/0zoaCa74O3q6IZjMqaXbtT92Q+Sb6pPk+phGzD8Xh+nueQmSRI3tSCaHKidzKw==} + engines: {node: '>=18.2.0'} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fastest-levenshtein@1.0.16: + resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} + engines: {node: '>= 4.9.1'} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + figures@6.1.0: + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} + + filelist@1.0.4: + resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} + + find-up@3.0.0: + resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} + engines: {node: '>=6'} + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + + git-repo-info@2.1.1: + resolution: {integrity: sha512-8aCohiDo4jwjOwma4FmYFd3i97urZulL8XL24nIPxuE+GZnfsAyy/g2Shqx6OjUiFKUXZM+Yy+KHnOmmA3FVcg==} + engines: {node: '>= 4.0'} + + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + hasBin: true + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.10: + resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + help-me@5.0.0: + resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} + + hosted-git-info@7.0.2: + resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} + engines: {node: ^16.14.0 || >=18.0.0} + + http-call@5.3.0: + resolution: {integrity: sha512-ahwimsC23ICE4kPl9xTBjKB4inbRaeLyZeRunC/1Jy/Z6X8tv22MEAjK+KBOMSVLaqXPTTmd8638waVIKLGx2w==} + engines: {node: '>=8.0.0'} + + human-signals@8.0.1: + resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} + engines: {node: '>=18.18.0'} + + iconv-lite@0.7.1: + resolution: {integrity: sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + ip-address@10.1.0: + resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} + engines: {node: '>= 12'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-obj@2.0.0: + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} + engines: {node: '>=8'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-retry-allowed@1.2.0: + resolution: {integrity: sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==} + engines: {node: '>=0.10.0'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isexe@3.1.1: + resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} + engines: {node: '>=16'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jake@10.9.4: + resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} + engines: {node: '>=10'} + hasBin: true + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + js-sdsl@4.3.0: + resolution: {integrity: sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==} + + json-parse-better-errors@1.0.2: + resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-schema-typed@7.0.3: + resolution: {integrity: sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==} + + json-stream-stringify@3.1.6: + resolution: {integrity: sha512-x7fpwxOkbhFCaJDJ8vb1fBY3DdSa4AlITaz+HHILQJzdPMnHEFjxPwVUi1ALIbcIxDE0PNe/0i7frnY8QnBQog==} + engines: {node: '>=7.10.1'} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jwt-decode@3.1.2: + resolution: {integrity: sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + locate-path@3.0.0: + resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} + engines: {node: '>=6'} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + luxon@3.7.2: + resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==} + engines: {node: '>=12'} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + mimic-fn@3.1.0: + resolution: {integrity: sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==} + engines: {node: '>=8'} + + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + mqtt-packet@9.0.2: + resolution: {integrity: sha512-MvIY0B8/qjq7bKxdN1eD+nrljoeaai+qjLJgfRn3TiMuz0pamsIWY2bFODPZMSNmabsLANXsLl4EMoWvlaTZWA==} + + mqtt@5.14.1: + resolution: {integrity: sha512-NxkPxE70Uq3Ph7goefQa7ggSsVzHrayCD0OyxlJgITN/EbzlZN+JEPmaAZdxP1LsIT5FamDyILoQTF72W7Nnbw==} + engines: {node: '>=16.0.0'} + hasBin: true + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-package-arg@11.0.3: + resolution: {integrity: sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==} + engines: {node: ^16.14.0 || >=18.0.0} + + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + npm-run-path@6.0.0: + resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + engines: {node: '>=18'} + + npm@10.9.4: + resolution: {integrity: sha512-OnUG836FwboQIbqtefDNlyR0gTHzIfwRfE3DuiNewBvnMnWEpB0VEXwBlFVgqpNzIgYo/MHh3d2Hel/pszapAA==} + engines: {node: ^18.17.0 || >=20.5.0} + hasBin: true + bundledDependencies: + - '@isaacs/string-locale-compare' + - '@npmcli/arborist' + - '@npmcli/config' + - '@npmcli/fs' + - '@npmcli/map-workspaces' + - '@npmcli/package-json' + - '@npmcli/promise-spawn' + - '@npmcli/redact' + - '@npmcli/run-script' + - '@sigstore/tuf' + - abbrev + - archy + - cacache + - chalk + - ci-info + - cli-columns + - fastest-levenshtein + - fs-minipass + - glob + - graceful-fs + - hosted-git-info + - ini + - init-package-json + - is-cidr + - json-parse-even-better-errors + - libnpmaccess + - libnpmdiff + - libnpmexec + - libnpmfund + - libnpmhook + - libnpmorg + - libnpmpack + - libnpmpublish + - libnpmsearch + - libnpmteam + - libnpmversion + - make-fetch-happen + - minimatch + - minipass + - minipass-pipeline + - ms + - node-gyp + - nopt + - normalize-package-data + - npm-audit-report + - npm-install-checks + - npm-package-arg + - npm-pick-manifest + - npm-profile + - npm-registry-fetch + - npm-user-validate + - p-map + - pacote + - parse-conflict-json + - proc-log + - qrcode-terminal + - read + - semver + - spdx-expression-parse + - ssri + - supports-color + - tar + - text-table + - tiny-relative-date + - treeverse + - validate-npm-package-name + - which + - write-file-atomic + + number-allocator@1.0.14: + resolution: {integrity: sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==} + + object-treeify@4.0.1: + resolution: {integrity: sha512-Y6tg5rHfsefSkfKujv2SwHulInROy/rCL5F4w0QOWxut8AnxYxf0YmNhTh95Zfyxpsudo66uqkux0ACFnyMSgQ==} + engines: {node: '>= 16'} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + open@8.4.2: + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} + engines: {node: '>=12'} + + p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-locate@3.0.0: + resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} + engines: {node: '>=6'} + + p-queue@6.6.2: + resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} + engines: {node: '>=8'} + + p-timeout@3.2.0: + resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} + engines: {node: '>=8'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + parse-json@4.0.0: + resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} + engines: {node: '>=4'} + + parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} + + path-exists@3.0.0: + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} + engines: {node: '>=4'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pkg-up@3.1.0: + resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} + engines: {node: '>=8'} + + playwright-core@1.57.0: + resolution: {integrity: sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.57.0: + resolution: {integrity: sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==} + engines: {node: '>=18'} + hasBin: true + + pretty-ms@9.3.0: + resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} + engines: {node: '>=18'} + + proc-log@4.2.0: + resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readable-stream@4.7.0: + resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + + recast@0.23.11: + resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==} + engines: {node: '>= 4'} + + registry-auth-token@5.1.0: + resolution: {integrity: sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==} + engines: {node: '>=14'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + socks@2.8.7: + resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + streamx@2.23.0: + resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + + strip-final-newline@4.0.0: + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + engines: {node: '>=18'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + tar-stream@3.1.7: + resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + + text-decoder@1.2.3: + resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} + + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + ts-api-utils@2.4.0: + resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + + tunnel@0.0.6: + resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} + engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + hasBin: true + + validate-npm-package-name@5.0.1: + resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + which@4.0.0: + resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} + engines: {node: ^16.13.0 || >=18.0.0} + hasBin: true + + widest-line@3.1.0: + resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} + engines: {node: '>=8'} + + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + + worker-factory@7.0.46: + resolution: {integrity: sha512-Sr1hq2FMgNa04UVhYQacsw+i58BtMimzDb4+CqYphZ97OfefRpURu0UZ+JxMr/H36VVJBfuVkxTK7MytsanC3w==} + + worker-timers-broker@8.0.13: + resolution: {integrity: sha512-PZnHHmqOY5oMKQPyfJhqPI9cb3QFmwD3lCIc/Zip6sShpfG2rvvCVDl0xeabGIspiEpP5exNNIlTUHjgP5VAcg==} + + worker-timers-worker@9.0.11: + resolution: {integrity: sha512-pArb5xtgHWImYpXhjg1OFv7JFG0ubmccb73TFoXHXjG830fFj+16N57q9YeBnZX52dn+itRrMoJZ9HaZBVzDaA==} + + worker-timers@8.0.27: + resolution: {integrity: sha512-+7ptDduAWj6Wd09Ga0weRFRx/MUwLhExazn+zu3IrwF0N2U2FPqFRR5W3Qz4scnI3cOILzdIEEytIJ2vbeD9Gw==} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + ws@8.19.0: + resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + yarn@1.22.22: + resolution: {integrity: sha512-prL3kGtyG7o9Z9Sv8IPfBNrWTDmXB4Qbes8A9rEzt6wkJV8mUvoirjU0Mp3GGAU06Y0XQyA3/2/RQFVuK7MTfg==} + engines: {node: '>=4.0.0'} + hasBin: true + + yoctocolors-cjs@2.1.3: + resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} + engines: {node: '>=18'} + + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} + + zip-stream@6.0.1: + resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} + engines: {node: '>= 14'} + +snapshots: + + '@babel/runtime@7.28.4': {} + + '@inquirer/ansi@1.0.2': {} + + '@inquirer/checkbox@4.3.2(@types/node@25.0.3)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@25.0.3) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@25.0.3) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.0.3 + + '@inquirer/confirm@5.1.21(@types/node@25.0.3)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.0.3) + '@inquirer/type': 3.0.10(@types/node@25.0.3) + optionalDependencies: + '@types/node': 25.0.3 + + '@inquirer/core@10.3.2(@types/node@25.0.3)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@25.0.3) + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.0.3 + + '@inquirer/editor@4.2.23(@types/node@25.0.3)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.0.3) + '@inquirer/external-editor': 1.0.3(@types/node@25.0.3) + '@inquirer/type': 3.0.10(@types/node@25.0.3) + optionalDependencies: + '@types/node': 25.0.3 + + '@inquirer/expand@4.0.23(@types/node@25.0.3)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.0.3) + '@inquirer/type': 3.0.10(@types/node@25.0.3) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.0.3 + + '@inquirer/external-editor@1.0.3(@types/node@25.0.3)': + dependencies: + chardet: 2.1.1 + iconv-lite: 0.7.1 + optionalDependencies: + '@types/node': 25.0.3 + + '@inquirer/figures@1.0.15': {} + + '@inquirer/input@4.3.1(@types/node@25.0.3)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.0.3) + '@inquirer/type': 3.0.10(@types/node@25.0.3) + optionalDependencies: + '@types/node': 25.0.3 + + '@inquirer/number@3.0.23(@types/node@25.0.3)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.0.3) + '@inquirer/type': 3.0.10(@types/node@25.0.3) + optionalDependencies: + '@types/node': 25.0.3 + + '@inquirer/password@4.0.23(@types/node@25.0.3)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@25.0.3) + '@inquirer/type': 3.0.10(@types/node@25.0.3) + optionalDependencies: + '@types/node': 25.0.3 + + '@inquirer/prompts@7.10.1(@types/node@25.0.3)': + dependencies: + '@inquirer/checkbox': 4.3.2(@types/node@25.0.3) + '@inquirer/confirm': 5.1.21(@types/node@25.0.3) + '@inquirer/editor': 4.2.23(@types/node@25.0.3) + '@inquirer/expand': 4.0.23(@types/node@25.0.3) + '@inquirer/input': 4.3.1(@types/node@25.0.3) + '@inquirer/number': 3.0.23(@types/node@25.0.3) + '@inquirer/password': 4.0.23(@types/node@25.0.3) + '@inquirer/rawlist': 4.1.11(@types/node@25.0.3) + '@inquirer/search': 3.2.2(@types/node@25.0.3) + '@inquirer/select': 4.4.2(@types/node@25.0.3) + optionalDependencies: + '@types/node': 25.0.3 + + '@inquirer/rawlist@4.1.11(@types/node@25.0.3)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.0.3) + '@inquirer/type': 3.0.10(@types/node@25.0.3) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.0.3 + + '@inquirer/search@3.2.2(@types/node@25.0.3)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.0.3) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@25.0.3) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.0.3 + + '@inquirer/select@4.4.2(@types/node@25.0.3)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@25.0.3) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@25.0.3) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.0.3 + + '@inquirer/type@3.0.10(@types/node@25.0.3)': + optionalDependencies: + '@types/node': 25.0.3 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@oclif/core@4.8.0': + dependencies: + ansi-escapes: 4.3.2 + ansis: 3.17.0 + clean-stack: 3.0.1 + cli-spinners: 2.9.2 + debug: 4.4.3(supports-color@8.1.1) + ejs: 3.1.10 + get-package-type: 0.1.0 + indent-string: 4.0.0 + is-wsl: 2.2.0 + lilconfig: 3.1.3 + minimatch: 9.0.5 + semver: 7.7.3 + string-width: 4.2.3 + supports-color: 8.1.1 + tinyglobby: 0.2.15 + widest-line: 3.1.0 + wordwrap: 1.0.0 + wrap-ansi: 7.0.0 + + '@oclif/plugin-help@6.2.36': + dependencies: + '@oclif/core': 4.8.0 + + '@oclif/plugin-not-found@3.2.73(@types/node@25.0.3)': + dependencies: + '@inquirer/prompts': 7.10.1(@types/node@25.0.3) + '@oclif/core': 4.8.0 + ansis: 3.17.0 + fast-levenshtein: 3.0.0 + transitivePeerDependencies: + - '@types/node' + + '@oclif/plugin-plugins@5.4.54': + dependencies: + '@oclif/core': 4.8.0 + ansis: 3.17.0 + debug: 4.4.3(supports-color@8.1.1) + npm: 10.9.4 + npm-package-arg: 11.0.3 + npm-run-path: 5.3.0 + object-treeify: 4.0.1 + semver: 7.7.3 + validate-npm-package-name: 5.0.1 + which: 4.0.0 + yarn: 1.22.22 + transitivePeerDependencies: + - supports-color + + '@oclif/plugin-warn-if-update-available@3.1.53': + dependencies: + '@oclif/core': 4.8.0 + ansis: 3.17.0 + debug: 4.4.3(supports-color@8.1.1) + http-call: 5.3.0 + lodash: 4.17.21 + registry-auth-token: 5.1.0 + transitivePeerDependencies: + - supports-color + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@playwright/test@1.57.0': + dependencies: + playwright: 1.57.0 + + '@pnpm/config.env-replace@1.1.0': {} + + '@pnpm/network.ca-file@1.0.2': + dependencies: + graceful-fs: 4.2.10 + + '@pnpm/npm-conf@2.3.1': + dependencies: + '@pnpm/config.env-replace': 1.1.0 + '@pnpm/network.ca-file': 1.0.2 + config-chain: 1.1.13 + + '@sec-ant/readable-stream@0.4.1': {} + + '@sindresorhus/merge-streams@4.0.0': {} + + '@types/node@25.0.3': + dependencies: + undici-types: 7.16.0 + + '@types/readable-stream@4.0.23': + dependencies: + '@types/node': 25.0.3 + + '@types/ws@8.18.1': + dependencies: + '@types/node': 25.0.3 + + '@typescript-eslint/project-service@8.52.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.52.0(typescript@5.9.3) + '@typescript-eslint/types': 8.52.0 + debug: 4.4.3(supports-color@8.1.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/tsconfig-utils@8.52.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/types@8.52.0': {} + + '@typescript-eslint/typescript-estree@8.52.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.52.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.52.0(typescript@5.9.3) + '@typescript-eslint/types': 8.52.0 + '@typescript-eslint/visitor-keys': 8.52.0 + debug: 4.4.3(supports-color@8.1.1) + minimatch: 9.0.5 + semver: 7.7.3 + tinyglobby: 0.2.15 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.52.0': + dependencies: + '@typescript-eslint/types': 8.52.0 + eslint-visitor-keys: 4.2.1 + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + acorn-walk@8.3.4: + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv-formats@2.1.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + ansis@3.17.0: {} + + archiver-utils@5.0.2: + dependencies: + glob: 10.5.0 + graceful-fs: 4.2.11 + is-stream: 2.0.1 + lazystream: 1.0.1 + lodash: 4.17.21 + normalize-path: 3.0.0 + readable-stream: 4.7.0 + + archiver@7.0.1: + dependencies: + archiver-utils: 5.0.2 + async: 3.2.6 + buffer-crc32: 1.0.0 + readable-stream: 4.7.0 + readdir-glob: 1.1.3 + tar-stream: 3.1.7 + zip-stream: 6.0.1 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + ast-types@0.16.1: + dependencies: + tslib: 2.8.1 + + async@3.2.6: {} + + asynckit@0.4.0: {} + + atomically@1.7.0: {} + + axios@1.13.2: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.5 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + b4a@1.7.3: {} + + balanced-match@1.0.2: {} + + bare-events@2.8.2: {} + + base64-js@1.5.1: {} + + bl@6.1.6: + dependencies: + '@types/readable-stream': 4.0.23 + buffer: 6.0.3 + inherits: 2.0.4 + readable-stream: 4.7.0 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + broker-factory@3.1.11: + dependencies: + '@babel/runtime': 7.28.4 + fast-unique-numbers: 9.0.24 + tslib: 2.8.1 + worker-factory: 7.0.46 + + buffer-crc32@1.0.0: {} + + buffer-from@1.1.2: {} + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chardet@2.1.1: {} + + checkly@6.9.8(@types/node@25.0.3)(jiti@2.6.1)(typescript@5.9.3): + dependencies: + '@oclif/core': 4.8.0 + '@oclif/plugin-help': 6.2.36 + '@oclif/plugin-not-found': 3.2.73(@types/node@25.0.3) + '@oclif/plugin-plugins': 5.4.54 + '@oclif/plugin-warn-if-update-available': 3.1.53 + '@typescript-eslint/typescript-estree': 8.52.0(typescript@5.9.3) + acorn: 8.15.0 + acorn-walk: 8.3.4 + archiver: 7.0.1 + axios: 1.13.2 + chalk: 4.1.2 + ci-info: 4.3.1 + conf: 10.2.0 + dotenv: 16.6.1 + execa: 9.6.1 + git-repo-info: 2.1.1 + glob: 10.5.0 + indent-string: 4.0.0 + json-stream-stringify: 3.1.6 + json5: 2.2.3 + jwt-decode: 3.1.2 + log-symbols: 4.1.0 + luxon: 3.7.2 + minimatch: 9.0.5 + mqtt: 5.14.1 + open: 8.4.2 + p-queue: 6.6.2 + prompts: 2.4.2 + proxy-from-env: 1.1.0 + recast: 0.23.11 + semver: 7.7.3 + tunnel: 0.0.6 + uuid: 11.1.0 + optionalDependencies: + jiti: 2.6.1 + transitivePeerDependencies: + - '@types/node' + - bare-abort-controller + - bufferutil + - debug + - react-native-b4a + - supports-color + - typescript + - utf-8-validate + + ci-info@4.3.1: {} + + clean-stack@3.0.1: + dependencies: + escape-string-regexp: 4.0.0 + + cli-spinners@2.9.2: {} + + cli-width@4.1.0: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commist@3.2.0: {} + + compress-commons@6.0.2: + dependencies: + crc-32: 1.2.2 + crc32-stream: 6.0.0 + is-stream: 2.0.1 + normalize-path: 3.0.0 + readable-stream: 4.7.0 + + concat-stream@2.0.0: + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.2 + typedarray: 0.0.6 + + conf@10.2.0: + dependencies: + ajv: 8.17.1 + ajv-formats: 2.1.1(ajv@8.17.1) + atomically: 1.7.0 + debounce-fn: 4.0.0 + dot-prop: 6.0.1 + env-paths: 2.2.1 + json-schema-typed: 7.0.3 + onetime: 5.1.2 + pkg-up: 3.1.0 + semver: 7.7.3 + + config-chain@1.1.13: + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + + content-type@1.0.5: {} + + core-util-is@1.0.3: {} + + crc-32@1.2.2: {} + + crc32-stream@6.0.0: + dependencies: + crc-32: 1.2.2 + readable-stream: 4.7.0 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debounce-fn@4.0.0: + dependencies: + mimic-fn: 3.1.0 + + debug@4.4.3(supports-color@8.1.1): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 + + define-lazy-prop@2.0.0: {} + + delayed-stream@1.0.0: {} + + dot-prop@6.0.1: + dependencies: + is-obj: 2.0.0 + + dotenv@16.6.1: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + ejs@3.1.10: + dependencies: + jake: 10.9.4 + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + env-paths@2.2.1: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + escape-string-regexp@4.0.0: {} + + eslint-visitor-keys@4.2.1: {} + + esprima@4.0.1: {} + + event-target-shim@5.0.1: {} + + eventemitter3@4.0.7: {} + + events-universal@1.0.1: + dependencies: + bare-events: 2.8.2 + transitivePeerDependencies: + - bare-abort-controller + + events@3.3.0: {} + + execa@9.6.1: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + cross-spawn: 7.0.6 + figures: 6.1.0 + get-stream: 9.0.1 + human-signals: 8.0.1 + is-plain-obj: 4.1.0 + is-stream: 4.0.1 + npm-run-path: 6.0.0 + pretty-ms: 9.3.0 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + yoctocolors: 2.1.2 + + fast-deep-equal@3.1.3: {} + + fast-fifo@1.3.2: {} + + fast-levenshtein@3.0.0: + dependencies: + fastest-levenshtein: 1.0.16 + + fast-unique-numbers@9.0.24: + dependencies: + '@babel/runtime': 7.28.4 + tslib: 2.8.1 + + fast-uri@3.1.0: {} + + fastest-levenshtein@1.0.16: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + figures@6.1.0: + dependencies: + is-unicode-supported: 2.1.0 + + filelist@1.0.4: + dependencies: + minimatch: 5.1.6 + + find-up@3.0.0: + dependencies: + locate-path: 3.0.0 + + follow-redirects@1.15.11: {} + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + fsevents@2.3.2: + optional: true + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-package-type@0.1.0: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + + git-repo-info@2.1.1: {} + + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + gopd@1.2.0: {} + + graceful-fs@4.2.10: {} + + graceful-fs@4.2.11: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + help-me@5.0.0: {} + + hosted-git-info@7.0.2: + dependencies: + lru-cache: 10.4.3 + + http-call@5.3.0: + dependencies: + content-type: 1.0.5 + debug: 4.4.3(supports-color@8.1.1) + is-retry-allowed: 1.2.0 + is-stream: 2.0.1 + parse-json: 4.0.0 + tunnel-agent: 0.6.0 + transitivePeerDependencies: + - supports-color + + human-signals@8.0.1: {} + + iconv-lite@0.7.1: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + indent-string@4.0.0: {} + + inherits@2.0.4: {} + + ini@1.3.8: {} + + ip-address@10.1.0: {} + + is-arrayish@0.2.1: {} + + is-docker@2.2.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-obj@2.0.0: {} + + is-plain-obj@4.1.0: {} + + is-retry-allowed@1.2.0: {} + + is-stream@2.0.1: {} + + is-stream@4.0.1: {} + + is-unicode-supported@0.1.0: {} + + is-unicode-supported@2.1.0: {} + + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + + isarray@1.0.0: {} + + isexe@2.0.0: {} + + isexe@3.1.1: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jake@10.9.4: + dependencies: + async: 3.2.6 + filelist: 1.0.4 + picocolors: 1.1.1 + + jiti@2.6.1: {} + + js-sdsl@4.3.0: {} + + json-parse-better-errors@1.0.2: {} + + json-schema-traverse@1.0.0: {} + + json-schema-typed@7.0.3: {} + + json-stream-stringify@3.1.6: {} + + json5@2.2.3: {} + + jwt-decode@3.1.2: {} + + kleur@3.0.3: {} + + lazystream@1.0.1: + dependencies: + readable-stream: 2.3.8 + + lilconfig@3.1.3: {} + + locate-path@3.0.0: + dependencies: + p-locate: 3.0.0 + path-exists: 3.0.0 + + lodash@4.17.21: {} + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + lru-cache@10.4.3: {} + + luxon@3.7.2: {} + + math-intrinsics@1.1.0: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mimic-fn@2.1.0: {} + + mimic-fn@3.1.0: {} + + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.2 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minimist@1.2.8: {} + + minipass@7.1.2: {} + + mqtt-packet@9.0.2: + dependencies: + bl: 6.1.6 + debug: 4.4.3(supports-color@8.1.1) + process-nextick-args: 2.0.1 + transitivePeerDependencies: + - supports-color + + mqtt@5.14.1: + dependencies: + '@types/readable-stream': 4.0.23 + '@types/ws': 8.18.1 + commist: 3.2.0 + concat-stream: 2.0.0 + debug: 4.4.3(supports-color@8.1.1) + help-me: 5.0.0 + lru-cache: 10.4.3 + minimist: 1.2.8 + mqtt-packet: 9.0.2 + number-allocator: 1.0.14 + readable-stream: 4.7.0 + rfdc: 1.4.1 + socks: 2.8.7 + split2: 4.2.0 + worker-timers: 8.0.27 + ws: 8.19.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + ms@2.1.3: {} + + mute-stream@2.0.0: {} + + normalize-path@3.0.0: {} + + npm-package-arg@11.0.3: + dependencies: + hosted-git-info: 7.0.2 + proc-log: 4.2.0 + semver: 7.7.3 + validate-npm-package-name: 5.0.1 + + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + + npm-run-path@6.0.0: + dependencies: + path-key: 4.0.0 + unicorn-magic: 0.3.0 + + npm@10.9.4: {} + + number-allocator@1.0.14: + dependencies: + debug: 4.4.3(supports-color@8.1.1) + js-sdsl: 4.3.0 + transitivePeerDependencies: + - supports-color + + object-treeify@4.0.1: {} + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + open@8.4.2: + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + + p-finally@1.0.0: {} + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-locate@3.0.0: + dependencies: + p-limit: 2.3.0 + + p-queue@6.6.2: + dependencies: + eventemitter3: 4.0.7 + p-timeout: 3.2.0 + + p-timeout@3.2.0: + dependencies: + p-finally: 1.0.0 + + p-try@2.2.0: {} + + package-json-from-dist@1.0.1: {} + + parse-json@4.0.0: + dependencies: + error-ex: 1.3.4 + json-parse-better-errors: 1.0.2 + + parse-ms@4.0.0: {} + + path-exists@3.0.0: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + pkg-up@3.1.0: + dependencies: + find-up: 3.0.0 + + playwright-core@1.57.0: {} + + playwright@1.57.0: + dependencies: + playwright-core: 1.57.0 + optionalDependencies: + fsevents: 2.3.2 + + pretty-ms@9.3.0: + dependencies: + parse-ms: 4.0.0 + + proc-log@4.2.0: {} + + process-nextick-args@2.0.1: {} + + process@0.11.10: {} + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + proto-list@1.2.4: {} + + proxy-from-env@1.1.0: {} + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readable-stream@4.7.0: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + + readdir-glob@1.1.3: + dependencies: + minimatch: 5.1.6 + + recast@0.23.11: + dependencies: + ast-types: 0.16.1 + esprima: 4.0.1 + source-map: 0.6.1 + tiny-invariant: 1.3.3 + tslib: 2.8.1 + + registry-auth-token@5.1.0: + dependencies: + '@pnpm/npm-conf': 2.3.1 + + require-from-string@2.0.2: {} + + rfdc@1.4.1: {} + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + + semver@7.7.3: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@4.1.0: {} + + sisteransi@1.0.5: {} + + smart-buffer@4.2.0: {} + + socks@2.8.7: + dependencies: + ip-address: 10.1.0 + smart-buffer: 4.2.0 + + source-map@0.6.1: {} + + split2@4.2.0: {} + + streamx@2.23.0: + dependencies: + events-universal: 1.0.1 + fast-fifo: 1.3.2 + text-decoder: 1.2.3 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.2 + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + + strip-final-newline@4.0.0: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + tar-stream@3.1.7: + dependencies: + b4a: 1.7.3 + fast-fifo: 1.3.2 + streamx: 2.23.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + text-decoder@1.2.3: + dependencies: + b4a: 1.7.3 + transitivePeerDependencies: + - react-native-b4a + + tiny-invariant@1.3.3: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + ts-api-utils@2.4.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + tslib@2.8.1: {} + + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + + tunnel@0.0.6: {} + + type-fest@0.21.3: {} + + typedarray@0.0.6: {} + + typescript@5.9.3: {} + + undici-types@7.16.0: {} + + unicorn-magic@0.3.0: {} + + util-deprecate@1.0.2: {} + + uuid@11.1.0: {} + + validate-npm-package-name@5.0.1: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + which@4.0.0: + dependencies: + isexe: 3.1.1 + + widest-line@3.1.0: + dependencies: + string-width: 4.2.3 + + wordwrap@1.0.0: {} + + worker-factory@7.0.46: + dependencies: + '@babel/runtime': 7.28.4 + fast-unique-numbers: 9.0.24 + tslib: 2.8.1 + + worker-timers-broker@8.0.13: + dependencies: + '@babel/runtime': 7.28.4 + broker-factory: 3.1.11 + fast-unique-numbers: 9.0.24 + tslib: 2.8.1 + worker-timers-worker: 9.0.11 + + worker-timers-worker@9.0.11: + dependencies: + '@babel/runtime': 7.28.4 + tslib: 2.8.1 + worker-factory: 7.0.46 + + worker-timers@8.0.27: + dependencies: + '@babel/runtime': 7.28.4 + tslib: 2.8.1 + worker-timers-broker: 8.0.13 + worker-timers-worker: 9.0.11 + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.1.2 + + ws@8.19.0: {} + + yarn@1.22.22: {} + + yoctocolors-cjs@2.1.3: {} + + yoctocolors@2.1.2: {} + + zip-stream@6.0.1: + dependencies: + archiver-utils: 5.0.2 + compress-commons: 6.0.2 + readable-stream: 4.7.0 diff --git a/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/pnpm-workspace.yaml b/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/pnpm-workspace.yaml new file mode 100644 index 000000000..18ec407ef --- /dev/null +++ b/packages/cli/e2e/__tests__/fixtures/workspace-basic-pnpm/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'packages/*' diff --git a/packages/cli/package.json b/packages/cli/package.json index 6fe1b5cc6..d2c4f9b3b 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -90,6 +90,7 @@ "chalk": "^4.1.2", "ci-info": "^4.3.1", "conf": "^10.2.0", + "debug": "^4.4.3", "dotenv": "^16.5.0", "execa": "^9.6.1", "git-repo-info": "^2.1.1", @@ -115,6 +116,7 @@ "@playwright/test": "^1.57.0", "@types/archiver": "6.0.3", "@types/config": "^3.3.5", + "@types/debug": "^4.1.12", "@types/glob": "^8.1.0", "@types/luxon": "^3.7.1", "@types/node": "^22.14.1", diff --git a/packages/cli/src/commands/baseCommand.ts b/packages/cli/src/commands/baseCommand.ts index 9346b14b2..c6d60ede0 100644 --- a/packages/cli/src/commands/baseCommand.ts +++ b/packages/cli/src/commands/baseCommand.ts @@ -3,8 +3,8 @@ import prompts from 'prompts' import { Command } from '@oclif/core' import { api } from '../rest/api' import { CommandStyle } from '../helpers/command-style' -import { PackageFilesResolver } from '../services/check-parser/package-files/resolver' import { PackageJsonFile } from '../services/check-parser/package-files/package-json-file' +import { detectNearestPackageJson } from '../services/check-parser/package-files/package-manager' export type BaseCommandClass = typeof Command & { coreCommand: boolean @@ -18,12 +18,15 @@ export abstract class BaseCommand extends Command { #packageJsonLoader?: Promise async loadPackageJsonOfSelf (): Promise { - if (!this.#packageJsonLoader) { - const resolver = new PackageFilesResolver() - this.#packageJsonLoader = resolver.loadPackageJsonFile(__filename) - } + try { + if (!this.#packageJsonLoader) { + this.#packageJsonLoader = detectNearestPackageJson(__dirname) + } - return await this.#packageJsonLoader + return await this.#packageJsonLoader + } catch { + return + } } async checkEngineCompatibility (): Promise { diff --git a/packages/cli/src/commands/import/plan.ts b/packages/cli/src/commands/import/plan.ts index a81304f0a..d9902347a 100644 --- a/packages/cli/src/commands/import/plan.ts +++ b/packages/cli/src/commands/import/plan.ts @@ -26,9 +26,8 @@ import { ExitError } from '@oclif/core/errors' import { confirmCommit, performCommitAction } from './commit' import { confirmApply, performApplyAction } from './apply' import { generateChecklyConfig } from '../../services/checkly-config-codegen' -import { PackageFilesResolver } from '../../services/check-parser/package-files/resolver' import { PackageJsonFile } from '../../services/check-parser/package-files/package-json-file' -import { detectPackageManager, knownPackageManagers, PackageManager } from '../../services/check-parser/package-files/package-manager' +import { detectNearestPackageJson, detectPackageManager, knownPackageManagers, PackageManager } from '../../services/check-parser/package-files/package-manager' import { parseProject } from '../../services/project-parser' import { Runtime } from '../../rest/runtimes' import { ConstructExport, Project, Session } from '../../constructs/project' @@ -704,10 +703,11 @@ ${chalk.cyan('For safety, resources are not deletable until the plan has been co } async #loadPackageJson (): Promise { - const resolver = new PackageFilesResolver() - return await resolver.loadPackageJsonFile(process.cwd(), { - isDir: true, - }) + try { + return await detectNearestPackageJson(process.cwd()) + } catch { + return + } } #createPackageJson (logicalId: string): PackageJsonFile { diff --git a/packages/cli/src/constructs/__tests__/api-check.spec.ts b/packages/cli/src/constructs/__tests__/api-check.spec.ts index 7d1377b46..249adccbf 100644 --- a/packages/cli/src/constructs/__tests__/api-check.spec.ts +++ b/packages/cli/src/constructs/__tests__/api-check.spec.ts @@ -5,6 +5,7 @@ import { describe, it, expect, beforeEach, afterAll } from 'vitest' import { ApiCheck, CheckGroup, Request } from '../index' import { Project, Session } from '../project' +import { usingIsolatedFixture } from '../../services/check-parser/__tests__/helper' const runtimes = { '2022.10': { name: '2022.10', default: false, stage: 'CURRENT', description: 'Main updates are Playwright 1.28.0, Node.js 16.x and Typescript support. We are also dropping support for Puppeteer', dependencies: { '@playwright/test': '1.28.0', '@opentelemetry/api': '1.0.4', '@opentelemetry/sdk-trace-base': '1.0.1', '@faker-js/faker': '5.5.3', 'aws4': '1.11.0', 'axios': '0.27.2', 'btoa': '1.2.1', 'chai': '4.3.7', 'chai-string': '1.5.0', 'crypto-js': '4.1.1', 'expect': '29.3.1', 'form-data': '4.0.0', 'jsonwebtoken': '8.5.1', 'lodash': '4.17.21', 'mocha': '10.1.0', 'moment': '2.29.2', 'node': '16.x', 'otpauth': '9.0.2', 'playwright': '1.28.0', 'typescript': '4.8.4', 'uuid': '9.0.0' } }, @@ -24,77 +25,92 @@ describe('ApiCheck', () => { Session.resetSharedFiles() }) + async function usingFixture (handle: ({ fixtureDir }: { fixtureDir: string }) => Promise) { + await usingIsolatedFixture(path.join(__dirname, 'fixtures', 'api-check'), async fixtureDir => { + try { + Session.basePath = fixtureDir + Session.contextPath = fixtureDir + + await handle({ + fixtureDir, + }) + } finally { + Session.reset() + } + }) + } + it('should correctly load file script dependencies', async () => { - Session.basePath = __dirname - Session.availableRuntimes = runtimes - const getFilePath = (filename: string) => path.join(__dirname, 'fixtures', 'api-check', filename) - const bundle = await ApiCheck.bundle(getFilePath('entrypoint.js'), '2022.10') - Session.basePath = undefined - - expect(bundle).toEqual({ - script: fs.readFileSync(getFilePath('entrypoint.js')).toString(), - scriptPath: 'fixtures/api-check/entrypoint.js', - dependencies: [ - 0, - 1, - ], - }) - - expect(Session.sharedFiles).toEqual([ - { - path: 'fixtures/api-check/dep1.js', - content: fs.readFileSync(getFilePath('dep1.js')).toString(), - }, - { - path: 'fixtures/api-check/dep2.js', - content: fs.readFileSync(getFilePath('dep2.js')).toString(), - }, - ]) + await usingFixture(async ({ fixtureDir }) => { + Session.availableRuntimes = runtimes + + const getFilePath = (filename: string) => path.join(fixtureDir, filename) + const bundle = await ApiCheck.bundle(getFilePath('entrypoint.js'), '2022.10') + + expect(bundle).toEqual({ + script: fs.readFileSync(getFilePath('entrypoint.js')).toString(), + scriptPath: 'entrypoint.js', + dependencies: [ + 0, + 1, + ], + }) + + expect(Session.sharedFiles).toEqual([ + { + path: 'dep1.js', + content: fs.readFileSync(getFilePath('dep1.js')).toString(), + }, + { + path: 'dep2.js', + content: fs.readFileSync(getFilePath('dep2.js')).toString(), + }, + ]) + }) }) it('should fail to bundle if runtime is not specified and default runtime is not set', async () => { - const getFilePath = (filename: string) => path.join(__dirname, 'fixtures', 'api-check', filename) - const bundle = async () => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const _bundle = await ApiCheck.bundle(getFilePath('entrypoint.js'), undefined) - } - - Session.basePath = __dirname - Session.availableRuntimes = runtimes - Session.defaultRuntimeId = undefined - await expect(bundle()).rejects.toThrow('runtime is not set') - Session.basePath = undefined - Session.defaultRuntimeId = undefined + await usingFixture(async ({ fixtureDir }) => { + const getFilePath = (filename: string) => path.join(fixtureDir, filename) + const bundle = async () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const _bundle = await ApiCheck.bundle(getFilePath('entrypoint.js'), undefined) + } + + Session.availableRuntimes = runtimes + Session.defaultRuntimeId = undefined + await expect(bundle()).rejects.toThrow('runtime is not set') + }) }) it('should successfully bundle if runtime is not specified but default runtime is set', async () => { - const getFilePath = (filename: string) => path.join(__dirname, 'fixtures', 'api-check', filename) - const bundle = async () => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const _bundle = await ApiCheck.bundle(getFilePath('entrypoint.js'), undefined) - } - - Session.basePath = __dirname - Session.availableRuntimes = runtimes - Session.defaultRuntimeId = '2022.10' - await expect(bundle()).resolves.not.toThrow() - Session.basePath = undefined - Session.defaultRuntimeId = undefined + await usingFixture(async ({ fixtureDir }) => { + const getFilePath = (filename: string) => path.join(fixtureDir, filename) + const bundle = async () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const _bundle = await ApiCheck.bundle(getFilePath('entrypoint.js'), undefined) + } + + Session.basePath = __dirname + Session.availableRuntimes = runtimes + Session.defaultRuntimeId = '2022.10' + await expect(bundle()).resolves.not.toThrow() + }) }) it('should fail to bundle if runtime is not supported even if default runtime is set', async () => { - const getFilePath = (filename: string) => path.join(__dirname, 'fixtures', 'api-check', filename) - const bundle = async () => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const _bundle = await ApiCheck.bundle(getFilePath('entrypoint.js'), '9999.99') - } - - Session.basePath = __dirname - Session.availableRuntimes = runtimes - Session.defaultRuntimeId = '2022.02' - await expect(bundle()).rejects.toThrow('9999.99 is not supported') - Session.basePath = undefined - Session.defaultRuntimeId = undefined + await usingFixture(async ({ fixtureDir }) => { + const getFilePath = (filename: string) => path.join(fixtureDir, filename) + const bundle = async () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const _bundle = await ApiCheck.bundle(getFilePath('entrypoint.js'), '9999.99') + } + + Session.basePath = __dirname + Session.availableRuntimes = runtimes + Session.defaultRuntimeId = '2022.02' + await expect(bundle()).rejects.toThrow('9999.99 is not supported') + }) }) it('should not synthesize runtime if not specified even if default runtime is set', async () => { @@ -111,7 +127,6 @@ describe('ApiCheck', () => { const bundle = await apiCheck.bundle() const payload = bundle.synthesize() expect(payload.runtimeId).toBeUndefined() - Session.defaultRuntimeId = undefined }) it('should synthesize runtime if specified', async () => { diff --git a/packages/cli/src/constructs/__tests__/browser-check.spec.ts b/packages/cli/src/constructs/__tests__/browser-check.spec.ts index 1dc11ec9f..3538779fd 100644 --- a/packages/cli/src/constructs/__tests__/browser-check.spec.ts +++ b/packages/cli/src/constructs/__tests__/browser-check.spec.ts @@ -5,6 +5,7 @@ import { describe, it, expect, beforeEach, afterAll } from 'vitest' import { BrowserCheck, CheckGroup } from '../index' import { Project, Session } from '../project' +import { usingIsolatedFixture } from '../../services/check-parser/__tests__/helper' const runtimes = { '2022.10': { name: '2022.10', default: false, stage: 'CURRENT', description: 'Main updates are Playwright 1.28.0, Node.js 16.x and Typescript support. We are also dropping support for Puppeteer', dependencies: { '@playwright/test': '1.28.0', '@opentelemetry/api': '1.0.4', '@opentelemetry/sdk-trace-base': '1.0.1', '@faker-js/faker': '5.5.3', 'aws4': '1.11.0', 'axios': '0.27.2', 'btoa': '1.2.1', 'chai': '4.3.7', 'chai-string': '1.5.0', 'crypto-js': '4.1.1', 'expect': '29.3.1', 'form-data': '4.0.0', 'jsonwebtoken': '8.5.1', 'lodash': '4.17.21', 'mocha': '10.1.0', 'moment': '2.29.2', 'node': '16.x', 'otpauth': '9.0.2', 'playwright': '1.28.0', 'typescript': '4.8.4', 'uuid': '9.0.0' } }, @@ -19,32 +20,47 @@ describe('BrowserCheck', () => { Session.resetSharedFiles() }) - it('should correctly load file dependencies', async () => { - Session.basePath = __dirname - Session.availableRuntimes = runtimes - const getFilePath = (filename: string) => path.join(__dirname, 'fixtures', 'browser-check', filename) - const bundle = await BrowserCheck.bundle(getFilePath('entrypoint.js'), '2022.10') - Session.basePath = undefined + async function usingFixture (handle: ({ fixtureDir }: { fixtureDir: string }) => Promise) { + await usingIsolatedFixture(path.join(__dirname, 'fixtures', 'browser-check'), async fixtureDir => { + try { + Session.basePath = fixtureDir + Session.contextPath = fixtureDir - expect(bundle).toMatchObject({ - script: fs.readFileSync(getFilePath('entrypoint.js')).toString(), - scriptPath: 'fixtures/browser-check/entrypoint.js', - dependencies: [ - 0, - 1, - ], + await handle({ + fixtureDir, + }) + } finally { + Session.reset() + } }) + } + + it('should correctly load file dependencies', async () => { + await usingFixture(async ({ fixtureDir }) => { + Session.availableRuntimes = runtimes + const getFilePath = (filename: string) => path.join(fixtureDir, filename) + const bundle = await BrowserCheck.bundle(getFilePath('entrypoint.js'), '2022.10') + + expect(bundle).toMatchObject({ + script: fs.readFileSync(getFilePath('entrypoint.js')).toString(), + scriptPath: 'entrypoint.js', + dependencies: [ + 0, + 1, + ], + }) - expect(Session.sharedFiles).toEqual([ - { - path: 'fixtures/browser-check/dep1.js', - content: fs.readFileSync(getFilePath('dep1.js')).toString(), - }, - { - path: 'fixtures/browser-check/dep2.js', - content: fs.readFileSync(getFilePath('dep2.js')).toString(), - }, - ]) + expect(Session.sharedFiles).toEqual([ + { + path: 'dep1.js', + content: fs.readFileSync(getFilePath('dep1.js')).toString(), + }, + { + path: 'dep2.js', + content: fs.readFileSync(getFilePath('dep2.js')).toString(), + }, + ]) + }) }) it('should fail to bundle if runtime is not specified and default runtime is not set', async () => { diff --git a/packages/cli/src/constructs/__tests__/playwright-check.spec.ts b/packages/cli/src/constructs/__tests__/playwright-check.spec.ts index 668c91d18..af8612136 100644 --- a/packages/cli/src/constructs/__tests__/playwright-check.spec.ts +++ b/packages/cli/src/constructs/__tests__/playwright-check.spec.ts @@ -6,6 +6,9 @@ import { AxiosHeaders } from 'axios' import { CheckGroupV2, Diagnostics, PlaywrightCheck, RetryStrategyBuilder } from '../index' import { Project, Session } from '../project' import { checklyStorage } from '../../rest/api' +import { usingIsolatedFixture } from '../../services/check-parser/__tests__/helper' +import { Package, Workspace } from '../../services/check-parser/package-files/workspace' +import { Err, Ok } from '../../services/check-parser/package-files/result' describe('PlaywrightCheck', () => { beforeEach(() => { @@ -21,99 +24,42 @@ describe('PlaywrightCheck', () => { }) }) - it('should synthesize groupName', async () => { - Session.basePath = path.resolve(__dirname, './fixtures/playwright-check') - Session.project = new Project('project-id', { - name: 'Test Project', - repoUrl: 'https://github.com/checkly/checkly-cli', - }) - - const group = new CheckGroupV2('group', { - name: 'Test Group', - }) - - const check = new PlaywrightCheck('foo', { - name: 'Test Check', - groupName: 'Test Group', - playwrightConfigPath: path.resolve(__dirname, './fixtures/playwright-check/playwright.config.ts'), - }) - - const diags = new Diagnostics() - await check.validate(diags) - - expect(diags.isFatal()).toEqual(false) - - const bundle = await check.bundle() - const payload = bundle.synthesize() - - expect(payload.groupId).toEqual(group.ref()) - }) - - it('should synthesize group', async () => { - Session.basePath = path.resolve(__dirname, './fixtures/playwright-check') - Session.project = new Project('project-id', { - name: 'Test Project', - repoUrl: 'https://github.com/checkly/checkly-cli', - }) - - const group = new CheckGroupV2('group', { - name: 'Test Group', - }) - - const check = new PlaywrightCheck('foo', { - name: 'Test Check', - group, - playwrightConfigPath: path.resolve(__dirname, './fixtures/playwright-check/playwright.config.ts'), + async function usingFixture ( + handle: ({ fixtureDir }: { fixtureDir: string }) => Promise | void, + ) { + await usingIsolatedFixture(path.join(__dirname, 'fixtures', 'playwright-check'), async fixtureDir => { + try { + Session.workspace = Ok(new Workspace({ + root: new Package({ + name: 'playwright-bundle-test', + path: fixtureDir, + }), + packages: [], + lockfile: Ok(path.join(fixtureDir, 'package-lock.json')), + configFile: Err(new Error('configFile not set')), + })) + + Session.basePath = fixtureDir + Session.contextPath = fixtureDir + + await handle({ + fixtureDir, + }) + } finally { + Session.reset() + } }) + } - const diags = new Diagnostics() - await check.validate(diags) - - expect(diags.isFatal()).toEqual(false) - - const bundle = await check.bundle() - const payload = bundle.synthesize() - - expect(payload.groupId).toEqual(group.ref()) - }) - - it('should synthesize groupId', async () => { - Session.basePath = path.resolve(__dirname, './fixtures/playwright-check') + beforeEach(() => { Session.project = new Project('project-id', { name: 'Test Project', repoUrl: 'https://github.com/checkly/checkly-cli', }) - - const group = new CheckGroupV2('group', { - name: 'Test Group', - }) - - const check = new PlaywrightCheck('foo', { - name: 'Test Check', - groupId: group.ref(), - playwrightConfigPath: path.resolve(__dirname, './fixtures/playwright-check/playwright.config.ts'), - }) - - const diags = new Diagnostics() - await check.validate(diags) - - expect(diags.isFatal()).toEqual(false) - - const bundle = await check.bundle() - const payload = bundle.synthesize() - - expect(payload.groupId).toEqual(group.ref()) }) - describe('validation', () => { - it('should warn that groupName is deprecated', async () => { - Session.basePath = path.resolve(__dirname, './fixtures/playwright-check') - Session.project = new Project('project-id', { - name: 'Test Project', - repoUrl: 'https://github.com/checkly/checkly-cli', - }) - - // eslint-disable-next-line @typescript-eslint/no-unused-vars + it('should synthesize groupName', async () => { + await usingFixture(async ({ fixtureDir }) => { const group = new CheckGroupV2('group', { name: 'Test Group', }) @@ -121,56 +67,23 @@ describe('PlaywrightCheck', () => { const check = new PlaywrightCheck('foo', { name: 'Test Check', groupName: 'Test Group', - playwrightConfigPath: path.resolve(__dirname, './fixtures/playwright-check/playwright.config.ts'), + playwrightConfigPath: path.resolve(fixtureDir, 'playwright.config.ts'), }) const diags = new Diagnostics() await check.validate(diags) expect(diags.isFatal()).toEqual(false) - expect(diags.observations).toEqual(expect.arrayContaining([ - expect.objectContaining({ - message: expect.stringContaining('Property "groupName" is deprecated and will eventually be removed.'), - }), - ])) - }) - - it('should error if groupName is not found', async () => { - Session.basePath = path.resolve(__dirname, './fixtures/playwright-check') - Session.project = new Project('project-id', { - name: 'Test Project', - repoUrl: 'https://github.com/checkly/checkly-cli', - }) - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const group = new CheckGroupV2('group', { - name: 'Test Group', - }) - const check = new PlaywrightCheck('foo', { - name: 'Test Check', - groupName: 'Missing Group', - playwrightConfigPath: path.resolve(__dirname, './fixtures/playwright-check/playwright.config.ts'), - }) - - const diags = new Diagnostics() - await check.validate(diags) + const bundle = await check.bundle() + const payload = bundle.synthesize() - expect(diags.isFatal()).toEqual(true) - expect(diags.observations).toEqual(expect.arrayContaining([ - expect.objectContaining({ - message: expect.stringContaining('The value provided for property "groupName" is not valid.'), - }), - ])) + expect(payload.groupId).toEqual(group.ref()) }) + }) - it('should error if both group and groupName are set', async () => { - Session.basePath = path.resolve(__dirname, './fixtures/playwright-check') - Session.project = new Project('project-id', { - name: 'Test Project', - repoUrl: 'https://github.com/checkly/checkly-cli', - }) - + it('should synthesize group', async () => { + await usingFixture(async ({ fixtureDir }) => { const group = new CheckGroupV2('group', { name: 'Test Group', }) @@ -178,28 +91,23 @@ describe('PlaywrightCheck', () => { const check = new PlaywrightCheck('foo', { name: 'Test Check', group, - groupName: 'Test Group', - playwrightConfigPath: path.resolve(__dirname, './fixtures/playwright-check/playwright.config.ts'), + playwrightConfigPath: path.resolve(fixtureDir, 'playwright.config.ts'), }) const diags = new Diagnostics() await check.validate(diags) - expect(diags.isFatal()).toEqual(true) - expect(diags.observations).toEqual(expect.arrayContaining([ - expect.objectContaining({ - message: expect.stringContaining('Property "groupName" cannot be set when "group" is set.'), - }), - ])) - }) + expect(diags.isFatal()).toEqual(false) - it('should error if both groupId and groupName are set', async () => { - Session.basePath = path.resolve(__dirname, './fixtures/playwright-check') - Session.project = new Project('project-id', { - name: 'Test Project', - repoUrl: 'https://github.com/checkly/checkly-cli', - }) + const bundle = await check.bundle() + const payload = bundle.synthesize() + expect(payload.groupId).toEqual(group.ref()) + }) + }) + + it('should synthesize groupId', async () => { + await usingFixture(async ({ fixtureDir }) => { const group = new CheckGroupV2('group', { name: 'Test Group', }) @@ -207,109 +115,194 @@ describe('PlaywrightCheck', () => { const check = new PlaywrightCheck('foo', { name: 'Test Check', groupId: group.ref(), - groupName: 'Test Group', - playwrightConfigPath: path.resolve(__dirname, './fixtures/playwright-check/playwright.config.ts'), + playwrightConfigPath: path.resolve(fixtureDir, 'playwright.config.ts'), }) const diags = new Diagnostics() await check.validate(diags) - expect(diags.isFatal()).toEqual(true) - expect(diags.observations).toEqual(expect.arrayContaining([ - expect.objectContaining({ - message: expect.stringContaining('Property "groupName" cannot be set when "group" is set.'), - }), - ])) + expect(diags.isFatal()).toEqual(false) + + const bundle = await check.bundle() + const payload = bundle.synthesize() + + expect(payload.groupId).toEqual(group.ref()) }) + }) - it('should error if retryStrategy is set', async () => { - Session.basePath = path.resolve(__dirname, './fixtures/playwright-check') - Session.project = new Project('project-id', { - name: 'Test Project', - repoUrl: 'https://github.com/checkly/checkly-cli', + describe('validation', () => { + it('should warn that groupName is deprecated', async () => { + await usingFixture(async ({ fixtureDir }) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const group = new CheckGroupV2('group', { + name: 'Test Group', + }) + + const check = new PlaywrightCheck('foo', { + name: 'Test Check', + groupName: 'Test Group', + playwrightConfigPath: path.resolve(fixtureDir, 'playwright.config.ts'), + }) + + const diags = new Diagnostics() + await check.validate(diags) + + expect(diags.isFatal()).toEqual(false) + expect(diags.observations).toEqual(expect.arrayContaining([ + expect.objectContaining({ + message: expect.stringContaining('Property "groupName" is deprecated and will eventually be removed.'), + }), + ])) }) + }) - const check = new PlaywrightCheck('foo', { - name: 'Test Check', - playwrightConfigPath: path.resolve(__dirname, './fixtures/playwright-check/playwright.config.ts'), - // @ts-expect-error - Testing runtime validation. TypeScript should prevent this at compile time. - retryStrategy: RetryStrategyBuilder.fixedStrategy({ maxRetries: 3 }), + it('should error if groupName is not found', async () => { + await usingFixture(async ({ fixtureDir }) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const group = new CheckGroupV2('group', { + name: 'Test Group', + }) + + const check = new PlaywrightCheck('foo', { + name: 'Test Check', + groupName: 'Missing Group', + playwrightConfigPath: path.resolve(fixtureDir, 'playwright.config.ts'), + }) + + const diags = new Diagnostics() + await check.validate(diags) + + expect(diags.isFatal()).toEqual(true) + expect(diags.observations).toEqual(expect.arrayContaining([ + expect.objectContaining({ + message: expect.stringContaining('The value provided for property "groupName" is not valid.'), + }), + ])) }) - - const diags = new Diagnostics() - await check.validate(diags) - - expect(diags.isFatal()).toEqual(true) - expect(diags.observations).toEqual(expect.arrayContaining([ - expect.objectContaining({ - message: expect.stringContaining('Property "retryStrategy" is not supported.'), - }), - ])) }) - it('should error if doubleCheck is set', async () => { - Session.basePath = path.resolve(__dirname, './fixtures/playwright-check') - Session.project = new Project('project-id', { - name: 'Test Project', - repoUrl: 'https://github.com/checkly/checkly-cli', + it('should error if both group and groupName are set', async () => { + await usingFixture(async ({ fixtureDir }) => { + const group = new CheckGroupV2('group', { + name: 'Test Group', + }) + + const check = new PlaywrightCheck('foo', { + name: 'Test Check', + group, + groupName: 'Test Group', + playwrightConfigPath: path.resolve(fixtureDir, 'playwright.config.ts'), + }) + + const diags = new Diagnostics() + await check.validate(diags) + + expect(diags.isFatal()).toEqual(true) + expect(diags.observations).toEqual(expect.arrayContaining([ + expect.objectContaining({ + message: expect.stringContaining('Property "groupName" cannot be set when "group" is set.'), + }), + ])) }) + }) - const check = new PlaywrightCheck('foo', { - name: 'Test Check', - playwrightConfigPath: path.resolve(__dirname, './fixtures/playwright-check/playwright.config.ts'), - // @ts-expect-error Testing a property that isn't part of the type. - doubleCheck: true, + it('should error if both groupId and groupName are set', async () => { + await usingFixture(async ({ fixtureDir }) => { + const group = new CheckGroupV2('group', { + name: 'Test Group', + }) + + const check = new PlaywrightCheck('foo', { + name: 'Test Check', + groupId: group.ref(), + groupName: 'Test Group', + playwrightConfigPath: path.resolve(fixtureDir, 'playwright.config.ts'), + }) + + const diags = new Diagnostics() + await check.validate(diags) + + expect(diags.isFatal()).toEqual(true) + expect(diags.observations).toEqual(expect.arrayContaining([ + expect.objectContaining({ + message: expect.stringContaining('Property "groupName" cannot be set when "group" is set.'), + }), + ])) }) + }) - const diags = new Diagnostics() - await check.validate(diags) + it('should error if retryStrategy is set', async () => { + await usingFixture(async ({ fixtureDir }) => { + const check = new PlaywrightCheck('foo', { + name: 'Test Check', + playwrightConfigPath: path.resolve(fixtureDir, 'playwright.config.ts'), + // @ts-expect-error - Testing runtime validation. TypeScript should prevent this at compile time. + retryStrategy: RetryStrategyBuilder.fixedStrategy({ maxRetries: 3 }), + }) + + const diags = new Diagnostics() + await check.validate(diags) + + expect(diags.isFatal()).toEqual(true) + expect(diags.observations).toEqual(expect.arrayContaining([ + expect.objectContaining({ + message: expect.stringContaining('Property "retryStrategy" is not supported.'), + }), + ])) + }) + }) - expect(diags.isFatal()).toEqual(true) - expect(diags.observations).toEqual(expect.arrayContaining([ - expect.objectContaining({ - message: expect.stringContaining('Property "doubleCheck" is not supported.'), - }), - ])) + it('should error if doubleCheck is set', async () => { + await usingFixture(async ({ fixtureDir }) => { + const check = new PlaywrightCheck('foo', { + name: 'Test Check', + playwrightConfigPath: path.resolve(fixtureDir, 'playwright.config.ts'), + // @ts-expect-error Testing a property that isn't part of the type. + doubleCheck: true, + }) + + const diags = new Diagnostics() + await check.validate(diags) + + expect(diags.isFatal()).toEqual(true) + expect(diags.observations).toEqual(expect.arrayContaining([ + expect.objectContaining({ + message: expect.stringContaining('Property "doubleCheck" is not supported.'), + }), + ])) + }) }) }) describe('defaults', () => { - it('should ignore retryStrategy from session check defaults', () => { - Session.basePath = path.resolve(__dirname, './fixtures/playwright-check') - Session.project = new Project('project-id', { - name: 'Test Project', - repoUrl: 'https://github.com/checkly/checkly-cli', - }) - - Session.checkDefaults = { - retryStrategy: RetryStrategyBuilder.fixedStrategy({ maxRetries: 3 }), - } - - const check = new PlaywrightCheck('foo', { - name: 'Test Check', - playwrightConfigPath: path.resolve(__dirname, './fixtures/playwright-check/playwright.config.ts'), + it('should ignore retryStrategy from session check defaults', async () => { + await usingFixture(({ fixtureDir }) => { + Session.checkDefaults = { + retryStrategy: RetryStrategyBuilder.fixedStrategy({ maxRetries: 3 }), + } + + const check = new PlaywrightCheck('foo', { + name: 'Test Check', + playwrightConfigPath: path.resolve(fixtureDir, 'playwright.config.ts'), + }) + + expect(check.retryStrategy).toBeUndefined() }) - - expect(check.retryStrategy).toBeUndefined() }) - it('should ignore doubleCheck from session check defaults', () => { - Session.basePath = path.resolve(__dirname, './fixtures/playwright-check') - Session.project = new Project('project-id', { - name: 'Test Project', - repoUrl: 'https://github.com/checkly/checkly-cli', - }) + it('should ignore doubleCheck from session check defaults', async () => { + await usingFixture(({ fixtureDir }) => { + Session.checkDefaults = { + doubleCheck: true, + } - Session.checkDefaults = { - doubleCheck: true, - } + const check = new PlaywrightCheck('foo', { + name: 'Test Check', + playwrightConfigPath: path.resolve(fixtureDir, 'playwright.config.ts'), + }) - const check = new PlaywrightCheck('foo', { - name: 'Test Check', - playwrightConfigPath: path.resolve(__dirname, './fixtures/playwright-check/playwright.config.ts'), + expect(check.doubleCheck).toBeUndefined() }) - - expect(check.doubleCheck).toBeUndefined() }) }) }) diff --git a/packages/cli/src/constructs/browser-check.ts b/packages/cli/src/constructs/browser-check.ts index 53963d290..7ec27ec7f 100644 --- a/packages/cli/src/constructs/browser-check.ts +++ b/packages/cli/src/constructs/browser-check.ts @@ -1,9 +1,7 @@ import fs from 'node:fs/promises' -import path from 'node:path' import { CheckProps, RuntimeCheck, RuntimeCheckProps } from './check' import { Session, SharedFileRef } from './project' -import { pathToPosix } from '../services/util' import { Content, Entrypoint, isContent, isEntrypoint } from './construct' import { detectSnapshots } from '../services/snapshot-service' import { PlaywrightConfig } from './playwright-config' @@ -11,6 +9,7 @@ import { Diagnostics } from './diagnostics' import { InvalidPropertyValueDiagnostic } from './construct-diagnostics' import { BrowserCheckBundle } from './browser-check-bundle' import { ConfigDefaultsGetter, makeConfigDefaultsGetter } from './check-config' +import { CheckConfigDefaults } from '../services/checkly-config-loader' export interface BrowserCheckProps extends RuntimeCheckProps { /** @@ -106,7 +105,7 @@ export class BrowserCheck extends RuntimeCheck { return `BrowserCheck:${this.logicalId}` } - protected configDefaultsGetter (props: CheckProps): ConfigDefaultsGetter { + protected configDefaultsGetter (props: CheckProps): ConfigDefaultsGetter { return makeConfigDefaultsGetter( props.group?.getBrowserCheckDefaults(), Session.browserCheckDefaults, @@ -168,7 +167,7 @@ export class BrowserCheck extends RuntimeCheck { const deps: SharedFileRef[] = [] for (const { filePath, content } of parsed.dependencies) { deps.push(Session.registerSharedFile({ - path: pathToPosix(path.relative(Session.basePath!, filePath)), + path: Session.relativePosixPath(filePath), content, })) } diff --git a/packages/cli/src/constructs/check-config.ts b/packages/cli/src/constructs/check-config.ts index 7f4595555..53ad43440 100644 --- a/packages/cli/src/constructs/check-config.ts +++ b/packages/cli/src/constructs/check-config.ts @@ -1,13 +1,13 @@ -import { CheckConfigDefaults } from '../services/checkly-config-loader' +export type ConfigDefaultsGetter< + T extends object, +> = (key: K) => T[K] | undefined -export type ConfigDefaultsGetter = (key: K) => CheckConfigDefaults[K] - -export function makeConfigDefaultsGetter ( - ...defaults: (Partial | undefined)[] -): ConfigDefaultsGetter { +export function makeConfigDefaultsGetter ( + ...defaults: (Partial | undefined)[] +): ConfigDefaultsGetter { const ok = defaults.filter(value => value !== undefined) - function get (key: K): CheckConfigDefaults[K] { + function get (key: K): T[K] | undefined { for (const config of ok) { // Older TS seems to need this check. if (config === undefined) { diff --git a/packages/cli/src/constructs/check-group-v1.ts b/packages/cli/src/constructs/check-group-v1.ts index 213c5d62b..31b539b1d 100644 --- a/packages/cli/src/constructs/check-group-v1.ts +++ b/packages/cli/src/constructs/check-group-v1.ts @@ -17,7 +17,6 @@ import { Diagnostics } from './diagnostics' import { DeprecatedConstructDiagnostic, DeprecatedPropertyDiagnostic, InvalidPropertyValueDiagnostic } from './construct-diagnostics' import CheckTypes from '../constants' import { CheckConfigDefaults } from '../services/checkly-config-loader' -import { pathToPosix } from '../services/util' import { AlertChannelSubscription } from './alert-channel-subscription' import { BrowserCheck } from './browser-check' import { CheckGroupRef } from './check-group-ref' @@ -438,7 +437,7 @@ export class CheckGroupV1 extends Construct { }, // the browserChecks props inherited from the group are applied in BrowserCheck.constructor() } - const checkLogicalId = pathToPosix(path.relative(Session.basePath!, filepath)) + const checkLogicalId = Session.relativePosixPath(filepath) if (checkType === CheckTypes.BROWSER) { new BrowserCheck(checkLogicalId, props) } else { diff --git a/packages/cli/src/constructs/check.ts b/packages/cli/src/constructs/check.ts index 83d95aef4..23af8ccea 100644 --- a/packages/cli/src/constructs/check.ts +++ b/packages/cli/src/constructs/check.ts @@ -23,6 +23,7 @@ import { ConfigDefaultsGetter, makeConfigDefaultsGetter } from './check-config' import { Diagnostics } from './diagnostics' import { validateDeprecatedDoubleCheck } from './internal/common-diagnostics' import { InvalidPropertyValueDiagnostic } from './construct-diagnostics' +import { CheckConfigDefaults } from '../services/checkly-config-loader' /** * Retry strategies supported by checks. @@ -329,7 +330,7 @@ export abstract class Check extends Construct { await this.validateRetryStrategyOnlyOn(diagnostics) } - protected configDefaultsGetter (props: CheckProps): ConfigDefaultsGetter { + protected configDefaultsGetter (props: CheckProps): ConfigDefaultsGetter { return makeConfigDefaultsGetter( props.group?.getCheckDefaults(), Session.checkDefaults, diff --git a/packages/cli/src/constructs/construct-diagnostics.ts b/packages/cli/src/constructs/construct-diagnostics.ts index 2bbd9a91e..335cbdd49 100644 --- a/packages/cli/src/constructs/construct-diagnostics.ts +++ b/packages/cli/src/constructs/construct-diagnostics.ts @@ -137,6 +137,20 @@ export class UnsupportedRuntimeFeatureDiagnostic extends ErrorDiagnostic { } } +export class UnsatisfiedLocalPrerequisitesDiagnostic extends ErrorDiagnostic { + constructor (error: Error) { + super({ + title: `Unsatisfied local prerequisites`, + message: + `Local environment does not satisfy the prerequisites for this ` + + `functionality.` + + `\n\n` + + `Cause: ${error.message}`, + error, + }) + } +} + export class ConstructDiagnostic extends Diagnostic { underlying: Diagnostic diff --git a/packages/cli/src/constructs/internal/codegen/snippet.ts b/packages/cli/src/constructs/internal/codegen/snippet.ts index 1d87c81fd..a875a9840 100644 --- a/packages/cli/src/constructs/internal/codegen/snippet.ts +++ b/packages/cli/src/constructs/internal/codegen/snippet.ts @@ -54,6 +54,6 @@ export function parseSnippetDependencies (content: string): string[] { } return dependencies - .filter(value => value.startsWith(SNIPPET_PATH_PREFIX)) - .map(value => value.slice(SNIPPET_PATH_PREFIX.length)) + .filter(({ importPath }) => importPath.startsWith(SNIPPET_PATH_PREFIX)) + .map(({ importPath }) => importPath.slice(SNIPPET_PATH_PREFIX.length)) } diff --git a/packages/cli/src/constructs/multi-step-check.ts b/packages/cli/src/constructs/multi-step-check.ts index 56c86cf12..cb2066de8 100644 --- a/packages/cli/src/constructs/multi-step-check.ts +++ b/packages/cli/src/constructs/multi-step-check.ts @@ -9,6 +9,7 @@ import { Diagnostics } from './diagnostics' import { InvalidPropertyValueDiagnostic, UnsupportedRuntimeFeatureDiagnostic } from './construct-diagnostics' import { MultiStepCheckBundle } from './multi-step-check-bundle' import { ConfigDefaultsGetter, makeConfigDefaultsGetter } from './check-config' +import { CheckConfigDefaults } from '../services/checkly-config-loader' export interface MultiStepCheckProps extends RuntimeCheckProps { /** @@ -96,7 +97,7 @@ export class MultiStepCheck extends RuntimeCheck { } } - protected configDefaultsGetter (props: CheckProps): ConfigDefaultsGetter { + protected configDefaultsGetter (props: CheckProps): ConfigDefaultsGetter { return makeConfigDefaultsGetter( props.group?.getMultiStepCheckDefaults(), Session.multiStepCheckDefaults, diff --git a/packages/cli/src/constructs/playwright-check.ts b/packages/cli/src/constructs/playwright-check.ts index 28e78f8c6..750fbbd88 100644 --- a/packages/cli/src/constructs/playwright-check.ts +++ b/packages/cli/src/constructs/playwright-check.ts @@ -2,6 +2,8 @@ import { createReadStream } from 'node:fs' import fs from 'node:fs/promises' import type { AxiosResponse } from 'axios' +import Debug from 'debug' + import { checklyStorage } from '../rest/api' import { bundlePlayWrightProject, cleanup, @@ -11,6 +13,7 @@ import { ConflictingPropertyDiagnostic, DeprecatedPropertyDiagnostic, InvalidPropertyValueDiagnostic, + UnsatisfiedLocalPrerequisitesDiagnostic, UnsupportedPropertyDiagnostic, } from './construct-diagnostics' import { Diagnostics } from './diagnostics' @@ -18,6 +21,9 @@ import { PlaywrightCheckBundle } from './playwright-check-bundle' import { Session } from './project' import { Ref } from './ref' import { ConfigDefaultsGetter, makeConfigDefaultsGetter } from './check-config' +import { CheckConfigDefaults } from '../services/checkly-config-loader' + +const debug = Debug('checkly:cli:constructs:playwright-check') export interface PlaywrightCheckProps extends Omit { /** @@ -125,7 +131,7 @@ export interface PlaywrightCheckProps extends Omit { const group = PlaywrightCheck.#resolveGroupFromProps(props) return makeConfigDefaultsGetter( @@ -239,6 +245,7 @@ export class PlaywrightCheck extends RuntimeCheck { async validate (diagnostics: Diagnostics): Promise { await super.validate(diagnostics) + await this.#validateWorkspace(diagnostics) await this.validateRetryStrategy(diagnostics) try { @@ -253,6 +260,29 @@ export class PlaywrightCheck extends RuntimeCheck { this.#validateGroupReferences(diagnostics) } + // eslint-disable-next-line require-await + async #validateWorkspace (diagnostics: Diagnostics): Promise { + const workspace = Session.workspace + if (workspace.isOk()) { + const lockfile = workspace.ok().lockfile + if (lockfile.isErr()) { + diagnostics.add(new UnsatisfiedLocalPrerequisitesDiagnostic(new Error( + `A lockfile is required for Playwright checks, but none could be ` + + `detected.` + + '\n\n' + + `Cause: ${lockfile.err().message}`, + ))) + } + } else if (workspace.isErr()) { + diagnostics.add(new UnsatisfiedLocalPrerequisitesDiagnostic(new Error( + `A workspace is required for Playwright checks, but none could be ` + + `detected.` + + '\n\n' + + `Cause: ${workspace.err().message}`, + ))) + } + } + #validateGroupReferences (diagnostics: Diagnostics): void { if (this.groupName) { diagnostics.add(new DeprecatedPropertyDiagnostic( @@ -294,6 +324,12 @@ export class PlaywrightCheck extends RuntimeCheck { return `${testCommand} --config ${quotedPath}${projectArg}${tagArg}` } + static contextifyCommand (command: string): string { + return Session.basePath === Session.contextPath + ? command + : `env --chdir "${Session.relativePosixPath(Session.contextPath!)}" -- ${command}` + } + static async bundleProject (playwrightConfigPath: string, include: string[]) { let dir = '' try { @@ -304,7 +340,12 @@ export class PlaywrightCheck extends RuntimeCheck { const { data: { key } } = await PlaywrightCheck.uploadPlaywrightProject(dir) return { key, browsers, relativePlaywrightConfigPath, cacheHash, playwrightVersion } } finally { - await cleanup(dir) + if (process.env['CHECKLY_PLAYWRIGHT_DEBUG_PERSIST_BUNDLE'] === '1') { + debug(`Skip cleaning up bundle '${dir}'`) + } else { + debug(`Cleaning up bundle '${dir}'`) + await cleanup(dir) + } } } @@ -332,12 +373,12 @@ export class PlaywrightCheck extends RuntimeCheck { relativePlaywrightConfigPath, } = await PlaywrightCheck.bundleProject(this.playwrightConfigPath, this.include ?? []) - const testCommand = PlaywrightCheck.buildTestCommand( - this.testCommand, + const testCommand = PlaywrightCheck.contextifyCommand(PlaywrightCheck.buildTestCommand( + this.testCommand ?? this.#defaultTestCommand(), relativePlaywrightConfigPath, this.pwProjects, this.pwTags, - ) + )) return new PlaywrightCheckBundle(this, { groupId, @@ -350,6 +391,10 @@ export class PlaywrightCheck extends RuntimeCheck { }) } + #defaultTestCommand (): string { + return Session.packageManager.execCommand(['playwright', 'test']).unsafeDisplayCommand + } + synthesize () { return { ...super.synthesize(), diff --git a/packages/cli/src/constructs/project.ts b/packages/cli/src/constructs/project.ts index d8d796cf9..83a7bcdd0 100644 --- a/packages/cli/src/constructs/project.ts +++ b/packages/cli/src/constructs/project.ts @@ -24,6 +24,9 @@ import { Diagnostics } from './diagnostics' import { ConstructDiagnostics, InvalidPropertyValueDiagnostic } from './construct-diagnostics' import { ProjectBundle, ProjectDataBundle } from './project-bundle' import { pathToPosix } from '../services/util' +import { Workspace } from '../services/check-parser/package-files/workspace' +import { npmPackageManager, PackageManager } from '../services/check-parser/package-files/package-manager' +import { Err, Result } from '../services/check-parser/package-files/result' export interface ProjectProps { /** @@ -229,6 +232,7 @@ export class Session { static project?: Project static basePath?: string + static contextPath?: string static checkDefaults?: CheckConfigDefaults static checkFilter?: CheckFilter static browserCheckDefaults?: CheckConfigDefaults @@ -244,6 +248,32 @@ export class Session { static parsers = new Map() static constructExports: ConstructExport[] = [] static ignoreDirectoriesMatch: string[] = [] + static packageManager: PackageManager = npmPackageManager + static workspace: Result = Err(new Error(`Workspace support not initialized`)) + + static reset () { + this.project = undefined + this.basePath = undefined + this.contextPath = undefined + this.checkDefaults = undefined + this.checkFilter = undefined + this.browserCheckDefaults = undefined + this.multiStepCheckDefaults = undefined + this.checkFilePath = undefined + this.checkFileAbsolutePath = undefined + this.availableRuntimes = {} + this.defaultRuntimeId = undefined + this.verifyRuntimeDependencies = true + this.loadingChecklyConfigFile = false + this.checklyConfigFileConstructs = undefined + this.privateLocations = [] + this.parsers = new Map() + this.constructExports = [] + this.ignoreDirectoriesMatch = [] + this.packageManager = npmPackageManager + this.workspace = Err(new Error(`Workspace support not initialized`)) + this.resetSharedFiles() + } static async loadFile (filePath: string): Promise { const loader = this.loader @@ -338,6 +368,7 @@ export class Session { const parser = new Parser({ supportedNpmModules: Object.keys(runtime.dependencies), checkUnsupportedModules: Session.verifyRuntimeDependencies, + workspace: Session.workspace.ok(), }) Session.parsers.set(runtime.name, parser) @@ -349,6 +380,10 @@ export class Session { return pathToPosix(path.relative(Session.basePath!, filePath)) } + static contextRelativePosixPath (filePath: string): string { + return pathToPosix(path.relative(Session.contextPath!, filePath)) + } + static sharedFileRefs = new Map() static sharedFiles: SharedFile[] = [] diff --git a/packages/cli/src/loader/jiti.ts b/packages/cli/src/loader/jiti.ts index 5e74f65f8..218c216c0 100644 --- a/packages/cli/src/loader/jiti.ts +++ b/packages/cli/src/loader/jiti.ts @@ -1,7 +1,11 @@ +import Debug from 'debug' + import { FileLoader, FileLoaderOptions, UnsupportedFileLoaderError } from './loader' import { FileMatch } from './match' import { preferenceDelta } from './config' +const debug = Debug('checkly:cli:loader:jiti') + interface JitiExports { createJiti (id: string, userOptions?: any): Jiti } @@ -18,11 +22,16 @@ export class UninitializedJitiFileLoaderState extends FileLoader { async loadFile (filePath: string): Promise { UninitializedJitiFileLoaderState.init ??= (async () => { + debug('Initializing loader') try { const jitiExports: JitiExports = await import('jiti') - const jiti = jitiExports.createJiti(__filename) + const jiti = jitiExports.createJiti(__filename, { + tsx: true, + }) + debug(`Successfully initialized loader`) JitiFileLoader.state = new InitializedJitiFileLoaderState(jiti) } catch (err) { + debug(`Failed to initialize loader: ${err}`) JitiFileLoader.state = new FailedJitiFileLoaderState(err as Error) } })() @@ -58,8 +67,14 @@ export class InitializedJitiFileLoaderState extends FileLoader { } async loadFile (filePath: string): Promise { - const moduleExports = await this.jiti.import(filePath) - return moduleExports + debug(`Loading file ${filePath}`) + try { + const moduleExports = await this.jiti.import(filePath) + return moduleExports + } catch (err) { + debug(`Failed to load file ${filePath}: ${err}`) + throw err + } } } diff --git a/packages/cli/src/loader/ts-node.ts b/packages/cli/src/loader/ts-node.ts index 4acd32b42..326954935 100644 --- a/packages/cli/src/loader/ts-node.ts +++ b/packages/cli/src/loader/ts-node.ts @@ -1,7 +1,11 @@ +import Debug from 'debug' + import { preferenceDelta } from './config' import { FileLoader, FileLoaderOptions, UnsupportedFileLoaderError } from './loader' import { FileMatch } from './match' +const debug = Debug('checkly:cli:loader:ts-node') + interface TSNodeExports { register (opts?: any): TSNodeService } @@ -15,6 +19,7 @@ export class UninitializedTSNodeFileLoaderState extends FileLoader { async loadFile (filePath: string): Promise { UninitializedTSNodeFileLoaderState.init ??= (async () => { + debug('Initializing loader') try { const tsNodeExports: TSNodeExports = await import('ts-node') const service = tsNodeExports.register({ @@ -25,8 +30,10 @@ export class UninitializedTSNodeFileLoaderState extends FileLoader { module: 'CommonJS', }, }) + debug(`Successfully initialized loader`) TSNodeFileLoader.state = new InitializedTSNodeFileLoaderState(service) } catch (err) { + debug(`Failed to initialize loader: ${err}`) TSNodeFileLoader.state = new FailedTSNodeFileLoaderState(err as Error) } })() @@ -63,6 +70,7 @@ export class InitializedTSNodeFileLoaderState extends FileLoader { // eslint-disable-next-line require-await async loadFile (filePath: string): Promise { + debug(`Loading file ${filePath}`) try { this.service.enabled(true) @@ -70,6 +78,8 @@ export class InitializedTSNodeFileLoaderState extends FileLoader { const moduleExports = require(filePath) return moduleExports } catch (err: any) { + debug(`Failed to load file ${filePath}: ${err}`) + if (err.message?.includes('Unable to compile TypeScript')) { throw new Error(`Unable to load file '${filePath}' with 'ts-node' (hint: consider installing 'jiti' for improved TypeScript support)\n${err.stack}`, { cause: err as Error, diff --git a/packages/cli/src/services/__tests__/checkly-config-loader.spec.ts b/packages/cli/src/services/__tests__/checkly-config-loader.spec.ts index cf88fd8d6..2f1146754 100644 --- a/packages/cli/src/services/__tests__/checkly-config-loader.spec.ts +++ b/packages/cli/src/services/__tests__/checkly-config-loader.spec.ts @@ -2,7 +2,7 @@ import path from 'node:path' import { describe, it, expect } from 'vitest' -import { loadChecklyConfig } from '../checkly-config-loader' +import { loadChecklyConfig, defaultFilenames } from '../checkly-config-loader' import { splitConfigFilePath } from '../util' describe('loadChecklyConfig()', () => { @@ -25,8 +25,10 @@ describe('loadChecklyConfig()', () => { try { await loadChecklyConfig(configDir) } catch (e: any) { - expect(e.message).toContain(`Unable to locate a config at ${configDir} with ${ - ['checkly.config.ts', 'checkly.config.mts', 'checkly.config.cts', 'checkly.config.js', 'checkly.config.mjs', 'checkly.config.cjs'].join(', ')}.`) + expect(e.message).toContain(`Unable to detect a Checkly configuration file`) + for (const filename of defaultFilenames) { + expect(e.message).toContain(filename) + } } }) it('config TS file should export an object', async () => { diff --git a/packages/cli/src/services/__tests__/util.spec.ts b/packages/cli/src/services/__tests__/util.spec.ts index 53daa566a..ac0f6ea00 100644 --- a/packages/cli/src/services/__tests__/util.spec.ts +++ b/packages/cli/src/services/__tests__/util.spec.ts @@ -10,6 +10,9 @@ import { bundlePlayWrightProject, } from '../util' import { Session } from '../../constructs/project' +import { Err, Ok } from '../check-parser/package-files/result' +import { Package, Workspace } from '../check-parser/package-files/workspace' +import { usingIsolatedFixture } from '../check-parser/__tests__/helper' describe('util', () => { describe('pathToPosix()', () => { @@ -52,154 +55,168 @@ describe('util', () => { }) describe('bundlePlayWrightProject()', () => { - let originalBasePath: string | undefined - let extractDir: string - - beforeEach(async () => { - // Save original Session state - originalBasePath = Session.basePath - - // Set up Session for bundling - const fixtureDir = path.join(__dirname, 'fixtures', 'playwright-bundle-test') - Session.basePath = fixtureDir - - // Create temp directory for extraction - extractDir = await fs.mkdtemp(path.join(__dirname, 'temp-extract-')) - }) - - afterEach(async () => { - // Restore Session state - Session.basePath = originalBasePath - Session.ignoreDirectoriesMatch = [] - - // Clean up extraction directory - try { - await fs.rm(extractDir, { recursive: true, force: true }) - } catch { - // Ignore cleanup errors - } - }) + async function usingFixture ( + handle: ({ fixtureDir, extractDir }: { fixtureDir: string, extractDir: string }) => Promise, + ) { + await usingIsolatedFixture(path.join(__dirname, 'fixtures', 'playwright-bundle-test'), async fixtureDir => { + const extractDir = await fs.mkdtemp(`${fixtureDir}-extracted`) + + try { + Session.workspace = Ok(new Workspace({ + root: new Package({ + name: 'playwright-bundle-test', + path: fixtureDir, + }), + packages: [], + lockfile: Ok(path.join(fixtureDir, 'package-lock.json')), + configFile: Err(new Error('configFile not set')), + })) + + Session.basePath = fixtureDir + Session.contextPath = fixtureDir + + await handle({ + fixtureDir, + extractDir, + }) + } finally { + Session.reset() + + // Clean up extraction directory + try { + await fs.rm(extractDir, { recursive: true, force: true }) + } catch { + // Ignore cleanup errors + } + } + }) + } it('should exclude directories matching ignoreDirectoriesMatch pattern', async () => { - const fixtureDir = path.join(__dirname, 'fixtures', 'playwright-bundle-test') - const playwrightConfigPath = path.join(fixtureDir, 'playwright.config.ts') + await usingFixture(async ({ fixtureDir, extractDir }) => { + const playwrightConfigPath = path.join(fixtureDir, 'playwright.config.ts') - // Set ignoreDirectoriesMatch to exclude fixtures directory - Session.ignoreDirectoriesMatch = ['**/fixtures/**'] + // Set ignoreDirectoriesMatch to exclude fixtures directory + Session.ignoreDirectoriesMatch = ['**/fixtures/**'] - // Bundle the project - const result = await bundlePlayWrightProject(playwrightConfigPath, []) + // Bundle the project + const result = await bundlePlayWrightProject(playwrightConfigPath, []) - // Extract the bundle - await extract({ - file: result.outputFile, - cwd: extractDir, - }) + // Extract the bundle + await extract({ + file: result.outputFile, + cwd: extractDir, + }) - // Check that test files are included - const testsDir = path.join(extractDir, 'tests') - const testFiles = await fs.readdir(testsDir) - expect(testFiles).toContain('example.spec.ts') + // Check that test files are included + const testsDir = path.join(extractDir, 'tests') + const testFiles = await fs.readdir(testsDir) + expect(testFiles).toContain('example.spec.ts') - // Check that fixtures directory is NOT included - const fixturesPath = path.join(extractDir, 'fixtures') - await expect(fs.access(fixturesPath)).rejects.toThrow() + // Check that fixtures directory is NOT included + const fixturesPath = path.join(extractDir, 'fixtures') + await expect(fs.access(fixturesPath)).rejects.toThrow() + }) }, 30000) it('should include all directories when ignoreDirectoriesMatch is empty', async () => { - const fixtureDir = path.join(__dirname, 'fixtures', 'playwright-bundle-test') - const playwrightConfigPath = path.join(fixtureDir, 'playwright.config.ts') - - // Set empty ignoreDirectoriesMatch - Session.ignoreDirectoriesMatch = [] - - // Bundle the project with include pattern that matches fixtures - const result = await bundlePlayWrightProject(playwrightConfigPath, ['fixtures/**/*']) - - // Extract the bundle - await extract({ - file: result.outputFile, - cwd: extractDir, + await usingFixture(async ({ fixtureDir, extractDir }) => { + const playwrightConfigPath = path.join(fixtureDir, 'playwright.config.ts') + + // Set empty ignoreDirectoriesMatch + Session.ignoreDirectoriesMatch = [] + + // Bundle the project with include pattern that matches fixtures + const result = await bundlePlayWrightProject(playwrightConfigPath, ['fixtures/**/*']) + + // Extract the bundle + await extract({ + file: result.outputFile, + cwd: extractDir, + }) + + // Check that fixtures directory IS included when explicitly in include + const fixturesPath = path.join(extractDir, 'fixtures') + const fixturesExists = await fs.access(fixturesPath).then(() => true).catch(() => false) + expect(fixturesExists).toBe(true) + + if (fixturesExists) { + const fixtureFiles = await fs.readdir(fixturesPath) + expect(fixtureFiles).toContain('mock-data.json') + } }) - - // Check that fixtures directory IS included when explicitly in include - const fixturesPath = path.join(extractDir, 'fixtures') - const fixturesExists = await fs.access(fixturesPath).then(() => true).catch(() => false) - expect(fixturesExists).toBe(true) - - if (fixturesExists) { - const fixtureFiles = await fs.readdir(fixturesPath) - expect(fixtureFiles).toContain('mock-data.json') - } }, 30000) it('should include explicit node_modules patterns bypassing default ignores', async () => { - const fixtureDir = path.join(__dirname, 'fixtures', 'playwright-bundle-test') - const playwrightConfigPath = path.join(fixtureDir, 'playwright.config.ts') - - // Set empty ignoreDirectoriesMatch - Session.ignoreDirectoriesMatch = [] - - // Bundle the project with explicit node_modules pattern - const result = await bundlePlayWrightProject(playwrightConfigPath, ['node_modules/@internal/test-helpers/**']) - - // Extract the bundle - await extract({ - file: result.outputFile, - cwd: extractDir, + await usingFixture(async ({ fixtureDir, extractDir }) => { + const playwrightConfigPath = path.join(fixtureDir, 'playwright.config.ts') + + // Set empty ignoreDirectoriesMatch + Session.ignoreDirectoriesMatch = [] + + // Bundle the project with explicit node_modules pattern + const result = await bundlePlayWrightProject(playwrightConfigPath, ['node_modules/@internal/test-helpers/**']) + + // Extract the bundle + await extract({ + file: result.outputFile, + cwd: extractDir, + }) + + // Check that node_modules directory IS included when explicitly specified + const nodeModulesPath = path.join(extractDir, 'node_modules', '@internal', 'test-helpers') + const nodeModulesExists = await fs.access(nodeModulesPath).then(() => true).catch(() => false) + expect(nodeModulesExists).toBe(true) + + if (nodeModulesExists) { + const helperFiles = await fs.readdir(nodeModulesPath) + expect(helperFiles).toContain('helper.js') + } }) - - // Check that node_modules directory IS included when explicitly specified - const nodeModulesPath = path.join(extractDir, 'node_modules', '@internal', 'test-helpers') - const nodeModulesExists = await fs.access(nodeModulesPath).then(() => true).catch(() => false) - expect(nodeModulesExists).toBe(true) - - if (nodeModulesExists) { - const helperFiles = await fs.readdir(nodeModulesPath) - expect(helperFiles).toContain('helper.js') - } }, 30000) it('should still respect custom ignoreDirectoriesMatch for explicit patterns', async () => { - const fixtureDir = path.join(__dirname, 'fixtures', 'playwright-bundle-test') - const playwrightConfigPath = path.join(fixtureDir, 'playwright.config.ts') + await usingFixture(async ({ fixtureDir, extractDir }) => { + const playwrightConfigPath = path.join(fixtureDir, 'playwright.config.ts') - // Set custom ignoreDirectoriesMatch to exclude @internal - Session.ignoreDirectoriesMatch = ['**/@internal/**'] + // Set custom ignoreDirectoriesMatch to exclude @internal + Session.ignoreDirectoriesMatch = ['**/@internal/**'] - // Bundle the project with explicit node_modules pattern - const result = await bundlePlayWrightProject(playwrightConfigPath, ['node_modules/@internal/test-helpers/**']) + // Bundle the project with explicit node_modules pattern + const result = await bundlePlayWrightProject(playwrightConfigPath, ['node_modules/@internal/test-helpers/**']) - // Extract the bundle - await extract({ - file: result.outputFile, - cwd: extractDir, - }) + // Extract the bundle + await extract({ + file: result.outputFile, + cwd: extractDir, + }) - // Check that @internal is NOT included (custom ignore still applies) - const nodeModulesPath = path.join(extractDir, 'node_modules', '@internal') - await expect(fs.access(nodeModulesPath)).rejects.toThrow() + // Check that @internal is NOT included (custom ignore still applies) + const nodeModulesPath = path.join(extractDir, 'node_modules', '@internal') + await expect(fs.access(nodeModulesPath)).rejects.toThrow() + }) }, 30000) it('should exclude node_modules with broad patterns despite include', async () => { - const fixtureDir = path.join(__dirname, 'fixtures', 'playwright-bundle-test') - const playwrightConfigPath = path.join(fixtureDir, 'playwright.config.ts') + await usingFixture(async ({ fixtureDir, extractDir }) => { + const playwrightConfigPath = path.join(fixtureDir, 'playwright.config.ts') - // Set empty ignoreDirectoriesMatch - Session.ignoreDirectoriesMatch = [] + // Set empty ignoreDirectoriesMatch + Session.ignoreDirectoriesMatch = [] - // Bundle with a broad pattern that would match node_modules but doesn't explicitly target it - const result = await bundlePlayWrightProject(playwrightConfigPath, ['**/*.js']) + // Bundle with a broad pattern that would match node_modules but doesn't explicitly target it + const result = await bundlePlayWrightProject(playwrightConfigPath, ['**/*.js']) - // Extract the bundle - await extract({ - file: result.outputFile, - cwd: extractDir, - }) + // Extract the bundle + await extract({ + file: result.outputFile, + cwd: extractDir, + }) - // Check that node_modules is NOT included (default ignore still applies for broad patterns) - const nodeModulesPath = path.join(extractDir, 'node_modules') - await expect(fs.access(nodeModulesPath)).rejects.toThrow() + // Check that node_modules is NOT included (default ignore still applies for broad patterns) + const nodeModulesPath = path.join(extractDir, 'node_modules') + await expect(fs.access(nodeModulesPath)).rejects.toThrow() + }) }, 30000) }) }) diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/package-lock.json b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/package-lock.json new file mode 100644 index 000000000..ed7a7f891 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/package-lock.json @@ -0,0 +1,40 @@ +{ + "name": "workspace-example", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "workspace-example", + "version": "1.0.0", + "workspaces": [ + "packages/*" + ] + }, + "node_modules/bar": { + "resolved": "packages/bar", + "link": true + }, + "node_modules/baz": { + "resolved": "packages/baz", + "link": true + }, + "node_modules/foo": { + "resolved": "packages/foo", + "link": true + }, + "packages/bar": { + "version": "1.0.0", + "dependencies": { + "baz": "*", + "foo": "*" + } + }, + "packages/baz": { + "version": "1.0.0" + }, + "packages/foo": { + "version": "1.0.0" + } + } +} diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/package.json b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/package.json new file mode 100644 index 000000000..6f6c36ca4 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/package.json @@ -0,0 +1,7 @@ +{ + "name": "workspace-example", + "version": "1.0.0", + "workspaces": [ + "packages/*" + ] +} diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/bar/checkly.config.ts b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/bar/checkly.config.ts new file mode 100644 index 000000000..fffb104f3 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/bar/checkly.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'checkly' + +const config = defineConfig({ + projectName: 'Workspace Example Project', + logicalId: '56fbaf4d-fc2c-418c-868a-3f461809ed37', + checks: { + checkMatch: '**/__checks__/**/*.check.ts', + tags: [ + 'mac', + ], + }, +}) + +export default config diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/bar/package.json b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/bar/package.json new file mode 100644 index 000000000..f4d29576a --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/bar/package.json @@ -0,0 +1,12 @@ +{ + "name": "bar", + "version": "1.0.0", + "main": "dist/index.js", + "exports": { + ".": "./dist/index.js" + }, + "dependencies": { + "foo": "*", + "baz": "*" + } +} \ No newline at end of file diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/bar/src/__checks__/api-check-1/api.check.ts b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/bar/src/__checks__/api-check-1/api.check.ts new file mode 100644 index 000000000..23491303b --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/bar/src/__checks__/api-check-1/api.check.ts @@ -0,0 +1,12 @@ +import { ApiCheck } from 'checkly/constructs' + +new ApiCheck('api-check-1', { + name: 'API Check #1', + request: { + url: 'https://api.checklyhq.com', + method: 'GET', + }, + setupScript: { + entrypoint: './setup.ts' + } +}) diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/bar/src/__checks__/api-check-1/setup.ts b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/bar/src/__checks__/api-check-1/setup.ts new file mode 100644 index 000000000..9e9a34d15 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/bar/src/__checks__/api-check-1/setup.ts @@ -0,0 +1,3 @@ +import { value } from '@lib/helper' + +console.log(value) diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/bar/src/index.ts b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/bar/src/index.ts new file mode 100644 index 000000000..5ff6d2ff1 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/bar/src/index.ts @@ -0,0 +1 @@ +export { value } from '@lib/helper' diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/bar/src/lib/helper.ts b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/bar/src/lib/helper.ts new file mode 100644 index 000000000..3e02cd133 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/bar/src/lib/helper.ts @@ -0,0 +1,2 @@ +export { value } from 'foo' +export { value as value2 } from 'baz/lib/dep2' diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/bar/tsconfig.json b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/bar/tsconfig.json new file mode 100644 index 000000000..6d5cf4dfe --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/bar/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "declaration": true, + "module": "nodenext", + "esModuleInterop": true, + "outDir": "dist", + "rootDirs": [ + "src", + ], + "baseUrl": "./src", + "strict": true, + "target": "esnext", + "sourceMap": true, + "paths": { + "@lib/*": [ + "./lib/*" + ] + } + }, + "include": [ + "src/**/*", + ], +} \ No newline at end of file diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/baz/package.json b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/baz/package.json new file mode 100644 index 000000000..cb030520a --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/baz/package.json @@ -0,0 +1,9 @@ +{ + "name": "baz", + "version": "1.0.0", + "main": "dist/index.js", + "exports": { + ".": "./dist/index.js", + "./lib/dep2": "./dist/lib/dep2.js" + } +} \ No newline at end of file diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/baz/src/index.ts b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/baz/src/index.ts new file mode 100644 index 000000000..435db7ccb --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/baz/src/index.ts @@ -0,0 +1 @@ +export { value } from './lib/dep1' diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/baz/src/lib/dep1.ts b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/baz/src/lib/dep1.ts new file mode 100644 index 000000000..1d772ee2c --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/baz/src/lib/dep1.ts @@ -0,0 +1 @@ +export const value = 1 diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/baz/src/lib/dep2.ts b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/baz/src/lib/dep2.ts new file mode 100644 index 000000000..3aee44a1e --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/baz/src/lib/dep2.ts @@ -0,0 +1 @@ +export const value = 2 diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/baz/tsconfig.json b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/baz/tsconfig.json new file mode 100644 index 000000000..8c751e160 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/baz/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "declaration": true, + "module": "nodenext", + "esModuleInterop": true, + "outDir": "dist", + "rootDirs": [ + "src" + ], + "strict": true, + "target": "esnext", + "sourceMap": true + }, + "include": [ + "src/**/*" + ], +} \ No newline at end of file diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/foo/package.json b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/foo/package.json new file mode 100644 index 000000000..81e70e9bb --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/foo/package.json @@ -0,0 +1,8 @@ +{ + "name": "foo", + "version": "1.0.0", + "main": "dist/index.js", + "exports": { + ".": "./dist/index.js" + } +} \ No newline at end of file diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/foo/src/index.ts b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/foo/src/index.ts new file mode 100644 index 000000000..435db7ccb --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/foo/src/index.ts @@ -0,0 +1 @@ +export { value } from './lib/dep1' diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/foo/src/lib/dep1.ts b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/foo/src/lib/dep1.ts new file mode 100644 index 000000000..1d772ee2c --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/foo/src/lib/dep1.ts @@ -0,0 +1 @@ +export const value = 1 diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/foo/tsconfig.json b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/foo/tsconfig.json new file mode 100644 index 000000000..8c751e160 --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/packages/foo/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "declaration": true, + "module": "nodenext", + "esModuleInterop": true, + "outDir": "dist", + "rootDirs": [ + "src" + ], + "strict": true, + "target": "esnext", + "sourceMap": true + }, + "include": [ + "src/**/*" + ], +} \ No newline at end of file diff --git a/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/tsconfig.json b/packages/cli/src/services/check-parser/__tests__/check-parser-fixtures/workspace-example/tsconfig.json new file mode 100644 index 000000000..e69de29bb 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 c331e475d..61b80ae33 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 @@ -3,6 +3,7 @@ import path from 'node:path' import { describe, it, expect } from 'vitest' import { Parser } from '../parser' +import { usingIsolatedFixture } from './helper' const defaultNpmModules = [ 'timers', 'tls', 'url', 'util', 'zlib', '@faker-js/faker', '@opentelemetry/api', '@opentelemetry/sd-trace-base', @@ -12,267 +13,312 @@ const defaultNpmModules = [ describe('dependency-parser - parser()', () => { it('should handle JS file with no dependencies', async () => { - const parser = new Parser({ - supportedNpmModules: defaultNpmModules, + await usingIsolatedFixture(path.join(__dirname, 'check-parser-fixtures'), async dir => { + const parser = new Parser({ + supportedNpmModules: defaultNpmModules, + }) + const { dependencies } = await parser.parse(path.join(dir, 'no-dependencies.js')) + expect(dependencies.map(d => d.filePath)).toHaveLength(0) }) - const { dependencies } = await parser.parse(path.join(__dirname, 'check-parser-fixtures', 'no-dependencies.js')) - expect(dependencies.map(d => d.filePath)).toHaveLength(0) }) it('should handle JS file with dependencies', async () => { - const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', 'simple-example', ...filepath) - const parser = new Parser({ - supportedNpmModules: defaultNpmModules, + await usingIsolatedFixture(path.join(__dirname, 'check-parser-fixtures', 'simple-example'), async dir => { + const toAbsolutePath = (...filepath: string[]) => path.join(dir, ...filepath) + const parser = new Parser({ + supportedNpmModules: defaultNpmModules, + }) + const { dependencies } = await parser.parse(toAbsolutePath('entrypoint.js')) + expect(dependencies.map(d => d.filePath).sort()).toEqual([ + toAbsolutePath('dep1.js'), + toAbsolutePath('dep2.js'), + toAbsolutePath('dep3.js'), + toAbsolutePath('module-package', 'main.js'), + toAbsolutePath('module-package', 'package.json'), + toAbsolutePath('module', 'index.js'), + ]) }) - const { dependencies } = await parser.parse(toAbsolutePath('entrypoint.js')) - expect(dependencies.map(d => d.filePath).sort()).toEqual([ - toAbsolutePath('dep1.js'), - toAbsolutePath('dep2.js'), - toAbsolutePath('dep3.js'), - toAbsolutePath('module-package', 'main.js'), - toAbsolutePath('module-package', 'package.json'), - toAbsolutePath('module', 'index.js'), - ]) }) it('should report a missing entrypoint file', async () => { - const missingEntrypoint = path.join(__dirname, 'check-parser-fixtures', 'does-not-exist.js') - expect.assertions(1) - try { - const parser = new Parser({ - supportedNpmModules: defaultNpmModules, - }) - await parser.parse(missingEntrypoint) - } catch (err) { - expect(err).toMatchObject({ missingFiles: [missingEntrypoint] }) - } + await usingIsolatedFixture(path.join(__dirname, 'check-parser-fixtures'), async dir => { + const missingEntrypoint = path.join(dir, 'does-not-exist.js') + expect.assertions(1) + try { + const parser = new Parser({ + supportedNpmModules: defaultNpmModules, + }) + await parser.parse(missingEntrypoint) + } catch (err) { + expect(err).toMatchObject({ missingFiles: [missingEntrypoint] }) + } + }) }) it('should report missing check dependencies', async () => { - const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', ...filepath) - expect.assertions(1) - try { - const parser = new Parser({ - supportedNpmModules: defaultNpmModules, - }) - await parser.parse(toAbsolutePath('missing-dependencies.js')) - } catch (err) { - expect(err).toMatchObject({ missingFiles: [toAbsolutePath('does-not-exist.js'), toAbsolutePath('does-not-exist2.js')] }) - } + await usingIsolatedFixture(path.join(__dirname, 'check-parser-fixtures'), async dir => { + const toAbsolutePath = (...filepath: string[]) => path.join(dir, ...filepath) + expect.assertions(1) + try { + const parser = new Parser({ + supportedNpmModules: defaultNpmModules, + }) + await parser.parse(toAbsolutePath('missing-dependencies.js')) + } catch (err) { + expect(err).toMatchObject({ missingFiles: [toAbsolutePath('does-not-exist.js'), toAbsolutePath('does-not-exist2.js')] }) + } + }) }) it('should report syntax errors', async () => { - const entrypoint = path.join(__dirname, 'check-parser-fixtures', 'syntax-error.js') - expect.assertions(1) - try { - const parser = new Parser({ - supportedNpmModules: defaultNpmModules, - }) - await parser.parse(entrypoint) - } catch (err) { - expect(err).toMatchObject({ parseErrors: [{ file: entrypoint, error: 'Unexpected token (4:70)' }] }) - } + await usingIsolatedFixture(path.join(__dirname, 'check-parser-fixtures'), async dir => { + const entrypoint = path.join(dir, 'syntax-error.js') + expect.assertions(1) + try { + const parser = new Parser({ + supportedNpmModules: defaultNpmModules, + }) + await parser.parse(entrypoint) + } catch (err) { + expect(err).toMatchObject({ parseErrors: [{ file: entrypoint, error: 'Unexpected token (4:70)' }] }) + } + }) }) it('should report unsupported dependencies', async () => { - const entrypoint = path.join(__dirname, 'check-parser-fixtures', 'unsupported-dependencies.js') - expect.assertions(1) - try { + await usingIsolatedFixture(path.join(__dirname, 'check-parser-fixtures'), async dir => { + const entrypoint = path.join(dir, 'unsupported-dependencies.js') + expect.assertions(1) + try { + const parser = new Parser({ + supportedNpmModules: defaultNpmModules, + }) + await parser.parse(entrypoint) + } catch (err) { + expect(err).toMatchObject({ unsupportedNpmDependencies: [{ file: entrypoint, unsupportedDependencies: ['left-pad', 'right-pad'] }] }) + } + }) + }) + + it('should allow unsupported dependencies if configured to do so', async () => { + await usingIsolatedFixture(path.join(__dirname, 'check-parser-fixtures'), async dir => { + const entrypoint = path.join(dir, 'unsupported-dependencies.js') const parser = new Parser({ supportedNpmModules: defaultNpmModules, + checkUnsupportedModules: false, }) await parser.parse(entrypoint) - } catch (err) { - expect(err).toMatchObject({ unsupportedNpmDependencies: [{ file: entrypoint, unsupportedDependencies: ['left-pad', 'right-pad'] }] }) - } - }) - - it('should allow unsupported dependencies if configured to do so', async () => { - const entrypoint = path.join(__dirname, 'check-parser-fixtures', 'unsupported-dependencies.js') - const parser = new Parser({ - supportedNpmModules: defaultNpmModules, - checkUnsupportedModules: false, }) - await parser.parse(entrypoint) }) it('should handle circular dependencies', async () => { - const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', 'circular-dependencies', ...filepath) - const parser = new Parser({ - supportedNpmModules: defaultNpmModules, - }) - const { dependencies } = await parser.parse(toAbsolutePath('entrypoint.js')) + await usingIsolatedFixture(path.join(__dirname, 'check-parser-fixtures', 'circular-dependencies'), async dir => { + const toAbsolutePath = (...filepath: string[]) => path.join(dir, ...filepath) + const parser = new Parser({ + supportedNpmModules: defaultNpmModules, + }) + const { dependencies } = await parser.parse(toAbsolutePath('entrypoint.js')) - // Circular dependencies are allowed in Node.js - // We just need to test that parsing the dependencies doesn't loop indefinitely - // https://nodejs.org/api/modules.html#modules_cycles - expect(dependencies.map(d => d.filePath).sort()).toEqual([ - toAbsolutePath('dep1.js'), - toAbsolutePath('dep2.js'), - ]) + // Circular dependencies are allowed in Node.js + // We just need to test that parsing the dependencies doesn't loop indefinitely + // https://nodejs.org/api/modules.html#modules_cycles + expect(dependencies.map(d => d.filePath).sort()).toEqual([ + toAbsolutePath('dep1.js'), + toAbsolutePath('dep2.js'), + ]) + }) }) it('should parse typescript dependencies', async () => { - const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', 'typescript-example', ...filepath) - const parser = new Parser({ - supportedNpmModules: defaultNpmModules, + await usingIsolatedFixture(path.join(__dirname, 'check-parser-fixtures', 'typescript-example'), async dir => { + const toAbsolutePath = (...filepath: string[]) => path.join(dir, ...filepath) + const parser = new Parser({ + supportedNpmModules: defaultNpmModules, + }) + const { dependencies } = await parser.parse(toAbsolutePath('entrypoint.ts')) + expect(dependencies.map(d => d.filePath).sort()).toEqual([ + toAbsolutePath('dep1.ts'), + toAbsolutePath('dep2.ts'), + toAbsolutePath('dep3.ts'), + toAbsolutePath('dep4.js'), + toAbsolutePath('dep5.ts'), + toAbsolutePath('dep6.ts'), + toAbsolutePath('module-package', 'main.js'), + toAbsolutePath('module-package', 'package.json'), + toAbsolutePath('module', 'index.ts'), + toAbsolutePath('pages/external.first.page.js'), + toAbsolutePath('pages/external.second.page.ts'), + toAbsolutePath('type.ts'), + ]) }) - const { dependencies } = await parser.parse(toAbsolutePath('entrypoint.ts')) - expect(dependencies.map(d => d.filePath).sort()).toEqual([ - toAbsolutePath('dep1.ts'), - toAbsolutePath('dep2.ts'), - toAbsolutePath('dep3.ts'), - toAbsolutePath('dep4.js'), - toAbsolutePath('dep5.ts'), - toAbsolutePath('dep6.ts'), - toAbsolutePath('module-package', 'main.js'), - toAbsolutePath('module-package', 'package.json'), - toAbsolutePath('module', 'index.ts'), - toAbsolutePath('pages/external.first.page.js'), - toAbsolutePath('pages/external.second.page.ts'), - toAbsolutePath('type.ts'), - ]) }) it('should parse typescript dependencies relying on tsconfig when tsconfig has comments', async () => { - const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', 'tsconfig-json-text', ...filepath) - const parser = new Parser({ - supportedNpmModules: defaultNpmModules, + await usingIsolatedFixture(path.join(__dirname, 'check-parser-fixtures', 'tsconfig-json-text'), async dir => { + const toAbsolutePath = (...filepath: string[]) => path.join(dir, ...filepath) + const parser = new Parser({ + supportedNpmModules: defaultNpmModules, + }) + const { dependencies } = await parser.parse(toAbsolutePath('src', 'entrypoint.ts')) + expect(dependencies.map(d => d.filePath).sort()).toEqual([ + toAbsolutePath('lib', 'foo1.ts'), + toAbsolutePath('tsconfig.json'), + ]) }) - const { dependencies } = await parser.parse(toAbsolutePath('src', 'entrypoint.ts')) - expect(dependencies.map(d => d.filePath).sort()).toEqual([ - toAbsolutePath('lib', 'foo1.ts'), - toAbsolutePath('tsconfig.json'), - ]) }) it('should parse typescript dependencies using tsconfig paths', async () => { - const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', 'tsconfig-paths-sample-project', ...filepath) - const parser = new Parser({ - supportedNpmModules: defaultNpmModules, + await usingIsolatedFixture(path.join(__dirname, 'check-parser-fixtures', 'tsconfig-paths-sample-project'), async dir => { + const toAbsolutePath = (...filepath: string[]) => path.join(dir, ...filepath) + const parser = new Parser({ + supportedNpmModules: defaultNpmModules, + }) + const { dependencies } = await parser.parse(toAbsolutePath('src', 'entrypoint.ts')) + expect(dependencies.map(d => d.filePath).sort()).toEqual([ + toAbsolutePath('lib1', 'file1.ts'), + toAbsolutePath('lib1', 'file2.ts'), + toAbsolutePath('lib1', 'folder', 'file1.ts'), + toAbsolutePath('lib1', 'folder', 'file2.ts'), + toAbsolutePath('lib1', 'index.ts'), + toAbsolutePath('lib1', 'package.json'), + toAbsolutePath('lib1', 'tsconfig.json'), + toAbsolutePath('lib2', 'index.ts'), + toAbsolutePath('lib3', 'foo', 'bar.ts'), + toAbsolutePath('lib3', 'jsconfig.json'), + toAbsolutePath('package.json'), + toAbsolutePath('tsconfig.json'), + ]) }) - const { dependencies } = await parser.parse(toAbsolutePath('src', 'entrypoint.ts')) - expect(dependencies.map(d => d.filePath).sort()).toEqual([ - toAbsolutePath('lib1', 'file1.ts'), - toAbsolutePath('lib1', 'file2.ts'), - toAbsolutePath('lib1', 'folder', 'file1.ts'), - toAbsolutePath('lib1', 'folder', 'file2.ts'), - toAbsolutePath('lib1', 'index.ts'), - toAbsolutePath('lib1', 'package.json'), - toAbsolutePath('lib1', 'tsconfig.json'), - toAbsolutePath('lib2', 'index.ts'), - toAbsolutePath('lib3', 'foo', 'bar.ts'), - toAbsolutePath('tsconfig.json'), - ]) }) it('should parse typescript dependencies using tsconfig paths relative to baseUrl', async () => { - const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', 'tsconfig-paths-baseurl-relative', ...filepath) - const parser = new Parser({ - supportedNpmModules: defaultNpmModules, + await usingIsolatedFixture(path.join(__dirname, 'check-parser-fixtures', 'tsconfig-paths-baseurl-relative'), async dir => { + const toAbsolutePath = (...filepath: string[]) => path.join(dir, ...filepath) + const parser = new Parser({ + supportedNpmModules: defaultNpmModules, + }) + const { dependencies } = await parser.parse(toAbsolutePath('src', 'entrypoint.ts')) + expect(dependencies.map(d => d.filePath).sort()).toEqual([ + toAbsolutePath('package.json'), + toAbsolutePath('src', 'lib1', 'file1.ts'), + toAbsolutePath('src', 'lib1', 'file2.ts'), + toAbsolutePath('src', 'lib1', 'folder', 'file1.ts'), + toAbsolutePath('src', 'lib1', 'folder', 'file2.ts'), + toAbsolutePath('src', 'lib1', 'index.ts'), + toAbsolutePath('src', 'lib1', 'package.json'), + toAbsolutePath('src', 'lib1', 'tsconfig.json'), + toAbsolutePath('src', 'lib2', 'index.ts'), + toAbsolutePath('src', 'lib3', 'foo', 'bar.ts'), + toAbsolutePath('src', 'lib3', 'jsconfig.json'), + toAbsolutePath('tsconfig.json'), + ]) }) - const { dependencies } = await parser.parse(toAbsolutePath('src', 'entrypoint.ts')) - expect(dependencies.map(d => d.filePath).sort()).toEqual([ - toAbsolutePath('src', 'lib1', 'file1.ts'), - toAbsolutePath('src', 'lib1', 'file2.ts'), - toAbsolutePath('src', 'lib1', 'folder', 'file1.ts'), - toAbsolutePath('src', 'lib1', 'folder', 'file2.ts'), - toAbsolutePath('src', 'lib1', 'index.ts'), - toAbsolutePath('src', 'lib1', 'package.json'), - toAbsolutePath('src', 'lib1', 'tsconfig.json'), - toAbsolutePath('src', 'lib2', 'index.ts'), - toAbsolutePath('src', 'lib3', 'foo', 'bar.ts'), - toAbsolutePath('tsconfig.json'), - ]) }) - it('should not include tsconfig if not needed', async () => { - const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', 'tsconfig-paths-unused', ...filepath) - const parser = new Parser({ - supportedNpmModules: defaultNpmModules, + it('should always include tsconfig even if not needed', async () => { + await usingIsolatedFixture(path.join(__dirname, 'check-parser-fixtures', 'tsconfig-paths-unused'), async dir => { + const toAbsolutePath = (...filepath: string[]) => path.join(dir, ...filepath) + const parser = new Parser({ + supportedNpmModules: defaultNpmModules, + }) + const { dependencies } = await parser.parse(toAbsolutePath('src', 'entrypoint.ts')) + expect(dependencies.map(d => d.filePath).sort()).toEqual([ + toAbsolutePath('tsconfig.json'), + ]) }) - const { dependencies } = await parser.parse(toAbsolutePath('src', 'entrypoint.ts')) - expect(dependencies.map(d => d.filePath).sort()).toEqual([]) }) it('should support importing ts extensions if allowed', async () => { - const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', 'tsconfig-allow-importing-ts-extensions', ...filepath) - const parser = new Parser({ - supportedNpmModules: defaultNpmModules, + await usingIsolatedFixture(path.join(__dirname, 'check-parser-fixtures', 'tsconfig-allow-importing-ts-extensions'), async dir => { + const toAbsolutePath = (...filepath: string[]) => path.join(dir, ...filepath) + const parser = new Parser({ + supportedNpmModules: defaultNpmModules, + }) + const { dependencies } = await parser.parse(toAbsolutePath('src', 'entrypoint.ts')) + expect(dependencies.map(d => d.filePath).sort()).toEqual([ + toAbsolutePath('src', 'dep1.ts'), + toAbsolutePath('src', 'dep2.ts'), + toAbsolutePath('src', 'dep3.ts'), + toAbsolutePath('tsconfig.json'), + ]) }) - const { dependencies } = await parser.parse(toAbsolutePath('src', 'entrypoint.ts')) - expect(dependencies.map(d => d.filePath).sort()).toEqual([ - toAbsolutePath('src', 'dep1.ts'), - toAbsolutePath('src', 'dep2.ts'), - toAbsolutePath('src', 'dep3.ts'), - ]) }) it('should not import TS files from a JS file', async () => { - const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', 'no-import-ts-from-js', ...filepath) - const parser = new Parser({ - supportedNpmModules: defaultNpmModules, - }) - expect.assertions(1) - try { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { dependencies } = await parser.parse(toAbsolutePath('entrypoint.js')) - } catch (err) { - expect(err).toMatchObject({ - missingFiles: [ - toAbsolutePath('dep1'), - toAbsolutePath('dep1.ts'), - toAbsolutePath('dep1.js'), - ], + await usingIsolatedFixture(path.join(__dirname, 'check-parser-fixtures', 'no-import-ts-from-js'), async dir => { + const toAbsolutePath = (...filepath: string[]) => path.join(dir, ...filepath) + const parser = new Parser({ + supportedNpmModules: defaultNpmModules, }) - } + expect.assertions(1) + try { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { dependencies } = await parser.parse(toAbsolutePath('entrypoint.js')) + } catch (err) { + expect(err).toMatchObject({ + missingFiles: [ + toAbsolutePath('dep1'), + toAbsolutePath('dep1.ts'), + toAbsolutePath('dep1.js'), + ], + }) + } + }) }) it('should import JS files from a TS file', async () => { - const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', 'import-js-from-ts', ...filepath) - const parser = new Parser({ - supportedNpmModules: defaultNpmModules, + await usingIsolatedFixture(path.join(__dirname, 'check-parser-fixtures', 'import-js-from-ts'), async dir => { + const toAbsolutePath = (...filepath: string[]) => path.join(dir, ...filepath) + const parser = new Parser({ + supportedNpmModules: defaultNpmModules, + }) + const { dependencies } = await parser.parse(toAbsolutePath('entrypoint.ts')) + expect(dependencies.map(d => d.filePath).sort()).toEqual([ + toAbsolutePath('dep1.js'), + toAbsolutePath('dep2.js'), + toAbsolutePath('dep3.ts'), + ]) }) - const { dependencies } = await parser.parse(toAbsolutePath('entrypoint.ts')) - expect(dependencies.map(d => d.filePath).sort()).toEqual([ - toAbsolutePath('dep1.js'), - toAbsolutePath('dep2.js'), - toAbsolutePath('dep3.ts'), - ]) }) it('should handle ES Modules', async () => { - const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', 'esmodules-example', ...filepath) - const parser = new Parser({ - supportedNpmModules: defaultNpmModules, + await usingIsolatedFixture(path.join(__dirname, 'check-parser-fixtures', 'esmodules-example'), async dir => { + const toAbsolutePath = (...filepath: string[]) => path.join(dir, ...filepath) + const parser = new Parser({ + supportedNpmModules: defaultNpmModules, + }) + const { dependencies } = await parser.parse(toAbsolutePath('entrypoint.js')) + expect(dependencies.map(d => d.filePath).sort()).toEqual([ + toAbsolutePath('dep1.js'), + toAbsolutePath('dep2.js'), + toAbsolutePath('dep3.js'), + toAbsolutePath('dep5.js'), + toAbsolutePath('dep6.js'), + ]) }) - const { dependencies } = await parser.parse(toAbsolutePath('entrypoint.js')) - expect(dependencies.map(d => d.filePath).sort()).toEqual([ - toAbsolutePath('dep1.js'), - toAbsolutePath('dep2.js'), - toAbsolutePath('dep3.js'), - toAbsolutePath('dep5.js'), - toAbsolutePath('dep6.js'), - ]) }) it('should handle Common JS and ES Modules', async () => { - const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', 'common-esm-example', ...filepath) - const parser = new Parser({ - supportedNpmModules: defaultNpmModules, + await usingIsolatedFixture(path.join(__dirname, 'check-parser-fixtures', 'common-esm-example'), async dir => { + const toAbsolutePath = (...filepath: string[]) => path.join(dir, ...filepath) + const parser = new Parser({ + supportedNpmModules: defaultNpmModules, + }) + const { dependencies } = await parser.parse(toAbsolutePath('entrypoint.mjs')) + expect(dependencies.map(d => d.filePath).sort()).toEqual([ + toAbsolutePath('dep1.js'), + toAbsolutePath('dep2.mjs'), + toAbsolutePath('dep3.mjs'), + toAbsolutePath('dep4.mjs'), + toAbsolutePath('dep5.mjs'), + toAbsolutePath('dep6.mjs'), + ]) }) - const { dependencies } = await parser.parse(toAbsolutePath('entrypoint.mjs')) - expect(dependencies.map(d => d.filePath).sort()).toEqual([ - toAbsolutePath('dep1.js'), - toAbsolutePath('dep2.mjs'), - toAbsolutePath('dep3.mjs'), - toAbsolutePath('dep4.mjs'), - toAbsolutePath('dep5.mjs'), - toAbsolutePath('dep6.mjs'), - ]) }) it('should handle node: prefix for built-ins', async () => { + await usingIsolatedFixture(path.join(__dirname, 'check-parser-fixtures'), async dir => { + }) const toAbsolutePath = (...filepath: string[]) => path.join(__dirname, 'check-parser-fixtures', 'builtin-with-node-prefix', ...filepath) const parser = new Parser({ supportedNpmModules: defaultNpmModules, @@ -286,28 +332,34 @@ describe('dependency-parser - parser()', () => { * We could address this by keeping track of assignments as we walk the AST. */ it.skip('should ignore cases where require is reassigned', async () => { - const entrypoint = path.join(__dirname, 'check-parser-fixtures', 'reassign-require.js') - const parser = new Parser({ - supportedNpmModules: defaultNpmModules, + await usingIsolatedFixture(path.join(__dirname, 'check-parser-fixtures'), async dir => { + const entrypoint = path.join(dir, 'reassign-require.js') + const parser = new Parser({ + supportedNpmModules: defaultNpmModules, + }) + await parser.parse(entrypoint) }) - await parser.parse(entrypoint) }) // Checks run on Checkly are wrapped to support top level await. // For consistency with checks created via the UI, the CLI should support this as well. it('should allow top-level await', async () => { - const entrypoint = path.join(__dirname, 'check-parser-fixtures', 'top-level-await.js') - const parser = new Parser({ - supportedNpmModules: defaultNpmModules, + await usingIsolatedFixture(path.join(__dirname, 'check-parser-fixtures'), async dir => { + const entrypoint = path.join(dir, 'top-level-await.js') + const parser = new Parser({ + supportedNpmModules: defaultNpmModules, + }) + await parser.parse(entrypoint) }) - await parser.parse(entrypoint) }) it('should allow top-level await in TypeScript', async () => { - const entrypoint = path.join(__dirname, 'check-parser-fixtures', 'top-level-await.ts') - const parser = new Parser({ - supportedNpmModules: defaultNpmModules, + await usingIsolatedFixture(path.join(__dirname, 'check-parser-fixtures'), async dir => { + const entrypoint = path.join(dir, 'top-level-await.ts') + const parser = new Parser({ + supportedNpmModules: defaultNpmModules, + }) + await parser.parse(entrypoint) }) - await parser.parse(entrypoint) }) }) diff --git a/packages/cli/src/services/check-parser/__tests__/helper.ts b/packages/cli/src/services/check-parser/__tests__/helper.ts new file mode 100644 index 000000000..c13d05aca --- /dev/null +++ b/packages/cli/src/services/check-parser/__tests__/helper.ts @@ -0,0 +1,19 @@ +import path from 'node:path' +import fs from 'node:fs/promises' +import { tmpdir } from 'node:os' + +export async function usingIsolatedFixture (fixturePath: string, handle: (dir: string) => Promise) { + const tempPath = await fs.mkdtemp(path.join(tmpdir(), 'check-parser-')) + try { + await fs.cp(fixturePath, tempPath, { + recursive: true, + }) + + await handle(tempPath) + } finally { + await fs.rm(tempPath, { + recursive: true, + force: true, + }) + } +} diff --git a/packages/cli/src/services/check-parser/__tests__/parse-files.spec.ts b/packages/cli/src/services/check-parser/__tests__/parse-files.spec.ts index ecbb0f17f..a232125fe 100644 --- a/packages/cli/src/services/check-parser/__tests__/parse-files.spec.ts +++ b/packages/cli/src/services/check-parser/__tests__/parse-files.spec.ts @@ -5,31 +5,36 @@ import { describe, it, expect } from 'vitest' import { Parser } from '../parser' import { PlaywrightConfig } from '../../playwright-config' import { Session } from '../../../constructs' +import { usingIsolatedFixture } from './helper' const fixturePath = path.join(__dirname, 'check-parser-fixtures') describe('project parser - getFilesAndDependencies()', () => { it('should handle spec file', async () => { - const projectPath = path.join(fixturePath, 'playwright-project') + await usingIsolatedFixture(fixturePath, async dir => { + const projectPath = path.join(dir, 'playwright-project') - const playwrightConfig = new PlaywrightConfig( - path.join(projectPath, 'playwright.config.ts'), - await Session.loadFile(path.join(projectPath, 'playwright.config.ts')), - ) - const parser = new Parser({}) - const res = await parser.getFilesAndDependencies(playwrightConfig) - expect(res.files).toHaveLength(2) - expect(res.errors).toHaveLength(0) + const playwrightConfig = new PlaywrightConfig( + path.join(projectPath, 'playwright.config.ts'), + await Session.loadFile(path.join(projectPath, 'playwright.config.ts')), + ) + const parser = new Parser({}) + const res = await parser.getFilesAndDependencies(playwrightConfig) + expect(res.files).toHaveLength(2) + expect(res.errors).toHaveLength(0) + }) }) it('should handle a spec file with snapshots', async () => { - const projectPath = path.join(fixturePath, 'playwright-project-snapshots') - const playwrightConfig = new PlaywrightConfig( - path.join(projectPath, 'playwright.config.ts'), - await Session.loadFile(path.join(projectPath, 'playwright.config.ts')), - ) - const parser = new Parser({}) - const res = await parser.getFilesAndDependencies(playwrightConfig) - expect(res.files).toHaveLength(3) - expect(res.errors).toHaveLength(0) + await usingIsolatedFixture(fixturePath, async dir => { + const projectPath = path.join(dir, 'playwright-project-snapshots') + const playwrightConfig = new PlaywrightConfig( + path.join(projectPath, 'playwright.config.ts'), + await Session.loadFile(path.join(projectPath, 'playwright.config.ts')), + ) + const parser = new Parser({}) + const res = await parser.getFilesAndDependencies(playwrightConfig) + expect(res.files).toHaveLength(3) + expect(res.errors).toHaveLength(0) + }) }) }) diff --git a/packages/cli/src/services/check-parser/package-files/extension.ts b/packages/cli/src/services/check-parser/package-files/extension.ts index 6f28c9a8c..d1937d9ef 100644 --- a/packages/cli/src/services/check-parser/package-files/extension.ts +++ b/packages/cli/src/services/check-parser/package-files/extension.ts @@ -4,6 +4,10 @@ export type CoreExtension = '.js' | '.mjs' | '.cjs' | '.json' export const CoreExtensions: CoreExtension[] = ['.js', '.mjs', '.cjs', '.json'] +export function isCoreExtension (value: string): value is CoreExtension { + return CoreExtensions.includes(value as CoreExtension) +} + type CoreExtensionMapping = { [key in CoreExtension]: string[] } @@ -26,9 +30,13 @@ export const tsCoreExtensionLookupOrder: CoreExtensionMapping = { '.json': ['.json'], } -export type TSExtension = '.ts' | '.mts' | '.tsx' +export type TSExtension = '.ts' | '.mts' | '.cts' | '.tsx' -export const TSExtensions: TSExtension[] = ['.ts', '.mts', '.tsx'] +export const TSExtensions: TSExtension[] = ['.ts', '.mts', '.cts', '.tsx'] + +export function isTSExtension (value: string): value is TSExtension { + return TSExtensions.includes(value as TSExtension) +} type TSExtensionMapping = { [key in TSExtension]: string[] @@ -37,6 +45,7 @@ type TSExtensionMapping = { export const tsExtensionLookupOrder: TSExtensionMapping = { '.ts': ['.ts'], '.mts': ['.mts'], + '.cts': ['.cts'], '.tsx': ['.tsx'], } diff --git a/packages/cli/src/services/check-parser/package-files/json-source-file.ts b/packages/cli/src/services/check-parser/package-files/json-source-file.ts index 521da653d..0b274c586 100644 --- a/packages/cli/src/services/check-parser/package-files/json-source-file.ts +++ b/packages/cli/src/services/check-parser/package-files/json-source-file.ts @@ -26,4 +26,13 @@ export class JsonSourceFile { // Ignore. } } + + static async loadFromFilePath (filePath: string): Promise | undefined> { + const sourceFile = await SourceFile.loadFromFilePath(filePath) + if (!sourceFile) { + return + } + + return JsonSourceFile.loadFromSourceFile(sourceFile) + } } diff --git a/packages/cli/src/services/check-parser/package-files/json-text-source-file-parser.ts b/packages/cli/src/services/check-parser/package-files/json-text-source-file-parser.ts index 08c0858f3..3d357c84b 100644 --- a/packages/cli/src/services/check-parser/package-files/json-text-source-file-parser.ts +++ b/packages/cli/src/services/check-parser/package-files/json-text-source-file-parser.ts @@ -6,7 +6,8 @@ class UninitializedJsonTextSourceFileParserState extends SourceFileParser { async #parser (): Promise { try { - const { parseJsonText, convertToObject } = await import('typescript') + const typescriptExports = await import('typescript') + const { parseJsonText, convertToObject } = typescriptExports.default const parser = new SourceFileParserFuncState((sourceFile: SourceFile) => { const errors: any[] = [] diff --git a/packages/cli/src/services/check-parser/package-files/package-json-file.ts b/packages/cli/src/services/check-parser/package-files/package-json-file.ts index e2fd55e3f..92c052244 100644 --- a/packages/cli/src/services/check-parser/package-files/package-json-file.ts +++ b/packages/cli/src/services/check-parser/package-files/package-json-file.ts @@ -4,9 +4,24 @@ import semver from 'semver' import { JsonSourceFile } from './json-source-file' import { FileMeta, SourceFile } from './source-file' +import { PathResolver, ResolveResult } from './paths' -type ExportCondition = - 'node-addons' | 'node' | 'import' | 'require' | 'module-sync' | 'default' +type ConditionKey = + | 'node-addons' + | 'node' + | 'import' + | 'require' + | 'module-sync' + | 'default' + // Allow any string value, but keep auto complete for known values. + | (string & Record) + +type Exports = + | string + | Record> + +type Imports = + | Record> type Schema = { name?: string @@ -14,10 +29,12 @@ type Schema = { license?: string main?: string engines?: Record - exports?: string | string[] | Record | Record> + exports?: Exports + imports?: Imports dependencies?: Record devDependencies?: Record private?: boolean + workspaces?: string[] } export interface EngineSupportResult { @@ -47,10 +64,66 @@ export class PackageJsonFile { : [fallbackMainPath] } + hasExports (): boolean { + return !!this.jsonFile.data.exports + } + + resolveExportPath (exportPath: string, conditions: ConditionKey[]): ResolveResult { + const resolver = PathResolver.createFromPaths( + this.basePath, + this.#resolveExports(this.jsonFile.data.exports ?? {}, conditions), + ) + + // Exports must always start with "./" - make sure that the path we're + // matching against also starts with that prefix. + if (!exportPath.startsWith('./')) { + exportPath = `./${exportPath}` + } + + return resolver.resolve(exportPath) + } + + #resolveExports (exports: Exports, conditions: ConditionKey[]): Record { + if (typeof exports === 'string') { + return { + '.': [exports], + } + } + + const resolved: Record = {} + + Resolve: + for (const [from, rules] of Object.entries(exports)) { + if (typeof rules === 'string') { + resolved[from] = [rules] + continue Resolve + } + + for (const [condition, to] of Object.entries(rules)) { + if (conditions.includes(condition)) { + resolved[from] = [to] + continue Resolve + } + } + + const fallback = rules['default'] + if (fallback) { + resolved[from] = [fallback] + continue Resolve + } + } + + return resolved + } + public get meta () { return this.jsonFile.meta } + public get name () { + return this.jsonFile.data.name + } + public get version () { return this.jsonFile.data.version } @@ -67,6 +140,10 @@ export class PackageJsonFile { return this.jsonFile.data.engines } + public get workspaces () { + return this.jsonFile.data.workspaces + } + supportsEngine (engine: string, version: string): EngineSupportResult { const requirements = this.engines?.[engine] if (requirements === undefined) { @@ -105,6 +182,24 @@ export class PackageJsonFile { return new PackageJsonFile(jsonFile) } + static async loadFromSourceFile (sourceFile: SourceFile): Promise { + const jsonSourceFile = await JsonSourceFile.loadFromSourceFile(sourceFile) + if (!jsonSourceFile) { + return + } + + return PackageJsonFile.loadFromJsonSourceFile(jsonSourceFile) + } + + static async loadFromFilePath (filePath: string): Promise { + const sourceFile = await SourceFile.loadFromFilePath(filePath) + if (!sourceFile) { + return + } + + return PackageJsonFile.loadFromSourceFile(sourceFile) + } + static filePath (dirPath: string) { return path.join(dirPath, PackageJsonFile.FILENAME) } diff --git a/packages/cli/src/services/check-parser/package-files/package-manager.ts b/packages/cli/src/services/check-parser/package-files/package-manager.ts index 89a697191..cd9b27ea1 100644 --- a/packages/cli/src/services/check-parser/package-files/package-manager.ts +++ b/packages/cli/src/services/check-parser/package-files/package-manager.ts @@ -2,6 +2,10 @@ import fs from 'node:fs/promises' import path from 'node:path' import { lineage } from './walk' +import { PackageJsonFile } from './package-json-file' +import { JsonSourceFile } from './json-source-file' +import { OptionalWorkspaceFile, Package, Workspace, WorkspaceOptions } from './workspace' +import { Err, Ok } from './result' export class Runnable { executable: string @@ -36,8 +40,12 @@ function unsafeQuoteArg (arg: string) { export interface PackageManager { get name (): string + get representativeLockfile (): string | undefined + get representativeConfigFile (): string | undefined installCommand (): Runnable execCommand (args: string[]): Runnable + lookupWorkspace (dir: string): Promise + detector (): PackageManagerDetector } class NotDetectedError extends Error {} @@ -47,10 +55,16 @@ export abstract class PackageManagerDetector { abstract detectUserAgent (userAgent: string): boolean abstract detectRuntime (): boolean abstract get representativeLockfile (): string | undefined + abstract get representativeConfigFile (): string | undefined abstract detectLockfile (dir: string): Promise + abstract detectConfigFile (dir: string): Promise abstract detectExecutable (lookup: PathLookup): Promise abstract installCommand (): Runnable abstract execCommand (args: string[]): Runnable + abstract lookupWorkspace (dir: string): Promise + detector (): PackageManagerDetector { + return this + } } export class NpmDetector extends PackageManagerDetector implements PackageManager { @@ -70,10 +84,19 @@ export class NpmDetector extends PackageManagerDetector implements PackageManage return 'package-lock.json' } + get representativeConfigFile (): undefined { + return + } + async detectLockfile (dir: string): Promise { return await accessR(path.join(dir, this.representativeLockfile)) } + // eslint-disable-next-line require-await, @typescript-eslint/no-unused-vars + async detectConfigFile (dir: string): Promise { + throw new NotDetectedError() + } + async detectExecutable (lookup: PathLookup): Promise { await lookup.detectPresence('npm') } @@ -85,6 +108,10 @@ export class NpmDetector extends PackageManagerDetector implements PackageManage execCommand (args: string[]): Runnable { return new Runnable('npx', args) } + + async lookupWorkspace (dir: string): Promise { + return await lookupNearestPackageJsonWorkspace(this, dir) + } } export class CNpmDetector extends PackageManagerDetector implements PackageManager { @@ -104,11 +131,20 @@ export class CNpmDetector extends PackageManagerDetector implements PackageManag return } + get representativeConfigFile (): undefined { + return + } + // eslint-disable-next-line require-await async detectLockfile (): Promise { throw new NotDetectedError() } + // eslint-disable-next-line require-await, @typescript-eslint/no-unused-vars + async detectConfigFile (dir: string): Promise { + throw new NotDetectedError() + } + async detectExecutable (lookup: PathLookup): Promise { await lookup.detectPresence('cnpm') } @@ -120,6 +156,10 @@ export class CNpmDetector extends PackageManagerDetector implements PackageManag execCommand (args: string[]): Runnable { return new Runnable('npx', args) } + + async lookupWorkspace (dir: string): Promise { + return await lookupNearestPackageJsonWorkspace(this, dir) + } } export class PNpmDetector extends PackageManagerDetector implements PackageManager { @@ -139,10 +179,18 @@ export class PNpmDetector extends PackageManagerDetector implements PackageManag return 'pnpm-lock.yaml' } + get representativeConfigFile (): string { + return 'pnpm-workspace.yaml' + } + async detectLockfile (dir: string): Promise { return await accessR(path.join(dir, this.representativeLockfile)) } + async detectConfigFile (dir: string): Promise { + return await accessR(path.join(dir, this.representativeConfigFile)) + } + async detectExecutable (lookup: PathLookup): Promise { await lookup.detectPresence('pnpm') } @@ -154,6 +202,81 @@ export class PNpmDetector extends PackageManagerDetector implements PackageManag execCommand (args: string[]): Runnable { return new Runnable('pnpm', args) } + + async lookupWorkspace (dir: string): Promise { + // To avoid having to bring in a yaml parser, just call pnpm directly. + // However, to avoid calling pnpm if it's likely not installed, detect + // the presence of the workspace file first. + for (const searchPath of lineage(dir)) { + try { + await this.detectConfigFile(searchPath) + } catch { + continue + } + + const { execa } = await import('execa') + + const pnpmArgs = [ + 'list', + '--json', + '--recursive', + '--depth', + '1', + ] + + const result = await execa('pnpm', pnpmArgs, { + cwd: searchPath, + }) + + type PnpmProjectOutput = { + name: string + path: string + } + + const output: PnpmProjectOutput[] = JSON.parse(result.stdout) + if (!Array.isArray(output)) { + throw new Error(`The output of 'pnpm list' was not an array (stdout=${result.stdout}, stderr=${result.stderr})`) + } + + const [root, dependencies] = output.reduce( + ([root, dependencies]: [PnpmProjectOutput | undefined, PnpmProjectOutput[]], project) => { + if (root === undefined) { + return [project, dependencies] + } + + // The project with the shortest path should be the workspace root. + if (root.path.length > project.path.length) { + return [project, [...dependencies, root]] + } + + return [root, [...dependencies, project]] + }, + [undefined, []], + ) + + if (root === undefined) { + return + } + + const rootPackage = new Package({ + name: root.name, + path: root.path, + workspaces: Object.values(dependencies).map(dep => dep.path), + }) + + const packages = Object.entries(dependencies).map(([name, { path }]) => { + return new Package({ + name, + path, + }) + }) + + return await initWorkspace(this, { + root: rootPackage, + packages, + }) + } + } } export class YarnDetector extends PackageManagerDetector implements PackageManager { @@ -173,10 +296,19 @@ export class YarnDetector extends PackageManagerDetector implements PackageManag return 'yarn.lock' } + get representativeConfigFile (): undefined { + return + } + async detectLockfile (dir: string): Promise { return await accessR(path.join(dir, this.representativeLockfile)) } + // eslint-disable-next-line require-await, @typescript-eslint/no-unused-vars + async detectConfigFile (dir: string): Promise { + throw new NotDetectedError() + } + async detectExecutable (lookup: PathLookup): Promise { await lookup.detectPresence('yarn') } @@ -188,6 +320,10 @@ export class YarnDetector extends PackageManagerDetector implements PackageManag execCommand (args: string[]): Runnable { return new Runnable('yarn', args) } + + async lookupWorkspace (dir: string): Promise { + return await lookupNearestPackageJsonWorkspace(this, dir) + } } export class DenoDetector extends PackageManagerDetector implements PackageManager { @@ -207,10 +343,18 @@ export class DenoDetector extends PackageManagerDetector implements PackageManag return 'deno.lock' } + get representativeConfigFile (): string { + return 'deno.json' + } + async detectLockfile (dir: string): Promise { return await accessR(path.join(dir, this.representativeLockfile)) } + async detectConfigFile (dir: string): Promise { + return await accessR(path.join(dir, this.representativeConfigFile)) + } + async detectExecutable (lookup: PathLookup): Promise { await lookup.detectPresence('deno') } @@ -222,6 +366,54 @@ export class DenoDetector extends PackageManagerDetector implements PackageManag execCommand (args: string[]): Runnable { return new Runnable('deno', ['run', '-A', `npm:${args[0]}`, ...args.slice(1)]) } + + async lookupWorkspace (dir: string): Promise { + for (const searchPath of lineage(dir)) { + try { + const configFile = await this.detectConfigFile(searchPath) + + type Schema = { + workspace?: string[] + } + + const jsonFile = await JsonSourceFile.loadFromFilePath(configFile) + if (!jsonFile) { + continue + } + + const rootPackage = await Package.loadFromDirPath(searchPath) + if (rootPackage === undefined) { + continue + } + + const workspaces = jsonFile.data.workspace?.map(packagePath => { + return path.resolve(searchPath, packagePath) + }) + + if (!workspaces) { + continue + } + + const packages: Package[] = [] + + for (const workspace of workspaces) { + const workspacePackage = await Package.loadFromDirPath(workspace) + if (workspacePackage === undefined) { + continue + } + + packages.push(workspacePackage) + } + + return await initWorkspace(this, { + root: rootPackage, + packages, + }) + } catch { + continue + } + } + } } export class BunDetector extends PackageManagerDetector implements PackageManager { @@ -241,10 +433,19 @@ export class BunDetector extends PackageManagerDetector implements PackageManage return 'bun.lockb' } + get representativeConfigFile (): undefined { + return + } + async detectLockfile (dir: string): Promise { return await accessR(path.join(dir, this.representativeLockfile)) } + // eslint-disable-next-line require-await, @typescript-eslint/no-unused-vars + async detectConfigFile (dir: string): Promise { + throw new NotDetectedError() + } + async detectExecutable (lookup: PathLookup): Promise { await lookup.detectPresence('bun') } @@ -256,6 +457,10 @@ export class BunDetector extends PackageManagerDetector implements PackageManage execCommand (args: string[]): Runnable { return new Runnable('bunx', args) } + + async lookupWorkspace (dir: string): Promise { + return await lookupNearestPackageJsonWorkspace(this, dir) + } } async function accessR (filePath: string): Promise { @@ -350,7 +555,7 @@ export class PathLookup { } } -const npmDetector = new NpmDetector() +export const npmPackageManager = new NpmDetector() // The order of the detectors is relevant to the lookup order. export const knownPackageManagers: PackageManagerDetector[] = [ @@ -359,7 +564,7 @@ export const knownPackageManagers: PackageManagerDetector[] = [ new DenoDetector(), new YarnDetector(), new CNpmDetector(), - npmDetector, + npmPackageManager, ] export interface DetectOptions { @@ -402,6 +607,18 @@ export async function detectPackageManager ( // Nothing detected. } + // Next, try to find a config file. + try { + const { packageManager } = await detectNearestConfigFile(dir, { + detectors, + root: options?.root, + }) + + return packageManager + } catch { + // Nothing detected. + } + // Finally, try to find a relevant executable. // // This can generate a whole bunch of path lookups. Try one by one despite @@ -417,7 +634,7 @@ export async function detectPackageManager ( } // If all else fails, just assume npm. - return npmDetector + return npmPackageManager } export interface NearestLockFile { @@ -477,3 +694,163 @@ export async function detectNearestLockfile ( throw new NoLockfileFoundError(searchPaths, lockfiles) } + +export interface NearestConfigFile { + packageManager: PackageManager + configFile: string +} + +export class NoConfigFileFoundError extends Error { + searchPaths: string[] + configFiles: string[] + + constructor (searchPaths: string[], configFiles: string[], options?: ErrorOptions) { + const message = `Unable to detect a config file in any of the following paths:` + + `\n\n` + + `${searchPaths.map(searchPath => ` ${searchPath}`).join('\n')}` + + `\n\n` + + `Config files we looked for:` + + `\n\n` + + `${configFiles.map(lockfile => ` ${lockfile}`).join('\n')}` + super(message, options) + this.name = 'NoConfigFileFoundError' + this.searchPaths = searchPaths + this.configFiles = configFiles + } +} + +export async function detectNearestConfigFile ( + dir: string, + options?: DetectOptions, +): Promise { + const detectors = options?.detectors ?? knownPackageManagers + + const searchPaths: string[] = [] + + for (const searchPath of lineage(dir, { root: options?.root })) { + try { + searchPaths.push(searchPath) + + // Assume that only a single kind of config file exists, which means + // the resolve order does not matter. + return await Promise.any(detectors.map(async detector => { + const configFile = await detector.detectConfigFile(searchPath) + return { + packageManager: detector, + configFile, + } + })) + } catch { + // Nothing detected. + } + } + + const configFiles = detectors.reduce((acc, detector) => { + return acc.concat(detector.representativeConfigFile ?? []) + }, []) + + throw new NoConfigFileFoundError(searchPaths, configFiles) +} + +export class NoPackageJsonFoundError extends Error { + searchPaths: string[] + + constructor (searchPaths: string[], options?: ErrorOptions) { + const message = `Unable to detect a package.json in any of the following paths:` + + `\n\n` + + `${searchPaths.map(searchPath => ` ${searchPath}`).join('\n')}` + super(message, options) + this.name = 'NoPackageJsonFoundError' + this.searchPaths = searchPaths + } +} + +export interface DetectNearestPackageJsonOptions { + root?: string +} + +export async function detectNearestPackageJson ( + dir: string, + options?: DetectNearestPackageJsonOptions, +): Promise { + const searchPaths: string[] = [] + + for (const searchPath of lineage(dir, { root: options?.root })) { + searchPaths.push(searchPath) + + const packageJson = await PackageJsonFile.loadFromFilePath( + PackageJsonFile.filePath(searchPath), + ) + + if (packageJson) { + return packageJson + } + } + + throw new NoPackageJsonFoundError(searchPaths) +} + +export async function fauxWorkspaceFromPackageJson ( + packageManager: PackageManager, + packageJsonFile: PackageJsonFile, +): Promise { + const rootPackage = new Package({ + name: packageJsonFile.name!, + path: packageJsonFile.basePath, + }) + + return await initWorkspace(packageManager.detector(), { + root: rootPackage, + packages: [], + }) +} + +async function lookupNearestPackageJsonWorkspace ( + detector: PackageManagerDetector, + dir: string, +): Promise { + for (const searchPath of lineage(dir)) { + const rootPackage = await Package.loadFromDirPath(searchPath) + if (!rootPackage) { + continue + } + + if (rootPackage.workspaces === undefined || rootPackage.workspaces.length === 0) { + continue + } + + const packages = await Workspace.resolvePatterns(searchPath, rootPackage.workspaces) + + return await initWorkspace(detector, { + root: rootPackage, + packages, + }) + } +} + +async function initWorkspace ( + detector: PackageManagerDetector, + options: Pick, +) { + const lockfile: OptionalWorkspaceFile = await detectNearestLockfile(options.root.path, { + root: options.root.path, + detectors: [detector], + }).then( + ({ lockfile }) => Ok(lockfile), + reason => Err(reason), + ) + + const configFile: OptionalWorkspaceFile = await detectNearestConfigFile(options.root.path, { + root: options.root.path, + detectors: [detector], + }).then( + ({ configFile }) => Ok(configFile), + reason => Err(reason), + ) + + return new Workspace({ + ...options, + lockfile, + configFile, + }) +} diff --git a/packages/cli/src/services/check-parser/package-files/paths.ts b/packages/cli/src/services/check-parser/package-files/paths.ts index 818d60451..5f9bc6f6f 100644 --- a/packages/cli/src/services/check-parser/package-files/paths.ts +++ b/packages/cli/src/services/check-parser/package-files/paths.ts @@ -275,3 +275,33 @@ export function isBuiltinPath (importPath: string) { return false } + +export function isImportsPath (importPath: string) { + if (importPath.startsWith('#')) { + return true + } + + return false +} + +export interface ExternalPath { + name: string + path: string +} + +export function splitExternalPath (importPath: string): ExternalPath { + if (importPath.startsWith('@')) { + const [namespace, pkg, ...rest] = importPath.split('/') + return { + name: pkg ? `${namespace}/${pkg}` : namespace, + path: rest.join('/'), + } + } + + const [pkg, ...rest] = importPath.split('/') + + return { + name: pkg, + path: rest.join('/'), + } +} 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 899e04373..e8a730d4b 100644 --- a/packages/cli/src/services/check-parser/package-files/resolver.ts +++ b/packages/cli/src/services/check-parser/package-files/resolver.ts @@ -4,12 +4,14 @@ import { SourceFile } from './source-file' import { PackageJsonFile } from './package-json-file' import { TSConfigFile } from './tsconfig-json-file' import { JSConfigFile } from './jsconfig-json-file' -import { isBuiltinPath, isLocalPath, PathResult } from './paths' +import { isBuiltinPath, isImportsPath, isLocalPath, PathResult, splitExternalPath } from './paths' import { FileLoader, LoadFile } from './loader' import { JsonSourceFile } from './json-source-file' import { JsonTextSourceFile } from './json-text-source-file' import { LookupContext } from './lookup' -import { walkUp, WalkUpOptions } from './walk' +import { lineage, LineageOptions } from './walk' +import { Workspace } from './workspace' +import { PackageManager } from './package-manager' class PackageFilesCache { #sourceFileCache = new FileLoader(SourceFile.loadFromFilePath) @@ -51,9 +53,13 @@ class PackageFilesCache { #tsconfigJsonCache = new FileLoader(this.#jsonTextFileLoader(TSConfigFile.loadFromJsonTextSourceFile)) #jsconfigJsonCache = new FileLoader(this.#jsonFileLoader(JSConfigFile.loadFromJsonSourceFile)) + async exactSourceFile (filePath: string): Promise { + return await this.#sourceFileCache.load(filePath) + } + async sourceFile (filePath: string, context: LookupContext): Promise { for (const lookupPath of context.collectLookupPaths(filePath)) { - const sourceFile = await this.#sourceFileCache.load(lookupPath) + const sourceFile = await this.exactSourceFile(lookupPath) if (sourceFile === undefined) { continue } @@ -112,23 +118,70 @@ class PackageFiles { } } -type TSConfigFileLocalDependency = { - kind: 'tsconfig-file' +export type RawDependencySource = 'require' | 'import' + +export type RawDependency = { + importPath: string + source: RawDependencySource +} + +type WorkspaceRootPackageJsonFileLocalDependency = { + kind: 'workspace-root-package-json-file' + importPath: string + sourceFile: SourceFile + packageJsonFile: PackageJsonFile +} + +type WorkspaceRootTSConfigFileLocalDependency = { + kind: 'workspace-root-tsconfig-file' + importPath: string + sourceFile: SourceFile + configFile: TSConfigFile +} + +type WorkspaceRootLockfileLocalDependency = { + kind: 'workspace-root-lockfile' + importPath: string + sourceFile: SourceFile +} + +type WorkspaceRootConfigFileLocalDependency = { + kind: 'workspace-root-config-file' + importPath: string + sourceFile: SourceFile +} + +type NearestPackageJsonFileLocalDependency = { + kind: 'nearest-package-json-file' + importPath: string + sourceFile: SourceFile + packageJsonFile: PackageJsonFile +} + +type NearestTSConfigFileLocalDependency = { + kind: 'nearest-tsconfig-file' + importPath: string + sourceFile: SourceFile + configFile: TSConfigFile +} + +type SupportingTSConfigFileLocalDependency = { + kind: 'supporting-tsconfig-file' importPath: string sourceFile: SourceFile configFile: TSConfigFile } -type TSConfigResolvedPathLocalDependency = { - kind: 'tsconfig-resolved-path' +type SupportingTSConfigResolvedPathLocalDependency = { + kind: 'supporting-tsconfig-resolved-path' importPath: string sourceFile: SourceFile configFile: TSConfigFile pathResult: PathResult } -type TSConfigBaseUrlRelativePathLocalDependency = { - kind: 'tsconfig-baseurl-relative-path' +type SupportingTSConfigBaseUrlRelativePathLocalDependency = { + kind: 'supporting-tsconfig-baseurl-relative-path' importPath: string configFile: TSConfigFile sourceFile: SourceFile @@ -140,11 +193,24 @@ type RelativePathLocalDependency = { sourceFile: SourceFile } +type WorkspaceNeighborLocalDependency = { + kind: 'workspace-neighbor' + importPath: string + sourceFile: SourceFile +} + type LocalDependency = - TSConfigFileLocalDependency - | TSConfigResolvedPathLocalDependency - | TSConfigBaseUrlRelativePathLocalDependency + | WorkspaceRootPackageJsonFileLocalDependency + | WorkspaceRootTSConfigFileLocalDependency + | WorkspaceRootLockfileLocalDependency + | WorkspaceRootConfigFileLocalDependency + | NearestPackageJsonFileLocalDependency + | NearestTSConfigFileLocalDependency + | SupportingTSConfigFileLocalDependency + | SupportingTSConfigResolvedPathLocalDependency + | SupportingTSConfigBaseUrlRelativePathLocalDependency | RelativePathLocalDependency + | WorkspaceNeighborLocalDependency type MissingDependency = { importPath: string @@ -161,32 +227,41 @@ export type Dependencies = { local: LocalDependency[] } -export class PackageFilesResolver { - cache = new PackageFilesCache() +interface ResolveSourceFileOptions { + exportPath?: string + source?: RawDependencySource +} - async loadPackageJsonFile (filePath: string, options?: WalkUpOptions): Promise { - let packageJson: PackageJsonFile | undefined +export interface PackageFilesResolverOptions { + workspace?: Workspace +} - await walkUp(filePath, async dirPath => { - packageJson = await this.cache.packageJson(PackageJsonFile.filePath(dirPath)) - return packageJson !== undefined - }, options) +export class PackageFilesResolver { + cache = new PackageFilesCache() + workspace?: Workspace - return packageJson + constructor (options?: PackageFilesResolverOptions) { + this.workspace = options?.workspace } - async loadPackageFiles (filePath: string, options?: WalkUpOptions): Promise { + async loadPackageFiles (filePath: string, options?: LineageOptions): Promise { const files = new PackageFiles() - await walkUp(filePath, async dirPath => { - const found = await files.satisfyFromDirPath(dirPath, this.cache) - return found - }, options) + for (const searchPath of lineage(path.dirname(filePath), options)) { + const found = await files.satisfyFromDirPath(searchPath, this.cache) + if (found) { + break + } + } return files } - private async resolveSourceFile (sourceFile: SourceFile, context: LookupContext): Promise { + private async resolveSourceFile ( + sourceFile: SourceFile, + context: LookupContext, + options?: ResolveSourceFileOptions, + ): Promise { if (sourceFile.meta.basename === PackageJsonFile.FILENAME) { const packageJson = await this.cache.packageJson(sourceFile.meta.filePath) if (packageJson === undefined) { @@ -195,11 +270,35 @@ export class PackageFilesResolver { return [sourceFile] } + const { + exportPath = '', + source = 'import', + } = options ?? {} + + const searchPaths: string[] = [] + + if (packageJson.hasExports()) { + const { root, paths } = packageJson.resolveExportPath(exportPath, [ + source, + 'node', + 'module-sync', + 'default', + ]) + + for (const { target: targetPath } of paths) { + searchPaths.push(path.resolve(root, targetPath.path)) + } + } + + if (searchPaths.length === 0 && exportPath === '') { + searchPaths.push(...packageJson.mainPaths) + } + // Go through each main path. A fallback path is included. If we can // find a tsconfig for the main file, look it up and attempt to find // the original TypeScript sources roughly the same way tsc does it. - for (const mainPath of packageJson.mainPaths) { - const { tsconfigJson, jsconfigJson } = await this.loadPackageFiles(mainPath, { + for (const searchPath of searchPaths) { + const { tsconfigJson, jsconfigJson } = await this.loadPackageFiles(searchPath, { root: packageJson.basePath, }) @@ -209,7 +308,7 @@ export class PackageFilesResolver { continue } - const candidatePaths = configJson.collectLookupPaths(mainPath).flatMap(filePath => { + const candidatePaths = configJson.collectLookupPaths(searchPath).flatMap(filePath => { return context.collectLookupPaths(filePath) }) for (const candidatePath of candidatePaths) { @@ -224,7 +323,7 @@ export class PackageFilesResolver { } } - const mainSourceFile = await this.cache.sourceFile(mainPath, context) + const mainSourceFile = await this.cache.sourceFile(searchPath, context) if (mainSourceFile === undefined) { continue } @@ -241,7 +340,7 @@ export class PackageFilesResolver { async resolveDependenciesForFilePath ( filePath: string, - dependencies: string[], + dependencies: RawDependency[], ): Promise { const resolved: Dependencies = { external: [], @@ -251,12 +350,106 @@ export class PackageFilesResolver { const dirname = path.dirname(filePath) - const { tsconfigJson, jsconfigJson } = await this.loadPackageFiles(filePath) + const { + packageJson, + tsconfigJson, + jsconfigJson, + } = await this.loadPackageFiles(filePath, { + root: this.workspace?.root.path, + }) + + if (this.workspace) { + const { + packageJson, + tsconfigJson, + jsconfigJson, + } = await this.loadPackageFiles(PackageJsonFile.filePath(this.workspace.root.path)) + + if (packageJson) { + resolved.local.push({ + kind: 'workspace-root-package-json-file', + importPath: filePath, + sourceFile: packageJson.jsonFile.sourceFile, + packageJsonFile: packageJson, + }) + } + + if (tsconfigJson) { + resolved.local.push({ + kind: 'workspace-root-tsconfig-file', + importPath: filePath, + sourceFile: tsconfigJson.jsonFile.sourceFile, + configFile: tsconfigJson, + }) + } + + if (jsconfigJson) { + resolved.local.push({ + kind: 'workspace-root-tsconfig-file', + importPath: filePath, + sourceFile: jsconfigJson.jsonFile.sourceFile, + configFile: jsconfigJson, + }) + } + + if (this.workspace.lockfile.isOk()) { + const lockfile = await this.cache.exactSourceFile( + this.workspace.lockfile.ok(), + ) + if (lockfile !== undefined) { + resolved.local.push({ + kind: 'workspace-root-lockfile', + importPath: filePath, + sourceFile: lockfile, + }) + } + } + + if (this.workspace.configFile.isOk()) { + const configFile = await this.cache.exactSourceFile( + this.workspace.configFile.ok(), + ) + if (configFile !== undefined) { + resolved.local.push({ + kind: 'workspace-root-config-file', + importPath: filePath, + sourceFile: configFile, + }) + } + } + } + + if (packageJson) { + resolved.local.push({ + kind: 'nearest-package-json-file', + importPath: filePath, + sourceFile: packageJson.jsonFile.sourceFile, + packageJsonFile: packageJson, + }) + } + + if (tsconfigJson) { + resolved.local.push({ + kind: 'nearest-tsconfig-file', + importPath: filePath, + sourceFile: tsconfigJson.jsonFile.sourceFile, + configFile: tsconfigJson, + }) + } + + if (jsconfigJson) { + resolved.local.push({ + kind: 'nearest-tsconfig-file', + importPath: filePath, + sourceFile: jsconfigJson.jsonFile.sourceFile, + configFile: jsconfigJson, + }) + } const context = LookupContext.forFilePath(filePath) resolve: - for (const importPath of dependencies) { + for (const { importPath, source } of dependencies) { if (isBuiltinPath(importPath)) { resolved.external.push({ importPath, @@ -305,7 +498,7 @@ export class PackageFilesResolver { for (const resolvedFile of resolvedFiles) { configJson.registerRelatedSourceFile(resolvedFile) resolved.local.push({ - kind: 'tsconfig-resolved-path', + kind: 'supporting-tsconfig-resolved-path', importPath, sourceFile: resolvedFile, configFile: configJson, @@ -315,7 +508,7 @@ export class PackageFilesResolver { }, }) resolved.local.push({ - kind: 'tsconfig-file', + kind: 'supporting-tsconfig-file', importPath, sourceFile: configJson.jsonFile.sourceFile, configFile: configJson, @@ -343,13 +536,13 @@ export class PackageFilesResolver { for (const resolvedFile of resolvedFiles) { configJson.registerRelatedSourceFile(resolvedFile) resolved.local.push({ - kind: 'tsconfig-baseurl-relative-path', + kind: 'supporting-tsconfig-baseurl-relative-path', importPath, sourceFile: resolvedFile, configFile: configJson, }) resolved.local.push({ - kind: 'tsconfig-file', + kind: 'supporting-tsconfig-file', importPath, sourceFile: configJson.jsonFile.sourceFile, configFile: configJson, @@ -363,6 +556,37 @@ export class PackageFilesResolver { } } + if (isImportsPath(importPath)) { + // TODO + continue resolve + } + + if (this.workspace) { + const { name, path: exportPath } = splitExternalPath(importPath) + const pkg = this.workspace.memberByName(name) + if (pkg) { + const sourceFile = await this.cache.sourceFile(pkg.path, context) + if (sourceFile !== undefined) { + const resolvedFiles = await this.resolveSourceFile(sourceFile, context, { + exportPath, + source, + }) + let found = false + for (const resolvedFile of resolvedFiles) { + resolved.local.push({ + kind: 'workspace-neighbor', + importPath, + sourceFile: resolvedFile, + }) + found = true + } + if (found) { + continue resolve + } + } + } + } + resolved.external.push({ importPath, }) diff --git a/packages/cli/src/services/check-parser/package-files/result.ts b/packages/cli/src/services/check-parser/package-files/result.ts new file mode 100644 index 000000000..94fb2886e --- /dev/null +++ b/packages/cli/src/services/check-parser/package-files/result.ts @@ -0,0 +1,116 @@ +export interface Result { + /** + * Returns `true` if the {@link Result} is {@link Ok}, or `false` otherwise. + */ + isOk (): this is Ok + + /** + * Returns `true` if the {@link Result} is {@link Err}, or `false` otherwise. + */ + isErr (): this is Err + + /** + * Returns `T` if the {@link Result} is {@link Ok}, or throws `E` otherwise. + */ + unwrap (): T + + /** + * Returns `T` if the {@link Result} is {@link Ok}, or `undefined` otherwise. + */ + ok (): T | undefined + + /** + * Returns `E` if the {@link Result} is {@link Err}, or `undefined` otherwise. + */ + err (): E | undefined +} + +export interface Ok extends Result { + isOk (): this is Ok + isErr (): this is Err + unwrap (): T + ok (): T + err (): undefined +} + +export interface Err extends Result { + isOk (): this is Ok + isErr (): this is Err + unwrap (): never + ok (): undefined + err (): E +} + +export function Ok (value: T): Result { + return new _Ok(value) +} + +export function Err (error: E): Result { + return new _Err(error) +} + +abstract class _Result implements Result { + abstract isOk (): this is Ok + abstract isErr (): this is Err + abstract unwrap (): T + abstract ok (): T | undefined + abstract err (): E | undefined +} + +class _Ok extends _Result implements Ok { + #value: T + + constructor (value: T) { + super() + this.#value = value + } + + isOk (): this is Ok { + return true + } + + isErr (): this is Err { + return false + } + + unwrap (): T { + return this.#value + } + + ok (): T { + return this.#value + } + + err (): undefined { + return + } +} + +class _Err extends _Result implements Err { + #error: E + + constructor (error: E) { + super() + this.#error = error + } + + isOk (): this is Ok { + return false + } + + isErr (): this is Err { + return true + } + + unwrap (): never { + throw this.#error + } + + ok (): undefined { + return + } + + err (): E { + return this.#error + } +} diff --git a/packages/cli/src/services/check-parser/package-files/workspace.ts b/packages/cli/src/services/check-parser/package-files/workspace.ts new file mode 100644 index 000000000..518345300 --- /dev/null +++ b/packages/cli/src/services/check-parser/package-files/workspace.ts @@ -0,0 +1,152 @@ +import path from 'node:path' + +import { glob } from 'glob' + +import { PackageJsonFile } from './package-json-file' +import { Result } from './result' + +export interface PackageOptions { + /** + * The name of the package. + */ + name: string + + /** + * An absolute path to the package directory. + */ + path: string + + /** + * Whether the package is a workspace. + */ + workspaces?: string[] +} + +export class Package { + name: string + path: string + workspaces?: string[] + + constructor ({ name, path, workspaces }: PackageOptions) { + this.name = name + this.path = path + this.workspaces = workspaces + } + + // eslint-disable-next-line require-await + static async loadFromPackageJsonFile (packageJson: PackageJsonFile): Promise { + const { name, workspaces } = packageJson + if (name === undefined) { + return + } + + return new Package({ + name, + path: packageJson.meta.dirname, + workspaces, + }) + } + + static async loadFromDirPath (dirPath: string): Promise { + const packageJson = await PackageJsonFile.loadFromFilePath(PackageJsonFile.filePath(dirPath)) + if (!packageJson) { + return + } + + return await Package.loadFromPackageJsonFile(packageJson) + } +} + +export type OptionalWorkspaceFile = Result + +export interface WorkspaceOptions { + root: Package + packages: Package[] + lockfile: OptionalWorkspaceFile + configFile: OptionalWorkspaceFile +} + +export class Workspace { + /** + * The workspace root package. + */ + root: Package + + /** + * Packages that are a part of the workspace, excluding the root package. + */ + packages: Package[] + + /** + * The package manager specific lockfile of the workspace. + */ + lockfile: OptionalWorkspaceFile + + /** + * The package manager specific config file of the workspace. + */ + configFile: OptionalWorkspaceFile + + #membersByName = new Map() + #membersByPath = new Map() + + constructor (options: WorkspaceOptions) { + this.root = options.root + this.packages = options.packages + this.#membersByName = [options.root, ...options.packages].reduce( + (map, pkg) => map.set(pkg.name, pkg), + new Map(), + ) + this.#membersByPath = [options.root, ...options.packages].reduce( + (map, pkg) => map.set(pkg.path, pkg), + new Map(), + ) + this.lockfile = options.lockfile + this.configFile = options.configFile + } + + memberByName (name: string): Package | undefined { + return this.#membersByName.get(name) + } + + memberByPath (path: string): Package | undefined { + return this.#membersByPath.get(path) + } + + /** + * @param root An absolute path to the workspace root. + * @param patterns Relative workspace patterns. + * @returns Absolute paths to every package in the workspace. + */ + static async resolvePatterns ( + root: string, + patterns: string[], + ): Promise { + const lookup = patterns.map(pattern => + path.join(pattern, PackageJsonFile.FILENAME), + ) + + const results = await glob(lookup, { + cwd: root, + absolute: true, + }) + + const packages: Package[] = [] + + for (const result of results) { + const packageJson = await PackageJsonFile.loadFromFilePath(result) + if (packageJson === undefined) { + continue + } + + const workspacePackage = await Package.loadFromPackageJsonFile(packageJson) + if (workspacePackage === undefined) { + continue + } + + packages.push(workspacePackage) + } + + return packages + } +} diff --git a/packages/cli/src/services/check-parser/parser.ts b/packages/cli/src/services/check-parser/parser.ts index 28425ce7e..cc517aead 100644 --- a/packages/cli/src/services/check-parser/parser.ts +++ b/packages/cli/src/services/check-parser/parser.ts @@ -7,12 +7,17 @@ import * as walk from 'acorn-walk' import { minimatch } from 'minimatch' // Only import types given this is an optional dependency import type { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree' +import Debug from 'debug' import { Collector } from './collector' import { DependencyParseError } from './errors' -import { PackageFilesResolver, Dependencies } from './package-files/resolver' +import { PackageFilesResolver, Dependencies, RawDependency, RawDependencySource } from './package-files/resolver' import type { PlaywrightConfig } from '../playwright-config' import { findFilesWithPattern, pathToPosix } from '../util' +import { Workspace } from './package-files/workspace' +import { isCoreExtension, isTSExtension } from './package-files/extension' + +const debug = Debug('checkly:cli:services:check-parser:parser') // Our custom configuration to handle walking errors @@ -20,13 +25,23 @@ import { findFilesWithPattern, pathToPosix } from '../util' const ignore = (_node: any, _st: any, _c: any) => {} type Module = { - dependencies: Array + dependencies: Array } -type SupportedFileExtension = '.js' | '.mjs' | '.ts' +type LegacySupportedFileExtension = '.js' | '.mjs' | '.ts' + +function isLegacySupportedFileExtension (value: string): value is LegacySupportedFileExtension { + switch (value) { + case '.js': + case '.mjs': + case '.ts': + return true + default: + return false + } +} const PACKAGE_EXTENSION = `${path.sep}package.json` -const STATIC_FILE_EXTENSION = ['.json', '.txt', '.jpeg', '.jpg', '.png', '.yml'] const supportedBuiltinModules = [ 'node:assert', @@ -46,24 +61,6 @@ const supportedBuiltinModules = [ 'node:zlib', ] -async function validateEntrypoint ( - entrypoint: string, -): Promise<{ - extension: SupportedFileExtension - content: string -}> { - const extension = path.extname(entrypoint) - if (extension !== '.js' && extension !== '.ts' && extension !== '.mjs') { - throw new Error(`Unsupported file extension for ${entrypoint}`) - } - try { - const content = await fs.readFile(entrypoint, { encoding: 'utf-8' }) - return { extension, content } - } catch { - throw new DependencyParseError(entrypoint, [entrypoint], [], []) - } -} - let tsParser: any function getTsParser (): any { if (tsParser) { @@ -77,12 +74,11 @@ function getTsParser (): any { // Our custom configuration to handle walking errors Object.values(AST_NODE_TYPES).forEach(astType => { - // Only handle the TS specific ones - if (!astType.startsWith('TS')) { - return + // Only handle the TS/JSX specific ones + if (astType.startsWith('TS') || astType.startsWith('JSX')) { + const base: any = walk.base + base[astType] = base[astType] ?? ignore } - const base: any = walk.base - base[astType] = base[astType] ?? ignore }) return tsParser } catch (err: any) { @@ -93,15 +89,30 @@ function getTsParser (): any { } } +class RawDependencyCollector { + #dependencies: RawDependency[] = [] + + add (dependency: RawDependency) { + this.#dependencies.push(dependency) + } + + state (): RawDependency[] { + return this.#dependencies + } +} + type ParserOptions = { supportedNpmModules?: Array checkUnsupportedModules?: boolean + workspace?: Workspace + restricted?: boolean } export class Parser { supportedModules: Set checkUnsupportedModules: boolean - resolver = new PackageFilesResolver() + resolver: PackageFilesResolver + restricted: boolean cache = new Map { + private async validateFile ( + filePath: string, + ): Promise<{ + filePath: string + content: string + }> { const extension = path.extname(filePath) - if (extension !== '.js' && extension !== '.ts' && extension !== '.mjs') { + if (!this.isProcessableExtension(extension)) { throw new Error(`Unsupported file extension for ${filePath}`) } try { @@ -140,6 +160,22 @@ export class Parser { } } + private isProcessableExtension (extension: string): boolean { + if (this.restricted) { + return isLegacySupportedFileExtension(extension) + } + + if (isCoreExtension(extension) && extension !== '.json') { + return true + } + + if (isTSExtension(extension)) { + return true + } + + return false + } + async getFilesAndDependencies (playwrightConfig: PlaywrightConfig): Promise<{ files: string[], errors: string[] }> { const files = new Set(await this.getFilesFromPaths(playwrightConfig)) @@ -151,8 +187,8 @@ export class Parser { if (resultFileSet.has(file)) { continue } - if (STATIC_FILE_EXTENSION.includes(path.extname(file))) { - // Holds info about the main file and doesn't need to be parsed + const extension = path.extname(file) + if (!this.isProcessableExtension(extension)) { resultFileSet.add(file) continue } @@ -267,7 +303,7 @@ export class Parser { } async parse (entrypoint: string) { - const { content } = await validateEntrypoint(entrypoint) + const { content } = await this.validateFile(entrypoint) /* * The importing of files forms a directed graph. @@ -341,11 +377,12 @@ export class Parser { static parseDependencies (filePath: string, contents: string): { module: Module, error?: any } { - const dependencies = new Set() + debug(`Parsing dependencies of ${filePath}`) + const dependencies = new RawDependencyCollector() const extension = path.extname(filePath) try { - if (extension === '.js' || extension === '.mjs') { + if (isCoreExtension(extension) && extension !== '.json') { const ast = acorn.parse(contents, { allowReturnOutsideFunction: true, ecmaVersion: 'latest', @@ -353,22 +390,24 @@ export class Parser { allowAwaitOutsideFunction: true, }) walk.simple(ast, Parser.jsNodeVisitor(dependencies)) - } else if (extension === '.ts') { + } else if (isTSExtension(extension)) { const tsParser = getTsParser() - const ast = tsParser.parse(contents, {}) + const ast = tsParser.parse(contents, { + // We must only enable jsx for tsx/jsx files. Otherwise type brackets + // may confuse the parser and cause an error. + jsx: extension.endsWith('x'), + }) // The AST from typescript-estree is slightly different from the type used by acorn-walk. // This doesn't actually cause problems (both are "ESTree's"), but we need to ignore type errors here. // @ts-ignore walk.simple(ast, Parser.tsNodeVisitor(tsParser, dependencies)) - } else if (extension === '.json') { - // No dependencies to check. - } else { - throw new Error(`Unsupported file extension for ${filePath}`) } } catch (err) { + debug(`Failed to parse dependencies of ${filePath}: ${err}`) + return { module: { - dependencies: Array.from(dependencies), + dependencies: dependencies.state(), }, error: err, } @@ -376,60 +415,60 @@ export class Parser { return { module: { - dependencies: Array.from(dependencies), + dependencies: dependencies.state(), }, } } - static jsNodeVisitor (dependencies: Set): any { + static jsNodeVisitor (dependencies: RawDependencyCollector): any { return { CallExpression (node: Node) { if (!Parser.isRequireExpression(node)) return const requireStringArg = Parser.getRequireStringArg(node) - Parser.registerDependency(requireStringArg, dependencies) + Parser.registerDependency(requireStringArg, 'require', dependencies) }, ImportDeclaration (node: any) { if (node.source.type !== 'Literal') return - Parser.registerDependency(node.source.value, dependencies) + Parser.registerDependency(node.source.value, 'import', dependencies) }, ExportNamedDeclaration (node: any) { if (node.source === null) return if (node.source.type !== 'Literal') return - Parser.registerDependency(node.source.value, dependencies) + Parser.registerDependency(node.source.value, 'import', dependencies) }, ExportAllDeclaration (node: any) { if (node.source === null) return if (node.source.type !== 'Literal') return - Parser.registerDependency(node.source.value, dependencies) + Parser.registerDependency(node.source.value, 'import', dependencies) }, } } - static tsNodeVisitor (tsParser: any, dependencies: Set): any { + static tsNodeVisitor (tsParser: any, dependencies: RawDependencyCollector): any { return { // While rare, TypeScript files may also use require. CallExpression (node: Node) { if (!Parser.isRequireExpression(node)) return const requireStringArg = Parser.getRequireStringArg(node) - Parser.registerDependency(requireStringArg, dependencies) + Parser.registerDependency(requireStringArg, 'require', dependencies) }, ImportDeclaration (node: TSESTree.ImportDeclaration) { // For now, we only support literal strings in the import statement if (node.source.type !== tsParser.TSESTree.AST_NODE_TYPES.Literal) return - Parser.registerDependency(node.source.value, dependencies) + Parser.registerDependency(node.source.value, 'import', dependencies) }, ExportNamedDeclaration (node: TSESTree.ExportNamedDeclaration) { // The statement isn't importing another dependency if (node.source === null) return // For now, we only support literal strings in the export statement if (node.source.type !== tsParser.TSESTree.AST_NODE_TYPES.Literal) return - Parser.registerDependency(node.source.value, dependencies) + Parser.registerDependency(node.source.value, 'import', dependencies) }, ExportAllDeclaration (node: TSESTree.ExportAllDeclaration) { if (node.source === null) return // For now, we only support literal strings in the export statement if (node.source.type !== tsParser.TSESTree.AST_NODE_TYPES.Literal) return - Parser.registerDependency(node.source.value, dependencies) + Parser.registerDependency(node.source.value, 'import', dependencies) }, } } @@ -471,12 +510,19 @@ export class Parser { } } - static registerDependency (importArg: string | null, dependencies: Set) { - // TODO: We currently don't support import path aliases, f.ex: `import { Something } from '@services/my-service'` - if (!importArg) { - // If there's no importArg, don't register a dependency - } else { - dependencies.add(importArg) + static registerDependency ( + importPath: string | null, + source: RawDependencySource, + dependencies: RawDependencyCollector, + ) { + if (!importPath) { + // If there's no importPath, don't register a dependency. + return } + + dependencies.add({ + importPath, + source, + }) } } diff --git a/packages/cli/src/services/checkly-config-loader.ts b/packages/cli/src/services/checkly-config-loader.ts index 8175ca653..b02816616 100644 --- a/packages/cli/src/services/checkly-config-loader.ts +++ b/packages/cli/src/services/checkly-config-loader.ts @@ -143,9 +143,40 @@ export async function getChecklyConfigFile (): Promise<{ checklyConfig: string, return config } -export class ConfigNotFoundError extends Error {} +export class ConfigNotFoundError extends Error { + searchPaths: string[] + configFiles: string[] -export async function loadChecklyConfig (dir: string, filenames = ['checkly.config.ts', 'checkly.config.mts', 'checkly.config.cts', 'checkly.config.js', 'checkly.config.mjs', 'checkly.config.cjs'], writeChecklyConfig: boolean = true, playwrightConfigPath?: string): Promise<{ config: ChecklyConfig, constructs: Construct[] }> { + constructor (searchPaths: string[], configFiles: string[], options?: ErrorOptions) { + const message = `Unable to detect a Checkly configuration file in any of the following paths:` + + `\n\n` + + `${searchPaths.map(searchPath => ` ${searchPath}`).join('\n')}` + + `\n\n` + + `Configuration files we looked for:` + + `\n\n` + + `${configFiles.map(lockfile => ` ${lockfile}`).join('\n')}` + super(message, options) + this.name = 'ConfigNotFoundError' + this.searchPaths = searchPaths + this.configFiles = configFiles + } +} + +export const defaultFilenames = [ + 'checkly.config.ts', + 'checkly.config.mts', + 'checkly.config.cts', + 'checkly.config.js', + 'checkly.config.mjs', + 'checkly.config.cjs', +] + +export async function loadChecklyConfig ( + dir: string, + filenames = defaultFilenames, + writeChecklyConfig: boolean = true, + playwrightConfigPath?: string, +): Promise<{ config: ChecklyConfig, constructs: Construct[] }> { let config: ChecklyConfig | undefined Session.loadingChecklyConfigFile = true Session.checklyConfigFileConstructs = [] @@ -188,7 +219,7 @@ async function handleMissingConfig ( } return checklyConfig } - throw new ConfigNotFoundError(`Unable to locate a config at ${dir} with ${filenames.join(', ')}.`) + throw new ConfigNotFoundError([dir], filenames) } function validateConfigFields (config: ChecklyConfig, fields: (keyof ChecklyConfig)[]): void { diff --git a/packages/cli/src/services/playwright-config.ts b/packages/cli/src/services/playwright-config.ts index de5702e4a..82505ca54 100644 --- a/packages/cli/src/services/playwright-config.ts +++ b/packages/cli/src/services/playwright-config.ts @@ -65,9 +65,9 @@ export class PlaywrightConfig { continue } if (Array.isArray(definition)) { - definition.forEach((file: string) => this.files.add(file)) + definition.forEach((file: string) => this.files.add(toAbsolutePath(dir, file))) } else { - this.files.add(definition) + this.files.add(toAbsolutePath(dir, definition)) } } diff --git a/packages/cli/src/services/project-parser.ts b/packages/cli/src/services/project-parser.ts index 2d2eec072..137117a72 100644 --- a/packages/cli/src/services/project-parser.ts +++ b/packages/cli/src/services/project-parser.ts @@ -1,4 +1,5 @@ import * as path from 'path' +import Debug from 'debug' import { findFilesWithPattern, getPlaywrightConfigPath, pathToPosix, @@ -13,6 +14,17 @@ import { CheckConfigDefaults, PlaywrightSlimmedProp } from './checkly-config-loa import type { Runtime } from '../rest/runtimes' import { isEntrypoint, type Construct } from '../constructs/construct' import { PlaywrightCheck } from '../constructs/playwright-check' +import { + detectNearestPackageJson, + detectPackageManager, + fauxWorkspaceFromPackageJson, + NoPackageJsonFoundError, + PackageManager, +} from './check-parser/package-files/package-manager' +import { Err, Ok, Result } from './check-parser/package-files/result' +import { Workspace } from './check-parser/package-files/workspace' + +const debug = Debug('checkly:cli:services:project-parser') type ProjectParseOpts = { directory: string @@ -34,11 +46,78 @@ type ProjectParseOpts = { playwrightConfigPath?: string include?: string | string[] playwrightChecks?: PlaywrightSlimmedProp[] + enableWorkspaces?: boolean } const BASE_CHECK_DEFAULTS = { } +async function findBasePath ( + packageManager: PackageManager, + directory: string, + { ignoreWorkspaces = false }, +): Promise<{ + basePath: string + contextPath: string + workspace: Result +}> { + try { + if (ignoreWorkspaces) { + const nearestPackageJson = await detectNearestPackageJson(directory) + + const workspace = await fauxWorkspaceFromPackageJson( + packageManager, + nearestPackageJson, + ) + + return { + basePath: workspace.root.path, + contextPath: workspace.root.path, + workspace: Ok(workspace), + } + } + + const workspace = await packageManager.lookupWorkspace(directory) + + // If we can't locate a real workspace, set up a faux workspace instead. + // Makes usage easier since we can rely on a workspace being available. + if (!workspace) { + return await findBasePath(packageManager, directory, { + ignoreWorkspaces: true, + }) + } + + const nearestPackageJson = await detectNearestPackageJson(directory, { + root: workspace.root.path, + }) + + const contextPath = nearestPackageJson.basePath + + // If the nearest workspace includes the nearest package, then use the + // workspace root as the project root. Otherwise, use the config dir as + // the project root. + const basePath = workspace.memberByPath(contextPath) !== undefined + ? workspace.root.path + : contextPath + + return { + basePath, + contextPath, + workspace: Ok(workspace), + } + } catch (err) { + if (err instanceof NoPackageJsonFoundError) { + return { + basePath: directory, + contextPath: directory, + workspace: Err(err), + } + } + + throw err + } +} + export async function parseProject (opts: ProjectParseOpts): Promise { const { directory, @@ -60,6 +139,7 @@ export async function parseProject (opts: ProjectParseOpts): Promise { playwrightConfigPath, include, playwrightChecks, + enableWorkspaces = true, } = opts const project = new Project(projectLogicalId, { name: projectName, @@ -70,11 +150,19 @@ export async function parseProject (opts: ProjectParseOpts): Promise { project.allowTestOnly(true) } + const packageManager = await detectPackageManager(directory) + debug(`Detected package manager %O`, packageManager) + + const { basePath, contextPath, workspace } = await findBasePath(packageManager, directory, { + ignoreWorkspaces: !enableWorkspaces, + }) + checklyConfigConstructs?.forEach( construct => project.addResource(construct.type, construct.logicalId, construct), ) Session.project = project - Session.basePath = directory + Session.basePath = basePath + Session.contextPath = contextPath Session.checkDefaults = Object.assign({}, BASE_CHECK_DEFAULTS, checkDefaults) Session.checkFilter = checkFilter Session.browserCheckDefaults = browserCheckDefaults @@ -82,6 +170,8 @@ export async function parseProject (opts: ProjectParseOpts): Promise { Session.defaultRuntimeId = defaultRuntimeId Session.verifyRuntimeDependencies = verifyRuntimeDependencies ?? true Session.ignoreDirectoriesMatch = ignoreDirectoriesMatch + Session.packageManager = packageManager + Session.workspace = workspace // TODO: Do we really need all of the ** globs, or could we just put node_modules? const ignoreDirectories = ['**/node_modules/**', '**/.git/**', ...ignoreDirectoriesMatch] diff --git a/packages/cli/src/services/util.ts b/packages/cli/src/services/util.ts index b1e9f3347..4e0146607 100644 --- a/packages/cli/src/services/util.ts +++ b/packages/cli/src/services/util.ts @@ -200,8 +200,7 @@ export async function bundlePlayWrightProject ( }> { const dir = path.resolve(path.dirname(playwrightConfig)) const filePath = path.resolve(dir, playwrightConfig) - const supportedDetectors = [new NpmDetector(), new YarnDetector(), new PNpmDetector()] - const { lockfile } = await detectNearestLockfile(dir, { detectors: supportedDetectors, root: Session.basePath }) + const lockfile = Session.workspace.unwrap().lockfile.unwrap() // No need of loading everything if there is no lockfile const pwtConfig = await Session.loadFile(filePath) @@ -230,7 +229,7 @@ export async function bundlePlayWrightProject ( const [cacheHash] = await Promise.all([ getCacheHash(lockfile), - loadPlaywrightProjectFiles(dir, pwConfigParsed, include, archive, lockfile), + loadPlaywrightProjectFiles(dir, pwConfigParsed, include, archive), ]) await archive.finalize() @@ -240,7 +239,7 @@ export async function bundlePlayWrightProject ( outputFile, browsers: pwConfigParsed.getBrowsers(), playwrightVersion, - relativePlaywrightConfigPath: Session.relativePosixPath(filePath), + relativePlaywrightConfigPath: Session.contextRelativePosixPath(filePath), cacheHash, }) }) @@ -279,24 +278,19 @@ export function getPlaywrightVersionFromPackage (cwd: string): string { } } -// Temporarily always include these extra files (if present) until they can -// be properly supported. -const extraFiles = [ - 'pnpm-workspace.yaml', -] - export async function loadPlaywrightProjectFiles ( dir: string, pwConfigParsed: PlaywrightConfig, include: string[], archive: Archiver, - lockFile: string, ) { const ignoredFiles = ['**/node_modules/**', '.git/**', ...Session.ignoreDirectoriesMatch] - const parser = new Parser({}) + const parser = new Parser({ + workspace: Session.workspace.ok(), + restricted: false, + }) const { files, errors } = await parser.getFilesAndDependencies(pwConfigParsed) if (errors.length) { throw new Error(`Error loading playwright project files: ${errors.map((e: string) => e).join(', ')}`) } const root = Session.basePath! - const prefix = Session.relativePosixPath(dir) const entryDefaults = { mode: 0o755, // Default mode for files in the archive } @@ -306,24 +300,6 @@ export async function loadPlaywrightProjectFiles ( name: Session.relativePosixPath(file), }) } - const lockFileDirName = path.dirname(lockFile) - const packageJsonFile = path.join(lockFileDirName, 'package.json') - archive.file(lockFile, { - ...entryDefaults, - name: Session.relativePosixPath(lockFile), - }) - archive.file(packageJsonFile, { - ...entryDefaults, - name: Session.relativePosixPath(packageJsonFile), - }) - // handle workspaces - archive.glob('**/package.json', { - cwd: dir, - ignore: ignoredFiles, - }, { - ...entryDefaults, - prefix, - }) for (const includePattern of include) { // If pattern explicitly targets an ignored directory, only apply custom ignores const explicitlyTargetsIgnored = @@ -337,18 +313,6 @@ export async function loadPlaywrightProjectFiles ( ...entryDefaults, }) } - for (const filePath of extraFiles) { - archive.file(path.resolve(root, filePath), { - ...entryDefaults, - name: Session.relativePosixPath(filePath), - }) - } -} - -export async function findRegexFiles (directory: string, regex: RegExp, ignorePattern: string[]): -Promise { - const files = await findFilesWithPattern(directory, '**/*.{js,ts,mjs}', ignorePattern) - return files.filter(file => regex.test(file)).map(file => pathToPosix(path.relative(directory, file))) } export async function findFilesWithPattern (