From e1d3ae699bf1526d373ffd7c7d92d5d75c477773 Mon Sep 17 00:00:00 2001 From: Julien Date: Wed, 5 Jul 2023 14:53:28 +0200 Subject: [PATCH 1/2] feat: add Configure.installPackages method --- commands/configure.ts | 36 ++++++++ package.json | 1 + tests/commands/configure.spec.ts | 137 +++++++++++++++++++++++++++++++ 3 files changed, 174 insertions(+) diff --git a/commands/configure.ts b/commands/configure.ts index 78571838..39915432 100644 --- a/commands/configure.ts +++ b/commands/configure.ts @@ -11,6 +11,8 @@ import { slash } from '@poppinss/utils' import { EnvEditor } from '../modules/env.js' import type { ApplicationService } from '../src/types.js' import { args, BaseCommand } from '../modules/ace/main.js' +import { installPackage, detectPackageManager } from '@antfu/install-pkg' +import { fileURLToPath } from 'node:url' /** * The configure command is used to configure packages after installation @@ -111,6 +113,40 @@ export default class Configure extends BaseCommand { this.logger.action('update .adonisrc.json file').succeeded() } + /** + * Install packages using the correct package manager + * You can specify version of each package by setting it in the + * name like : + * + * ``` + * installPackages(['@adonisjs/lucid@next', '@adonisjs/auth@3.0.0']) + * ``` + */ + async installPackages(packages: { name: string; isDevDependency: boolean }[]) { + const appPath = fileURLToPath(this.app.appRoot) + + const devDeps = packages.filter((pkg) => pkg.isDevDependency).map(({ name }) => name) + const deps = packages.filter((pkg) => !pkg.isDevDependency).map(({ name }) => name) + + const packageManager = await detectPackageManager(appPath) + let spinner = this.logger + .await(`installing dependencies using ${packageManager || 'npm'}`) + .start() + + try { + await installPackage(deps, { cwd: appPath, silent: true }) + await installPackage(devDeps, { dev: true, cwd: appPath, silent: true }) + + spinner.stop() + this.logger.success('dependencies installed') + this.logger.log(devDeps.map((dep) => ` ${this.colors.dim('dev')} ${dep}`).join('\n')) + this.logger.log(deps.map((dep) => ` ${this.colors.dim('prod')} ${dep}`).join('\n')) + } catch (error) { + spinner.stop() + this.logger.error(`unable to install dependencies :\n ${this.colors.red(error.message)}`) + } + } + /** * List the packages one should install before using the packages */ diff --git a/package.json b/package.json index 004d3a91..5f0e0dc0 100644 --- a/package.json +++ b/package.json @@ -142,6 +142,7 @@ "@adonisjs/hash": "^8.3.1-3", "@adonisjs/http-server": "^6.8.2-7", "@adonisjs/logger": "^5.4.2-3", + "@antfu/install-pkg": "^0.1.1", "@paralleldrive/cuid2": "^2.2.0", "@poppinss/macroable": "^1.0.0-7", "@poppinss/utils": "^6.5.0-3", diff --git a/tests/commands/configure.spec.ts b/tests/commands/configure.spec.ts index d7bb4d20..eeb9f7dc 100644 --- a/tests/commands/configure.spec.ts +++ b/tests/commands/configure.spec.ts @@ -400,4 +400,141 @@ test.group('Configure command | run', (group) => { await command.exec() assert.equal(command.result, 'configured') }) + + test('install packages', async ({ assert, fs }) => { + const ace = await new AceFactory().make(fs.baseUrl, { + importer: (filePath) => { + return import(new URL(filePath, fs.baseUrl).href) + }, + }) + + await ace.app.init() + ace.ui.switchMode('raw') + + await fs.create('pnpm-lock.yaml', '') + await fs.createJson('package.json', { type: 'module' }) + await fs.create( + 'dummy-pkg.js', + ` + export const stubsRoot = './' + export async function configure (command) { + await command.installPackages([ + { name: 'is-odd@2.0.0', isDevDependency: true }, + { name: 'is-even@1.0.0', isDevDependency: false } + ]) + } + ` + ) + + const command = await ace.create(Configure, ['./dummy-pkg.js?v=3']) + await command.exec() + + const packageJson = await fs.contentsJson('package.json') + assert.deepEqual(packageJson.dependencies, { 'is-even': '1.0.0' }) + assert.deepEqual(packageJson.devDependencies, { 'is-odd': '2.0.0' }) + }).timeout(5000) + + test('install packages and detect pnpm', async ({ assert, fs }) => { + const ace = await new AceFactory().make(fs.baseUrl, { + importer: (filePath) => { + return import(new URL(filePath, fs.baseUrl).href) + }, + }) + + await ace.app.init() + ace.ui.switchMode('raw') + + await fs.create('pnpm-lock.yaml', '') + await fs.createJson('package.json', { type: 'module' }) + await fs.create( + 'dummy-pkg.js', + ` + export const stubsRoot = './' + export async function configure (command) { + await command.installPackages([ + { name: 'is-odd@2.0.0', isDevDependency: true, }, + ]) + } + ` + ) + + const command = await ace.create(Configure, ['./dummy-pkg.js?v=4']) + await command.exec() + + const logs = ace.ui.logger.getLogs() + assert.deepInclude(logs, { + message: '[ cyan(wait) ] installing dependencies using pnpm . ', + stream: 'stdout', + }) + }).timeout(5000) + + test('install packages and detect npm', async ({ assert, fs }) => { + const ace = await new AceFactory().make(fs.baseUrl, { + importer: (filePath) => { + return import(new URL(filePath, fs.baseUrl).href) + }, + }) + + await ace.app.init() + ace.ui.switchMode('raw') + + await fs.createJson('package-lock.json', {}) + await fs.createJson('package.json', { type: 'module' }) + await fs.create( + 'dummy-pkg.js', + ` + export const stubsRoot = './' + export async function configure (command) { + await command.installPackages([ + { name: 'is-odd@2.0.0', isDevDependency: true, }, + ]) + } + ` + ) + + const command = await ace.create(Configure, ['./dummy-pkg.js?v=5']) + await command.exec() + + const logs = ace.ui.logger.getLogs() + + assert.deepInclude(logs, { + message: '[ cyan(wait) ] installing dependencies using npm . ', + stream: 'stdout', + }) + }).timeout(5000) + + test('display error when installing packages', async ({ assert, fs }) => { + const ace = await new AceFactory().make(fs.baseUrl, { + importer: (filePath) => { + return import(new URL(filePath, fs.baseUrl).href) + }, + }) + + await ace.app.init() + ace.ui.switchMode('raw') + + await fs.createJson('package-lock.json', {}) + await fs.createJson('package.json', { type: 'module' }) + await fs.create( + 'dummy-pkg.js', + ` + export const stubsRoot = './' + export async function configure (command) { + await command.installPackages([ + { name: 'is-odd@15.0.0', isDevDependency: true, }, + ]) + } + ` + ) + + const command = await ace.create(Configure, ['./dummy-pkg.js?v=6']) + await command.exec() + + const logs = ace.ui.logger.getLogs() + assert.deepInclude(logs, { + message: + '[ red(error) ] unable to install dependencies :\n red(Command failed with exit code 1: npm install -D is-odd@15.0.0)', + stream: 'stderr', + }) + }).timeout(5000) }) From d3823a2994d919d049365a84d25e5eda3102f524 Mon Sep 17 00:00:00 2001 From: Julien Date: Thu, 6 Jul 2023 12:11:34 +0200 Subject: [PATCH 2/2] chore: feedbacks --- commands/configure.ts | 5 +++-- tests/commands/configure.spec.ts | 11 ++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/commands/configure.ts b/commands/configure.ts index 39915432..b2ff9ef2 100644 --- a/commands/configure.ts +++ b/commands/configure.ts @@ -123,7 +123,7 @@ export default class Configure extends BaseCommand { * ``` */ async installPackages(packages: { name: string; isDevDependency: boolean }[]) { - const appPath = fileURLToPath(this.app.appRoot) + const appPath = this.app.makePath() const devDeps = packages.filter((pkg) => pkg.isDevDependency).map(({ name }) => name) const deps = packages.filter((pkg) => !pkg.isDevDependency).map(({ name }) => name) @@ -142,8 +142,9 @@ export default class Configure extends BaseCommand { this.logger.log(devDeps.map((dep) => ` ${this.colors.dim('dev')} ${dep}`).join('\n')) this.logger.log(deps.map((dep) => ` ${this.colors.dim('prod')} ${dep}`).join('\n')) } catch (error) { + spinner.update('unable to install dependencies') spinner.stop() - this.logger.error(`unable to install dependencies :\n ${this.colors.red(error.message)}`) + this.logger.fatal(error) } } diff --git a/tests/commands/configure.spec.ts b/tests/commands/configure.spec.ts index eeb9f7dc..0e2a6c76 100644 --- a/tests/commands/configure.spec.ts +++ b/tests/commands/configure.spec.ts @@ -532,9 +532,14 @@ test.group('Configure command | run', (group) => { const logs = ace.ui.logger.getLogs() assert.deepInclude(logs, { - message: - '[ red(error) ] unable to install dependencies :\n red(Command failed with exit code 1: npm install -D is-odd@15.0.0)', - stream: 'stderr', + message: '[ cyan(wait) ] unable to install dependencies ...', + stream: 'stdout', }) + + const lastLog = logs[logs.length - 1] + assert.deepInclude( + lastLog.message, + '[ red(error) ] Command failed with exit code 1: npm install -D is-odd@15.0.0' + ) }).timeout(5000) })