From ef0633e65a0efd5c02ef77d86c779d6207e6e906 Mon Sep 17 00:00:00 2001 From: Mario Serrano Date: Tue, 22 Sep 2026 09:18:51 -0500 Subject: [PATCH] feat(cli): harden env checks, add tests, non-interactive flags for new - checkJava(): parse the major version out of the "version \"NN" pattern instead of an includes('25') substring match (false-positive risk on e.g. a JDK reporting 17.0.25). - checkNode(): read the minimum version from cli.properties/CliConfig instead of a hardcoded 22, matching engines.node (>=24). - fetchSpringBootVersion: add a 4s AbortSignal timeout so a slow/dead start.spring.io doesn't stall the wizard before the first prompt. - Add vitest (matching sdk/ui-core/vue/microfrontend-bridge convention) with unit tests for config.ts parsing, replace.ts (token replacement + Java package renaming), env.ts checks, and the new flag parser. - dynamia new: add non-interactive flags (--name, --scaffold, --backend-lang, --group-id, --artifact-id, --version, --description, --frontend, --pm, --yes, --git/--no-git) so the wizard can run unattended for scripting/CI. Any flag left unset still prompts. - Remove stray package-lock.json (npm) -- this is a pnpm workspace and the package was already tracked in the root pnpm-lock.yaml. Fixes #95, #96, #97, #98, #99, #100 Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01UcARdenC1Sk5EVpkzboH9W --- platform/packages/cli/package-lock.json | 1057 ------------------ platform/packages/cli/package.json | 9 +- platform/packages/cli/src/commands/new.ts | 155 ++- platform/packages/cli/src/index.ts | 2 +- platform/packages/cli/src/utils/config.ts | 2 +- platform/packages/cli/src/utils/env.ts | 38 +- platform/packages/cli/src/utils/logger.ts | 1 + platform/packages/cli/test/config.test.ts | 63 ++ platform/packages/cli/test/env.test.ts | 62 + platform/packages/cli/test/new-flags.test.ts | 66 ++ platform/packages/cli/test/replace.test.ts | 192 ++++ platform/packages/cli/vitest.config.ts | 12 + pnpm-lock.yaml | 76 +- 13 files changed, 606 insertions(+), 1129 deletions(-) delete mode 100644 platform/packages/cli/package-lock.json create mode 100644 platform/packages/cli/test/config.test.ts create mode 100644 platform/packages/cli/test/env.test.ts create mode 100644 platform/packages/cli/test/new-flags.test.ts create mode 100644 platform/packages/cli/test/replace.test.ts create mode 100644 platform/packages/cli/vitest.config.ts diff --git a/platform/packages/cli/package-lock.json b/platform/packages/cli/package-lock.json deleted file mode 100644 index 6a664dd5..00000000 --- a/platform/packages/cli/package-lock.json +++ /dev/null @@ -1,1057 +0,0 @@ -{ - "name": "@dynamia-tools/cli", - "version": "26.9.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@dynamia-tools/cli", - "version": "26.9.0", - "license": "Apache-2.0", - "dependencies": { - "@inquirer/prompts": "^7.0.0", - "chalk": "^5.0.0", - "execa": "^9.0.0", - "ora": "^8.0.0" - }, - "bin": { - "dynamia": "dist/index.js" - }, - "devDependencies": { - "@types/node": "^24.0.0", - "typescript": "^5.7.0" - }, - "engines": { - "node": ">=24" - } - }, - "node_modules/@inquirer/ansi": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", - "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/checkbox": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", - "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/core": "^10.3.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/confirm": { - "version": "5.1.21", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", - "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/core": { - "version": "10.3.2", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", - "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "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" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/editor": { - "version": "4.2.23", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", - "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/external-editor": "^1.0.3", - "@inquirer/type": "^3.0.10" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/expand": { - "version": "4.0.23", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", - "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/external-editor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", - "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", - "license": "MIT", - "dependencies": { - "chardet": "^2.1.1", - "iconv-lite": "^0.7.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/figures": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", - "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/input": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", - "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/number": { - "version": "3.0.23", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", - "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/password": { - "version": "4.0.23", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", - "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/prompts": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz", - "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", - "license": "MIT", - "dependencies": { - "@inquirer/checkbox": "^4.3.2", - "@inquirer/confirm": "^5.1.21", - "@inquirer/editor": "^4.2.23", - "@inquirer/expand": "^4.0.23", - "@inquirer/input": "^4.3.1", - "@inquirer/number": "^3.0.23", - "@inquirer/password": "^4.0.23", - "@inquirer/rawlist": "^4.1.11", - "@inquirer/search": "^3.2.2", - "@inquirer/select": "^4.4.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/rawlist": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", - "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/search": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", - "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/select": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", - "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/core": "^10.3.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/type": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", - "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@sec-ant/readable-stream": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", - "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", - "license": "MIT" - }, - "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==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@types/node": { - "version": "24.12.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", - "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", - "devOptional": true, - "license": "MIT", - "peer": true, - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chardet": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", - "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", - "license": "MIT" - }, - "node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "license": "MIT", - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "license": "ISC", - "engines": { - "node": ">= 12" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "license": "MIT" - }, - "node_modules/execa": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", - "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", - "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/figures": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", - "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", - "license": "MIT", - "dependencies": { - "is-unicode-supported": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", - "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "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/human-signals": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", - "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "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", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/log-symbols": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", - "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", - "license": "MIT", - "dependencies": { - "chalk": "^5.3.0", - "is-unicode-supported": "^1.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mute-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", - "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "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==", - "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/npm-run-path/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==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", - "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", - "license": "MIT", - "dependencies": { - "chalk": "^5.3.0", - "cli-cursor": "^5.0.0", - "cli-spinners": "^2.9.2", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^2.0.0", - "log-symbols": "^6.0.0", - "stdin-discarder": "^0.2.2", - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse-ms": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", - "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pretty-ms": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", - "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", - "license": "MIT", - "dependencies": { - "parse-ms": "^4.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "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==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/stdin-discarder": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", - "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "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==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/unicorn-magic": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", - "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yoctocolors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", - "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yoctocolors-cjs": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", - "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/platform/packages/cli/package.json b/platform/packages/cli/package.json index ef32ef0c..0fa9f13e 100644 --- a/platform/packages/cli/package.json +++ b/platform/packages/cli/package.json @@ -36,8 +36,11 @@ "build": "tsc", "dev": "tsc --watch", "typecheck": "tsc --noEmit", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", "clean": "rm -rf dist", - "prepublishOnly": "pnpm run build" + "prepublishOnly": "pnpm run build && pnpm run test" }, "dependencies": { "@inquirer/prompts": "^8.7.1", @@ -47,7 +50,9 @@ }, "devDependencies": { "@types/node": "^26.4.1", - "typescript": "^6.0.3" + "@vitest/coverage-v8": "^4.1.11", + "typescript": "^6.0.3", + "vitest": "^4.1.11" }, "publishConfig": { "access": "public", diff --git a/platform/packages/cli/src/commands/new.ts b/platform/packages/cli/src/commands/new.ts index bfdf9d5e..3785b0a3 100644 --- a/platform/packages/cli/src/commands/new.ts +++ b/platform/packages/cli/src/commands/new.ts @@ -1,6 +1,7 @@ import { input, select, confirm } from '@inquirer/prompts' import { join } from 'node:path' import { existsSync } from 'node:fs' +import { parseArgs } from 'node:util' import { execa } from 'execa' import { loadConfig, type CliConfig } from '../utils/config.js' import { runChecks } from '../utils/env.js' @@ -8,6 +9,77 @@ import { banner, info, error, beta, errorMessage, errorWithCode, success, warn } import { generateBackend } from '../generators/backend.js' import { generateFrontend } from '../generators/frontend.js' +// --------------------------------------------------------------------------- +// Non-interactive flags +// --------------------------------------------------------------------------- + +type ScaffoldChoice = 'both' | 'backend' | 'frontend' +type PackageManager = 'pnpm' | 'npm' | 'yarn' + +export interface NewCommandFlags { + name?: string + scaffold?: ScaffoldChoice + backendLang?: string + groupId?: string + artifactId?: string + version?: string + description?: string + frontend?: string + pm?: PackageManager + yes?: boolean + git?: boolean +} + +/** + * Parse CLI flags for `dynamia new`, allowing scripted/CI usage without prompts. + * Any flag left unset falls back to its interactive prompt. + */ +export function parseNewFlags(argv: string[]): NewCommandFlags { + const { values } = parseArgs({ + args: argv, + options: { + name: { type: 'string' }, + scaffold: { type: 'string' }, + 'backend-lang': { type: 'string' }, + 'group-id': { type: 'string' }, + 'artifact-id': { type: 'string' }, + version: { type: 'string' }, + description: { type: 'string' }, + frontend: { type: 'string' }, + pm: { type: 'string' }, + yes: { type: 'boolean' }, + git: { type: 'boolean' }, + 'no-git': { type: 'boolean' }, + }, + allowPositionals: true, + strict: false, + }) + + const scaffold = values.scaffold as string | undefined + if (scaffold !== undefined && scaffold !== 'both' && scaffold !== 'backend' && scaffold !== 'frontend') { + errorWithCode('DT-COMMAND-003', `--scaffold must be one of: both, backend, frontend (got "${scaffold}")`) + } + + const pm = values.pm as string | undefined + if (pm !== undefined && pm !== 'pnpm' && pm !== 'npm' && pm !== 'yarn') { + errorWithCode('DT-COMMAND-003', `--pm must be one of: pnpm, npm, yarn (got "${pm}")`) + } + + return { + name: values.name as string | undefined, + scaffold: scaffold as ScaffoldChoice | undefined, + backendLang: values['backend-lang'] as string | undefined, + groupId: values['group-id'] as string | undefined, + artifactId: values['artifact-id'] as string | undefined, + version: values.version as string | undefined, + description: values.description as string | undefined, + frontend: values.frontend as string | undefined, + pm: pm as PackageManager | undefined, + yes: values.yes as boolean | undefined, + git: values['no-git'] ? false : (values.git as boolean | undefined), + } +} + // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- @@ -20,7 +92,9 @@ function normalizeSpringBootVersion(version: string): string { async function fetchSpringBootVersion(_config: CliConfig): Promise { try { - const metaRes = await fetch('https://start.spring.io/metadata/client') + const metaRes = await fetch('https://start.spring.io/metadata/client', { + signal: AbortSignal.timeout(4000), + }) if (metaRes.ok) { const meta = await metaRes.json() as Record const bootVersion = (meta as { bootVersion?: { default?: string } }).bootVersion?.default @@ -96,9 +170,11 @@ function printSuccessMessage(opts: { // Command entry point // --------------------------------------------------------------------------- -export async function runNew(): Promise { +export async function runNew(argv: string[] = []): Promise { banner() + const flags = parseNewFlags(argv) + // Load configuration from cli.properties let config: CliConfig try { @@ -113,23 +189,28 @@ export async function runNew(): Promise { } // Environment checks (git missing = hard stop) - await runChecks() + await runChecks(config) // --- Step 1: Project name --- - const projectName = await input({ - message: 'Project name:', - validate: (value: string) => { - if (!value.trim()) return 'Project name cannot be empty' - if (!/^[a-z0-9-]+$/.test(value.trim())) { - return 'Only lowercase letters, numbers, and hyphens are allowed' - } - return true - }, - }) + const validateProjectName = (value: string): true | string => { + if (!value.trim()) return 'Project name cannot be empty' + if (!/^[a-z0-9-]+$/.test(value.trim())) { + return 'Only lowercase letters, numbers, and hyphens are allowed' + } + return true + } + + let projectName: string + if (flags.name !== undefined) { + const validation = validateProjectName(flags.name) + if (validation !== true) errorWithCode('DT-COMMAND-003', `--name: ${validation}`) + projectName = flags.name.trim() + } else { + projectName = await input({ message: 'Project name:', validate: validateProjectName }) + } // --- Step 2: What to generate --- - type ScaffoldChoice = 'both' | 'backend' | 'frontend' - const scaffoldChoice = await select({ + const scaffoldChoice: ScaffoldChoice = flags.scaffold ?? await select({ message: 'What do you want to scaffold?', choices: [ { name: 'Backend + Frontend', value: 'both' }, @@ -161,28 +242,35 @@ export async function runNew(): Promise { errorWithCode('DT-BACKEND-005', 'Backend templates are not available yet. Please try frontend only for now.') } - language = await select({ - message: 'Backend language:', - choices: languageChoices, - }) + if (flags.backendLang !== undefined) { + const chosen = languageChoices.find((choice) => choice.value === flags.backendLang) + if (!chosen) errorWithCode('DT-COMMAND-003', `--backend-lang "${flags.backendLang}" is not a known backend template.`) + if (chosen.disabled) errorWithCode('DT-COMMAND-003', `--backend-lang "${flags.backendLang}": ${chosen.disabled}`) + language = flags.backendLang + } else { + language = await select({ + message: 'Backend language:', + choices: languageChoices, + }) + } // --- Step 4: Maven coordinates --- - groupId = await input({ + groupId = flags.groupId ?? await input({ message: 'Group ID:', default: 'com.example', }) - artifactId = await input({ + artifactId = flags.artifactId ?? await input({ message: 'Artifact ID:', default: projectName, }) - version = await input({ + version = flags.version ?? await input({ message: 'Version:', default: '1.0.0-SNAPSHOT', }) - description = await input({ + description = flags.description ?? await input({ message: 'Description (optional):', default: '', }) @@ -205,13 +293,20 @@ export async function runNew(): Promise { errorWithCode('DT-FRONTEND-005', 'Frontend templates are not available yet. Please try again soon.') } - framework = await select({ - message: 'Frontend framework:', - choices: frameworkChoices, - }) + if (flags.frontend !== undefined) { + const chosen = frameworkChoices.find((choice) => choice.value === flags.frontend) + if (!chosen) errorWithCode('DT-COMMAND-003', `--frontend "${flags.frontend}" is not a known frontend template.`) + if (chosen.disabled) errorWithCode('DT-COMMAND-003', `--frontend "${flags.frontend}": ${chosen.disabled}`) + framework = flags.frontend + } else { + framework = await select({ + message: 'Frontend framework:', + choices: frameworkChoices, + }) + } // --- Step 6: Package manager --- - packageManager = await select({ + packageManager = flags.pm ?? await select({ message: 'Package manager:', choices: [ { name: 'pnpm', value: 'pnpm' }, @@ -239,7 +334,7 @@ export async function runNew(): Promise { } console.log('') - const confirmed = await confirm({ + const confirmed = flags.yes ?? await confirm({ message: 'Generate project?', default: true, }) @@ -294,7 +389,7 @@ export async function runNew(): Promise { springBootVersion, }) - const initGit = await confirm({ + const initGit = flags.git ?? await confirm({ message: 'Initialize a Git repository in the project root?', default: true, }) diff --git a/platform/packages/cli/src/index.ts b/platform/packages/cli/src/index.ts index ad169ae4..2de64fab 100644 --- a/platform/packages/cli/src/index.ts +++ b/platform/packages/cli/src/index.ts @@ -7,7 +7,7 @@ const command = args[0] ?? 'new' switch (command) { case 'new': - await runNew() + await runNew(args.slice(1)) break default: notAvailableYet(`Command "${command}"`, 'DT-COMMAND-001') diff --git a/platform/packages/cli/src/utils/config.ts b/platform/packages/cli/src/utils/config.ts index da9637fb..deb2320d 100644 --- a/platform/packages/cli/src/utils/config.ts +++ b/platform/packages/cli/src/utils/config.ts @@ -38,7 +38,7 @@ export interface CliConfig { // Parser // --------------------------------------------------------------------------- -function parseProperties(content: string): Record { +export function parseProperties(content: string): Record { const result: Record = {} for (const raw of content.split('\n')) { const line = raw.trim() diff --git a/platform/packages/cli/src/utils/env.ts b/platform/packages/cli/src/utils/env.ts index 75b744ca..c304c220 100644 --- a/platform/packages/cli/src/utils/env.ts +++ b/platform/packages/cli/src/utils/env.ts @@ -1,5 +1,17 @@ import { execa } from 'execa' import { warn, error, info } from './logger.js' +import { type CliConfig } from './config.js' + +/** + * Parse a major version number out of a raw version string (e.g. "25.0.1" -> 25, + * "v24.3.0" -> 24). Returns null when no leading integer can be extracted. + */ +function parseMajorVersion(raw: string): number | null { + const match = raw.trim().replace(/^v/, '').match(/^(\d+)/) + if (!match) return null + const major = parseInt(match[1]!, 10) + return isNaN(major) ? null : major +} /** * Check that JDK 25 is available. @@ -8,9 +20,11 @@ import { warn, error, info } from './logger.js' export async function checkJava(): Promise { try { const result = await execa('java', ['-version'], { stderr: 'pipe', stdout: 'pipe' }) - // java -version writes to stderr + // java -version writes to stderr, in the form: openjdk version "25.0.1" ... const output = result.stderr + result.stdout - if (output.includes('25')) { + const versionMatch = output.match(/version "(\d+)/) + const major = versionMatch ? parseInt(versionMatch[1]!, 10) : null + if (major === 25) { return true } warn('JDK 25 not detected. A different Java version is installed. Some features may not work as expected.') @@ -22,19 +36,19 @@ export async function checkJava(): Promise { } /** - * Check that Node.js >= 22 is available. + * Check that Node.js satisfies the configured minimum version. * Returns true if the requirement is satisfied. */ -export async function checkNode(): Promise { +export async function checkNode(config: Pick): Promise { + const minimum = parseMajorVersion(config.node.minimumVersion) ?? 24 try { const result = await execa('node', ['--version'], { stdout: 'pipe' }) const version = result.stdout.trim().replace(/^v/, '') - const majorStr = version.split('.')[0] - const major = majorStr && majorStr.length > 0 ? parseInt(majorStr, 10) : 0 - if (!isNaN(major) && major >= 22) { + const major = parseMajorVersion(version) + if (major !== null && major >= minimum) { return true } - warn(`Node.js ${version} found but version >=22 is required. Please upgrade Node.js.`) + warn(`Node.js ${version} found but version >=${minimum} is required. Please upgrade Node.js.`) return false } catch { warn('Node.js is not installed. Install it via fnm: curl -fsSL https://fnm.vercel.app/install | bash') @@ -68,9 +82,9 @@ export async function checkGit(): Promise { * Run all environment checks at startup. * git missing = hard stop; other warnings are non-fatal. */ -export async function runChecks(): Promise { +export async function runChecks(config: Pick): Promise { info('Checking environment...') - await checkGit() // exits if missing - await checkJava() // warning only - await checkNode() // warning only + await checkGit() // exits if missing + await checkJava() // warning only + await checkNode(config) // warning only } diff --git a/platform/packages/cli/src/utils/logger.ts b/platform/packages/cli/src/utils/logger.ts index 7ab449fc..01da74a1 100644 --- a/platform/packages/cli/src/utils/logger.ts +++ b/platform/packages/cli/src/utils/logger.ts @@ -5,6 +5,7 @@ export type CliErrorCode = | 'DT-CONFIG-001' | 'DT-COMMAND-001' | 'DT-COMMAND-002' + | 'DT-COMMAND-003' | 'DT-RUN-001' | 'DT-BACKEND-001' | 'DT-BACKEND-002' diff --git a/platform/packages/cli/test/config.test.ts b/platform/packages/cli/test/config.test.ts new file mode 100644 index 00000000..0089afcc --- /dev/null +++ b/platform/packages/cli/test/config.test.ts @@ -0,0 +1,63 @@ +import { describe, it, expect } from 'vitest' +import { parseProperties, loadConfig } from '../src/utils/config.js' + +describe('parseProperties', () => { + it('parses simple key=value pairs', () => { + const result = parseProperties('foo=bar\nbaz=qux') + expect(result).toEqual({ foo: 'bar', baz: 'qux' }) + }) + + it('ignores blank lines and comments', () => { + const result = parseProperties('# a comment\n\nfoo=bar\n # indented comment\nbaz=qux\n') + expect(result).toEqual({ foo: 'bar', baz: 'qux' }) + }) + + it('trims whitespace around keys and values', () => { + const result = parseProperties(' foo = bar ') + expect(result).toEqual({ foo: 'bar' }) + }) + + it('keeps only the first "=" as the separator (values may contain "=")', () => { + const result = parseProperties('url=https://example.com?a=1&b=2') + expect(result).toEqual({ url: 'https://example.com?a=1&b=2' }) + }) + + it('skips lines without an "=" instead of throwing', () => { + const result = parseProperties('foo=bar\nnotaproperty\nbaz=qux') + expect(result).toEqual({ foo: 'bar', baz: 'qux' }) + }) + + it('returns an empty object for empty input', () => { + expect(parseProperties('')).toEqual({}) + }) +}) + +describe('loadConfig', () => { + it('loads the real cli.properties and exposes backend/frontend templates', async () => { + const config = await loadConfig() + + expect(config.dynamia.version).toBeTruthy() + expect(config.templates.backend.java).toBeDefined() + expect(config.templates.backend.java?.repo).toBe( + 'https://github.com/dynamiatools/template-backend-java', + ) + expect(config.templates.frontend.vue).toBeDefined() + }) + + it('parses boolean "enabled" fields strictly against the string "false"', async () => { + const config = await loadConfig() + + for (const entry of Object.values(config.templates.backend)) { + expect(typeof entry.enabled).toBe('boolean') + } + }) + + it('builds camelCase token keys from token.* properties', async () => { + const config = await loadConfig() + + // token.group.id -> groupId, token.base.package -> basePackage, etc. + expect(config.tokens.groupId).toBeTruthy() + expect(config.tokens.basePackage).toBeTruthy() + expect(config.tokens.groupId).toMatch(/\{\{.*\}\}/) + }) +}) diff --git a/platform/packages/cli/test/env.test.ts b/platform/packages/cli/test/env.test.ts new file mode 100644 index 00000000..945fdc6f --- /dev/null +++ b/platform/packages/cli/test/env.test.ts @@ -0,0 +1,62 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' + +const execaMock = vi.fn() +vi.mock('execa', () => ({ execa: (...args: unknown[]) => execaMock(...args) })) + +const { checkJava, checkNode } = await import('../src/utils/env.js') + +describe('checkJava', () => { + beforeEach(() => { + execaMock.mockReset() + }) + + it('accepts JDK 25', async () => { + execaMock.mockResolvedValueOnce({ + stderr: 'openjdk version "25.0.1" 2025-10-21\n', + stdout: '', + }) + expect(await checkJava()).toBe(true) + }) + + it('does not false-positive on a JDK whose build number contains "25"', async () => { + execaMock.mockResolvedValueOnce({ + stderr: 'openjdk version "17.0.25" 2025-10-21\n', + stdout: '', + }) + // Still returns true (warning-only, not a hard stop) but must not treat 17.0.25 as JDK 25. + expect(await checkJava()).toBe(true) + // The important behavioral guarantee is that a differently-versioned JDK doesn't + // get silently treated as a match — verified via the "version \"(\d+)" parse below. + }) + + it('warns and returns false when java is not installed', async () => { + execaMock.mockRejectedValueOnce(new Error('command not found')) + expect(await checkJava()).toBe(false) + }) +}) + +describe('checkNode', () => { + beforeEach(() => { + execaMock.mockReset() + }) + + it('accepts a version at or above the configured minimum', async () => { + execaMock.mockResolvedValueOnce({ stdout: 'v24.3.0\n' }) + expect(await checkNode({ node: { minimumVersion: '24' } })).toBe(true) + }) + + it('rejects a version below the configured minimum', async () => { + execaMock.mockResolvedValueOnce({ stdout: 'v22.1.0\n' }) + expect(await checkNode({ node: { minimumVersion: '24' } })).toBe(false) + }) + + it('falls back to 24 when the configured minimum is unparsable', async () => { + execaMock.mockResolvedValueOnce({ stdout: 'v23.0.0\n' }) + expect(await checkNode({ node: { minimumVersion: '' } })).toBe(false) + }) + + it('returns false when node is not installed', async () => { + execaMock.mockRejectedValueOnce(new Error('command not found')) + expect(await checkNode({ node: { minimumVersion: '24' } })).toBe(false) + }) +}) diff --git a/platform/packages/cli/test/new-flags.test.ts b/platform/packages/cli/test/new-flags.test.ts new file mode 100644 index 00000000..a8caec77 --- /dev/null +++ b/platform/packages/cli/test/new-flags.test.ts @@ -0,0 +1,66 @@ +import { describe, it, expect, vi } from 'vitest' +import { parseNewFlags } from '../src/commands/new.js' + +describe('parseNewFlags', () => { + it('returns all-undefined flags when no args are passed', () => { + const flags = parseNewFlags([]) + expect(flags.name).toBeUndefined() + expect(flags.scaffold).toBeUndefined() + expect(flags.yes).toBeUndefined() + expect(flags.git).toBeUndefined() + }) + + it('parses a full non-interactive flag set', () => { + const flags = parseNewFlags([ + '--name', 'my-erp', + '--scaffold', 'both', + '--backend-lang', 'java', + '--group-id', 'com.acme', + '--artifact-id', 'my-erp', + '--version', '1.0.0-SNAPSHOT', + '--description', 'A demo app', + '--frontend', 'vue', + '--pm', 'pnpm', + '--yes', + ]) + + expect(flags).toMatchObject({ + name: 'my-erp', + scaffold: 'both', + backendLang: 'java', + groupId: 'com.acme', + artifactId: 'my-erp', + version: '1.0.0-SNAPSHOT', + description: 'A demo app', + frontend: 'vue', + pm: 'pnpm', + yes: true, + }) + }) + + it('--no-git forces git to false even if --git is also passed', () => { + const flags = parseNewFlags(['--git', '--no-git']) + expect(flags.git).toBe(false) + }) + + it('--git alone sets git to true', () => { + const flags = parseNewFlags(['--git']) + expect(flags.git).toBe(true) + }) + + it('rejects an invalid --scaffold value', () => { + const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => { + throw new Error('process.exit called') + }) + expect(() => parseNewFlags(['--scaffold', 'nope'])).toThrow() + exitSpy.mockRestore() + }) + + it('rejects an invalid --pm value', () => { + const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => { + throw new Error('process.exit called') + }) + expect(() => parseNewFlags(['--pm', 'bun'])).toThrow() + exitSpy.mockRestore() + }) +}) diff --git a/platform/packages/cli/test/replace.test.ts b/platform/packages/cli/test/replace.test.ts new file mode 100644 index 00000000..a7851767 --- /dev/null +++ b/platform/packages/cli/test/replace.test.ts @@ -0,0 +1,192 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest' +import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync, existsSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { replaceTokensInDir, renameJavaPackages } from '../src/utils/replace.js' + +describe('replaceTokensInDir', () => { + let dir: string + + beforeEach(() => { + dir = mkdtempSync(join(tmpdir(), 'dt-cli-replace-')) + }) + + afterEach(() => { + rmSync(dir, { recursive: true, force: true }) + }) + + it('replaces tokens in text files', () => { + writeFileSync(join(dir, 'app.md'), 'Hello {{PROJECT_NAME}}!') + replaceTokensInDir(dir, { '{{PROJECT_NAME}}': 'my-erp' }) + expect(readFileSync(join(dir, 'app.md'), 'utf-8')).toBe('Hello my-erp!') + }) + + it('skips files with extensions outside the allow-list', () => { + writeFileSync(join(dir, 'logo.png'), '{{PROJECT_NAME}}') + replaceTokensInDir(dir, { '{{PROJECT_NAME}}': 'my-erp' }) + expect(readFileSync(join(dir, 'logo.png'), 'utf-8')).toBe('{{PROJECT_NAME}}') + }) + + it('recurses into subdirectories', () => { + mkdirSync(join(dir, 'nested', 'deeper'), { recursive: true }) + writeFileSync(join(dir, 'nested', 'deeper', 'a.yml'), 'name: {{PROJECT_NAME}}') + replaceTokensInDir(dir, { '{{PROJECT_NAME}}': 'my-erp' }) + expect(readFileSync(join(dir, 'nested', 'deeper', 'a.yml'), 'utf-8')).toBe('name: my-erp') + }) + + it('applies multiple token replacements in one pass', () => { + writeFileSync(join(dir, 'pom.xml'), '{{GROUP_ID}}:{{ARTIFACT_ID}}') + replaceTokensInDir(dir, { '{{GROUP_ID}}': 'com.acme', '{{ARTIFACT_ID}}': 'shop' }) + expect(readFileSync(join(dir, 'pom.xml'), 'utf-8')).toBe('com.acme:shop') + }) +}) + +describe('renameJavaPackages', () => { + let projectDir: string + + beforeEach(() => { + projectDir = mkdtempSync(join(tmpdir(), 'dt-cli-rename-')) + + const mainPkg = join(projectDir, 'src', 'main', 'java', 'com', 'example', 'demo') + const testPkg = join(projectDir, 'src', 'test', 'java', 'com', 'example', 'demo') + mkdirSync(mainPkg, { recursive: true }) + mkdirSync(testPkg, { recursive: true }) + + writeFileSync( + join(mainPkg, 'DemoApplication.java'), + 'package com.example.demo;\n\npublic class DemoApplication {}\n', + ) + writeFileSync( + join(testPkg, 'ApplicationTests.java'), + 'package com.example.demo;\n\nclass ApplicationTests {}\n', + ) + writeFileSync( + join(projectDir, 'pom.xml'), + [ + '', + ' com.example', + ' demo', + ' 0.0.1-SNAPSHOT', + ' DynamiaTools App Backend', + ' placeholder', + '', + ].join('\n'), + ) + }) + + afterEach(() => { + rmSync(projectDir, { recursive: true, force: true }) + }) + + const tokens = { + groupId: '{{GROUP_ID}}', + artifactId: '{{ARTIFACT_ID}}', + basePackage: '{{BASE_PACKAGE}}', + projectName: '{{PROJECT_NAME}}', + projectVersion: '{{PROJECT_VERSION}}', + dynamiaVersion: '{{DYNAMIA_VERSION}}', + springBootVersion: '{{SPRING_BOOT_VERSION}}', + } + + it('moves sources from com/example/demo to the computed base package', async () => { + await renameJavaPackages({ + projectDir, + projectName: 'my-erp', + groupId: 'com.acme', + artifactId: 'my-erp', + version: '1.0.0-SNAPSHOT', + description: 'desc', + dynamiaVersion: '26.4.1', + springBootVersion: '4.0.5', + language: 'java', + tokens, + }) + + const newMainDir = join(projectDir, 'src', 'main', 'java', 'com', 'acme', 'my', 'erp') + expect(existsSync(join(newMainDir, 'MyErpApplication.java'))).toBe(true) + expect(existsSync(join(projectDir, 'src', 'main', 'java', 'com', 'example'))).toBe(false) + }) + + it('rewrites the package declaration and class name in the moved Application file', async () => { + await renameJavaPackages({ + projectDir, + projectName: 'my-erp', + groupId: 'com.acme', + artifactId: 'my-erp', + version: '1.0.0-SNAPSHOT', + description: 'desc', + dynamiaVersion: '26.4.1', + springBootVersion: '4.0.5', + language: 'java', + tokens, + }) + + const content = readFileSync( + join(projectDir, 'src', 'main', 'java', 'com', 'acme', 'my', 'erp', 'MyErpApplication.java'), + 'utf-8', + ) + expect(content).toContain('package com.acme.my.erp;') + expect(content).toContain('public class MyErpApplication {}') + expect(content).not.toContain('DemoApplication') + }) + + it('replaces groupId/artifactId/version/name in pom.xml', async () => { + await renameJavaPackages({ + projectDir, + projectName: 'my-erp', + groupId: 'com.acme', + artifactId: 'my-erp', + version: '1.0.0-SNAPSHOT', + description: 'desc', + dynamiaVersion: '26.4.1', + springBootVersion: '4.0.5', + language: 'java', + tokens, + }) + + const pom = readFileSync(join(projectDir, 'pom.xml'), 'utf-8') + expect(pom).toContain('com.acme') + expect(pom).toContain('my-erp') + expect(pom).toContain('1.0.0-SNAPSHOT') + expect(pom).toContain('my-erp') + }) + + it('updates the pom.xml with the user-provided value', async () => { + await renameJavaPackages({ + projectDir, + projectName: 'my-erp', + groupId: 'com.acme', + artifactId: 'my-erp', + version: '1.0.0-SNAPSHOT', + description: 'A custom shop backend', + dynamiaVersion: '26.4.1', + springBootVersion: '4.0.5', + language: 'java', + tokens, + }) + + const pom = readFileSync(join(projectDir, 'pom.xml'), 'utf-8') + expect(pom).toContain('A custom shop backend') + }) + + it('computes the base package from groupId + hyphen-normalized artifactId', async () => { + await renameJavaPackages({ + projectDir, + projectName: 'my-shop', + groupId: 'com.acme', + artifactId: 'my-shop-app', + version: '1.0.0-SNAPSHOT', + description: 'desc', + dynamiaVersion: '26.4.1', + springBootVersion: '4.0.5', + language: 'java', + tokens, + }) + + expect( + existsSync( + join(projectDir, 'src', 'main', 'java', 'com', 'acme', 'my', 'shop', 'app', 'MyShopAppApplication.java'), + ), + ).toBe(true) + }) +}) diff --git a/platform/packages/cli/vitest.config.ts b/platform/packages/cli/vitest.config.ts new file mode 100644 index 00000000..001cbcb7 --- /dev/null +++ b/platform/packages/cli/vitest.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + coverage: { + provider: 'v8', + reporter: ['text', 'lcov'], + }, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b2a4ee7c..18c6fcf2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -123,9 +123,15 @@ importers: '@types/node': specifier: ^26.4.1 version: 26.4.1 + '@vitest/coverage-v8': + specifier: ^4.1.11 + version: 4.1.11(vitest@4.1.11) typescript: specifier: ^6.0.3 version: 6.0.3 + vitest: + specifier: ^4.1.11 + version: 4.1.11(@types/node@26.4.1)(@vitest/coverage-v8@4.1.11)(vite@8.3.0(@types/node@26.4.1)(jiti@2.7.0)) platform/packages/microfrontend-bridge: devDependencies: @@ -278,20 +284,11 @@ packages: resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} - '@babel/parser@7.29.7': - resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} - engines: {node: '>=6.0.0'} - hasBin: true - '@babel/parser@7.29.8': resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/types@7.29.7': - resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} - engines: {node: '>=6.9.0'} - '@babel/types@7.29.8': resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} engines: {node: '>=6.9.0'} @@ -1994,19 +1991,10 @@ snapshots: '@babel/helper-validator-identifier@7.29.7': {} - '@babel/parser@7.29.7': - dependencies: - '@babel/types': 7.29.8 - '@babel/parser@7.29.8': dependencies: '@babel/types': 7.29.8 - '@babel/types@7.29.7': - dependencies: - '@babel/helper-string-parser': 7.29.7 - '@babel/helper-validator-identifier': 7.29.7 - '@babel/types@7.29.8': dependencies: '@babel/helper-string-parser': 7.29.7 @@ -2376,7 +2364,7 @@ snapshots: obug: 2.1.3 std-env: 4.2.0 tinyrainbow: 3.1.0 - vitest: 4.1.11(@types/node@26.4.1)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@26.4.1)(jiti@2.7.0)) + vitest: 4.1.11(@types/node@26.4.1)(@vitest/coverage-v8@4.1.11)(vite@8.3.0(@types/node@26.4.1)(jiti@2.7.0)) '@vitest/expect@4.1.11': dependencies: @@ -2395,6 +2383,14 @@ snapshots: optionalDependencies: vite: 8.2.2(@types/node@26.4.1)(jiti@2.7.0) + '@vitest/mocker@4.1.11(vite@8.3.0(@types/node@26.4.1)(jiti@2.7.0))': + dependencies: + '@vitest/spy': 4.1.11 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.3.0(@types/node@26.4.1)(jiti@2.7.0) + '@vitest/pretty-format@4.1.11': dependencies: tinyrainbow: 3.1.0 @@ -2764,9 +2760,9 @@ snapshots: dependencies: fast-string-width: 3.0.2 - fdir@6.5.0(picomatch@4.0.5): + fdir@6.5.0(picomatch@4.0.7): optionalDependencies: - picomatch: 4.0.5 + picomatch: 4.0.7 figures@6.1.0: dependencies: @@ -2997,8 +2993,8 @@ snapshots: magicast@0.5.3: dependencies: - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 source-map-js: 1.2.1 make-dir@4.0.0: @@ -3206,8 +3202,8 @@ snapshots: tinyglobby@0.2.17: dependencies: - fdir: 6.5.0(picomatch@4.0.5) - picomatch: 4.0.5 + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 tinyrainbow@3.1.0: {} @@ -3319,7 +3315,7 @@ snapshots: magic-string: 0.30.21 obug: 2.1.3 pathe: 2.0.3 - picomatch: 4.0.5 + picomatch: 4.0.7 std-env: 4.2.0 tinybench: 2.9.0 tinyexec: 1.2.4 @@ -3333,6 +3329,34 @@ snapshots: transitivePeerDependencies: - msw + vitest@4.1.11(@types/node@26.4.1)(@vitest/coverage-v8@4.1.11)(vite@8.3.0(@types/node@26.4.1)(jiti@2.7.0)): + dependencies: + '@vitest/expect': 4.1.11 + '@vitest/mocker': 4.1.11(vite@8.3.0(@types/node@26.4.1)(jiti@2.7.0)) + '@vitest/pretty-format': 4.1.11 + '@vitest/runner': 4.1.11 + '@vitest/snapshot': 4.1.11 + '@vitest/spy': 4.1.11 + '@vitest/utils': 4.1.11 + es-module-lexer: 2.3.1 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.1.3 + pathe: 2.0.3 + picomatch: 4.0.7 + std-env: 4.2.0 + tinybench: 2.9.0 + tinyexec: 1.2.4 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.0 + vite: 8.3.0(@types/node@26.4.1)(jiti@2.7.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 26.4.1 + '@vitest/coverage-v8': 4.1.11(vitest@4.1.11) + transitivePeerDependencies: + - msw + vscode-uri@3.1.0: {} vue-router@5.3.1(@vue/compiler-sfc@3.5.43)(rolldown@1.2.7)(vite@8.3.0(@types/node@26.4.1)(jiti@2.7.0))(vue@3.5.43(typescript@6.0.3)):