diff --git a/.changes/cta-derive-packageManager.md b/.changes/cta-derive-packageManager.md new file mode 100644 index 000000000..be9a0bf33 --- /dev/null +++ b/.changes/cta-derive-packageManager.md @@ -0,0 +1,5 @@ +--- +"create-tauri-app": patch +--- + +Use a test based on an npm env var to determine which package manager to use. diff --git a/tooling/create-tauri-app/bin/create-tauri-app.js b/tooling/create-tauri-app/bin/create-tauri-app.js index 6828c3ebb..837a0cfcb 100644 --- a/tooling/create-tauri-app/bin/create-tauri-app.js +++ b/tooling/create-tauri-app/bin/create-tauri-app.js @@ -12,9 +12,9 @@ const { recipeByDescriptiveName, recipeByShortName, install, + checkPackageManager, shell, } = require("../dist/"); -const { dir } = require("console"); /** * @type {object} @@ -38,6 +38,7 @@ const createTauriApp = async (cliArgs) => { v: "version", f: "force", l: "log", + m: "manager", d: "directory", b: "binary", t: "tauri-path", @@ -81,6 +82,7 @@ function printUsage() { --ci Skip prompts --force, -f Force init to overwrite [conf|template|all] --log, -l Logging [boolean] + --manager, -d Set package manager to use [npm|yarn] --directory, -d Set target directory for init --binary, -b Optional path to a tauri binary from which to run init --app-name, -A Name of your Tauri application @@ -142,6 +144,8 @@ async function runInit(argv, config = {}) { window: { title }, }, } = config; + // this little fun snippet pulled from vite determines the package manager the script was run from + const packageManager = /yarn/.test(process.env.npm_execpath) ? "yarn" : "npm"; let recipe; @@ -157,7 +161,7 @@ async function runInit(argv, config = {}) { }; if (recipe !== undefined) { - buildConfig = recipe.configUpdate(buildConfig); + buildConfig = recipe.configUpdate({ buildConfig, packageManager }); } const directory = argv.d || process.cwd(); @@ -166,14 +170,18 @@ async function runInit(argv, config = {}) { appName: appName || argv.A, windowTitle: title || argv.w, }; + // note that our app directory is reliant on the appName and // generally there are issues if the path has spaces (see Windows) // future TODO prevent app names with spaces or escape here? const appDirectory = join(directory, cfg.appName); + // this throws an error if we can't run the package manager they requested + await checkPackageManager({ cwd: directory, packageManager }); + if (recipe.preInit) { console.log("===== running initial command(s) ====="); - await recipe.preInit({ cwd: directory, cfg }); + await recipe.preInit({ cwd: directory, cfg, packageManager }); } const initArgs = [ @@ -183,24 +191,24 @@ async function runInit(argv, config = {}) { ["--dev-path", cfg.devPath], ].reduce((final, argSet) => { if (argSet[1]) { - return final.concat([argSet[0], `\"${argSet[1]}\"`]); + return final.concat(argSet); } else { return final; } }, []); - const installed = await install({ + console.log("===== installing any additional needed deps ====="); + await install({ appDir: appDirectory, dependencies: recipe.extraNpmDependencies, - devDependencies: ["tauri", ...recipe.extraNpmDevDependencies], + devDependencies: ["@tauri-apps/cli", ...recipe.extraNpmDevDependencies], + packageManager, }); console.log("===== running tauri init ====="); - const binary = !argv.b - ? installed.packageManager - : resolve(appDirectory, argv.b); + const binary = !argv.b ? packageManager : resolve(appDirectory, argv.b); const runTauriArgs = - installed.packageManager === "npm" && !argv.b + packageManager === "npm" && !argv.b ? ["run", "tauri", "--", "init"] : ["tauri", "init"]; await shell(binary, [...runTauriArgs, ...initArgs], { @@ -212,6 +220,7 @@ async function runInit(argv, config = {}) { await recipe.postInit({ cwd: appDirectory, cfg, + packageManager, }); } } diff --git a/tooling/create-tauri-app/src/dependency-manager.ts b/tooling/create-tauri-app/src/dependency-manager.ts index ab86d14d6..734c0fba1 100644 --- a/tooling/create-tauri-app/src/dependency-manager.ts +++ b/tooling/create-tauri-app/src/dependency-manager.ts @@ -4,68 +4,54 @@ import { ManagementType, Result } from "./types/deps"; import { shell } from "./shell"; -import { existsSync } from "fs"; -import { join } from "path"; + +export type PackageManager = "npm" | "yarn"; export async function install({ appDir, dependencies, devDependencies, + packageManager, }: { appDir: string; dependencies: string[]; - devDependencies?: string[]; -}) { - return await manageDependencies(appDir, dependencies, devDependencies); -} - -async function manageDependencies( - appDir: string, - dependencies: string[] = [], - devDependencies: string[] = [] -): Promise<{ result: Result; packageManager: string }> { - const installedDeps = [...dependencies, ...devDependencies]; - console.log(`Installing ${installedDeps.join(", ")}...`); - - const packageManager = await usePackageManager(appDir); - - await installNpmDevPackage(devDependencies, packageManager, appDir); - await installNpmPackage(dependencies, packageManager, appDir); - + devDependencies: string[]; + packageManager: PackageManager; +}): Promise { const result: Result = new Map(); - result.set(ManagementType.Install, installedDeps); + await installNpmDevPackage(devDependencies, packageManager, appDir); + result.set(ManagementType.Install, devDependencies); - return { result, packageManager }; + await installNpmPackage(dependencies, packageManager, appDir); + result.set(ManagementType.Install, dependencies); + + return result; } -async function usePackageManager(appDir: string): Promise<"yarn" | "npm"> { - const hasYarnLockfile = existsSync(join(appDir, "yarn.lock")); - let yarnChild; - // try yarn first if there is a lockfile - if (hasYarnLockfile) { - yarnChild = await shell("yarn", ["--version"], { stdio: "pipe" }); - if (!yarnChild.failed) return "yarn"; +export async function checkPackageManager({ + cwd, + packageManager, +}: { + cwd: string; + packageManager: PackageManager; +}): Promise { + try { + await shell(packageManager, ["--version"], { stdio: "pipe", cwd }); + return true; + } catch (error) { + throw new Error( + `Must have ${packageManager} installed to manage dependencies. Is either in your PATH? We tried running in ${cwd}` + ); } - - // try npm then as the "default" - const npmChild = await shell("npm", ["--version"], { stdio: "pipe" }); - if (!npmChild.failed) return "npm"; - - // try yarn as maybe only yarn is installed - if (yarnChild && !yarnChild.failed) return "yarn"; - - // if we have reached here, we can't seem to run anything - throw new Error( - `Must have npm or yarn installed to manage dependencies. Is either in your PATH? We tried running in ${appDir}` - ); } async function installNpmPackage( packageNames: string[], - packageManager: string, + packageManager: PackageManager, appDir: string ): Promise { if (packageNames.length === 0) return; + console.log(`Installing ${packageNames.join(", ")}...`); if (packageManager === "yarn") { await shell("yarn", ["add", packageNames.join(" ")], { cwd: appDir, @@ -79,10 +65,11 @@ async function installNpmPackage( async function installNpmDevPackage( packageNames: string[], - packageManager: string, + packageManager: PackageManager, appDir: string ): Promise { if (packageNames.length === 0) return; + console.log(`Installing ${packageNames.join(", ")}...`); if (packageManager === "yarn") { await shell( "yarn", diff --git a/tooling/create-tauri-app/src/index.ts b/tooling/create-tauri-app/src/index.ts index ece356ca3..58a9503dd 100644 --- a/tooling/create-tauri-app/src/index.ts +++ b/tooling/create-tauri-app/src/index.ts @@ -8,27 +8,38 @@ import { reactjs, reactts } from "./recipes/react"; import { vanillajs } from "./recipes/vanilla"; export { shell } from "./shell"; -export { install } from "./dependency-manager"; +export { install, checkPackageManager } from "./dependency-manager"; +import { PackageManager } from "./dependency-manager"; export interface Recipe { descriptiveName: string; shortName: string; - configUpdate?: (cfg: TauriBuildConfig) => TauriBuildConfig; + configUpdate?: ({ + cfg, + packageManager, + }: { + cfg: TauriBuildConfig; + packageManager: PackageManager; + }) => TauriBuildConfig; extraNpmDependencies: string[]; extraNpmDevDependencies: string[]; preInit?: ({ cwd, cfg, + packageManager, }: { cwd: string; cfg: TauriBuildConfig; + packageManager: PackageManager; }) => Promise; postInit?: ({ cwd, cfg, + packageManager, }: { cwd: string; cfg: TauriBuildConfig; + packageManager: PackageManager; }) => Promise; } diff --git a/tooling/create-tauri-app/src/recipes/react.ts b/tooling/create-tauri-app/src/recipes/react.ts index 65f59c9b3..4bd78e3b6 100644 --- a/tooling/create-tauri-app/src/recipes/react.ts +++ b/tooling/create-tauri-app/src/recipes/react.ts @@ -4,11 +4,6 @@ import { join } from "path"; import scaffe from "scaffe"; import { shell } from "../shell"; -const completeLogMsg = ` - Your installation completed. - To start, run yarn tauri dev -`; - const afterCra = async (cwd: string, appName: string, version: string) => { const templateDir = join(__dirname, "../src/templates/react"); const variables = { @@ -29,7 +24,7 @@ const afterCra = async (cwd: string, appName: string, version: string) => { const reactjs: Recipe = { descriptiveName: "React.js", shortName: "reactjs", - configUpdate: (cfg) => ({ + configUpdate: ({ cfg }) => ({ ...cfg, distDir: `../build`, devPath: "http://localhost:3000", @@ -38,17 +33,32 @@ const reactjs: Recipe = { }), extraNpmDevDependencies: [], extraNpmDependencies: [], - preInit: async ({ cwd, cfg }) => { + preInit: async ({ cwd, cfg, packageManager }) => { // CRA creates the folder for you - await shell("npx", ["create-react-app", `${cfg.appName}`], { cwd }); + if (packageManager === "yarn") { + await shell("yarn", ["create", "react-app", `${cfg.appName}`], { + cwd, + }); + } else { + await shell( + "npm", + ["init", "react-app", `${cfg.appName}`, "--", "--use-npm"], + { + cwd, + } + ); + } const version = await shell("npm", ["view", "tauri", "version"], { stdio: "pipe", }); const versionNumber = version.stdout.trim(); await afterCra(cwd, cfg.appName, versionNumber); }, - postInit: async ({ cfg }) => { - console.log(completeLogMsg); + postInit: async ({ packageManager }) => { + console.log(` + Your installation completed. + To start, run ${packageManager} tauri dev + `); }, }; @@ -57,16 +67,39 @@ const reactts: Recipe = { descriptiveName: "React with Typescript", shortName: "reactts", extraNpmDependencies: [], - preInit: async ({ cwd, cfg }) => { + preInit: async ({ cwd, cfg, packageManager }) => { // CRA creates the folder for you - await shell( - "npx", - ["create-react-app", "--template", "typescript", `${cfg.appName}`], - { cwd } - ); + if (packageManager === "yarn") { + await shell( + "yarn", + ["create", "react-app", "--template", "typescript", `${cfg.appName}`], + { + cwd, + } + ); + } else { + await shell( + "npm", + [ + "init", + "react-app", + `${cfg.appName}`, + "--", + "--use-npm", + "--template", + "typescript", + ], + { + cwd, + } + ); + } }, - postInit: async ({ cfg }) => { - console.log(completeLogMsg); + postInit: async ({ packageManager }) => { + console.log(` + Your installation completed. + To start, run ${packageManager} tauri dev + `); }, }; diff --git a/tooling/create-tauri-app/src/recipes/vanilla.ts b/tooling/create-tauri-app/src/recipes/vanilla.ts index 2b843318c..699887f39 100644 --- a/tooling/create-tauri-app/src/recipes/vanilla.ts +++ b/tooling/create-tauri-app/src/recipes/vanilla.ts @@ -8,7 +8,7 @@ import { shell } from "../shell"; export const vanillajs: Recipe = { descriptiveName: "Vanilla.js", shortName: "vanillajs", - configUpdate: (cfg) => ({ + configUpdate: ({ cfg }) => ({ ...cfg, distDir: `../dist`, devPath: `../dist`, @@ -24,16 +24,24 @@ export const vanillajs: Recipe = { const versionNumber = version.stdout.trim(); await run(cfg, cwd, versionNumber); }, - postInit: async ({ cfg }) => { + postInit: async ({ cfg, packageManager }) => { + const setApp = + packageManager === "npm" + ? ` +set tauri script once + $ npm set-script tauri tauri + ` + : ""; + console.log(` - change directory: - $ cd ${cfg.appName} - - install dependencies: - $ yarn # npm install - - run the app: - $ yarn tauri dev # npm run tauri dev +change directory: + $ cd ${cfg.appName} +${setApp} +install dependencies: + $ ${packageManager} install + +run the app: + $ ${packageManager} tauri ${packageManager === "npm" ? "-- " : ""}dev `); }, };