From 771e9d7f2f2161376a0539af9697abdf74ae5980 Mon Sep 17 00:00:00 2001 From: Babacar NIANG Date: Wed, 19 Jun 2024 17:48:39 +0000 Subject: [PATCH 1/6] refactor: externalize templates and abstract init --- package-lock.json | 318 +++++++++++++++++++++++-------- package.json | 3 +- src/actions/init/init.actions.ts | 203 ++++++++++++++++++++ src/actions/init/init.types.ts | 5 + src/commands/init.command.ts | 175 +---------------- src/constants.ts | 1 + src/utils/utilities.helper.ts | 30 +++ 7 files changed, 487 insertions(+), 248 deletions(-) create mode 100644 src/actions/init/init.actions.ts create mode 100644 src/actions/init/init.types.ts create mode 100644 src/constants.ts diff --git a/package-lock.json b/package-lock.json index a8120be..428775c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "figlet": "^1.7.0", "fs-extra": "^11.2.0", "gradient-string": "^2.0.2", + "inquirer": "^9.2.22", "log-symbols": "^6.0.0", "mongodb": "^6.3.0", "node-emoji": "^2.1.3", @@ -1457,6 +1458,14 @@ "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==", "dev": true }, + "node_modules/@inquirer/figures": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.3.tgz", + "integrity": "sha512-ErXXzENMH5pJt5/ssXV0DfWUZqly8nGzf0UcBV9xTnP+KyffE2mqyxIMBrZ8ijQck2nU0TQm40EQB53YreyWHw==", + "engines": { + "node": ">=18" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1602,12 +1611,11 @@ } }, "node_modules/@ljharb/through": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.11.tgz", - "integrity": "sha512-ccfcIDlogiXNq5KcbAwbaO7lMh3Tm1i3khMPYpxlK8hH/W53zN81KM9coerRLOnTGu3nfXIniAmQbRI9OxbC0w==", - "dev": true, + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.13.tgz", + "integrity": "sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==", "dependencies": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.7" }, "engines": { "node": ">= 0.4" @@ -2635,7 +2643,6 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, "dependencies": { "type-fest": "^0.21.3" }, @@ -2854,7 +2861,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -2889,7 +2895,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -3104,7 +3109,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, "funding": [ { "type": "github", @@ -3194,14 +3198,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", - "dev": true, + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3405,8 +3413,7 @@ "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, "node_modules/ci-info": { "version": "3.9.0", @@ -3563,7 +3570,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "dev": true, "engines": { "node": ">= 12" } @@ -3603,7 +3609,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true, "engines": { "node": ">=0.8" } @@ -5044,7 +5049,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dev": true, "dependencies": { "clone": "^1.0.2" }, @@ -5062,17 +5066,19 @@ } }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", - "dev": true, + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-lazy-prop": { @@ -5363,6 +5369,25 @@ "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", "dev": true }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-get-iterator": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", @@ -5953,7 +5978,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, "dependencies": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", @@ -6330,16 +6354,19 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", - "dev": true, + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -6975,7 +7002,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -7091,12 +7117,11 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", - "dev": true, + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7106,7 +7131,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -7118,7 +7142,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -7259,7 +7282,6 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -7271,7 +7293,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -7367,18 +7388,17 @@ "dev": true }, "node_modules/inquirer": { - "version": "9.2.12", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.12.tgz", - "integrity": "sha512-mg3Fh9g2zfuVWJn6lhST0O7x4n03k7G8Tx5nvikJkbq8/CK47WDVm+UznF0G6s5Zi0KcyUisr6DU8T67N5U+1Q==", - "dev": true, + "version": "9.2.23", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.23.tgz", + "integrity": "sha512-kod5s+FBPIDM2xiy9fu+6wdU/SkK5le5GS9lh4FEBjBHqiMgD9lLFbCbuqFNAjNL2ZOy9Wd9F694IOzN9pZHBA==", "dependencies": { - "@ljharb/through": "^2.3.11", + "@inquirer/figures": "^1.0.3", + "@ljharb/through": "^2.3.13", "ansi-escapes": "^4.3.2", "chalk": "^5.3.0", "cli-cursor": "^3.1.0", "cli-width": "^4.1.0", "external-editor": "^3.1.0", - "figures": "^5.0.0", "lodash": "^4.17.21", "mute-stream": "1.0.0", "ora": "^5.4.1", @@ -7389,14 +7409,13 @@ "wrap-ansi": "^6.2.0" }, "engines": { - "node": ">=14.18.0" + "node": ">=18" } }, "node_modules/inquirer/node_modules/chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -7408,7 +7427,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, "dependencies": { "restore-cursor": "^3.1.0" }, @@ -7420,7 +7438,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true, "engines": { "node": ">=8" } @@ -7429,7 +7446,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, "engines": { "node": ">=10" }, @@ -7441,7 +7457,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" @@ -7457,7 +7472,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -7473,7 +7487,6 @@ "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", @@ -7496,7 +7509,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -7512,7 +7524,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" @@ -7525,7 +7536,6 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -9135,7 +9145,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", - "dev": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -9565,7 +9574,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -10645,7 +10653,6 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -10785,6 +10792,18 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/release-it/node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/release-it/node_modules/cosmiconfig": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", @@ -10811,6 +10830,151 @@ } } }, + "node_modules/release-it/node_modules/inquirer": { + "version": "9.2.12", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.12.tgz", + "integrity": "sha512-mg3Fh9g2zfuVWJn6lhST0O7x4n03k7G8Tx5nvikJkbq8/CK47WDVm+UznF0G6s5Zi0KcyUisr6DU8T67N5U+1Q==", + "dev": true, + "dependencies": { + "@ljharb/through": "^2.3.11", + "ansi-escapes": "^4.3.2", + "chalk": "^5.3.0", + "cli-cursor": "^3.1.0", + "cli-width": "^4.1.0", + "external-editor": "^3.1.0", + "figures": "^5.0.0", + "lodash": "^4.17.21", + "mute-stream": "1.0.0", + "ora": "^5.4.1", + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/release-it/node_modules/inquirer/node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/release-it/node_modules/inquirer/node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/release-it/node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/release-it/node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/release-it/node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/release-it/node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/release-it/node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/release-it/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==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -11069,7 +11233,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", - "dev": true, "engines": { "node": ">=0.12.0" } @@ -11101,7 +11264,6 @@ "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, "dependencies": { "tslib": "^2.1.0" } @@ -11128,7 +11290,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -11164,8 +11325,7 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/scule": { "version": "1.3.0", @@ -11222,16 +11382,16 @@ "dev": true }, "node_modules/set-function-length": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", - "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==", - "dev": true, + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dependencies": { - "define-data-property": "^1.1.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.2", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.1" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -11611,7 +11771,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -11911,7 +12070,6 @@ "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, "dependencies": { "os-tmpdir": "~1.0.2" }, @@ -11982,8 +12140,7 @@ "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/type": { "version": "2.7.2", @@ -12006,7 +12163,6 @@ "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, "engines": { "node": ">=10" }, @@ -12386,8 +12542,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/validate-npm-package-license": { "version": "3.0.4", @@ -12402,7 +12557,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dev": true, "dependencies": { "defaults": "^1.0.3" } diff --git a/package.json b/package.json index bc87aba..55dfd16 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,8 @@ "portscanner": "^2.2.0", "shelljs": "^0.8.5", "spinnies": "^0.5.1", - "tree-kill": "^1.2.2" + "tree-kill": "^1.2.2", + "inquirer": "^9.2.22" }, "devDependencies": { "@commitlint/cli": "18.4.3", diff --git a/src/actions/init/init.actions.ts b/src/actions/init/init.actions.ts new file mode 100644 index 0000000..c8e292e --- /dev/null +++ b/src/actions/init/init.actions.ts @@ -0,0 +1,203 @@ +import { checkNodeVersion } from '../../utils/utilities.helper'; +import { Action } from '../abstract.action'; +import { InitActionOptions } from './init.types'; +import { MongoClient } from 'mongodb'; + +import fsExtra from 'fs-extra'; +import { $, execaCommand } from 'execa'; +import fs from 'fs'; +import inquirer from 'inquirer'; +import ora from 'ora'; +import path from 'path'; +import { changeDirectory } from '../../utils/files.helper'; +import { printWithMantisGradient } from '../../utils/prettyPrint.helper'; + +export default class InitAction extends Action { + private options: InitActionOptions; + + constructor(options: InitActionOptions) { + super('[INIT-ACTION]'); + this.options = options; + } + + validateParams() { + const params = this.options; + if ( + params.createMobile && + !['true', 'false'].includes(params.createMobile) + ) { + throw new Error( + "Invalid value for --createMobile. Expected 'true' or 'false'", + ); + } + } + + async promptForMissingValues() { + const { mongodbUri, name } = this.options; + type PromptResult = { + workspaceName: string; + mongoUrl: string; + }; + const answers = await inquirer.prompt([ + { + type: 'input', + name: 'workspaceName', + message: 'Enter the name of the workspace to create:', + default: 'mantis', + when: () => !name, + validate: async (value) => { + if (value.trim().length <= 0) + return 'Workspace name must not be empty'; + if (await fsExtra.pathExists(value)) { + return `Workspace already exists: ${value}`; + } + + return true; + }, + }, + { + type: 'input', + name: 'mongoUrl', + message: + "Enter the MongoDB URI (default is 'mongodb://localhost:27017/mantis'):", + default: 'mongodb://localhost:27017/mantis', + when: () => !mongodbUri, + validate: async (value) => { + if (value.trim().length <= 0) return 'Db url must not be empty'; + try { + await MongoClient.connect(value); + return true; + } catch (error) { + return `Db url seems to be invalid: ${error.message}`; + } + }, + }, + ]); + + return { + workspaceName: answers.workspaceName || name, + mongoUrl: answers.mongoUrl || mongodbUri, + }; + } + + async createWorkspace(workspaceName: string) { + const spinner = ora(`Creating ${workspaceName} workspace...`).start(); + try { + const { stdout } = await execaCommand( + `yes | npx create-nx-workspace ${workspaceName} --preset=ts --nxCloud=skip --interactive=false`, + { shell: true }, + ); + console.log(stdout); + spinner.succeed(`Workspace ${workspaceName} created`); + } catch (error) { + spinner.fail(`Failed to create ${workspaceName} workspace`); + throw error; + } + } + + async setupEnvironment( + workspacePath: string, + mongoUrl: string, + frontendUrl: string = 'http://localhost:4200', + ) { + const spinner = ora('Setting up environment variables...').start(); + const envPath = path.join(workspacePath, '.env'); + const envContent = `MONGODB_URI=${mongoUrl}\nFRONTEND_URLS=${frontendUrl}`; + fs.writeFileSync(envPath, envContent); + spinner.succeed('Environment variables set'); + } + + async cloneAndSetupTemplate(params: { + workspaceName: string; + workspacePath: string; + createMobile: boolean; + }) { + const { workspaceName, workspacePath, createMobile } = params; + const spinner = ora('Setting up project template...').start(); + try { + await $`git clone --no-checkout https://github.com/mantis-apps/mantis-templates.git`; + changeDirectory('mantis-templates'); + await $`git sparse-checkout init`; + await $`git sparse-checkout set todo`; + await $`git checkout`; + + await $`mv todo/app-web ${workspacePath}/app-web`; + if (createMobile) { + await $`mv todo/app-mobile ${workspacePath}/app-mobile`; + } + await $`mv todo/shared-ui ${workspacePath}/shared-ui`; + await $`mv todo/tsconfig.base.json ${workspacePath}/tsconfig.base.json`; + await $`mv todo/jest.config.ts ${workspacePath}/jest.config.ts`; + await $`mv todo/jest.preset.js ${workspacePath}/jest.preset.js`; + await $`mv todo/package.json ${workspacePath}/package.json`; + await $`mv todo/package-lock.json ${workspacePath}/package-lock.json`; + await $`mv todo/nx.json ${workspacePath}/nx.json`; + + changeDirectory(workspacePath); + await $`npm pkg set name=@${workspaceName}/source`; + await $`rm -rf mantis-templates`; + spinner.succeed('Project template setup complete.'); + } catch (error) { + spinner.fail('Failed to set up project template.'); + throw error; + } + } + + async installDependencies(workspacePath: string) { + const spinner = ora('Installing dependencies...').start(); + try { + changeDirectory(workspacePath); + await $`npm install`; + spinner.succeed('Dependencies installed'); + } catch (error) { + spinner.fail('Dependencies installation failed'); + this.logger.error(error); + this.logger.warning( + 'Please retry package installation inside the workspace. If the issue persists, please contact support.', + ); + } + } + + async execute() { + try { + printWithMantisGradient(`🛖 WORKSPACE CREATION 🛠️`); + await checkNodeVersion(); + this.validateParams(); + const { workspaceName, mongoUrl } = await this.promptForMissingValues(); + const workspacePath = path.join(process.cwd(), workspaceName); + this.logger.info('Node version is valid'); + await this.createWorkspace(workspaceName); + await this.setupEnvironment(workspacePath, mongoUrl); + + // navigate to the workspace + changeDirectory(workspacePath); + + // remove node_modules, package-lock.json & package.json + await $`rm -rf node_modules package-lock.json package.json`; + + // clone and setup template + await this.cloneAndSetupTemplate({ + workspaceName, + workspacePath, + createMobile: this.options.createMobile === 'true', + }); + + // install dependencies + await this.installDependencies(workspacePath); + + // add mantis json + const mantisJsonPath = path.join(workspacePath, 'mantis.json'); + const mantisJsonContent = `{ + "name": "${workspaceName}", + "version": "0.0.1", + "description": "Mantis Workspace", + "workspace": ["@${workspaceName}/source"] + }`; + fs.writeFileSync(mantisJsonPath, mantisJsonContent); + fs.writeFileSync(mantisJsonPath, mantisJsonContent); + this.logger.info('Workspace initialized'); + } catch (error) { + this.logger.error(error); + } + } +} diff --git a/src/actions/init/init.types.ts b/src/actions/init/init.types.ts new file mode 100644 index 0000000..1a1d2ee --- /dev/null +++ b/src/actions/init/init.types.ts @@ -0,0 +1,5 @@ +export type InitActionOptions = { + name: string; + mongodbUri: string; + createMobile: 'true' | 'false'; +}; diff --git a/src/commands/init.command.ts b/src/commands/init.command.ts index dff7898..79f24c5 100644 --- a/src/commands/init.command.ts +++ b/src/commands/init.command.ts @@ -1,71 +1,22 @@ import { Command } from 'commander'; -import fs from 'fs-extra'; -import path from 'path'; -import { PackageManager, PackageManagerName, installDependencies } from 'nypm'; -import { fileURLToPath } from 'url'; -import { execa } from 'execa'; import Enquirer from 'enquirer'; import { MongoClient } from 'mongodb'; -import ora from 'ora'; -import logSymbols from 'log-symbols'; -import { replaceInFiles } from '../utils/files.helper'; -const __dirname = fileURLToPath(new URL('.', import.meta.url)); +import InitAction from '../actions/init/init.actions'; + +// const __dirname = fileURLToPath(new URL('.', import.meta.url)); export default new Command('init') .description('Create a basic mantis app') - .action(async () => { - const templateName = 'mantis-todo'; - const templatePath = path.resolve( - __dirname, - `../templates/${templateName}`, - ); - const workspaceName = await promptForWorkspaceName({ - defaultName: templateName, - }); - const workspacePath = path.join(process.cwd(), workspaceName); - - const { isLocal, dbUrl } = await promptForDbUrl(); - - await copyTemplate({ - template: { name: templateName, path: templatePath }, - workspace: { name: workspaceName, path: workspacePath }, - secrets: { dbUrl }, - }); - await installDependenciesWithMessage(workspacePath); - await startApplications(workspacePath, { - startLocalDb: isLocal, - }); + .option('-m, --mongodb-uri ', 'The MongoDB URI') + .option('-n, --name ', 'The name of the workspace') + .option('-cm, --create-mobile ', 'Create mobile app', true) + .action(async (params) => { + await new InitAction(params).execute(); }); -const promptForWorkspaceName = async ({ - defaultName, -}: { - defaultName: string; -}) => { - interface PromptResult { - workspaceName: string; - } - const { workspaceName } = await Enquirer.prompt([ - { - type: 'input', - name: 'workspaceName' satisfies keyof PromptResult, - message: 'Enter the name of the workspace to create:', - initial: defaultName, - validate: async (value) => { - if (value.trim().length <= 0) return 'Workspace name must not be empty'; - if (await fs.pathExists(value)) { - return `Workspace already exists: ${value}`; - } - - return true; - }, - }, - ]); - return workspaceName; -}; - -const promptForDbUrl = async (): Promise<{ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const _promptForDbUrl = async (): Promise<{ dbUrl: string; isLocal: boolean; }> => { @@ -116,109 +67,3 @@ const promptForDbUrl = async (): Promise<{ ]); return { isLocal: false, dbUrl }; }; - -const copyTemplate = async ({ - template, - workspace, - secrets: { dbUrl }, -}: { - template: { name: string; path: string }; - workspace: { name: string; path: string }; - secrets: { dbUrl: string }; -}) => { - const spinner = ora('Creating workspace').start(); - await fs.copy(template.path, workspace.path); - await renameGitignoreFiles(workspace.path); - const envPath = path.join(workspace.path, 'apps/web-client/.env.local'); - await fs.ensureFile(envPath); - await fs.appendFile(envPath, `\nMONGODB_URI='${dbUrl}'`); - replaceInFiles({ - dir: workspace.path, - searchStr: template.name, - replaceStr: workspace.name, - }); - spinner.succeed('Created workspace'); -}; - -/** - * This is needed because npm can't publish .gitignore files - * https://github.com/npm/npm/issues/3763 - */ -const renameGitignoreFiles = async (workspacePath: string) => { - const gitignoreFiles = await fs.readdir(workspacePath, { - withFileTypes: true, - recursive: true, - }); - await Promise.all( - gitignoreFiles - .filter((entry) => entry.isFile() && entry.name === '_gitignore') - .map(async (entry) => { - await fs.rename( - path.join(entry.path, entry.name), - path.join(entry.path, '.gitignore'), - ); - }), - ); -}; - -type PMTypePromptResult = Pick; -const promptForPackageManagerSelect = async (): Promise => { - const temp = await Enquirer.prompt({ - type: 'select', - name: 'name' satisfies keyof PMTypePromptResult, - message: "Select the package manager you'd like to use:", - choices: ['npm', 'bun', 'pnpm', 'yarn'] satisfies PackageManagerName[], - // This is to workaround a bug - // https://github.com/enquirer/enquirer/issues/121 - result(choice) { - return (this as any).map(choice)[choice]; - }, - }); - return temp; -}; - -const installDependenciesWithMessage = async (workspacePath: string) => { - const pm = await promptForPackageManagerSelect(); - const spinner = ora('Installing dependencies').start(); - await installDependencies({ - packageManager: pm.name, - cwd: workspacePath, - silent: true, - }); - spinner.succeed('Installed dependencies'); -}; - -const startApplications = async ( - workspacePath: string, - { startLocalDb }: { startLocalDb: boolean }, -) => { - console.log(`${logSymbols.info} Starting applications...`); - try { - await execa( - 'npx', - [ - 'nx', - 'run-many', - startLocalDb ? '--targets=serve,start-local-db' : '--target=serve', - '--projects=web-client,mobile-client', - ], - { cwd: workspacePath, stdio: 'inherit' }, - ); - } catch (err: unknown) { - if ( - err && - typeof err === 'object' && - 'exitCode' in err && - // 130 is the exit code for SIGINT (ctrl + c) - // i.e. the user cancelled the process - err.exitCode === 130 - ) { - return; - } - console.error( - `${logSymbols.error} Failed to start applications: ${ - err instanceof Error ? err.message : 'Unknown cause' - }`, - ); - } -}; diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..8228ecd --- /dev/null +++ b/src/constants.ts @@ -0,0 +1 @@ +export const MIN_NODE_VERSION = 16; diff --git a/src/utils/utilities.helper.ts b/src/utils/utilities.helper.ts index e271158..292ea06 100644 --- a/src/utils/utilities.helper.ts +++ b/src/utils/utilities.helper.ts @@ -1,3 +1,7 @@ +import { exec } from 'child_process'; +import { promisify } from 'util'; +import { MIN_NODE_VERSION } from '../constants'; + /** * Clears the console screen. */ @@ -8,3 +12,29 @@ export const clearConsole = () => { process.stdout.write('\x1Bc'); // '\x1Bc' is an escape sequence to clear the screen. console.log(''); }; + +const execAsync = promisify(exec); + +export async function runCommand(command: string) { + const { stdout, stderr } = await execAsync(command); + if (stderr) throw new Error(stderr); + return stdout; +} + +export async function checkNodeVersion( + requiredVersion: number = MIN_NODE_VERSION, +): Promise { + try { + const stdout = await runCommand('node -v'); + const version = parseInt(stdout.slice(1).split('.')[0]); + if (version < requiredVersion) { + console.error( + `Node version must be at least ${requiredVersion}. You are running version ${stdout}. Please update Node.js.`, + ); + process.exit(1); + } + } catch (error) { + console.error('Node is not installed. Please install Node.js.'); + process.exit(1); + } +} From b94413fe2c789b5522d9af62ae96dad955acb9c1 Mon Sep 17 00:00:00 2001 From: Babacar NIANG Date: Wed, 19 Jun 2024 18:00:26 +0000 Subject: [PATCH 2/6] feat: start command Add a "start" command to launch all generated applications simultaneously. --- package-lock.json | 44 ++++++ package.json | 5 +- src/actions/abstract.action.ts | 11 +- src/actions/init/init.actions.ts | 2 +- src/actions/start/start.action.ts | 242 ++--------------------------- src/actions/start/start.types.ts | 5 - src/commands/start.command.ts | 6 +- src/utils/verify-mantis-project.ts | 27 ++++ 8 files changed, 101 insertions(+), 241 deletions(-) delete mode 100644 src/actions/start/start.types.ts create mode 100644 src/utils/verify-mantis-project.ts diff --git a/package-lock.json b/package-lock.json index 428775c..25cc4f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "fs-extra": "^11.2.0", "gradient-string": "^2.0.2", "inquirer": "^9.2.22", + "joi": "^17.13.3", "log-symbols": "^6.0.0", "mongodb": "^6.3.0", "node-emoji": "^2.1.3", @@ -1410,6 +1411,19 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -2101,6 +2115,24 @@ } } }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + }, "node_modules/@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -8191,6 +8223,18 @@ "jiti": "bin/jiti.js" } }, + "node_modules/joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/package.json b/package.json index 55dfd16..022cf78 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,8 @@ "figlet": "^1.7.0", "fs-extra": "^11.2.0", "gradient-string": "^2.0.2", + "inquirer": "^9.2.22", + "joi": "^17.13.3", "log-symbols": "^6.0.0", "mongodb": "^6.3.0", "node-emoji": "^2.1.3", @@ -67,8 +69,7 @@ "portscanner": "^2.2.0", "shelljs": "^0.8.5", "spinnies": "^0.5.1", - "tree-kill": "^1.2.2", - "inquirer": "^9.2.22" + "tree-kill": "^1.2.2" }, "devDependencies": { "@commitlint/cli": "18.4.3", diff --git a/src/actions/abstract.action.ts b/src/actions/abstract.action.ts index e773995..00084d9 100644 --- a/src/actions/abstract.action.ts +++ b/src/actions/abstract.action.ts @@ -1,3 +1,4 @@ +import { verifyMantisProject } from './../../../../mantis-cli/src/utils/verify-mantis-project'; import { OrbitLogger } from '../utils/orbitLogger.helper'; /** @@ -7,8 +8,16 @@ import { OrbitLogger } from '../utils/orbitLogger.helper'; export abstract class Action { protected logger: OrbitLogger; - constructor(loggerContext: string) { + constructor( + loggerContext: string, + options: { checkMantisProject: boolean } = { + checkMantisProject: true, + }, + ) { this.logger = new OrbitLogger(loggerContext); + if (options?.checkMantisProject) { + verifyMantisProject(); + } } /** diff --git a/src/actions/init/init.actions.ts b/src/actions/init/init.actions.ts index c8e292e..89f28eb 100644 --- a/src/actions/init/init.actions.ts +++ b/src/actions/init/init.actions.ts @@ -16,7 +16,7 @@ export default class InitAction extends Action { private options: InitActionOptions; constructor(options: InitActionOptions) { - super('[INIT-ACTION]'); + super('[INIT-ACTION]', { checkMantisProject: false }); this.options = options; } diff --git a/src/actions/start/start.action.ts b/src/actions/start/start.action.ts index d28eb32..3c6f3ef 100644 --- a/src/actions/start/start.action.ts +++ b/src/actions/start/start.action.ts @@ -1,238 +1,38 @@ -import Enquirer from 'enquirer'; -import { isValidVariableName } from '../../utils/globalValidators.helper'; -import { - printWithBadge, - printWithMantisGradient, -} from '../../utils/prettyPrint.helper'; -import { Action } from '../abstract.action'; -import { StartCommandOptions } from './start.types'; -import { - changeDirectory, - createDirectory, - moveFile, - removeFile, - replaceInFile, - replaceInFiles, -} from '../../utils/files.helper'; +import { execa } from 'execa'; import { checkPortAvailability, execCommand, findAvailablePort, } from '../../utils/process.helper'; -import fs from 'fs'; -import path from 'path'; +import { Action } from '../abstract.action'; export default class StartAction extends Action { - private options: StartCommandOptions; private createMobileApp: boolean = false; - constructor(options: StartCommandOptions) { + constructor() { super('[START-ACTION]'); - this.options = options; } async execute() { - printWithMantisGradient(`🛖 NX WORKSPACE CREATION 🛠️`); - - this.logger.debug(`Started [START] Action...`, this.options); - - console.log(printWithBadge({ text: 'Hello World !' })); - - const { workspace, createMobileApp } = - this.options && this.options.workspace - ? this.options - : await this.getWorkspaceInfo(); - - this.createMobileApp = createMobileApp || false; - - this.logger.debug(`Prompts Answers: ${workspace} - ${createMobileApp}`); - + this.logger.debug(`Started [START] Action...`); try { - this.createNxWorkspace(workspace); - changeDirectory(path.resolve(workspace)); - this.installDependencies(); - this.createApplications(workspace); - this.createLibraryDirectories(); - this.installAngularPackage(); - this.generateFeatureLibrary(); - this.moveContent(workspace); - this.cleanupDirectories(workspace); - this.replaceHomePage(); - this.updateImportPaths(workspace); - // this.installConcurrentlyPackage(); - // await this.checkPorts(); - this.launchApplications(); - - this.logger.info('Workspace setup complete!'); + await execa( + 'npx', + ['nx', 'run-many', '--target=serve', '--projects=app-web,app-mobile'], + { + stdout: 'inherit', + stderr: 'inherit', + }, + ); + this.logger.info('Workspace started!'); } catch (error) { this.logger.error( - `Error occurred during workspace setup: ${error.message}`, + `Error occurred during workspace start: ${error.message}`, ); throw new Error(error); } } - private async getWorkspaceInfo(): Promise { - return Enquirer.prompt([ - { - type: 'input', - name: 'workspace', - message: 'Enter the name of the workspace (default is mantis):', - initial: 'mantis', - validate: (value) => isValidVariableName(value), - }, - ]) as Promise; - } - - private createNxWorkspace(workspace: string) { - try { - this.logger.info(`Creating NX workspace in ${process.cwd()}...`); - const command = `npx -y create-nx-workspace ${workspace} --preset nest --name ${workspace} --appName server --nxCloud false --docker true --create`; - - execCommand({ - command, - useSpinner: false, - }); - - this.logger.info(`NX workspace '${workspace}' created successfully.`); - } catch (error) { - this.logger.error(`Failed to create NX workspace: ${error.message}`); - throw new Error(error); - } - } - - private installDependencies() { - this.logger.info('Installing necessary plugins and dependencies...'); - execCommand({ - command: 'npm install --save-dev @nxext/ionic-angular @nxext/capacitor', - useSpinner: false, - }); - } - - private createApplications(workspace: string) { - const slugifiedName = workspace.toLowerCase().replace(/\s+/g, '-'); - this.logger.info(`Creating Ionic applications in ${process.cwd()}...`); - execCommand({ - command: `npx nx generate @nxext/ionic-angular:application ${slugifiedName}-app --template=blank`, - useSpinner: false, - }); - if (this.createMobileApp) { - execCommand({ - command: `npx nx generate @nxext/ionic-angular:application ${slugifiedName}-mobile --template=blank`, - useSpinner: false, - }); - } - } - - private createLibraryDirectories() { - this.logger.info('Creating library directories...'); - const LibsFolders = ['libs/shared/home', 'libs/web']; - - if (this.createMobileApp) LibsFolders.push('libs/mobile'); - - createDirectory('-p', LibsFolders); - } - - private installAngularPackage() { - this.logger.info('Installing @nrwl/angular package...'); - execCommand({ - command: 'npm install --save-dev @nrwl/angular', - useSpinner: false, - }); - } - - private generateFeatureLibrary() { - this.logger.info('Generating new feature library...'); - execCommand({ - command: - 'npx nx generate @nx/angular:library --name=feature --directory=shared/home --inlineStyle=true --inlineTemplate=true --standalone=true --no-interactive', - useSpinner: false, - }); - } - - private moveContent(workspace: string) { - const slugifiedName = workspace.toLowerCase().replace(/\s+/g, '-'); - const workspaceRoot = process.cwd(); - const sourcePath = `${workspaceRoot}/apps/${slugifiedName}-app/src/app/home`; - const destinationPath = `${workspaceRoot}/libs/shared/home/feature/src/lib`; - - if (fs.existsSync(sourcePath)) { - this.logger.info('Moving content...'); - moveFile(`${sourcePath}/*`, destinationPath); - } else { - this.logger.error(`Directory ${sourcePath} not found.`); - } - } - - private cleanupDirectories(workspace: string) { - const slugifiedName = workspace.toLowerCase().replace(/\s+/g, '-'); - const workspaceRoot = process.cwd(); - const webAppPath = `${workspaceRoot}/apps/${slugifiedName}-app/src/app/home`; - const mobileAppPath = `${workspaceRoot}/apps/${slugifiedName}-mobile/src/app/home`; - const featureLibPath = `${workspaceRoot}/libs/shared/home/feature/src/lib/shared-home-feature`; - - const pathsToRemove = [webAppPath, featureLibPath]; - if (this.createMobileApp) pathsToRemove.push(mobileAppPath); - - this.logger.info('Cleaning up unnecessary directories...'); - removeFile('-rf', pathsToRemove); - } - - private replaceHomePage() { - const workspaceRoot = process.cwd(); - const featureLibPath = `${workspaceRoot}/libs/shared/home/feature/src/lib`; - - this.logger.info( - 'Replacing instances of HomePage with HomePageComponent...', - ); - replaceInFiles({ - dir: featureLibPath, - searchStr: 'HomePage', - replaceStr: 'HomePageComponent', - }); - } - - private updateImportPaths(workspace: string) { - const workspaceRoot = process.cwd(); - const slugifiedName = workspace.toLowerCase().replace(/\s+/g, '-'); - - const featureLibIndexPath = path.resolve( - `${workspaceRoot}/libs/shared/home/feature/src/index.ts`, - ); - const webAppRoutingModulePath = path.resolve( - `${workspaceRoot}/apps/${slugifiedName}-app/src/app/app-routing.module.ts`, - ); - const webAppGlobalStylesPath = path.resolve( - `${workspaceRoot}/apps/${slugifiedName}-app/src/styles.scss`, - ); - const mobileAppRoutingModulePath = this.createMobileApp - ? path.resolve( - `${workspaceRoot}/apps/${slugifiedName}-mobile/src/app/app-routing.module.ts`, - ) - : ''; - const sharedHomeFeaturePath = `@${workspace}/shared/home/feature`; - - this.logger.info('-> Updating import paths...'); - - replaceInFile(webAppGlobalStylesPath, '~@', '@'); - replaceInFile( - featureLibIndexPath, - 'lib/shared-home-feature/shared-home-feature.component', - 'lib/home.module', - ); - replaceInFile( - webAppRoutingModulePath, - "import\\('./home/home.module'\\)\\.then\\(\\(m\\) => m\\.HomePageModule\\)", - `import('${sharedHomeFeaturePath}').then((m) => m.HomePageComponentModule)`, - ); - if (this.createMobileApp) - replaceInFile( - mobileAppRoutingModulePath, - "import\\('./home/home.module'\\)\\.then\\(\\(m\\) => m\\.HomePageModule\\)", - `import('${sharedHomeFeaturePath}').then((m) => m.HomePageComponentModule)`, - ); - } - private installConcurrentlyPackage() { this.logger.info('Installing concurrently package...'); execCommand({ @@ -279,18 +79,4 @@ export default class StartAction extends Action { ).toString(); } } - - private launchApplications() { - const command = 'npx nx run-many --target=serve --all --maxParallel=100'; - - try { - this.logger.info('Launching applications...'); - execCommand({ command, useSpinner: true }); - - this.logger.sponsor('Applications launched successfully.'); - } catch (error) { - this.logger.error(`Error launching applications: ${error.message}`); - throw new Error(error); - } - } } diff --git a/src/actions/start/start.types.ts b/src/actions/start/start.types.ts deleted file mode 100644 index feb286b..0000000 --- a/src/actions/start/start.types.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface StartCommandOptions { - workspace: string; - workDir?: string; - createMobileApp?: boolean; -} diff --git a/src/commands/start.command.ts b/src/commands/start.command.ts index a3502ec..70c4f36 100644 --- a/src/commands/start.command.ts +++ b/src/commands/start.command.ts @@ -2,9 +2,7 @@ import { Command } from 'commander'; import StartAction from '../actions/start/start.action'; export default new Command('start') - .description('Description of someCommand') - .option('-w, --workspace ', 'Specify the name of the workspace') - .option('--createMobileApp', 'Create a Mobile App') + .description('Start the Mantis project') .action(async () => { - await new StartAction({ workspace: '' }).execute(); + await new StartAction().execute(); }); diff --git a/src/utils/verify-mantis-project.ts b/src/utils/verify-mantis-project.ts new file mode 100644 index 0000000..13582c7 --- /dev/null +++ b/src/utils/verify-mantis-project.ts @@ -0,0 +1,27 @@ +import fs from 'fs-extra'; +import Joi from 'joi'; + +export const verifyMantisProject = () => { + // check if mantis.json exists + if (!fs.existsSync('mantis.json')) { + throw new Error('Mantis project not found'); + } + // check if mantis.json is valid + const mantisTxt = fs.readFileSync('mantis.json', 'utf8'); + let mantisJson; + try { + mantisJson = JSON.parse(mantisTxt); + } catch (error) { + throw new Error('Mantis project is not valid'); + } + const mantisJsonSchema = Joi.object({ + name: Joi.string().required(), + version: Joi.string().required(), + workspace: Joi.array().items(Joi.string()).required(), + description: Joi.string().optional(), + }); + const result = mantisJsonSchema.validate(mantisJson); + if (result.error) { + throw new Error('Mantis project is not valid'); + } +}; From 879dc03e743367012c16097e3ccae94699458480 Mon Sep 17 00:00:00 2001 From: Babacar NIANG Date: Wed, 19 Jun 2024 18:12:53 +0000 Subject: [PATCH 3/6] feat: generate command - [component] Implement a new "mantis" command within the CLI to facilitate the generation of shared components. This command will streamline the process of creating reusable components that can be utilized across multiple projects or parts of an application, enhancing modularity and reducing code duplication. --- src/actions/generate/generate.actions.ts | 86 ++++++++++++++++++++++++ src/actions/generate/generate.types.ts | 7 ++ src/commands/generate.command.ts | 11 +++ src/mantis-app.ts | 2 + 4 files changed, 106 insertions(+) create mode 100644 src/actions/generate/generate.actions.ts create mode 100644 src/actions/generate/generate.types.ts create mode 100644 src/commands/generate.command.ts diff --git a/src/actions/generate/generate.actions.ts b/src/actions/generate/generate.actions.ts new file mode 100644 index 0000000..fae6def --- /dev/null +++ b/src/actions/generate/generate.actions.ts @@ -0,0 +1,86 @@ +import ora from 'ora'; +import { Action } from '../abstract.action'; +import { GenerateActionOptions } from './generate.types'; +import { execa } from 'execa'; + +import fs from 'fs'; +import path from 'path'; + +export class GenerateAction extends Action { + private options: GenerateActionOptions; + + constructor(options: GenerateActionOptions) { + super('[GENERATE-ACTION]'); + this.options = options; + } + + updateIndexTs = async (name: string) => { + const filePath = path.join('shared-ui/src', 'index.ts'); + let content = await fs.promises.readFile(filePath, 'utf8'); + // add new line to the end of the file + content += `\nexport * from './lib/${name}/${name}.component';`; + await fs.promises.writeFile(filePath, content, 'utf8'); + }; + + addNewComponentToTsConfig = async (name: string) => { + const filePath = path.join('tsconfig.base.json'); + const content = await fs.promises.readFile(filePath, 'utf8'); + // Update tsconfig.base.json to add the new component in compilerOptions.paths array + const json = JSON.parse(content); + const COMPONENT_PATH = `shared-ui/src/lib/${name}/${name}.component`; + json.compilerOptions.paths[`@todo/${name}`] = [COMPONENT_PATH]; + await fs.promises.writeFile( + filePath, + JSON.stringify(json, null, 2), + 'utf8', + ); + }; + + generateComponent = async (name: string) => { + const spinner = ora(); + spinner.start('Generating component'); + // generate new shared ui component + await execa('npx', [ + 'nx', + 'generate', + '@nx/angular:component', + name, + '--directory', + `shared-ui/src/lib/${name}`, + '--nameAndDirectoryFormat', + 'as-provided', + ]); + // export the component from the index.ts file + await this.updateIndexTs(name); + // add new component to tsconfig.base.json + await this.addNewComponentToTsConfig(name); + spinner.succeed(); + }; + + async execute() { + try { + this.logger.info('Generating...'); + const { type, name, filePath } = this.options; + switch (type) { + case 'component': + await this.generateComponent(name); + break; + case 'service': + this.logger.warning('Unimplemented'); + break; + case 'mongo-schema': + if (!filePath) { + throw new Error('File path is required'); + } + this.logger.warning('Unimplemented'); + // await generateMongoSchema({ + // name, + // filePath, + // }); + break; + } + } catch (error) { + this.logger.error(error); + } + } +} diff --git a/src/actions/generate/generate.types.ts b/src/actions/generate/generate.types.ts new file mode 100644 index 0000000..bf82ad9 --- /dev/null +++ b/src/actions/generate/generate.types.ts @@ -0,0 +1,7 @@ +export type GeneratorType = 'component' | 'service' | 'mongo-schema'; + +export type GenerateActionOptions = { + type: GeneratorType; + name: string; + filePath?: string; +}; diff --git a/src/commands/generate.command.ts b/src/commands/generate.command.ts new file mode 100644 index 0000000..1c3e4a9 --- /dev/null +++ b/src/commands/generate.command.ts @@ -0,0 +1,11 @@ +import { Command } from 'commander'; +import { GenerateAction } from '../actions/generate/generate.actions'; + +export default new Command('generate') + .description('Generate a new Mantis feature') + .argument('', 'The type of thing to generate') + .argument('', 'The name of the thing to generate') + .option('-p, --path ', 'The path to the file') + .action(async (type, name, options) => { + await new GenerateAction({ type, name, filePath: options.path }).execute(); + }); diff --git a/src/mantis-app.ts b/src/mantis-app.ts index defdfdf..49c5bb4 100644 --- a/src/mantis-app.ts +++ b/src/mantis-app.ts @@ -4,6 +4,7 @@ import { welcome } from './utils/welcome.helper'; import packageJson from '../package.json'; import initCommand from './commands/init.command'; import startCommand from './commands/start.command'; +import generateCommand from './commands/generate.command'; const logger = new OrbitLogger('[BOOTSTRAP]'); @@ -33,6 +34,7 @@ const bootstrap = async () => { program.addCommand(initCommand); program.addCommand(startCommand); + program.addCommand(generateCommand); await program.parseAsync(process.argv); From 6daab03d8e57e8c5fd7f38054e9b8d528960c71d Mon Sep 17 00:00:00 2001 From: Babacar NIANG Date: Wed, 19 Jun 2024 18:33:40 +0000 Subject: [PATCH 4/6] fix(src/actions/abstract.action.ts): fix type type-check error --- src/actions/abstract.action.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/actions/abstract.action.ts b/src/actions/abstract.action.ts index 00084d9..c545b0f 100644 --- a/src/actions/abstract.action.ts +++ b/src/actions/abstract.action.ts @@ -1,4 +1,4 @@ -import { verifyMantisProject } from './../../../../mantis-cli/src/utils/verify-mantis-project'; +import { verifyMantisProject } from '../utils/verify-mantis-project'; import { OrbitLogger } from '../utils/orbitLogger.helper'; /** From 7b585272e5cd3838c7881f92882c6dc43192e381 Mon Sep 17 00:00:00 2001 From: Babacar NIANG Date: Sun, 30 Jun 2024 20:15:30 +0000 Subject: [PATCH 5/6] feat: package manager selection --- src/actions/init/init.actions.ts | 87 ++++++++++++++++++-------------- src/actions/init/init.types.ts | 4 +- src/commands/init.command.ts | 4 +- 3 files changed, 52 insertions(+), 43 deletions(-) diff --git a/src/actions/init/init.actions.ts b/src/actions/init/init.actions.ts index 89f28eb..6ce2682 100644 --- a/src/actions/init/init.actions.ts +++ b/src/actions/init/init.actions.ts @@ -2,6 +2,7 @@ import { checkNodeVersion } from '../../utils/utilities.helper'; import { Action } from '../abstract.action'; import { InitActionOptions } from './init.types'; import { MongoClient } from 'mongodb'; +import { PackageManager, PackageManagerName, installDependencies } from 'nypm'; import fsExtra from 'fs-extra'; import { $, execaCommand } from 'execa'; @@ -12,6 +13,8 @@ import path from 'path'; import { changeDirectory } from '../../utils/files.helper'; import { printWithMantisGradient } from '../../utils/prettyPrint.helper'; +type PMTypePromptResult = Pick; + export default class InitAction extends Action { private options: InitActionOptions; @@ -20,23 +23,33 @@ export default class InitAction extends Action { this.options = options; } - validateParams() { - const params = this.options; - if ( - params.createMobile && - !['true', 'false'].includes(params.createMobile) - ) { - throw new Error( - "Invalid value for --createMobile. Expected 'true' or 'false'", - ); - } - } + promptForPackageManagerSelect = async (): Promise => { + const temp = await inquirer.prompt({ + type: 'list', + name: 'name' satisfies keyof PMTypePromptResult, + message: "Select the package manager you'd like to use:", + choices: ['npm', 'bun', 'pnpm', 'yarn'] satisfies PackageManagerName[], + }); + return temp; + }; + + installDependenciesWithMessage = async (workspacePath: string) => { + const pm = await this.promptForPackageManagerSelect(); + const spinner = ora('Installing dependencies').start(); + await installDependencies({ + packageManager: pm.name, + cwd: workspacePath, + silent: true, + }); + spinner.succeed('Installed dependencies'); + return pm; + }; async promptForMissingValues() { - const { mongodbUri, name } = this.options; type PromptResult = { workspaceName: string; mongoUrl: string; + createMobile: boolean; }; const answers = await inquirer.prompt([ { @@ -44,7 +57,7 @@ export default class InitAction extends Action { name: 'workspaceName', message: 'Enter the name of the workspace to create:', default: 'mantis', - when: () => !name, + when: () => !this.options.workspaceName, validate: async (value) => { if (value.trim().length <= 0) return 'Workspace name must not be empty'; @@ -61,7 +74,7 @@ export default class InitAction extends Action { message: "Enter the MongoDB URI (default is 'mongodb://localhost:27017/mantis'):", default: 'mongodb://localhost:27017/mantis', - when: () => !mongodbUri, + when: () => !this.options.mongodbUri, validate: async (value) => { if (value.trim().length <= 0) return 'Db url must not be empty'; try { @@ -72,11 +85,20 @@ export default class InitAction extends Action { } }, }, + { + type: 'confirm', + name: 'createMobile', + message: 'Create mobile app?', + when: () => ![true, false].includes(this.options.createMobile), + }, ]); return { - workspaceName: answers.workspaceName || name, - mongoUrl: answers.mongoUrl || mongodbUri, + workspaceName: answers.workspaceName || this.options.workspaceName, + mongoUrl: answers.mongoUrl || this.options.mongodbUri, + createMobile: [true, false].includes(answers.createMobile) + ? answers.createMobile + : this.options.createMobile, }; } @@ -112,7 +134,7 @@ export default class InitAction extends Action { workspacePath: string; createMobile: boolean; }) { - const { workspaceName, workspacePath, createMobile } = params; + const { workspaceName, workspacePath } = params; const spinner = ora('Setting up project template...').start(); try { await $`git clone --no-checkout https://github.com/mantis-apps/mantis-templates.git`; @@ -122,7 +144,7 @@ export default class InitAction extends Action { await $`git checkout`; await $`mv todo/app-web ${workspacePath}/app-web`; - if (createMobile) { + if (params.createMobile) { await $`mv todo/app-mobile ${workspacePath}/app-mobile`; } await $`mv todo/shared-ui ${workspacePath}/shared-ui`; @@ -130,7 +152,7 @@ export default class InitAction extends Action { await $`mv todo/jest.config.ts ${workspacePath}/jest.config.ts`; await $`mv todo/jest.preset.js ${workspacePath}/jest.preset.js`; await $`mv todo/package.json ${workspacePath}/package.json`; - await $`mv todo/package-lock.json ${workspacePath}/package-lock.json`; + // await $`mv todo/package-lock.json ${workspacePath}/package-lock.json`; await $`mv todo/nx.json ${workspacePath}/nx.json`; changeDirectory(workspacePath); @@ -143,27 +165,13 @@ export default class InitAction extends Action { } } - async installDependencies(workspacePath: string) { - const spinner = ora('Installing dependencies...').start(); - try { - changeDirectory(workspacePath); - await $`npm install`; - spinner.succeed('Dependencies installed'); - } catch (error) { - spinner.fail('Dependencies installation failed'); - this.logger.error(error); - this.logger.warning( - 'Please retry package installation inside the workspace. If the issue persists, please contact support.', - ); - } - } - async execute() { try { printWithMantisGradient(`🛖 WORKSPACE CREATION 🛠️`); await checkNodeVersion(); - this.validateParams(); - const { workspaceName, mongoUrl } = await this.promptForMissingValues(); + const { workspaceName, mongoUrl, createMobile } = + await this.promptForMissingValues(); + const workspacePath = path.join(process.cwd(), workspaceName); this.logger.info('Node version is valid'); await this.createWorkspace(workspaceName); @@ -179,11 +187,11 @@ export default class InitAction extends Action { await this.cloneAndSetupTemplate({ workspaceName, workspacePath, - createMobile: this.options.createMobile === 'true', + createMobile, }); // install dependencies - await this.installDependencies(workspacePath); + const pm = await this.installDependenciesWithMessage(workspacePath); // add mantis json const mantisJsonPath = path.join(workspacePath, 'mantis.json'); @@ -191,7 +199,8 @@ export default class InitAction extends Action { "name": "${workspaceName}", "version": "0.0.1", "description": "Mantis Workspace", - "workspace": ["@${workspaceName}/source"] + "workspace": ["@${workspaceName}/source"], + "packageManager": "${pm.name}" }`; fs.writeFileSync(mantisJsonPath, mantisJsonContent); fs.writeFileSync(mantisJsonPath, mantisJsonContent); diff --git a/src/actions/init/init.types.ts b/src/actions/init/init.types.ts index 1a1d2ee..a0e3024 100644 --- a/src/actions/init/init.types.ts +++ b/src/actions/init/init.types.ts @@ -1,5 +1,5 @@ export type InitActionOptions = { - name: string; + workspaceName: string; mongodbUri: string; - createMobile: 'true' | 'false'; + createMobile: boolean; }; diff --git a/src/commands/init.command.ts b/src/commands/init.command.ts index 79f24c5..b4796b6 100644 --- a/src/commands/init.command.ts +++ b/src/commands/init.command.ts @@ -9,8 +9,8 @@ import InitAction from '../actions/init/init.actions'; export default new Command('init') .description('Create a basic mantis app') .option('-m, --mongodb-uri ', 'The MongoDB URI') - .option('-n, --name ', 'The name of the workspace') - .option('-cm, --create-mobile ', 'Create mobile app', true) + .option('-n, --workspace-name ', 'The name of the workspace') + .option('-cm, --create-mobile ', 'Create mobile app') .action(async (params) => { await new InitAction(params).execute(); }); From 103f127faaaed74e59877663c1b64ec599fb8e21 Mon Sep 17 00:00:00 2001 From: Babacar NIANG Date: Sun, 30 Jun 2024 20:30:18 +0000 Subject: [PATCH 6/6] fix: mantis project verification --- src/actions/start/start.action.ts | 54 ------------------------------ src/utils/verify-mantis-project.ts | 1 + 2 files changed, 1 insertion(+), 54 deletions(-) diff --git a/src/actions/start/start.action.ts b/src/actions/start/start.action.ts index 3c6f3ef..14224a0 100644 --- a/src/actions/start/start.action.ts +++ b/src/actions/start/start.action.ts @@ -1,14 +1,7 @@ import { execa } from 'execa'; -import { - checkPortAvailability, - execCommand, - findAvailablePort, -} from '../../utils/process.helper'; import { Action } from '../abstract.action'; export default class StartAction extends Action { - private createMobileApp: boolean = false; - constructor() { super('[START-ACTION]'); } @@ -32,51 +25,4 @@ export default class StartAction extends Action { throw new Error(error); } } - - private installConcurrentlyPackage() { - this.logger.info('Installing concurrently package...'); - execCommand({ - command: 'npm install --save-dev concurrently', - useSpinner: false, - }); - } - - private async checkPorts() { - const webPort = Number(process.env.WebPort) || 4200; - const mobilePort = Number(process.env.MobilePort) || 4300; - const serverPort = Number(process.env.ServerPort) || 3000; - - const isWebPortAvailable = await checkPortAvailability({ port: webPort }); - const isMobilePortAvailable = this.createMobileApp - ? await checkPortAvailability({ port: mobilePort }) - : true; - const isServerPortAvailable = await checkPortAvailability({ - port: serverPort, - }); - - if (!isWebPortAvailable) { - this.logger.warning(`Port ${webPort} (Web) is already in use`); - process.env.WebPort = ( - await findAvailablePort({ startPort: webPort, endPort: webPort + 99 }) - ).toString(); - } - if (!isMobilePortAvailable) { - this.logger.warning(`Port ${mobilePort} (Mobile) is already in use`); - process.env.MobilePort = ( - await findAvailablePort({ - startPort: mobilePort, - endPort: mobilePort + 99, - }) - ).toString(); - } - if (!isServerPortAvailable) { - this.logger.warning(`Port ${serverPort} (Server) is already in use`); - process.env.ServerPort = ( - await findAvailablePort({ - startPort: serverPort, - endPort: serverPort + 99, - }) - ).toString(); - } - } } diff --git a/src/utils/verify-mantis-project.ts b/src/utils/verify-mantis-project.ts index 13582c7..ffe7dd7 100644 --- a/src/utils/verify-mantis-project.ts +++ b/src/utils/verify-mantis-project.ts @@ -19,6 +19,7 @@ export const verifyMantisProject = () => { version: Joi.string().required(), workspace: Joi.array().items(Joi.string()).required(), description: Joi.string().optional(), + packageManager: Joi.string().required(), }); const result = mantisJsonSchema.validate(mantisJson); if (result.error) {