/* eslint-disable @typescript-eslint/no-this-alias */ import chalk from "chalk"; import fsExtra from "fs-extra"; import os from "os"; import path from "path"; import { POLAR_NAME } from "../../lib/contants"; import { PolarError } from "../core/errors"; import { ERRORS } from "../core/errors-list"; import { ExecutionMode, getExecutionMode } from "../core/execution-mode"; import { getPackageJson, getPackageRoot } from "../util/packageInfo"; import { initialize } from "./initialize-template"; const SAMPLE_PROJECT_DEPENDENCIES = [ "chai" ]; export async function printWelcomeMessage (): Promise { const packageJson = await getPackageJson(); console.log( chalk.cyan(`ā˜… Welcome to ${POLAR_NAME} v${packageJson.version}`)); } function copySampleProject (projectName: string): void { const packageRoot = getPackageRoot(); const sampleProjDir = path.join(packageRoot, "sample-project"); const currDir = process.cwd(); const projectPath = path.join(currDir, projectName); console.log(chalk.greenBright("Initializing new project in " + projectPath + ".")); fsExtra.copySync(sampleProjDir, projectPath, { // User doesn't choose the directory so overwrite should be avoided overwrite: false, filter: (src: string, dest: string) => { const relPath = path.relative(process.cwd(), dest); if (relPath === '') { return true; } if (path.basename(dest) === ".gitkeep") { return false; } if (fsExtra.pathExistsSync(dest)) { throw new PolarError(ERRORS.GENERAL.INIT_INSIDE_PROJECT, { clashingFile: relPath }); } return true; } }); } export function printSuggestedCommands (projectName: string): void { const currDir = process.cwd(); const projectPath = path.join(currDir, projectName); console.log(`Success! Created project at ${chalk.greenBright(projectPath)}.`); // TODO: console.log(`Inside that directory, you can run several commands:`); // list commands and respective description console.log(`Begin by typing:`); console.log(` cd ${projectName}`); console.log(` ${POLAR_NAME} help`); console.log(` ${POLAR_NAME} compile`); } async function printPluginInstallationInstructions (): Promise { console.log( `You need to install these dependencies to run the sample project:` ); const cmd = await npmInstallCmd(); console.log(` ${cmd.join(" ")}`); } // eslint-disable-next-line export async function createProject ( projectName: string, templateName?: string, destination?: string ): Promise { // eslint-disable-line @typescript-eslint/no-explicit-any if (templateName !== undefined) { const currDir = process.cwd(); const projectPath = destination ?? path.join(currDir, projectName); await initialize({ force: false, projectName: projectName, templateName: templateName, destination: projectPath }); return; } await printWelcomeMessage(); copySampleProject(projectName); let shouldShowInstallationInstructions = true; if (await canInstallPlugin()) { const installedRecommendedDeps = SAMPLE_PROJECT_DEPENDENCIES.filter( isInstalled ); if ( installedRecommendedDeps.length === SAMPLE_PROJECT_DEPENDENCIES.length ) { shouldShowInstallationInstructions = false; } else if (installedRecommendedDeps.length === 0) { const shouldInstall = await confirmPluginInstallation(); if (shouldInstall) { const installed = await installRecommendedDependencies(); if (!installed) { console.warn( chalk.red("Failed to install the sample project's dependencies") ); } shouldShowInstallationInstructions = !installed; } } } console.log("\nā˜…", chalk.cyan("Project created"), "ā˜…\n"); if (shouldShowInstallationInstructions) { await printPluginInstallationInstructions(); console.log(``); } printSuggestedCommands(projectName); } export function createConfirmationPrompt (name: string, message: string): any { // eslint-disable-line @typescript-eslint/no-explicit-any return { type: "confirm", name, message, initial: "y", default: "(Y/n)", isTrue (input: string | boolean) { if (typeof input === "string") { return input.toLowerCase() === "y"; } return input; }, isFalse (input: string | boolean) { if (typeof input === "string") { return input.toLowerCase() === "n"; } return input; }, format (): string { const that = this; // eslint-disable-line @typescript-eslint/no-explicit-any const value = that.value === true ? "y" : "n"; if (that.state.submitted === true) { return that.styles.submitted(value); } return value; } }; } async function canInstallPlugin (): Promise { return ( (await fsExtra.pathExists("package.json")) && (getExecutionMode() === ExecutionMode.EXECUTION_MODE_LOCAL_INSTALLATION || getExecutionMode() === ExecutionMode.EXECUTION_MODE_LINKED) && // TODO: Figure out why this doesn't work on Win os.type() !== "Windows_NT" ); } function isInstalled (dep: string): boolean { const packageJson = fsExtra.readJSONSync("package.json"); const allDependencies = { ...packageJson.dependencies, ...packageJson.devDependencies, ...packageJson.optionalDependencies }; return dep in allDependencies; } function isYarnProject (): boolean { return fsExtra.pathExistsSync("yarn.lock"); } async function installRecommendedDependencies (): Promise { console.log(""); const installCmd = await npmInstallCmd(); return await installDependencies(installCmd[0], installCmd.slice(1)); } async function confirmPluginInstallation (): Promise { const { default: enquirer } = await import("enquirer"); let responses: { shouldInstallPlugin: boolean }; const packageManager = isYarnProject() ? "yarn" : "npm"; try { responses = await enquirer.prompt([ createConfirmationPrompt( "shouldInstallPlugin", `Do you want to install the sample project's dependencies with ${packageManager} (${SAMPLE_PROJECT_DEPENDENCIES.join( " " )})?` ) ]); } catch (e) { if (e === "") { return false; } throw e; } return responses.shouldInstallPlugin; } export async function installDependencies ( packageManager: string, args: string[], location?: string ): Promise { const { spawn } = await import("child_process"); console.log(`${packageManager} ${args.join(" ")}`); const childProcess = spawn(packageManager, args, { stdio: "inherit" as any, // eslint-disable-line @typescript-eslint/no-explicit-any cwd: location }); return await new Promise((resolve, reject) => { childProcess.once("close", (status) => { childProcess.removeAllListeners("error"); if (status === 0) { resolve(true); return; } reject(new Error("script process returned not 0 status")); }); childProcess.once("error", (status) => { childProcess.removeAllListeners("close"); reject(new Error("script process returned not 0 status")); }); }); } async function npmInstallCmd (): Promise { const isGlobal = getExecutionMode() === ExecutionMode.EXECUTION_MODE_GLOBAL_INSTALLATION; if (isYarnProject()) { const cmd = ["yarn"]; if (isGlobal) { cmd.push("global"); } cmd.push("add", "--dev", ...SAMPLE_PROJECT_DEPENDENCIES); return cmd; } const npmInstall = ["npm", "install"]; if (isGlobal) { npmInstall.push("--global"); } return [...npmInstall, "--save-dev", ...SAMPLE_PROJECT_DEPENDENCIES]; }