From 8beaadf476da0075b848bbb2afc4e7e2b4a98476 Mon Sep 17 00:00:00 2001 From: Kim T Date: Thu, 30 Jul 2026 22:04:55 -0700 Subject: [PATCH] [refactor] Remove inquirer from core; expose create question metadata instead ManagerLocal.create() imported and drove `inquirer` directly, pulling an interactive-CLI/UX dependency into an isomorphic browser/server library - any consumer embedding ManagerLocal for programmatic install/uninstall pulled in inquirer transitively even though they'd never call create(). Interactive prompting belongs in the CLI that uses this package, not the package itself. Replace create() with: - helpers/createQuestions.ts: createPackageQuestions() (org/package/ version) and createPackageVersionQuestions(type, org, pkg) (the rest - name/author/license/type/tags/url/etc, with type-appropriate choices and org+pkg-derived url/audio/image defaults), returning a library-agnostic question shape (name/type/message/default/choices/ validate/filter) rather than importing inquirer's own types. - ManagerLocal.createQuestions()/createVersionQuestions(org, pkg): thin delegating instance methods, so a CLI can still call them off an existing manager instance the way it called create() before. - createSave() now defaults `files` to [] and, for Presets/Projects, `plugins` to {} when the caller hasn't set them, rather than requiring every caller to remember to - this also fixes a latent bug in the removed code, which defaulted `plugins` to [] (an array) where the type is actually a slug->version map (PresetPlugins/ProjectPlugins). Removes `inquirer` from dependencies entirely. studiorack-cli (the current consumer of ManagerLocal.create()) will need a follow-up change to call createQuestions()/createVersionQuestions() with its own prompt library and pass the answers to createSave() - not included here, since this package needs to be merged and published first. --- package-lock.json | 542 +------------------------- package.json | 1 - src/classes/ManagerLocal.ts | 133 ++----- src/helpers/createQuestions.ts | 103 +++++ src/index-browser.ts | 1 + src/index.ts | 1 + tests/classes/ManagerLocal.test.ts | 39 ++ tests/helpers/createQuestions.test.ts | 59 +++ 8 files changed, 236 insertions(+), 643 deletions(-) create mode 100644 src/helpers/createQuestions.ts create mode 100644 tests/helpers/createQuestions.test.ts diff --git a/package-lock.json b/package-lock.json index 976061f..7a4d9b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,6 @@ "chalk": "^5.3.0", "fs-extra": "^11.2.0", "glob": "^11.0.0", - "inquirer": "^12.4.1", "js-yaml": "^4.1.0", "mime-types": "^3.0.2", "semver": "^7.6.3", @@ -770,340 +769,6 @@ "url": "https://github.com/sponsors/nzakas" } }, - "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/@isaacs/cliui": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", @@ -1614,7 +1279,7 @@ "version": "22.20.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -2071,30 +1736,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "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/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/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2166,12 +1807,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/chardet": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", - "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", - "license": "MIT" - }, "node_modules/chownr": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", @@ -2181,33 +1816,6 @@ "node": ">=18" } }, - "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/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -2264,12 +1872,6 @@ "node": ">=8" } }, - "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/es-module-lexer": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", @@ -2715,22 +2317,6 @@ "dev": true, "license": "MIT" }, - "node_modules/iconv-lite": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", - "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", - "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/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2751,32 +2337,6 @@ "node": ">=0.8.19" } }, - "node_modules/inquirer": { - "version": "12.11.1", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.11.1.tgz", - "integrity": "sha512-9VF7mrY+3OmsAfjH3yKz/pLbJ5z22E23hENKw3/LNSaA/sAt3v49bDRY+Ygct1xwuKT+U+cBfTzjCPySna69Qw==", - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/core": "^10.3.2", - "@inquirer/prompts": "^7.10.1", - "@inquirer/type": "^3.0.10", - "mute-stream": "^2.0.0", - "run-async": "^4.0.6", - "rxjs": "^7.8.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2787,15 +2347,6 @@ "node": ">=0.10.0" } }, - "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-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -3359,15 +2910,6 @@ "dev": true, "license": "MIT" }, - "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/nanoid": { "version": "3.3.16", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", @@ -3625,30 +3167,6 @@ "@rolldown/binding-win32-x64-msvc": "1.1.5" } }, - "node_modules/run-async": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-4.0.6.tgz", - "integrity": "sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "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/semver": { "version": "7.8.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", @@ -3734,32 +3252,6 @@ "dev": true, "license": "MIT" }, - "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/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/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -3850,7 +3342,9 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" + "dev": true, + "license": "0BSD", + "optional": true }, "node_modules/tsx": { "version": "4.23.1", @@ -3926,7 +3420,7 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/universalify": { @@ -4158,20 +3652,6 @@ "node": ">=0.10.0" } }, - "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/yallist": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", @@ -4194,18 +3674,6 @@ "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" - } - }, "node_modules/zod": { "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", diff --git a/package.json b/package.json index 8bc64fb..5f39435 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,6 @@ "chalk": "^5.3.0", "fs-extra": "^11.2.0", "glob": "^11.0.0", - "inquirer": "^12.4.1", "js-yaml": "^4.1.0", "mime-types": "^3.0.2", "semver": "^7.6.3", diff --git a/src/classes/ManagerLocal.ts b/src/classes/ManagerLocal.ts index 9e67150..b944b4e 100644 --- a/src/classes/ManagerLocal.ts +++ b/src/classes/ManagerLocal.ts @@ -26,16 +26,10 @@ import { isAdmin, runCliAsAdmin, } from '../helpers/file.js'; -import { - isValidGithubRepo, - isValidSlug, - isValidVersion, - pathGetSlug, - pathGetVersion, - toSlug, -} from '../helpers/utils.js'; +import { isValidGithubRepo, isValidSlug, isValidVersion, pathGetSlug, pathGetVersion } from '../helpers/utils.js'; import { commandExists, getArchitecture, getSystem, isTests } from '../helpers/utilsLocal.js'; import { apiBuffer } from '../helpers/api.js'; +import { CreateQuestion, createPackageQuestions, createPackageVersionQuestions } from '../helpers/createQuestions.js'; import { FileInterface } from '../types/File.js'; import { FileType } from '../types/FileType.js'; import { RegistryType } from '../types/Registry.js'; @@ -43,16 +37,14 @@ import { PluginFormat, pluginFormatDir } from '../types/PluginFormat.js'; import { ConfigInterface } from '../types/Config.js'; import { ConfigLocal } from './ConfigLocal.js'; import { packageCompatibleFiles, packageErrors, packageRecommendations } from '../helpers/package.js'; +import { PresetInterface } from '../types/Preset.js'; import { presetFormatDir } from '../types/PresetFormat.js'; +import { ProjectInterface } from '../types/Project.js'; import { projectFormatDir } from '../types/ProjectFormat.js'; import { FileFormat } from '../types/FileFormat.js'; -import { licenses } from '../types/License.js'; -import { PluginType, PluginTypeOption, pluginTypes } from '../types/PluginType.js'; -import { PresetTypeOption, presetTypes } from '../types/PresetType.js'; -import { ProjectTypeOption, projectTypes } from '../types/ProjectType.js'; +import { PluginType } from '../types/PluginType.js'; import { SystemType } from '../types/SystemType.js'; import { packageLoadFile, packageSaveFile } from '../helpers/packageLocal.js'; -import inquirer from 'inquirer'; export class ManagerLocal extends Manager { protected typeDir: string; @@ -138,102 +130,33 @@ export class ManagerLocal extends Manager { return dirTarget; } - async create(dirPath?: string) { - // TODO Rewrite this code after prototype is proven. - const pkgQuestions = [ - { - name: 'org', - type: 'input', - message: 'Org id', - default: 'org-name', - validate: (value: string) => value === toSlug(value), - }, - { - name: 'package', - type: 'input', - message: 'Package id', - default: 'package-name', - validate: (value: string) => value === toSlug(value), - }, - { - name: 'version', - type: 'input', - message: 'Package version', - default: '1.0.0', - validate: (value: string) => isValidVersion(value), - }, - ]; - const pkgAnswers = await inquirer.prompt(pkgQuestions as any); - let types: PluginTypeOption[] | PresetTypeOption[] | ProjectTypeOption[] = pluginTypes; - if (this.type === RegistryType.Apps) { - types = pluginTypes; - } else if (this.type === RegistryType.Presets) { - types = presetTypes; - } else if (this.type === RegistryType.Projects) { - types = projectTypes; - } - const pkgVersionQuestions = [ - { name: 'name', type: 'input', message: 'Package name' }, - { name: 'author', type: 'input', message: 'Author name' }, - { name: 'description', type: 'input', message: 'Description' }, - { name: 'license', type: 'list', message: 'License', choices: licenses }, - { name: 'type', type: 'list', message: 'Type', choices: types }, - { - name: 'tags', - type: 'input', - message: 'Tags (comma-separated)', - filter: (input: string) => - input - .split(',') - .map(tag => tag.trim()) - .filter(tag => tag.length > 0), - }, - { - name: 'url', - type: 'input', - message: 'Website url', - default: `https://github.com/${pkgAnswers.org}/${pkgAnswers.package}`, - }, - { - name: 'donate', - type: 'input', - message: 'Donation url', - }, - { - name: 'audio', - type: 'input', - message: 'Audio preview url', - default: `https://open-audio-stack.github.io/open-audio-stack-registry/${this.type}/${pkgAnswers.org}/${pkgAnswers.package}/${pkgAnswers.package}.flac`, - }, - { - name: 'image', - type: 'input', - message: 'Image preview url', - default: `https://open-audio-stack.github.io/open-audio-stack-registry/${this.type}/${pkgAnswers.org}/${pkgAnswers.package}/${pkgAnswers.package}.jpg`, - }, - { name: 'date', type: 'input', message: 'Date released', default: new Date().toISOString() }, - { name: 'changes', type: 'input', message: 'List of changes' }, - ]; - - const pkgVersionAnswers = await inquirer.prompt(pkgVersionQuestions as any); - // TODO prompt for each file. Left empty here deliberately - a freshly created package has no - // built/published release yet, so there's nothing to fill `files` with. createSave() reports - // this as a recommendation rather than treating it as a fatal validation error. - pkgVersionAnswers.files = []; - if (this.type === RegistryType.Presets || this.type === RegistryType.Projects) { - pkgVersionAnswers.plugins = []; - } - return this.createSave(`${pkgAnswers.org}/${pkgAnswers.package}`, pkgVersionAnswers as PackageVersion, dirPath); + // Interactive prompting (previously driven by the `inquirer` package directly from this + // method) doesn't belong in an isomorphic browser/server library - see review.md item 5. The + // question metadata below is what a CLI needs to drive its own prompt library; createSave() + // then persists whatever it collects. createQuestions() first, to obtain org/package (needed + // to compute createVersionQuestions()'s own defaults), then createVersionQuestions(org, pkg). + + createQuestions(): CreateQuestion[] { + return createPackageQuestions(); } - // Split out from create() so package persistence is testable without driving inquirer's - // interactive prompts. A freshly created package is expected to be incomplete (e.g. `files` - // stays empty until a release is built and published) so, unlike Package.addVersion() which - // throws on any validation error, this only logs errors/recommendations as a report and always - // persists - the point of `create` is to scaffold the metadata file for a developer to fill in - // over time, not to produce a fully valid, publishable package on the first pass. + createVersionQuestions(org: string, pkg: string): CreateQuestion[] { + return createPackageVersionQuestions(this.type, org, pkg); + } + + // A freshly created package is expected to be incomplete - there is no built/published release + // yet, so `files` (and, for Presets/Projects, `plugins`) default to empty rather than requiring + // the caller to remember to set them - so, unlike Package.addVersion() which throws on any + // validation error, this only logs errors/recommendations as a report and always persists - the + // point of `create` is to scaffold the metadata file for a developer to fill in over time, not + // to produce a fully valid, publishable package on the first pass. createSave(slug: string, pkgVersion: PackageVersion, dirPath?: string) { if (!isValidSlug(slug)) throw new Error(`Invalid package slug: ${slug}`); + if (!pkgVersion.files) pkgVersion.files = []; + if (this.type === RegistryType.Presets || this.type === RegistryType.Projects) { + const pkgVersionWithPlugins = pkgVersion as PresetInterface | ProjectInterface; + if (!pkgVersionWithPlugins.plugins) pkgVersionWithPlugins.plugins = {}; + } const errors = packageErrors(pkgVersion); const recs = packageRecommendations(pkgVersion); this.logReport(slug, errors, recs); diff --git a/src/helpers/createQuestions.ts b/src/helpers/createQuestions.ts new file mode 100644 index 0000000..02ab929 --- /dev/null +++ b/src/helpers/createQuestions.ts @@ -0,0 +1,103 @@ +import { isValidVersion, toSlug } from './utils.js'; +import { licenses } from '../types/License.js'; +import { PluginTypeOption, pluginTypes } from '../types/PluginType.js'; +import { PresetTypeOption, presetTypes } from '../types/PresetType.js'; +import { ProjectTypeOption, projectTypes } from '../types/ProjectType.js'; +import { RegistryType } from '../types/Registry.js'; + +// Deliberately not the `inquirer` package's own Question type - core has no interactive-prompt +// dependency (previously it depended on `inquirer` directly, which pulled a CLI/UX concern into +// this isomorphic browser/server library - see review.md item 5). This shape happens to line up +// with what most JS prompt libraries (inquirer included) expect for a single question, so a CLI +// can typically pass these straight through, but it's defined here independently. +export interface CreateQuestion { + name: string; + type: 'input' | 'list'; + message: string; + default?: string; + choices?: readonly { name: string; value: string; description?: string }[]; + validate?: (value: string) => boolean; + filter?: (value: string) => unknown; +} + +// The org/package/version questions needed to identify a new package before anything else can be +// asked (e.g. `packageVersionQuestions()` below needs `org`/`pkg` to compute its own defaults). +export function createPackageQuestions(): CreateQuestion[] { + return [ + { + name: 'org', + type: 'input', + message: 'Org id', + default: 'org-name', + validate: (value: string) => value === toSlug(value), + }, + { + name: 'package', + type: 'input', + message: 'Package id', + default: 'package-name', + validate: (value: string) => value === toSlug(value), + }, + { + name: 'version', + type: 'input', + message: 'Package version', + default: '1.0.0', + validate: (value: string) => isValidVersion(value), + }, + ]; +} + +// The remaining package version fields (see specification.md "Packages fields to populate") - +// parameterized by registry type and the org/package answered via createPackageQuestions() above, +// since several defaults (url/audio/image) are derived from them. +export function createPackageVersionQuestions(type: RegistryType, org: string, pkg: string): CreateQuestion[] { + let types: PluginTypeOption[] | PresetTypeOption[] | ProjectTypeOption[] = pluginTypes; + if (type === RegistryType.Presets) { + types = presetTypes; + } else if (type === RegistryType.Projects) { + types = projectTypes; + } + return [ + { name: 'name', type: 'input', message: 'Package name' }, + { name: 'author', type: 'input', message: 'Author name' }, + { name: 'description', type: 'input', message: 'Description' }, + { name: 'license', type: 'list', message: 'License', choices: licenses }, + { name: 'type', type: 'list', message: 'Type', choices: types }, + { + name: 'tags', + type: 'input', + message: 'Tags (comma-separated)', + filter: (input: string) => + input + .split(',') + .map(tag => tag.trim()) + .filter(tag => tag.length > 0), + }, + { + name: 'url', + type: 'input', + message: 'Website url', + default: `https://github.com/${org}/${pkg}`, + }, + { + name: 'donate', + type: 'input', + message: 'Donation url', + }, + { + name: 'audio', + type: 'input', + message: 'Audio preview url', + default: `https://open-audio-stack.github.io/open-audio-stack-registry/${type}/${org}/${pkg}/${pkg}.flac`, + }, + { + name: 'image', + type: 'input', + message: 'Image preview url', + default: `https://open-audio-stack.github.io/open-audio-stack-registry/${type}/${org}/${pkg}/${pkg}.jpg`, + }, + { name: 'date', type: 'input', message: 'Date released', default: new Date().toISOString() }, + { name: 'changes', type: 'input', message: 'List of changes' }, + ]; +} diff --git a/src/index-browser.ts b/src/index-browser.ts index c6aa878..f696647 100644 --- a/src/index-browser.ts +++ b/src/index-browser.ts @@ -18,6 +18,7 @@ export * from './classes/Registry.js'; export * from './helpers/api.js'; export * from './helpers/config.js'; // export * from './helpers/configLocal.js'; +export * from './helpers/createQuestions.js'; // export * from './helpers/file.js'; export * from './helpers/package.js'; // export * from './helpers/packageLocal.js'; diff --git a/src/index.ts b/src/index.ts index 335b5fc..0ad297f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,6 +18,7 @@ export * from './classes/RegistryLocal.js'; export * from './helpers/api.js'; export * from './helpers/config.js'; export * from './helpers/configLocal.js'; +export * from './helpers/createQuestions.js'; export * from './helpers/file.js'; export * from './helpers/package.js'; export * from './helpers/packageLocal.js'; diff --git a/tests/classes/ManagerLocal.test.ts b/tests/classes/ManagerLocal.test.ts index 7ae2a5a..1443180 100644 --- a/tests/classes/ManagerLocal.test.ts +++ b/tests/classes/ManagerLocal.test.ts @@ -328,6 +328,45 @@ test('Project sync, install project, add new dependency, remove new dependency', expect(omitDownloads(pkgNoDepsAgain)).toEqual(omitDownloads(PROJECT_NO_DEPS)); }); +test('createQuestions returns the org/package/version questions needed before createVersionQuestions', () => { + const manager = new ManagerLocal(RegistryType.Plugins, CONFIG); + const questions = manager.createQuestions(); + expect(questions.map(q => q.name)).toEqual(['org', 'package', 'version']); +}); + +test('createVersionQuestions returns type-appropriate choices and org/package-derived defaults', () => { + const pluginManager = new ManagerLocal(RegistryType.Plugins, CONFIG); + const pluginQuestions = pluginManager.createVersionQuestions('test-org', 'test-plugin'); + const pluginUrlQuestion = pluginQuestions.find(q => q.name === 'url'); + expect(pluginUrlQuestion?.default).toEqual('https://github.com/test-org/test-plugin'); + const pluginTypeQuestion = pluginQuestions.find(q => q.name === 'type'); + expect(pluginTypeQuestion?.choices?.length).toBeGreaterThan(0); + + const presetManager = new ManagerLocal(RegistryType.Presets, CONFIG); + const presetQuestions = presetManager.createVersionQuestions('test-org', 'test-preset'); + const presetTypeQuestion = presetQuestions.find(q => q.name === 'type'); + // Preset and Plugin types are different enums - a Presets manager must offer preset-specific + // choices, not fall through to the Plugins default. + expect(presetTypeQuestion?.choices).not.toEqual(pluginTypeQuestion?.choices); +}); + +test('Create save defaults files to an empty array and, for Presets/Projects, plugins to an empty object', () => { + const presetManager = new ManagerLocal(RegistryType.Presets, CONFIG); + const dirTarget: string = path.join(APP_DIR, 'create', 'test-org', 'test-preset-defaults'); + // Deliberately omit `files`/`plugins` entirely, as a CLI assembling answers from + // createQuestions()/createVersionQuestions() would - createSave() must fill both in rather + // than requiring every caller to remember this. + const pkgVersion = { ...PRESET } as Partial as PackageVersion; + delete (pkgVersion as any).files; + delete (pkgVersion as any).plugins; + + const filePath: string = presetManager.createSave('test-org/test-preset-defaults', pkgVersion, dirTarget); + + const saved = fileReadJson(filePath); + expect(saved.files).toEqual([]); + expect(saved.plugins).toEqual({}); +}); + test('Create save persists an incomplete package without throwing', () => { const manager = new ManagerLocal(RegistryType.Plugins, CONFIG); const pkgVersion: PackageVersion = { ...PLUGIN, files: [] }; diff --git a/tests/helpers/createQuestions.test.ts b/tests/helpers/createQuestions.test.ts new file mode 100644 index 0000000..f133773 --- /dev/null +++ b/tests/helpers/createQuestions.test.ts @@ -0,0 +1,59 @@ +import { expect, test } from 'vitest'; +import { createPackageQuestions, createPackageVersionQuestions } from '../../src/helpers/createQuestions'; +import { RegistryType } from '../../src/types/Registry'; +import { toSlug, isValidVersion } from '../../src/helpers/utils'; + +test('createPackageQuestions validates org/package as slugs and version as semver', () => { + const questions = createPackageQuestions(); + const org = questions.find(q => q.name === 'org'); + const pkg = questions.find(q => q.name === 'package'); + const version = questions.find(q => q.name === 'version'); + + expect(org?.validate?.('my-org')).toEqual(true); + expect(org?.validate?.('My Org')).toEqual(false); + expect(pkg?.validate?.('my-package')).toEqual(true); + expect(pkg?.validate?.('My Package')).toEqual(false); + expect(version?.validate?.('1.0.0')).toEqual(true); + expect(version?.validate?.('not-a-version')).toEqual(false); + + // Sanity check against the underlying helpers directly, so this test fails if their behavior + // ever diverges from what these questions assume. + expect(org?.validate?.('my-org')).toEqual('my-org' === toSlug('my-org')); + expect(version?.validate?.('1.0.0')).toEqual(isValidVersion('1.0.0')); +}); + +test('createPackageQuestions tags filter splits and trims a comma-separated string', () => { + // Tags live on createPackageVersionQuestions, not createPackageQuestions - covered below. + const questions = createPackageVersionQuestions(RegistryType.Plugins, 'test-org', 'test-plugin'); + const tags = questions.find(q => q.name === 'tags'); + expect(tags?.filter?.('Synth, Modulation, Effect ,')).toEqual(['Synth', 'Modulation', 'Effect']); +}); + +test('createPackageVersionQuestions derives url/audio/image defaults from type/org/package', () => { + const questions = createPackageVersionQuestions(RegistryType.Plugins, 'surge-synthesizer', 'surge'); + expect(questions.find(q => q.name === 'url')?.default).toEqual('https://github.com/surge-synthesizer/surge'); + expect(questions.find(q => q.name === 'audio')?.default).toEqual( + 'https://open-audio-stack.github.io/open-audio-stack-registry/plugins/surge-synthesizer/surge/surge.flac', + ); + expect(questions.find(q => q.name === 'image')?.default).toEqual( + 'https://open-audio-stack.github.io/open-audio-stack-registry/plugins/surge-synthesizer/surge/surge.jpg', + ); +}); + +test('createPackageVersionQuestions offers type-specific choices per registry type', () => { + const pluginChoices = createPackageVersionQuestions(RegistryType.Plugins, 'org', 'pkg').find( + q => q.name === 'type', + )?.choices; + const presetChoices = createPackageVersionQuestions(RegistryType.Presets, 'org', 'pkg').find( + q => q.name === 'type', + )?.choices; + const projectChoices = createPackageVersionQuestions(RegistryType.Projects, 'org', 'pkg').find( + q => q.name === 'type', + )?.choices; + + expect(pluginChoices?.length).toBeGreaterThan(0); + expect(presetChoices?.length).toBeGreaterThan(0); + expect(projectChoices?.length).toBeGreaterThan(0); + expect(pluginChoices).not.toEqual(presetChoices); + expect(presetChoices).not.toEqual(projectChoices); +});