From ff314b17b6a96f31e0f3add9fa1f3a8e54cc194d Mon Sep 17 00:00:00 2001 From: nassim Date: Mon, 27 Oct 2025 15:21:27 +0100 Subject: [PATCH 1/2] fix dev env --- packages/cli/src/utils/environment.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/utils/environment.ts b/packages/cli/src/utils/environment.ts index 3698eff..bdb245f 100644 --- a/packages/cli/src/utils/environment.ts +++ b/packages/cli/src/utils/environment.ts @@ -14,7 +14,7 @@ export const environmentConfig = { scope: 'openid profile email offline_access', }, develop: { - endpoint: 'https://platform-dev.aignostics.com', + endpoint: 'https://platform-dev.aignostics.ai', issuerURL: 'https://dev-8ouohmmrbuh2h4vu.eu.auth0.com/oauth', clientID: 'gqduveFvx7LX90drQPGzr4JGUYdh24gA', audience: 'https://dev-8ouohmmrbuh2h4vu-samia', From 792dc0dc5bfd037f85933787eae7cd90f8ffc242 Mon Sep 17 00:00:00 2001 From: nassim Date: Mon, 3 Nov 2025 16:13:23 +0100 Subject: [PATCH 2/2] add e2e setup --- .github/workflows/e2e-cli.yml | 49 ++ .gitignore | 1 + package-lock.json | 1017 ++++++++++++++--------- package.json | 5 +- packages/cli/e2e/specs/base.e2e.test.ts | 28 + packages/cli/e2e/utils/command.ts | 16 + packages/cli/e2e/vite.config.e2e.ts | 18 + packages/cli/e2e/vitest.setup.ts | 24 + packages/cli/package.json | 1 + packages/cli/project.json | 7 + packages/cli/src/cli-functions.spec.ts | 56 ++ packages/cli/src/cli-functions.ts | 13 + packages/cli/src/cli.test.ts | 24 + packages/cli/src/cli.ts | 26 +- packages/cli/src/utils/auth.spec.ts | 142 ++++ packages/cli/src/utils/auth.ts | 42 +- packages/cli/tsconfig.app.json | 11 +- packages/cli/tsconfig.json | 3 +- packages/cli/vite.config.ts | 1 + packages/sdk/vite.config.ts | 1 + tsconfig.eslint.json | 13 +- 21 files changed, 1090 insertions(+), 408 deletions(-) create mode 100644 .github/workflows/e2e-cli.yml create mode 100644 packages/cli/e2e/specs/base.e2e.test.ts create mode 100644 packages/cli/e2e/utils/command.ts create mode 100644 packages/cli/e2e/vite.config.e2e.ts create mode 100644 packages/cli/e2e/vitest.setup.ts diff --git a/.github/workflows/e2e-cli.yml b/.github/workflows/e2e-cli.yml new file mode 100644 index 0000000..3e4ce00 --- /dev/null +++ b/.github/workflows/e2e-cli.yml @@ -0,0 +1,49 @@ +name: CLI E2E Tests + +permissions: {} + +on: + schedule: + # run every day at 7:00 AM UTC + - cron: '0 7 * * *' + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +jobs: + e2e-tests: + name: CLI E2E Tests + runs-on: ubuntu-latest + env: + E2E_REFRESH_TOKEN: ${{ secrets.E2E_REFRESH_TOKEN }} + E2E_TEST_ENVIRONMENT: ${{ secrets.E2E_TEST_ENVIRONMENT }} + strategy: + matrix: + node-version: [18.x, 20.x] + fail-fast: false + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build packages + run: npx nx run-many -t build + + - name: Run CLI E2E tests + run: npm run test:cli:e2e + env: + CI: true + NODE_ENV: test diff --git a/.gitignore b/.gitignore index 26e9d3b..d294db4 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ logs/ # Generated code packages/sdk/src/generated/ +test-results/ # Environment variables .env diff --git a/package-lock.json b/package-lock.json index 486ee48..f53ec96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@nx/eslint": "^21.3.5", "@nx/js": "^21.3.5", "@nx/node": "^21.3.5", - "@nx/vite": "^21.3.5", + "@nx/vite": "^21.6.6", "@semantic-release/commit-analyzer": "^13.0.0", "@semantic-release/github": "^11.0.0", "@semantic-release/npm": "^12.0.1", @@ -30,7 +30,9 @@ "eslint": "^9.31.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.3", + "execa": "^9.6.0", "fishery": "^2.3.1", + "fs-extra": "^11.3.2", "husky": "^9.1.7", "license-checker-rseidelsohn": "^4.4.2", "msw": "^2.10.4", @@ -100,7 +102,6 @@ "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -4219,27 +4220,250 @@ ] }, "node_modules/@nx/vite": { - "version": "21.3.5", - "resolved": "https://registry.npmjs.org/@nx/vite/-/vite-21.3.5.tgz", - "integrity": "sha512-oM3g187KmDplm3Cag8mDYI70kM+jP/JOoffqe9Xi3g8ZsiB+HAz6ZL+tSZfPD7b1/I8PJPU0T4/v+CcE+mBvuQ==", + "version": "21.6.6", + "resolved": "https://registry.npmjs.org/@nx/vite/-/vite-21.6.6.tgz", + "integrity": "sha512-mmcx17yhSOWX9iNwShPj85nxSI6t3mXc6EfBJWSHcrPtc9Q4pPLCzpYGnGzngIQtTjjqliojEFriIauoqP8e2w==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "21.3.5", - "@nx/js": "21.3.5", + "@nx/devkit": "21.6.6", + "@nx/js": "21.6.6", "@phenomnomnominal/tsquery": "~5.0.1", - "@swc/helpers": "~0.5.0", "ajv": "^8.0.0", "enquirer": "~2.3.6", "picomatch": "4.0.2", "semver": "^7.6.3", - "tsconfig-paths": "^4.1.2" + "tsconfig-paths": "^4.1.2", + "tslib": "^2.3.0" }, "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0", "vitest": "^1.3.1 || ^2.0.0 || ^3.0.0" } }, + "node_modules/@nx/vite/node_modules/@nx/devkit": { + "version": "21.6.6", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-21.6.6.tgz", + "integrity": "sha512-K3/xrJ87DcNpx5R2da9b5xS69uTRMvV38aEfkRKWBT7/L5iMshsU5bo/Fb602WHVFvIF07xW3wiHW1uO7i0k0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ejs": "^3.1.7", + "enquirer": "~2.3.6", + "ignore": "^5.0.4", + "minimatch": "9.0.3", + "semver": "^7.5.3", + "tslib": "^2.3.0", + "yargs-parser": "21.1.1" + }, + "peerDependencies": { + "nx": ">= 20 <= 22" + } + }, + "node_modules/@nx/vite/node_modules/@nx/js": { + "version": "21.6.6", + "resolved": "https://registry.npmjs.org/@nx/js/-/js-21.6.6.tgz", + "integrity": "sha512-cJVeuH695U2zS4D8jjuOw0iycdfathcuzTwmbYeaAZOMTt7BPJmMeHxHNVmBM3KNQR9xCy8AQCT5WWLUpZ7LNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.2", + "@babel/plugin-proposal-decorators": "^7.22.7", + "@babel/plugin-transform-class-properties": "^7.22.5", + "@babel/plugin-transform-runtime": "^7.23.2", + "@babel/preset-env": "^7.23.2", + "@babel/preset-typescript": "^7.22.5", + "@babel/runtime": "^7.22.6", + "@nx/devkit": "21.6.6", + "@nx/workspace": "21.6.6", + "@zkochan/js-yaml": "0.0.7", + "babel-plugin-const-enum": "^1.0.1", + "babel-plugin-macros": "^3.1.0", + "babel-plugin-transform-typescript-metadata": "^0.3.1", + "chalk": "^4.1.0", + "columnify": "^1.6.0", + "detect-port": "^1.5.1", + "enquirer": "~2.3.6", + "ignore": "^5.0.4", + "js-tokens": "^4.0.0", + "jsonc-parser": "3.2.0", + "npm-package-arg": "11.0.1", + "npm-run-path": "^4.0.1", + "ora": "5.3.0", + "picocolors": "^1.1.0", + "picomatch": "4.0.2", + "semver": "^7.5.3", + "source-map-support": "0.5.19", + "tinyglobby": "^0.2.12", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "verdaccio": "^6.0.5" + }, + "peerDependenciesMeta": { + "verdaccio": { + "optional": true + } + } + }, + "node_modules/@nx/vite/node_modules/@nx/nx-darwin-arm64": { + "version": "21.6.6", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-21.6.6.tgz", + "integrity": "sha512-QKx9eZ3lVSUeDKNL4KlWt+1d7lAXtx7s5bQ/oS8swuzWD5YSN5E5LtUyD2GAJfhc+GJsglXA+Fa0c3r3D2YXgg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@nx/vite/node_modules/@nx/nx-darwin-x64": { + "version": "21.6.6", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-21.6.6.tgz", + "integrity": "sha512-Z3xknouZZCV3Z5U8RJqMkuNKJ3v3tnqWgjKhe+wATBTDQJ85SRq1FkbUDIHhEmNDp8PeMwL8fp6Y/2jUic76/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@nx/vite/node_modules/@nx/nx-freebsd-x64": { + "version": "21.6.6", + "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-21.6.6.tgz", + "integrity": "sha512-q5iRafeqMYCC9Qe+1916itW/2qylORxct22vanRhh2VlOA56HQjiZbqulE/Pv3V0ThUM/CAOESQVYVHr+zeWeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@nx/vite/node_modules/@nx/nx-linux-arm-gnueabihf": { + "version": "21.6.6", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-21.6.6.tgz", + "integrity": "sha512-MTVWzplTbC4Pbd+RTTsf91x9r4sKDoQJQjuoUEd1icq3+JPhJgyz2s+EiHzXnjtXGbFCnjIQKLkK+SZLS+1iyQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/vite/node_modules/@nx/nx-linux-arm64-gnu": { + "version": "21.6.6", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-21.6.6.tgz", + "integrity": "sha512-Mh0nxqrmO1fXJMrxZJL6d/dc/37PdTh4XJ3snP91OfBxgDm4OeXpFldJP2wphXhNJi8jLKI4GSmsYUl2Ke+sXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/vite/node_modules/@nx/nx-linux-arm64-musl": { + "version": "21.6.6", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-21.6.6.tgz", + "integrity": "sha512-FyRQG58BwTu5+eh6Fdba3OFb9bW0XJCdtXvqaSHnt7+ptWXX9f2YvT2mpleWRKX1aRHN/hXyUG9No2r0b8VB5g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/vite/node_modules/@nx/nx-linux-x64-gnu": { + "version": "21.6.6", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-21.6.6.tgz", + "integrity": "sha512-r5Xa0px3lGfkJDa3N8Cep5ccOTl1SYRkarrTOhvWlxUvEPYGy2BmHucqtwGH3M6AjLX77vVwpgKNzdVtHK7VfQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/vite/node_modules/@nx/nx-linux-x64-musl": { + "version": "21.6.6", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-21.6.6.tgz", + "integrity": "sha512-yaUnoCI8DtgcA9aOGBENzkVhCnmwuiI7HT6ZNd85NiLpTl3J5dJBkZDqUQkbh6uaZXM0zeU48GOwm8xFdfTJFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/vite/node_modules/@nx/nx-win32-arm64-msvc": { + "version": "21.6.6", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-21.6.6.tgz", + "integrity": "sha512-W/MBKqXy0Bj3QmUdzoLM92hAg5kglwz9EBZCzxbcKpvSRmcSzFheq2zwMtLY/7FR0P8AbWcGtqk4kTHOdYwHRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@nx/vite/node_modules/@nx/nx-win32-x64-msvc": { + "version": "21.6.6", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-21.6.6.tgz", + "integrity": "sha512-Z1l7+FC0ALRB3BWYGJDnZWMI/r6pQz4TgLfeMCKrlnBriMIaFcyDj6/Zwb5W8kmNWPbWWEhRmuntffRSGBFOyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@nx/vite/node_modules/@nx/workspace": { + "version": "21.6.6", + "resolved": "https://registry.npmjs.org/@nx/workspace/-/workspace-21.6.6.tgz", + "integrity": "sha512-5dms7/p5tfEA7mu8al1ImWR0ujDzw1NclqsDT3Rch6QCOCToOIIpprx14PNla1bKv6pDqWV09ZTeZQuqfMLgKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nx/devkit": "21.6.6", + "@zkochan/js-yaml": "0.0.7", + "chalk": "^4.1.0", + "enquirer": "~2.3.6", + "nx": "21.6.6", + "picomatch": "4.0.2", + "semver": "^7.6.3", + "tslib": "^2.3.0", + "yargs-parser": "21.1.1" + } + }, "node_modules/@nx/vite/node_modules/ajv": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", @@ -4253,17 +4477,216 @@ "require-from-string": "^2.0.2" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@nx/vite/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@nx/vite/node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@nx/vite/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@nx/vite/node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nx/vite/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nx/vite/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nx/vite/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nx/vite/node_modules/lines-and-columns": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.3.tgz", + "integrity": "sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/@nx/vite/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@nx/vite/node_modules/nx": { + "version": "21.6.6", + "resolved": "https://registry.npmjs.org/nx/-/nx-21.6.6.tgz", + "integrity": "sha512-KybewPwpU+8Vdqd6xwSh5sWlsXTGs9a2L7np+rsTTBQvNshNHl1R1nRExRB1L50od0wnnBm/5A2BB0jvO10IWw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@napi-rs/wasm-runtime": "0.2.4", + "@yarnpkg/lockfile": "^1.1.0", + "@yarnpkg/parsers": "3.0.2", + "@zkochan/js-yaml": "0.0.7", + "axios": "^1.12.0", + "chalk": "^4.1.0", + "cli-cursor": "3.1.0", + "cli-spinners": "2.6.1", + "cliui": "^8.0.1", + "dotenv": "~16.4.5", + "dotenv-expand": "~11.0.6", + "enquirer": "~2.3.6", + "figures": "3.2.0", + "flat": "^5.0.2", + "front-matter": "^4.0.2", + "ignore": "^5.0.4", + "jest-diff": "^30.0.2", + "jsonc-parser": "3.2.0", + "lines-and-columns": "2.0.3", + "minimatch": "9.0.3", + "node-machine-id": "1.1.12", + "npm-run-path": "^4.0.1", + "open": "^8.4.0", + "ora": "5.3.0", + "resolve.exports": "2.0.3", + "semver": "^7.5.3", + "string-width": "^4.2.3", + "tar-stream": "~2.2.0", + "tmp": "~0.2.1", + "tree-kill": "^1.2.2", + "tsconfig-paths": "^4.1.2", + "tslib": "^2.3.0", + "yaml": "^2.6.0", + "yargs": "^17.6.2", + "yargs-parser": "21.1.1" + }, + "bin": { + "nx": "bin/nx.js", + "nx-cloud": "bin/nx-cloud.js" + }, + "optionalDependencies": { + "@nx/nx-darwin-arm64": "21.6.6", + "@nx/nx-darwin-x64": "21.6.6", + "@nx/nx-freebsd-x64": "21.6.6", + "@nx/nx-linux-arm-gnueabihf": "21.6.6", + "@nx/nx-linux-arm64-gnu": "21.6.6", + "@nx/nx-linux-arm64-musl": "21.6.6", + "@nx/nx-linux-x64-gnu": "21.6.6", + "@nx/nx-linux-x64-musl": "21.6.6", + "@nx/nx-win32-arm64-msvc": "21.6.6", + "@nx/nx-win32-x64-msvc": "21.6.6" + }, + "peerDependencies": { + "@swc-node/register": "^1.8.0", + "@swc/core": "^1.3.85" + }, + "peerDependenciesMeta": { + "@swc-node/register": { + "optional": true + }, + "@swc/core": { + "optional": true + } + } + }, + "node_modules/@nx/vite/node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@nx/vite/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, "node_modules/@nx/vite/node_modules/picomatch": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", @@ -4277,6 +4700,16 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/@nx/vite/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/@nx/workspace": { "version": "21.3.5", "resolved": "https://registry.npmjs.org/@nx/workspace/-/workspace-21.3.5.tgz", @@ -4350,7 +4783,6 @@ "integrity": "sha512-oNXsh2ywth5aowwIa7RKtawnkdH6LgU1ztfP9AIUCQCvzysB+WeU8o2kyyosDPwBZutPpjZDKPQGIzzrfTWweQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.1", @@ -5123,19 +5555,6 @@ "node": ">=18" } }, - "node_modules/@semantic-release/npm/node_modules/@sindresorhus/merge-streams": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", - "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@semantic-release/npm/node_modules/aggregate-error": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", @@ -5182,60 +5601,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/execa": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.0.tgz", - "integrity": "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sindresorhus/merge-streams": "^4.0.0", - "cross-spawn": "^7.0.6", - "figures": "^6.1.0", - "get-stream": "^9.0.0", - "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.2.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^4.0.0", - "yoctocolors": "^2.1.1" - }, - "engines": { - "node": "^18.19.0 || >=20.5.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@semantic-release/npm/node_modules/get-stream": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", - "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sec-ant/readable-stream": "^0.4.1", - "is-stream": "^4.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@semantic-release/npm/node_modules/human-signals": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", - "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, "node_modules/@semantic-release/npm/node_modules/indent-string": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", @@ -5249,88 +5614,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/is-stream": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", - "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@semantic-release/npm/node_modules/npm-run-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", - "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0", - "unicorn-magic": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@semantic-release/npm/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@semantic-release/npm/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@semantic-release/npm/node_modules/strip-final-newline": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", - "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@semantic-release/npm/node_modules/unicorn-magic": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", - "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@semantic-release/release-notes-generator": { "version": "14.0.3", "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-14.0.3.tgz", @@ -5471,16 +5754,6 @@ "@sinonjs/commons": "^3.0.1" } }, - "node_modules/@swc/helpers": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", - "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.8.0" - } - }, "node_modules/@tybys/wasm-util": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", @@ -5823,7 +6096,6 @@ "integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.38.0", "@typescript-eslint/types": "8.38.0", @@ -6450,7 +6722,6 @@ "integrity": "sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/utils": "3.2.4", "fflate": "^0.8.2", @@ -6533,7 +6804,6 @@ "integrity": "sha512-nrUSn7hzt7J6JWgWGz78ZYI8wj+gdIJdk0Ynjpp8l+trkn58Uqsf6RYrYkEK+3X18EX+TNdtJI0WxAtc+L84SQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "argparse": "^2.0.1" }, @@ -6591,7 +6861,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6954,7 +7223,6 @@ "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", @@ -7230,7 +7498,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001726", "electron-to-chromium": "^1.5.173", @@ -8605,7 +8872,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -8675,7 +8941,6 @@ "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -8737,7 +9002,6 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -8892,59 +9156,172 @@ "estraverse": "^5.1.0" }, "engines": { - "node": ">=0.10" + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/execa": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.0.tgz", + "integrity": "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "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.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "node_modules/execa/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "estraverse": "^5.2.0" + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" }, "engines": { - "node": ">=4.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/execa/node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, "engines": { - "node": ">=4.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "node_modules/execa/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "node_modules/execa/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "license": "BSD-2-Clause", + "license": "ISC", "engines": { - "node": ">=0.10.0" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "node_modules/execa/node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/exit-x": { @@ -9486,9 +9863,9 @@ "license": "MIT" }, "node_modules/fs-extra": { - "version": "11.3.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", - "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", + "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", "dev": true, "license": "MIT", "dependencies": { @@ -9919,6 +10296,16 @@ "node": ">= 14" } }, + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/husky": { "version": "9.1.7", "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", @@ -10271,6 +10658,19 @@ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-unicode-supported": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", @@ -11786,7 +12186,6 @@ "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -14775,7 +15174,6 @@ "dev": true, "inBundle": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -15034,7 +15432,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@napi-rs/wasm-runtime": "0.2.4", "@yarnpkg/lockfile": "^1.1.0", @@ -15852,7 +16249,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -15921,7 +16317,6 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -16667,7 +17062,6 @@ "integrity": "sha512-g7RssbTAbir1k/S7uSwSVZFfFXwpomUB9Oas0+xi9KStSCmeDXcA7rNhiskjLqvUe/Evhx8fVCT16OSa34eM5g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^13.0.0-beta.1", "@semantic-release/error": "^4.0.0", @@ -16716,19 +17110,6 @@ "node": ">=18" } }, - "node_modules/semantic-release/node_modules/@sindresorhus/merge-streams": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", - "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/semantic-release/node_modules/aggregate-error": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", @@ -16775,60 +17156,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/node_modules/execa": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.0.tgz", - "integrity": "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sindresorhus/merge-streams": "^4.0.0", - "cross-spawn": "^7.0.6", - "figures": "^6.1.0", - "get-stream": "^9.0.0", - "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.2.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^4.0.0", - "yoctocolors": "^2.1.1" - }, - "engines": { - "node": "^18.19.0 || >=20.5.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/semantic-release/node_modules/execa/node_modules/get-stream": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", - "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sec-ant/readable-stream": "^0.4.1", - "is-stream": "^4.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/semantic-release/node_modules/human-signals": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", - "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, "node_modules/semantic-release/node_modules/indent-string": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", @@ -16842,36 +17169,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/node_modules/is-stream": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", - "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/semantic-release/node_modules/npm-run-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", - "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0", - "unicorn-magic": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/semantic-release/node_modules/p-reduce": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-3.0.0.tgz", @@ -16885,19 +17182,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/semantic-release/node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -16908,45 +17192,6 @@ "node": ">=8" } }, - "node_modules/semantic-release/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/semantic-release/node_modules/strip-final-newline": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", - "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/semantic-release/node_modules/unicorn-magic": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", - "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -17589,6 +17834,19 @@ "node": ">=4" } }, + "node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -17961,7 +18219,6 @@ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -18336,7 +18593,6 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -18668,7 +18924,6 @@ "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -18782,7 +19037,6 @@ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -18796,7 +19050,6 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", diff --git a/package.json b/package.json index 21c8473..48f9697 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "test": "nx run-many -t test", "test:sdk": "nx test sdk", "test:cli": "nx test cli", + "test:cli:e2e": "nx test:e2e cli", "test:coverage": "nx run-many -t test --coverage", "lint": "nx run-many -t lint", "lint:sdk": "nx lint sdk", @@ -55,7 +56,7 @@ "@nx/eslint": "^21.3.5", "@nx/js": "^21.3.5", "@nx/node": "^21.3.5", - "@nx/vite": "^21.3.5", + "@nx/vite": "^21.6.6", "@semantic-release/commit-analyzer": "^13.0.0", "@semantic-release/github": "^11.0.0", "@semantic-release/npm": "^12.0.1", @@ -68,7 +69,9 @@ "eslint": "^9.31.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.3", + "execa": "^9.6.0", "fishery": "^2.3.1", + "fs-extra": "^11.3.2", "husky": "^9.1.7", "license-checker-rseidelsohn": "^4.4.2", "msw": "^2.10.4", diff --git a/packages/cli/e2e/specs/base.e2e.test.ts b/packages/cli/e2e/specs/base.e2e.test.ts new file mode 100644 index 0000000..53f635f --- /dev/null +++ b/packages/cli/e2e/specs/base.e2e.test.ts @@ -0,0 +1,28 @@ +import { describe, it, expect } from 'vitest'; +import { executeCLI } from '../utils/command.js'; + +describe('cli smoke', () => { + it('prints help and exits 0', async () => { + const { stdout, exitCode } = await executeCLI(['--help']); + expect(exitCode).toBe(0); + expect(stdout).toContain('Usage'); + }); + + it('prints version and exits 0', async () => { + const { stdout, exitCode } = await executeCLI(['--version']); + expect(exitCode).toBe(0); + expect(stdout).toMatch(/\d+\.\d+\.\d+/); // version pattern + }); + + it('shows info command output', async () => { + const { stdout, exitCode } = await executeCLI(['info']); + expect(exitCode).toBe(0); + expect(stdout).toContain('Aignostics Platform SDK'); + expect(stdout).toContain('Version'); + }); + + it('calls authenticated test-api command', async () => { + const { stdout } = await executeCLI(['test-api']); + expect(stdout).toContain('API connection successful'); + }); +}); diff --git a/packages/cli/e2e/utils/command.ts b/packages/cli/e2e/utils/command.ts new file mode 100644 index 0000000..800104a --- /dev/null +++ b/packages/cli/e2e/utils/command.ts @@ -0,0 +1,16 @@ +import { execa, Options } from 'execa'; +import path from 'path'; + +const CLI_BIN = path.resolve(__dirname, '../../dist/bin.cjs'); + +const testEnvironment: string = process.env.E2E_TEST_ENVIRONMENT || 'staging'; +/** + * Execute the CLI command with default options + * + * @param args - CLI command arguments + * @param options - Additional execa options + * @returns Promise with the execution result + */ +export function executeCLI(args: string[] = [], options: Options = {}) { + return execa('node', [CLI_BIN, ...args, '--environment', testEnvironment], options); +} diff --git a/packages/cli/e2e/vite.config.e2e.ts b/packages/cli/e2e/vite.config.e2e.ts new file mode 100644 index 0000000..3bba1b5 --- /dev/null +++ b/packages/cli/e2e/vite.config.e2e.ts @@ -0,0 +1,18 @@ +import { defineConfig } from 'vitest/config'; +import path from 'node:path'; + +export default defineConfig({ + test: { + include: ['./**/*.e2e.test.ts'], + environment: 'node', + pool: 'threads', + setupFiles: [path.resolve(__dirname, './vitest.setup.ts'), 'dotenv/config'], + hookTimeout: 60000, + testTimeout: 60000, + globals: true, + reporters: ['default', 'junit'], + outputFile: { + junit: './test-results/junit-e2e.xml', + }, + }, +}); diff --git a/packages/cli/e2e/vitest.setup.ts b/packages/cli/e2e/vitest.setup.ts new file mode 100644 index 0000000..29d0308 --- /dev/null +++ b/packages/cli/e2e/vitest.setup.ts @@ -0,0 +1,24 @@ +import { afterAll, beforeAll, expect } from 'vitest'; +import { executeCLI } from './utils/command.js'; + +const refreshToken = process.env.E2E_REFRESH_TOKEN; + +beforeAll(async () => { + console.log('🔐 Logging in using refresh token for e2e tests...'); + + if (!refreshToken) { + throw new Error('E2E_REFRESH_TOKEN environment variable is not set.'); + } + + const { stdout } = await executeCLI(['login', '--refreshToken', refreshToken]); + + expect(stdout).contain('🎉 Login with refresh token successful! Token saved securely.'); +}); + +afterAll(async () => { + console.log('🔓 Logging out after e2e tests...'); + + const { stdout } = await executeCLI(['logout']); + + expect(stdout).contain('✅ Logged out successfully. Token removed.'); +}); diff --git a/packages/cli/package.json b/packages/cli/package.json index 62d2921..5271c66 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -20,6 +20,7 @@ "scripts": { "build": "nx build", "test": "nx test", + "test:e2e": "nx test:e2e -- --reporter=verbose", "lint": "nx lint", "typecheck": "nx typecheck" }, diff --git a/packages/cli/project.json b/packages/cli/project.json index 2b252f7..12ac96e 100644 --- a/packages/cli/project.json +++ b/packages/cli/project.json @@ -19,6 +19,13 @@ "passWithNoTests": true } }, + "test:e2e": { + "executor": "@nx/vite:test", + "options": { + "config": "packages/cli/e2e/vite.config.e2e.ts", + "passWithNoTests": true + } + }, "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"], diff --git a/packages/cli/src/cli-functions.spec.ts b/packages/cli/src/cli-functions.spec.ts index aa4fd12..7cccc47 100644 --- a/packages/cli/src/cli-functions.spec.ts +++ b/packages/cli/src/cli-functions.spec.ts @@ -12,11 +12,13 @@ import { handleLogin, handleLogout, handleStatus, + handleLoginWithRefreshToken, } from './cli-functions.js'; import { PlatformSDK, PlatformSDKHttp } from '@aignostics/sdk'; import { AuthService, AuthState } from './utils/auth.js'; import { startCallbackServer, waitForCallback } from './utils/oauth-callback-server.js'; import crypto from 'crypto'; +import { EnvironmentKey } from './utils/environment.js'; // Mock external dependencies vi.mock('./utils/oauth-callback-server'); @@ -54,6 +56,7 @@ const mockAuthService = { completeLogin: vi.fn(), logout: vi.fn(), getAuthState: vi.fn(), + loginWithRefreshToken: vi.fn(), } as unknown as AuthService; // Mock package.json @@ -671,4 +674,57 @@ describe('CLI Functions Unit Tests', () => { expect(mockConsole.error).toHaveBeenCalledWith('❌ Error checking status:', mockError); }); }); + + describe('handleLoginWithRefreshToken', () => { + let consoleErrorSpy: ReturnType; + + beforeEach(() => { + consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + }); + + it('should successfully login with refresh token', async () => { + const environment: EnvironmentKey = 'production'; + const refreshToken = 'test-refresh-token'; + + vi.spyOn(mockAuthService, 'loginWithRefreshToken').mockResolvedValue(undefined); + + await handleLoginWithRefreshToken(environment, refreshToken, mockAuthService); + + expect(mockAuthService.loginWithRefreshToken).toHaveBeenCalledWith(environment, refreshToken); + expect(consoleErrorSpy).not.toHaveBeenCalled(); + }); + + it('should handle login failure and exit process', async () => { + const environment: EnvironmentKey = 'production'; + const refreshToken = 'invalid-refresh-token'; + const error = new Error('Invalid refresh token'); + + vi.spyOn(mockAuthService, 'loginWithRefreshToken').mockRejectedValue(error); + + await expect( + handleLoginWithRefreshToken(environment, refreshToken, mockAuthService) + ).rejects.toThrow('process.exit called'); + + expect(mockAuthService.loginWithRefreshToken).toHaveBeenCalledWith(environment, refreshToken); + expect(consoleErrorSpy).toHaveBeenCalledWith('❌ Login with refresh token failed:', error); + }); + + it('should handle network errors during login', async () => { + const environment: EnvironmentKey = 'staging'; + const refreshToken = 'test-refresh-token'; + const networkError = new Error('Network request failed'); + + vi.spyOn(mockAuthService, 'loginWithRefreshToken').mockRejectedValue(networkError); + + await expect( + handleLoginWithRefreshToken(environment, refreshToken, mockAuthService) + ).rejects.toThrow('process.exit called'); + + expect(mockAuthService.loginWithRefreshToken).toHaveBeenCalledWith(environment, refreshToken); + expect(consoleErrorSpy).toHaveBeenCalledWith( + '❌ Login with refresh token failed:', + networkError + ); + }); + }); }); diff --git a/packages/cli/src/cli-functions.ts b/packages/cli/src/cli-functions.ts index 899104b..a48affd 100644 --- a/packages/cli/src/cli-functions.ts +++ b/packages/cli/src/cli-functions.ts @@ -188,6 +188,19 @@ export async function createApplicationRun( } } +export async function handleLoginWithRefreshToken( + environment: EnvironmentKey, + refreshToken: string, + authService: AuthService +): Promise { + try { + await authService.loginWithRefreshToken(environment, refreshToken); + } catch (error) { + console.error('❌ Login with refresh token failed:', error); + process.exit(1); + } +} + export async function handleLogin(environment: EnvironmentKey, authService: AuthService) { const codeVerifier = crypto.randomBytes(32).toString('hex'); diff --git a/packages/cli/src/cli.test.ts b/packages/cli/src/cli.test.ts index db518a8..56d2d51 100644 --- a/packages/cli/src/cli.test.ts +++ b/packages/cli/src/cli.test.ts @@ -16,6 +16,7 @@ vi.mock('./utils/auth.js', () => ({ getValidAccessToken: vi.fn().mockResolvedValue('mock-token'), loginWithCallback: vi.fn().mockResolvedValue(''), completeLogin: vi.fn().mockResolvedValue(undefined), + loginWithRefreshToken: vi.fn().mockResolvedValue(undefined), logout: vi.fn().mockResolvedValue(undefined), getAuthState: vi.fn().mockResolvedValue({ isAuthenticated: true, @@ -429,4 +430,27 @@ describe('CLI Integration Tests', () => { ); }); }); + + describe('login command', () => { + it('should login with refresh token when --refreshToken is provided', async () => { + const refreshToken = 'test-refresh-token-12345'; + + process.argv = [ + 'node', + 'cli.js', + 'login', + '--refreshToken', + refreshToken, + '--environment', + 'production', + ]; + + await main(); + + // Verify that loginWithRefreshToken was called (it's mocked in the AuthService mock above) + // Since we're mocking the entire AuthService, we need to verify the command executed without errors + expect(consoleSpy.error).not.toHaveBeenCalled(); + expect(mockExit).not.toHaveBeenCalled(); + }); + }); }); diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 250565f..c3ca87f 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -16,6 +16,7 @@ import { handleLogin, handleLogout, handleStatus, + handleLoginWithRefreshToken, } from './cli-functions.js'; import { EnvironmentKey, environmentConfig } from './utils/environment.js'; import { AuthenticationError } from '@aignostics/sdk'; @@ -42,6 +43,7 @@ export async function main() { 'test-api', 'Test API connection', yargs => yargs, + // TODO: [TSSDK-20] eliminate the need for casting here argv => testApi(argv.environment as EnvironmentKey, authService) ) .command( @@ -144,9 +146,27 @@ export async function main() { argv.items ) ) - .command('login', 'Login to the Aignostics Platform', {}, async argv => { - await handleLogin(argv.environment as EnvironmentKey, authService); - }) + .command( + 'login', + 'Login to the Aignostics Platform', + yargs => + yargs.option('refreshToken', { + describe: 'Refresh token to use for login', + type: 'string', + demandOption: false, + }), + async argv => { + if (argv.refreshToken) { + await handleLoginWithRefreshToken( + argv.environment as EnvironmentKey, + argv.refreshToken, + authService + ); + return; + } + await handleLogin(argv.environment as EnvironmentKey, authService); + } + ) .command('logout', 'Logout and remove stored token', {}, async argv => { await handleLogout(argv.environment as EnvironmentKey, authService); }) diff --git a/packages/cli/src/utils/auth.spec.ts b/packages/cli/src/utils/auth.spec.ts index b99eaa0..f674677 100644 --- a/packages/cli/src/utils/auth.spec.ts +++ b/packages/cli/src/utils/auth.spec.ts @@ -254,6 +254,148 @@ describe('AuthService', () => { }); }); + describe('loginWithRefreshToken', () => { + it('should successfully login with refresh token and save new tokens', async () => { + const refreshToken = 'valid-refresh-token'; + const newTokenSet = { + access_token: 'new-access-token', + refresh_token: 'new-refresh-token', + expires_at: Math.floor((Date.now() + 3600000) / 1000), // 1 hour from now + token_type: 'Bearer' as const, + scope: 'openid profile email', + }; + + const mockClient = { + refresh: vi.fn(() => newTokenSet), + }; + const mockIssuer = { + Client: vi.fn(() => mockClient), + }; + + vi.mocked(Issuer.discover).mockResolvedValue(mockIssuer as unknown as Issuer); + mockTokenStorage.save.mockResolvedValue(undefined); + + await authService.loginWithRefreshToken(environment, refreshToken); + + expect(Issuer.discover).toHaveBeenCalledWith(environmentConfig.production.issuerURL); + expect(mockIssuer.Client).toHaveBeenCalledWith({ + client_id: environmentConfig.production.clientID, + redirect_uris: [], + response_types: ['code'], + scope: environmentConfig.production.scope || 'openid profile email offline_access', + audience: environmentConfig.production.audience || 'https://aignostics-platform-samia', + token_endpoint_auth_method: 'none', + }); + expect(mockClient.refresh).toHaveBeenCalledWith(refreshToken); + expect(mockTokenStorage.save).toHaveBeenCalledWith(environment, { + access_token: newTokenSet.access_token, + refresh_token: newTokenSet.refresh_token, + expires_at_ms: newTokenSet.expires_at * 1000, + token_type: newTokenSet.token_type, + scope: newTokenSet.scope, + stored_at: expect.any(Number) as number, + }); + expect(consoleSpy.log).toHaveBeenCalledWith( + '🎉 Login with refresh token successful! Token saved securely.' + ); + }); + + it('should keep old refresh token when new one is not provided', async () => { + const refreshToken = 'original-refresh-token'; + const newTokenSetWithoutRefreshToken = { + access_token: 'new-access-token', + // No refresh_token in response + expires_at: Math.floor((Date.now() + 3600000) / 1000), + token_type: 'Bearer' as const, + scope: 'openid profile email', + }; + + const mockClient = { + refresh: vi.fn(() => newTokenSetWithoutRefreshToken), + }; + const mockIssuer = { + Client: vi.fn(() => mockClient), + }; + const { Issuer } = await import('openid-client'); + + vi.mocked(Issuer.discover).mockResolvedValue(mockIssuer as unknown as Issuer); + mockTokenStorage.save.mockResolvedValue(undefined); + + await authService.loginWithRefreshToken(environment, refreshToken); + + expect(mockTokenStorage.save).toHaveBeenCalledWith(environment, { + access_token: 'new-access-token', + refresh_token: 'original-refresh-token', // Should keep original + expires_at_ms: newTokenSetWithoutRefreshToken.expires_at * 1000, + token_type: 'Bearer', + scope: 'openid profile email', + stored_at: expect.any(Number) as number, + }); + expect(consoleSpy.log).toHaveBeenCalledWith( + '🎉 Login with refresh token successful! Token saved securely.' + ); + }); + + it('should handle issuer discovery errors', async () => { + const refreshToken = 'valid-refresh-token'; + const error = new Error('Issuer discovery failed'); + const { Issuer } = await import('openid-client'); + + vi.mocked(Issuer.discover).mockRejectedValue(error); + + await expect(authService.loginWithRefreshToken(environment, refreshToken)).rejects.toThrow( + 'Issuer discovery failed' + ); + expect(consoleSpy.error).toHaveBeenCalledWith('❌ Login with refresh token failed:', error); + }); + + it('should handle token refresh errors', async () => { + const refreshToken = 'invalid-refresh-token'; + const error = new Error('Token refresh failed'); + + const mockClient = { + refresh: vi.fn(() => { + throw error; + }), + }; + const mockIssuer = { + Client: vi.fn(() => mockClient), + }; + const { Issuer } = await import('openid-client'); + + vi.mocked(Issuer.discover).mockResolvedValue(mockIssuer as unknown as Issuer); + + await expect(authService.loginWithRefreshToken(environment, refreshToken)).rejects.toThrow( + 'Token refresh failed' + ); + expect(consoleSpy.error).toHaveBeenCalledWith('❌ Login with refresh token failed:', error); + }); + + it('should handle invalid token response format', async () => { + const refreshToken = 'valid-refresh-token'; + const invalidTokenSet = { + access_token: 'new-access-token', + // Missing required fields like expires_at, token_type + }; + + const mockClient = { + refresh: vi.fn(() => invalidTokenSet), + }; + const mockIssuer = { + Client: vi.fn(() => mockClient), + }; + const { Issuer } = await import('openid-client'); + + vi.mocked(Issuer.discover).mockResolvedValue(mockIssuer as unknown as Issuer); + + await expect(authService.loginWithRefreshToken(environment, refreshToken)).rejects.toThrow(); + expect(consoleSpy.error).toHaveBeenCalledWith( + '❌ Login with refresh token failed:', + expect.any(Error) + ); + }); + }); + describe('loginWithCallback', () => { const mockConfig = { redirectUri: 'http://localhost:3000/callback', diff --git a/packages/cli/src/utils/auth.ts b/packages/cli/src/utils/auth.ts index 7ad2efd..b8cfb90 100644 --- a/packages/cli/src/utils/auth.ts +++ b/packages/cli/src/utils/auth.ts @@ -21,7 +21,7 @@ export interface TokenStorage { /** * Token data schema for validation */ -const tokenSchema = z.object({ +export const tokenSchema = z.object({ access_token: z.string(), refresh_token: z.string().nullable().default(null), expires_at_ms: z.number().nullable().default(null), @@ -30,7 +30,7 @@ const tokenSchema = z.object({ stored_at: z.number(), }); -const tokenSetValidationSchema = z.object({ +export const tokenSetValidationSchema = z.object({ access_token: z.string(), refresh_token: z.string().optional(), expires_at: z.number(), @@ -77,6 +77,44 @@ export interface LoginWithCallbackConfig { export class AuthService { constructor(private readonly tokenStorage: TokenStorage) {} + loginWithRefreshToken = async ( + environment: EnvironmentKey, + refreshToken: string + ): Promise => { + const oauthConfig = environmentConfig[environment]; + + try { + const issuer = await Issuer.discover(oauthConfig.issuerURL); + const client = new issuer.Client({ + client_id: oauthConfig.clientID, + redirect_uris: [], // Not needed for refresh + response_types: ['code'], + scope: oauthConfig.scope || 'openid profile email offline_access', + audience: oauthConfig.audience || 'https://aignostics-platform-samia', + token_endpoint_auth_method: 'none', + }); + + const tokenSet = tokenSetValidationSchema.parse(await client.refresh(refreshToken)); + + // Save the new token securely + await this.saveToken( + environment, + tokenSchema.omit({ stored_at: true }).parse({ + access_token: tokenSet.access_token, + refresh_token: tokenSet.refresh_token || refreshToken, // Keep old refresh token if not renewed + expires_at_ms: tokenSet.expires_at * 1000, + token_type: tokenSet.token_type, + scope: tokenSet.scope, + }) + ); + + console.log('🎉 Login with refresh token successful! Token saved securely.'); + } catch (error) { + console.error('❌ Login with refresh token failed:', error); + throw error; + } + }; + /** * Perform OAuth2 PKCE login flow with external callback handling */ diff --git a/packages/cli/tsconfig.app.json b/packages/cli/tsconfig.app.json index 92e9822..178ca67 100644 --- a/packages/cli/tsconfig.app.json +++ b/packages/cli/tsconfig.app.json @@ -4,13 +4,8 @@ "outDir": "../../dist/out-tsc", "types": ["node"], "incremental": true, - "tsBuildInfoFile": "./dist/.tsbuildinfo", + "tsBuildInfoFile": "./dist/.tsbuildinfo" }, - "include": [ - "src/**/*.ts" - ], - "exclude": [ - "src/**/*.spec.ts", - "src/**/*.test.ts" - ] + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts", "src/**/*.test.ts", "e2e/**/*.test.ts"] } diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index cd13ad9..386cda9 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -1,12 +1,13 @@ { "extends": "./tsconfig.app.json", "compilerOptions": { - "types": ["vitest/globals", "node"], + "types": ["vitest/globals", "node"] }, "include": [ "src/**/*.ts", "src/**/*.spec.ts", "src/**/*.test.ts", + "e2e/**/*.ts", "vite.config.ts", "vitest.setup.ts", "tsup.config.ts" diff --git a/packages/cli/vite.config.ts b/packages/cli/vite.config.ts index da17adc..1a4b1e1 100644 --- a/packages/cli/vite.config.ts +++ b/packages/cli/vite.config.ts @@ -9,6 +9,7 @@ export default defineConfig({ outputFile: { junit: './test-results/junit.xml', }, + clearMocks: true, setupFiles: ['../../vitest.setup.ts', './vitest.setup.ts'], include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], coverage: { diff --git a/packages/sdk/vite.config.ts b/packages/sdk/vite.config.ts index f76bbb8..568c67a 100644 --- a/packages/sdk/vite.config.ts +++ b/packages/sdk/vite.config.ts @@ -9,6 +9,7 @@ export default defineConfig({ junit: './test-results/junit.xml', }, environment: 'node', + clearMocks: true, setupFiles: ['../../vitest.setup.ts', './vitest.setup.ts'], include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], coverage: { diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 3cb56fd..8320855 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -3,15 +3,6 @@ "compilerOptions": { "noEmit": true }, - "include": [ - "packages/*/src/**/*" - ], - "exclude": [ - "node_modules", - "dist", - "docs", - "coverage", - "packages/*/dist", - "packages/*/coverage" - ] + "include": ["packages/*/src/**/*", "packages/*/e2e/**/*"], + "exclude": ["node_modules", "dist", "docs", "coverage", "packages/*/dist", "packages/*/coverage"] }