Skip to content

Commit

Permalink
feat(create): Allow selection of package manager
Browse files Browse the repository at this point in the history
Currently only Yarn and npm are supported, but this sets up future support
for other package managers & runtimes such as pnpm & Bun
  • Loading branch information
michaelbromley committed Sep 22, 2023
1 parent 3beb794 commit 6561bb7
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 22 deletions.
40 changes: 27 additions & 13 deletions packages/create/src/create-vendure-app.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable no-console */
import { intro, note, outro, spinner } from '@clack/prompts';
import { intro, note, outro, select, spinner } from '@clack/prompts';
import { program } from 'commander';
import detectPort from 'detect-port';
import fs from 'fs-extra';
Expand All @@ -8,7 +8,7 @@ import path from 'path';
import pc from 'picocolors';

import { REQUIRED_NODE_VERSION, SERVER_PORT } from './constants';
import { gatherCiUserResponses, gatherUserResponses } from './gather-user-responses';
import { checkCancel, gatherCiUserResponses, gatherUserResponses } from './gather-user-responses';
import {
checkDbConnection,
checkNodeVersion,
Expand All @@ -18,9 +18,9 @@ import {
isSafeToCreateProjectIn,
isServerPortInUse,
scaffoldAlreadyExists,
shouldUseYarn,
yarnIsAvailable,
} from './helpers';
import { CliLogLevel } from './types';
import { CliLogLevel, DbType, PackageManager } from './types';

// eslint-disable-next-line @typescript-eslint/no-var-requires
const packageJson = require('../package.json');
Expand Down Expand Up @@ -75,7 +75,21 @@ export async function createVendureApp(
const root = path.resolve(name);
const appName = path.basename(root);
const scaffoldExists = scaffoldAlreadyExists(root, name);
const useYarn = useNpm ? false : shouldUseYarn();

const yarnAvailable = yarnIsAvailable();
let packageManager: PackageManager = 'npm';
if (yarnAvailable && !useNpm) {
packageManager = (await select({
message: 'Which package manager should be used?',
options: [
{ label: 'npm', value: 'npm' },
{ label: 'yarn', value: 'yarn' },
],
initialValue: 'yarn' as PackageManager,
})) as PackageManager;
checkCancel(packageManager);
}

if (scaffoldExists) {
console.log(
pc.yellow(
Expand All @@ -97,11 +111,11 @@ export async function createVendureApp(
dockerComposeSource,
populateProducts,
} = isCi
? await gatherCiUserResponses(root, useYarn)
: await gatherUserResponses(root, scaffoldExists, useYarn);
? await gatherCiUserResponses(root, packageManager)
: await gatherUserResponses(root, scaffoldExists, packageManager);
const originalDirectory = process.cwd();
process.chdir(root);
if (!useYarn && !checkThatNpmCanReadCwd()) {
if (packageManager !== 'npm' && !checkThatNpmCanReadCwd()) {
process.exit(1);
}

Expand All @@ -112,11 +126,11 @@ export async function createVendureApp(
scripts: {
'dev:server': 'ts-node ./src/index.ts',
'dev:worker': 'ts-node ./src/index-worker.ts',
dev: useYarn ? 'concurrently yarn:dev:*' : 'concurrently npm:dev:*',
dev: packageManager === 'yarn' ? 'concurrently yarn:dev:*' : 'concurrently npm:dev:*',
build: 'tsc',
'start:server': 'node ./dist/index.js',
'start:worker': 'node ./dist/index-worker.js',
start: useYarn ? 'concurrently yarn:start:*' : 'concurrently npm:start:*',
start: packageManager === 'yarn' ? 'concurrently yarn:start:*' : 'concurrently npm:start:*',
'migration:generate': 'ts-node migration generate',
'migration:run': 'ts-node migration run',
'migration:revert': 'ts-node migration revert',
Expand All @@ -138,7 +152,7 @@ export async function createVendureApp(
const installSpinner = spinner();
installSpinner.start(`Installing ${dependencies[0]} + ${dependencies.length - 1} more dependencies`);
try {
await installPackages(root, useYarn, dependencies, false, logLevel, isCi);
await installPackages(root, packageManager === 'yarn', dependencies, false, logLevel, isCi);
} catch (e) {
outro(pc.red(`Failed to install dependencies. Please try again.`));
process.exit(1);
Expand All @@ -151,7 +165,7 @@ export async function createVendureApp(
`Installing ${devDependencies[0]} + ${devDependencies.length - 1} more dev dependencies`,
);
try {
await installPackages(root, useYarn, devDependencies, true, logLevel, isCi);
await installPackages(root, packageManager === 'yarn', devDependencies, true, logLevel, isCi);
} catch (e) {
outro(pc.red(`Failed to install dev dependencies. Please try again.`));
process.exit(1);
Expand Down Expand Up @@ -255,7 +269,7 @@ export async function createVendureApp(
}
populateSpinner.stop(`Server successfully initialized${populateProducts ? ' and populated' : ''}`);

const startCommand = useYarn ? 'yarn dev' : 'npm run dev';
const startCommand = packageManager === 'yarn' ? 'yarn dev' : 'npm run dev';
const nextSteps = [
`${pc.green('Success!')} Created a new Vendure server at:`,
`\n`,
Expand Down
23 changes: 15 additions & 8 deletions packages/create/src/gather-user-responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import fs from 'fs-extra';
import Handlebars from 'handlebars';
import path from 'path';

import { DbType, FileSources, UserResponses } from './types';
import { DbType, FileSources, PackageManager, UserResponses } from './types';

interface PromptAnswers {
dbType: DbType;
Expand All @@ -27,7 +27,7 @@ interface PromptAnswers {
export async function gatherUserResponses(
root: string,
alreadyRanScaffold: boolean,
useYarn: boolean,
packageManager: PackageManager,
): Promise<UserResponses> {
const dbType = (await select({
message: 'Which database are you using?',
Expand Down Expand Up @@ -119,7 +119,7 @@ export async function gatherUserResponses(
};

return {
...(await generateSources(root, answers, useYarn)),
...(await generateSources(root, answers, packageManager)),
dbType,
populateProducts: answers.populateProducts as boolean,
superadminIdentifier: answers.superadminIdentifier as string,
Expand All @@ -130,7 +130,10 @@ export async function gatherUserResponses(
/**
* Returns mock "user response" without prompting, for use in CI
*/
export async function gatherCiUserResponses(root: string, useYarn: boolean): Promise<UserResponses> {
export async function gatherCiUserResponses(
root: string,
packageManager: PackageManager,
): Promise<UserResponses> {
const ciAnswers = {
dbType: 'sqlite' as const,
dbHost: '',
Expand All @@ -144,15 +147,15 @@ export async function gatherCiUserResponses(root: string, useYarn: boolean): Pro
};

return {
...(await generateSources(root, ciAnswers, useYarn)),
...(await generateSources(root, ciAnswers, packageManager)),
dbType: ciAnswers.dbType,
populateProducts: ciAnswers.populateProducts,
superadminIdentifier: ciAnswers.superadminIdentifier,
superadminPassword: ciAnswers.superadminPassword,
};
}

function checkCancel<T>(value: T | symbol): value is T {
export function checkCancel<T>(value: T | symbol): value is T {
if (isCancel(value)) {
cancel('Setup cancelled.');
process.exit(0);
Expand All @@ -163,7 +166,11 @@ function checkCancel<T>(value: T | symbol): value is T {
/**
* Create the server index, worker and config source code based on the options specified by the CLI prompts.
*/
async function generateSources(root: string, answers: PromptAnswers, useYarn: boolean): Promise<FileSources> {
async function generateSources(
root: string,
answers: PromptAnswers,
packageManager: PackageManager,
): Promise<FileSources> {
const assetPath = (fileName: string) => path.join(__dirname, '../assets', fileName);

/**
Expand All @@ -177,7 +184,7 @@ async function generateSources(root: string, answers: PromptAnswers, useYarn: bo

const templateContext = {
...answers,
useYarn,
useYarn: packageManager === 'yarn',
dbType: answers.dbType === 'sqlite' ? 'better-sqlite3' : answers.dbType,
name: path.basename(root),
isSQLite: answers.dbType === 'sqlite',
Expand Down
14 changes: 13 additions & 1 deletion packages/create/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export function checkNodeVersion(requiredVersion: string) {
}
}

export function shouldUseYarn() {
export function yarnIsAvailable() {
try {
execSync('yarnpkg --version', { stdio: 'ignore' });
return true;
Expand All @@ -111,6 +111,18 @@ export function shouldUseYarn() {
}
}

// Bun support should not be exposed yet, see
// https://github.com/oven-sh/bun/issues/4947
// https://github.com/lovell/sharp/issues/3511
export function bunIsAvailable() {
try {
execSync('bun --version', { stdio: 'ignore' });
return true;
} catch (e: any) {
return false;
}
}

export function checkThatNpmCanReadCwd() {
const cwd = process.cwd();
let childOutput = null;
Expand Down
2 changes: 2 additions & 0 deletions packages/create/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ export interface UserResponses extends FileSources {
superadminPassword: string;
}

export type PackageManager = 'npm' | 'yarn';

export type CliLogLevel = 'silent' | 'info' | 'verbose';

0 comments on commit 6561bb7

Please sign in to comment.