From 952db95b083448759c5c4e5fd22095c084c76851 Mon Sep 17 00:00:00 2001 From: nachoaldamav Date: Wed, 14 Sep 2022 13:38:14 +0200 Subject: [PATCH 1/5] =?UTF-8?q?feat=20=E2=9C=A8:=20(cli)=20Add=20`fnpm=20r?= =?UTF-8?q?emove`=20command=20close=20#36?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/src/commands/index.ts | 4 ++++ packages/cli/src/commands/remove.ts | 32 +++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 packages/cli/src/commands/remove.ts diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 28389a8..453d734 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -7,6 +7,7 @@ import checkVersion from "../utils/checkVersion.js"; import run from "./run.js"; import { update } from "../utils/readConfig.js"; import create from "./create.js"; +import remove from "./remove.js"; export async function commands(args: string[]) { const [command, ...rest] = args; @@ -39,6 +40,9 @@ export async function commands(args: string[]) { case "create": await create(rest); break; + case "remove": + await remove(rest); + break; default: console.log(`Unknown command: ${command}`); } diff --git a/packages/cli/src/commands/remove.ts b/packages/cli/src/commands/remove.ts new file mode 100644 index 0000000..2d8ef54 --- /dev/null +++ b/packages/cli/src/commands/remove.ts @@ -0,0 +1,32 @@ +import rpjf from "read-package-json-fast"; +import chalk from "chalk"; +import { writeFile } from "fs/promises"; + +export default async function remove(args: string[]) { + if (args.length === 0) { + console.log( + chalk.red("Please provide packages to remove, e.g. fnpm remove react") + ); + return; + } + + // Read CWD package.json + const pkg = await rpjf(process.cwd() + "/package.json"); + + // Remove packages from dependencies + for (const arg of args) { + delete pkg.dependencies[arg]; + delete pkg.devDependencies[arg]; + delete pkg.peerDependencies[arg]; + delete pkg.optionalDependencies[arg]; + } + + // Write CWD package.json + await writeFile( + process.cwd() + "/package.json", + JSON.stringify(pkg, null, 2) + ); + + console.log(chalk.green("Removed packages from package.json")); + console.log(chalk.yellow("Run `fnpm install` to update your node_modules")); +} From 3cfdf12d282c08729fddc46bc9b5204a309f8be2 Mon Sep 17 00:00:00 2001 From: nachoaldamav Date: Wed, 14 Sep 2022 13:40:05 +0200 Subject: [PATCH 2/5] =?UTF-8?q?fix=20=F0=9F=90=9B:=20(cli)=20Fix=20`fnpm?= =?UTF-8?q?=20create`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Compatible with `fnpm create astro|next|remix|react-app|vite` for example. --- packages/cli/src/commands/create.ts | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/commands/create.ts b/packages/cli/src/commands/create.ts index a634aab..c39bcee 100644 --- a/packages/cli/src/commands/create.ts +++ b/packages/cli/src/commands/create.ts @@ -19,7 +19,18 @@ export default async function create(args: string[]) { return; } - const command = args[0]; + // Get global config path + const npmPath = await execa("npm", ["config", "get", "prefix"]).then( + (res) => res.stdout + ); + + let command = args[0]; + + // If command doesn't start with create- then add it + if (!command.startsWith("create-")) { + command = `create-${command}`; + } + args.shift(); const spinner = ora(`Searching ${command} in NPM Registry...`).start(); @@ -27,6 +38,8 @@ export default async function create(args: string[]) { spinner.succeed(); spinner.text = `Found ${command} in NPM Registry`; + // Check if the package is already installed + const { install } = await prompts({ type: "confirm", name: "install", @@ -37,11 +50,6 @@ export default async function create(args: string[]) { }); if (install) { - // Get global config path - const npmPath = await execa("npm", ["config", "get", "prefix"]).then( - (res) => res.stdout - ); - const globalPath = path.join(npmPath, "lib", "node_modules", manifest.name); const __downloading = ora(`Downloading ${manifest.name}...`).start(); From e1f3bd41fc4c6521fe59dfc4c00b21dfe111e732 Mon Sep 17 00:00:00 2001 From: nachoaldamav Date: Wed, 14 Sep 2022 13:40:49 +0200 Subject: [PATCH 3/5] =?UTF-8?q?style=20=F0=9F=92=85:=20(cli)=20Change=20fn?= =?UTF-8?q?pm=20to=20FNPM=20in=20benchmark?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/src/commands/benchmark.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/commands/benchmark.ts b/packages/cli/src/commands/benchmark.ts index bce2652..a413b6e 100644 --- a/packages/cli/src/commands/benchmark.ts +++ b/packages/cli/src/commands/benchmark.ts @@ -65,17 +65,17 @@ const tests = [ group: 3, }, { - name: "fnpm install (no cache)", + name: "FNPM install (no cache)", command: "fnpm install", pre: "npm cache clean -f && fnpm clear", - spinner: ora(chalk.green(`Running "fnpm install (no cache)"...`)).stop(), + spinner: ora(chalk.green(`Running "FNPM install (no cache)"...`)).stop(), group: 1, }, { - name: "fnpm install (with cache)", + name: "FNPM install (with cache)", command: "fnpm install", pre: "rm -rf node_modules", - spinner: ora(chalk.green(`Running "fnpm install (with cache)"...`)).stop(), + spinner: ora(chalk.green(`Running "FNPM install (with cache)"...`)).stop(), group: 3, }, { From 127b01d576cfb990f9de719dc616ee528faa70ca Mon Sep 17 00:00:00 2001 From: nachoaldamav Date: Wed, 14 Sep 2022 13:41:45 +0200 Subject: [PATCH 4/5] =?UTF-8?q?feat=20=E2=9C=A8:=20(cli)=20Compare=20insta?= =?UTF-8?q?lled=20versions=20in=20node=5Fmodules=20close=20#48?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 6 +- packages/cli/package.json | 12 +-- packages/cli/src/commands/install.ts | 124 +++++++++++++++++++-------- packages/cli/src/utils/parseTime.ts | 22 +++++ pnpm-lock.yaml | 58 +++---------- 5 files changed, 133 insertions(+), 89 deletions(-) create mode 100644 packages/cli/src/utils/parseTime.ts diff --git a/package.json b/package.json index 812bff2..b14a672 100644 --- a/package.json +++ b/package.json @@ -18,13 +18,13 @@ }, "devDependencies": { "eslint-config-custom": "*", + "ora": "6.1.2", "prettier": "latest", - "turbo": "latest", - "ora": "6.1.2" + "turbo": "latest" }, "engines": { "npm": ">=7.0.0", "node": ">=14.0.0" }, "packageManager": "pnpm@7.11.0" -} \ No newline at end of file +} diff --git a/packages/cli/package.json b/packages/cli/package.json index 84d85c4..90b82c6 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -27,13 +27,15 @@ "dependencies": { "axios": "0.27.2", "chalk": "5.0.1", - "execa": "^6.1.0", + "execa": "6.1.0", "glob": "8.0.3", - "markdown-table": "^3.0.2", + "markdown-table": "3.0.2", "ora": "6.1.2", "pacote": "13.6.2", - "prompts": "^2.4.2", - "read-package-json-fast": "2.0.3" + "prompts": "2.4.2", + "read-package-json-fast": "2.0.3", + "npm-pick-manifest": "7.0.2", + "compare-versions": "5.0.1" }, "devDependencies": { "@types/glob": "7.2.0", @@ -43,4 +45,4 @@ "@swc/core": "1.2.242", "@types/mv": "2.1.2" } -} \ No newline at end of file +} diff --git a/packages/cli/src/commands/install.ts b/packages/cli/src/commands/install.ts index 4c3e81f..179a18e 100644 --- a/packages/cli/src/commands/install.ts +++ b/packages/cli/src/commands/install.ts @@ -5,8 +5,10 @@ import { mkdir, rm, readdir, writeFile } from "fs/promises"; import { exec } from "child_process"; import path from "path"; import { existsSync, readFileSync } from "fs"; -import { getDeps } from "../utils/getDeps.js"; import pacote from "pacote"; +import { performance } from "perf_hooks"; +import { satisfies } from "compare-versions"; +import { getDeps } from "../utils/getDeps.js"; import { installBins } from "../utils/addBinaries.js"; import { getDepsWorkspaces } from "../utils/getDepsWorkspaces.js"; import { installLocalDep } from "../utils/installLocalDep.js"; @@ -14,21 +16,27 @@ import { createModules } from "../utils/createModules.js"; import { hardLink } from "../utils/hardLink.js"; import getParamsDeps from "../utils/parseDepsParams.js"; import readConfig from "../utils/readConfig.js"; +import parseTime from "../utils/parseTime.js"; -let pkgs: { +type pkg = { name: string; version: string; + spec: string; tarball: string; parent?: string; -}[] = []; +}; + +let pkgs: pkg[] = []; -const __DOWNLOADING: string[] = []; -const __DOWNLOADED: string[] = []; const __INSTALLED: { name: string; version: string; }[] = []; +const __DOWNLOADING: string[] = []; +const __DOWNLOADED: string[] = []; +const __SKIPPED: string[] = []; + const userFnpmCache = readConfig().cache; const downloadFile = ".fnpm"; @@ -55,6 +63,7 @@ export default async function install(opts: string[]) { const deps = getDeps(pkg).concat(wsDeps).concat(addDeps); const __fetch = ora(chalk.green("Fetching packages...")).start(); + const __fetch_start = performance.now(); await Promise.all( deps.map(async (dep) => { @@ -74,15 +83,22 @@ export default async function install(opts: string[]) { pkgs.push({ name: dep.name, version: manifest.version, + spec: dep.version, tarball: manifest.dist.tarball, parent: dep.parent || undefined, }); }) ); - __fetch.succeed(chalk.green("Fetched all packages!")); + const __fetch_end = performance.now(); + __fetch.succeed( + chalk.green( + `Fetched packages in ${chalk.gray(parseTime(__fetch_start, __fetch_end))}` + ) + ); const __install = ora(chalk.green("Installing packages...")).start(); + const __install_start = performance.now(); await Promise.all( pkgs.map(async (pkg) => { @@ -90,12 +106,53 @@ export default async function install(opts: string[]) { }) ); - __install.succeed(chalk.green("Installed all packages!")); + const __install_end = performance.now(); + __install.succeed( + chalk.green( + `Installed packages in ${chalk.gray( + parseTime(__install_start, __install_end) + )}` + ) + ); + + // Get all __SKIPPED packages, check if they are in __INSTALLED and install them if not + await Promise.all( + [...__SKIPPED].map(async (pkg) => { + const isInstalled = __INSTALLED.find((i) => i.name === pkg); + + if (!isInstalled) { + const manifest = await pacote.manifest(`${pkg}@latest`, { + registry: REGISTRY, + }); + + ora(chalk.gray(`Installing ${pkg}@latest...`)).info(); + await installPkg( + { + name: pkg, + version: manifest.version, + spec: "latest", + tarball: manifest.dist.tarball, + }, + undefined, + undefined + ); + } + + return; + }) + ); const __binaries = ora(chalk.blue("Installing binaries...")).start(); + const __binaries_start = performance.now(); await installBins(); - - __binaries.succeed(chalk.blue("Installed binaries!")); + const __binaries_end = performance.now(); + __binaries.succeed( + chalk.blue( + `Installed binaries in ${chalk.gray( + parseTime(__binaries_start, __binaries_end) + )}` + ) + ); // If addDeps is not empty, add them to package.json using flag if (addDeps.length > 0) { @@ -153,7 +210,7 @@ export async function installPkg( spinner?: Ora ) { const cacheFolder = `${userFnpmCache}/${manifest.name}/${manifest.version}`; - // Check if package is already installed + if ( __INSTALLED.find( (pkg) => pkg.name === manifest.name && pkg.version === manifest.version @@ -162,6 +219,23 @@ export async function installPkg( return; } + const pkgInstalled = + manifest.spec && + __INSTALLED.find( + (pkg) => + pkg.name === manifest.name && satisfies(pkg.version, manifest.spec) + ); + + if (pkgInstalled) { + return; + } + + // Check if spec is * and add it to __SKIPPED + if (manifest.spec === "*") { + __SKIPPED.push(manifest.name); + return; + } + // Check if package is already in root node_modules const isSuitable = __INSTALLED.find((pkg) => pkg.name === manifest.name); @@ -177,18 +251,6 @@ export async function installPkg( name: manifest.name, version: manifest.version, }); - } else { - ora( - chalk.yellow( - `Package ${ - manifest.name - } is already installed in root node_modules! (${chalk.gray( - isSuitable.version - )} -> ${chalk.gray(manifest.version)}) Installing in ${chalk.grey( - pkgProjectDir.replace(process.cwd(), "") - )}...` - ) - ).warn(); } // Check if parent exists @@ -216,21 +278,11 @@ export async function installPkg( readFileSync(`${cacheFolder}/${downloadFile}`, "utf-8") ); - const thisPath = path.join( - process.cwd(), - "node_modules", - parent ? parent : "" - ); - for (const dep of Object.keys(cachedDeps)) { const name = dep; const version = Object.keys(cachedDeps[dep])[0]; const { tarball, pathname = path } = cachedDeps[dep][version]; - const depPath = path.join(thisPath, "node_modules", name); - - const installed = existsSync(depPath); - await installPkg( { name, @@ -265,8 +317,9 @@ export async function installPkg( dev: true, }); - if (deps.length > 0) - mkdir(`${cacheFolder}/node_modules`, { recursive: true }); + // Disable dir creation to test if it works :) + /* if (deps.length > 0) + mkdir(`${cacheFolder}/node_modules`, { recursive: true }); */ // Install production deps const installed = await Promise.all( @@ -283,6 +336,7 @@ export async function installPkg( name: dep.name, version: manifest.version, tarball: manifest.dist.tarball, + spec: dep.version, }, pkgProjectDir, spinner @@ -290,6 +344,7 @@ export async function installPkg( return { name: dep.name, version: manifest.version, + spec: dep.version, tarball: manifest.dist.tarball, path: path.join(userFnpmCache, dep.name, manifest.version), }; @@ -304,6 +359,7 @@ export async function installPkg( [dep.version]: { path: dep.path, tarball: dep.tarball, + spec: dep.spec, }, }; }); diff --git a/packages/cli/src/utils/parseTime.ts b/packages/cli/src/utils/parseTime.ts new file mode 100644 index 0000000..9279955 --- /dev/null +++ b/packages/cli/src/utils/parseTime.ts @@ -0,0 +1,22 @@ +export default function parseTime(a: number, b: number) { + // Convert diff to seconds, minutes, hours, days + const diff = Math.abs(a - b); + const seconds = Math.floor(diff / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + // Return the appropriate string + if (days > 0) { + return `${days} d!`; + } + if (hours > 0) { + return `${hours} h!`; + } + if (minutes > 0) { + return `${minutes} min!`; + } + if (seconds > 0) { + return `${seconds} s!`; + } + return `${diff.toFixed(2)} ms!`; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 69ead33..eaae865 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,13 +4,10 @@ importers: .: specifiers: - bin-links: ^3.0.3 eslint-config-custom: '*' ora: 6.1.2 prettier: latest turbo: latest - dependencies: - bin-links: 3.0.3 devDependencies: eslint-config-custom: link:packages/eslint-config-custom ora: 6.1.2 @@ -125,19 +122,23 @@ importers: '@types/prompts': ^2.0.14 axios: 0.27.2 chalk: 5.0.1 - execa: ^6.1.0 + compare-versions: 5.0.1 + execa: 6.1.0 glob: 8.0.3 - markdown-table: ^3.0.2 + markdown-table: 3.0.2 + npm-pick-manifest: 7.0.2 ora: 6.1.2 pacote: 13.6.2 - prompts: ^2.4.2 + prompts: 2.4.2 read-package-json-fast: 2.0.3 dependencies: axios: 0.27.2 chalk: 5.0.1 + compare-versions: 5.0.1 execa: 6.1.0 glob: 8.0.3 markdown-table: 3.0.2 + npm-pick-manifest: 7.0.2 ora: 6.1.2 pacote: 13.6.2 prompts: 2.4.2 @@ -3271,18 +3272,6 @@ packages: resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} dev: true - /bin-links/3.0.3: - resolution: {integrity: sha512-zKdnMPWEdh4F5INR07/eBrodC7QrF5JKvqskjz/ZZRXg5YSAZIbn8zGhbhUrElzHBZ2fvEQdOU59RHcTG3GiwA==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - dependencies: - cmd-shim: 5.0.0 - mkdirp-infer-owner: 2.0.0 - npm-normalize-package-bin: 2.0.0 - read-cmd-shim: 3.0.1 - rimraf: 3.0.2 - write-file-atomic: 4.0.2 - dev: false - /binary-extensions/2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} @@ -3646,13 +3635,6 @@ packages: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} - /cmd-shim/5.0.0: - resolution: {integrity: sha512-qkCtZ59BidfEwHltnJwkyVZn+XQojdAySM1D1gSeh11Z4pW1Kpolkyo53L5noc0nrxmIvyFwTmJRo4xs7FFLPw==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - dependencies: - mkdirp-infer-owner: 2.0.0 - dev: false - /code-block-writer/10.1.1: resolution: {integrity: sha512-67ueh2IRGst/51p0n6FvPrnRjAGHY5F8xdjkgrYE7DDzpJe6qA07RYQ9VcoUeo5ATOjSOiWpSL3SWBRRbempMw==} dev: false @@ -3706,6 +3688,10 @@ packages: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} dev: true + /compare-versions/5.0.1: + resolution: {integrity: sha512-v8Au3l0b+Nwkp4G142JcgJFh1/TUhdxut7wzD1Nq1dyp5oa3tXaqb03EXOAB6jS4gMlalkjAUPZBMiAfKUixHQ==} + dev: false + /component-emitter/1.3.0: resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==} dev: true @@ -7280,15 +7266,6 @@ packages: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} dev: true - /mkdirp-infer-owner/2.0.0: - resolution: {integrity: sha512-sdqtiFt3lkOaYvTXSRIUjkIdPTcxgv5+fgqYE/5qgwdw12cOrAuzzgzvVExIkH/ul1oeHN3bCLOWSG3XOqbKKw==} - engines: {node: '>=10'} - dependencies: - chownr: 2.0.0 - infer-owner: 1.0.4 - mkdirp: 1.0.4 - dev: false - /mkdirp/1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} @@ -8231,11 +8208,6 @@ packages: pify: 2.3.0 dev: true - /read-cmd-shim/3.0.1: - resolution: {integrity: sha512-kEmDUoYf/CDy8yZbLTmhB1X9kkjf9Q80PCNsDMb7ufrGd6zZSQA1+UyjrO+pZm5K/S4OXCWJeiIt1JA8kAsa6g==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - dev: false - /read-package-json-fast/2.0.3: resolution: {integrity: sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ==} engines: {node: '>=10'} @@ -9786,14 +9758,6 @@ packages: signal-exit: 3.0.7 dev: true - /write-file-atomic/4.0.2: - resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - dependencies: - imurmurhash: 0.1.4 - signal-exit: 3.0.7 - dev: false - /ws/7.5.9: resolution: {integrity: sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==} engines: {node: '>=8.3.0'} From a3e21e0c9271fe12aa60aed909c5967fd5b8b3ec Mon Sep 17 00:00:00 2001 From: nachoaldamav Date: Wed, 14 Sep 2022 13:41:47 +0200 Subject: [PATCH 5/5] =?UTF-8?q?chore=20=F0=9F=94=A7:=20bump=20version=20to?= =?UTF-8?q?=20v0.4.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 4 ++-- packages/cli/package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index b14a672..987c572 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fnpm", - "version": "0.3.10", + "version": "0.4.0", "private": true, "repository": { "type": "git", @@ -27,4 +27,4 @@ "node": ">=14.0.0" }, "packageManager": "pnpm@7.11.0" -} +} \ No newline at end of file diff --git a/packages/cli/package.json b/packages/cli/package.json index 90b82c6..ed76ead 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@fnpm-io/cli", - "version": "0.3.10", + "version": "0.4.0", "description": "FNPM CLI Tool.", "private": false, "main": "build/bin/cli.js", @@ -45,4 +45,4 @@ "@swc/core": "1.2.242", "@types/mv": "2.1.2" } -} +} \ No newline at end of file