2025-09-05 14:59:21 +08:00

389 lines
13 KiB
JavaScript

import { existsSync } from 'node:fs';
import process from 'node:process';
import { defineCommand } from 'citty';
import { colors } from 'consola/utils';
import { downloadTemplate, startShell } from 'giget';
import { installDependencies } from 'nypm';
import { $fetch } from 'ofetch';
import { resolve, relative, join } from 'pathe';
import { readPackageJSON, writePackageJSON } from 'pkg-types';
import { hasTTY } from 'std-env';
import { x } from 'tinyexec';
import { r as runCommand } from '../shared/cli.vXg4eLNu.mjs';
import { l as logger } from '../shared/cli.B9AmABr3.mjs';
import { a as logLevelArgs, c as cwdArgs } from '../shared/cli.CTXRG5Cu.mjs';
import 'node:url';
import 'node:crypto';
import 'node:path';
import '../shared/cli.DhJ3cH8w.mjs';
import 'consola';
const themeColor = "\x1B[38;2;0;220;130m";
const icon = [
` .d$b.`,
` i$$A$$L .d$b`,
` .$$F\` \`$$L.$$A$$.`,
` j$$' \`4$$:\` \`$$.`,
` j$$' .4$: \`$$.`,
` j$$\` .$$: \`4$L`,
` :$$:____.d$$: _____.:$$:`,
` \`4$$$$$$$$P\` .i$$$$$$$$P\``
];
const nuxtIcon = icon.map((line) => line.split("").join(themeColor)).join("\n");
const DEFAULT_REGISTRY = "https://raw.githubusercontent.com/nuxt/starter/templates/templates";
const DEFAULT_TEMPLATE_NAME = "v4";
const pms = {
npm: void 0,
pnpm: void 0,
yarn: void 0,
bun: void 0,
deno: void 0
};
const packageManagerOptions = Object.keys(pms);
async function getModuleDependencies(moduleName) {
try {
const response = await $fetch(`https://registry.npmjs.org/${moduleName}/latest`);
const dependencies = response.dependencies || {};
return Object.keys(dependencies);
} catch (err) {
logger.warn(`Could not get dependencies for ${moduleName}: ${err}`);
return [];
}
}
function filterModules(modules, allDependencies) {
const result = {
toInstall: [],
skipped: []
};
for (const module of modules) {
const isDependency = modules.some((otherModule) => {
if (otherModule === module)
return false;
const deps = allDependencies[otherModule] || [];
return deps.includes(module);
});
if (isDependency) {
result.skipped.push(module);
} else {
result.toInstall.push(module);
}
}
return result;
}
async function getTemplateDependencies(templateDir) {
try {
const packageJsonPath = join(templateDir, "package.json");
if (!existsSync(packageJsonPath)) {
return [];
}
const packageJson = await import(packageJsonPath);
const directDeps = {
...packageJson.dependencies,
...packageJson.devDependencies
};
const directDepNames = Object.keys(directDeps);
const allDeps = new Set(directDepNames);
const transitiveDepsResults = await Promise.all(
directDepNames.map((dep) => getModuleDependencies(dep))
);
transitiveDepsResults.forEach((deps) => {
deps.forEach((dep) => allDeps.add(dep));
});
return Array.from(allDeps);
} catch (err) {
logger.warn(`Could not read template dependencies: ${err}`);
return [];
}
}
const init = defineCommand({
meta: {
name: "init",
description: "Initialize a fresh project"
},
args: {
...cwdArgs,
...logLevelArgs,
dir: {
type: "positional",
description: "Project directory",
default: ""
},
template: {
type: "string",
alias: "t",
description: "Template name"
},
force: {
type: "boolean",
alias: "f",
description: "Override existing directory"
},
offline: {
type: "boolean",
description: "Force offline mode"
},
preferOffline: {
type: "boolean",
description: "Prefer offline mode"
},
install: {
type: "boolean",
default: true,
description: "Skip installing dependencies"
},
gitInit: {
type: "boolean",
description: "Initialize git repository"
},
shell: {
type: "boolean",
description: "Start shell after installation in project directory"
},
packageManager: {
type: "string",
description: "Package manager choice (npm, pnpm, yarn, bun)"
},
modules: {
type: "string",
required: false,
description: "Nuxt modules to install (comma separated without spaces)",
negativeDescription: "Skip module installation prompt",
alias: "M"
},
nightly: {
type: "string",
description: "Use Nuxt nightly release channel (3x or latest)"
}
},
async run(ctx) {
if (hasTTY) {
process.stdout.write(`
${nuxtIcon}
`);
}
logger.info(colors.bold(`Welcome to Nuxt!`.split("").map((m) => `${themeColor}${m}`).join("")));
if (ctx.args.dir === "") {
ctx.args.dir = await logger.prompt("Where would you like to create your project?", {
placeholder: "./nuxt-app",
type: "text",
default: "nuxt-app",
cancel: "reject"
}).catch(() => process.exit(1));
}
const cwd = resolve(ctx.args.cwd);
let templateDownloadPath = resolve(cwd, ctx.args.dir);
logger.info(`Creating a new project in ${colors.cyan(relative(cwd, templateDownloadPath) || templateDownloadPath)}.`);
const templateName = ctx.args.template || DEFAULT_TEMPLATE_NAME;
if (typeof templateName !== "string") {
logger.error("Please specify a template!");
process.exit(1);
}
let shouldForce = Boolean(ctx.args.force);
const shouldVerify = !shouldForce && existsSync(templateDownloadPath);
if (shouldVerify) {
const selectedAction = await logger.prompt(
`The directory ${colors.cyan(templateDownloadPath)} already exists. What would you like to do?`,
{
type: "select",
options: ["Override its contents", "Select different directory", "Abort"]
}
);
switch (selectedAction) {
case "Override its contents":
shouldForce = true;
break;
case "Select different directory": {
templateDownloadPath = resolve(cwd, await logger.prompt("Please specify a different directory:", {
type: "text",
cancel: "reject"
}).catch(() => process.exit(1)));
break;
}
// 'Abort' or Ctrl+C
default:
process.exit(1);
}
}
let template;
try {
template = await downloadTemplate(templateName, {
dir: templateDownloadPath,
force: shouldForce,
offline: Boolean(ctx.args.offline),
preferOffline: Boolean(ctx.args.preferOffline),
registry: process.env.NUXI_INIT_REGISTRY || DEFAULT_REGISTRY
});
} catch (err) {
if (process.env.DEBUG) {
throw err;
}
logger.error(err.toString());
process.exit(1);
}
if (ctx.args.nightly !== void 0 && !ctx.args.offline && !ctx.args.preferOffline) {
const response = await $fetch("https://registry.npmjs.org/nuxt-nightly");
const nightlyChannelTag = ctx.args.nightly || "latest";
const nightlyChannelVersion = response["dist-tags"][nightlyChannelTag];
if (!nightlyChannelVersion) {
logger.error(`Nightly channel version for tag '${nightlyChannelTag}' not found.`);
process.exit(1);
}
const nightlyNuxtPackageJsonVersion = `npm:nuxt-nightly@${nightlyChannelVersion}`;
const packageJsonPath = resolve(cwd, ctx.args.dir);
const packageJson = await readPackageJSON(packageJsonPath);
if (packageJson.dependencies && "nuxt" in packageJson.dependencies) {
packageJson.dependencies.nuxt = nightlyNuxtPackageJsonVersion;
} else if (packageJson.devDependencies && "nuxt" in packageJson.devDependencies) {
packageJson.devDependencies.nuxt = nightlyNuxtPackageJsonVersion;
}
await writePackageJSON(join(packageJsonPath, "package.json"), packageJson);
}
function detectCurrentPackageManager() {
const userAgent = process.env.npm_config_user_agent;
if (!userAgent) {
return;
}
const [name] = userAgent.split("/");
if (packageManagerOptions.includes(name)) {
return name;
}
}
const currentPackageManager = detectCurrentPackageManager();
const packageManagerArg = ctx.args.packageManager;
const packageManagerSelectOptions = packageManagerOptions.map((pm) => ({
label: pm,
value: pm,
hint: currentPackageManager === pm ? "current" : void 0
}));
const selectedPackageManager = packageManagerOptions.includes(packageManagerArg) ? packageManagerArg : await logger.prompt("Which package manager would you like to use?", {
type: "select",
options: packageManagerSelectOptions,
initial: currentPackageManager,
cancel: "reject"
}).catch(() => process.exit(1));
if (ctx.args.install === false) {
logger.info("Skipping install dependencies step.");
} else {
logger.start("Installing dependencies...");
try {
await installDependencies({
cwd: template.dir,
packageManager: {
name: selectedPackageManager,
command: selectedPackageManager
}
});
} catch (err) {
if (process.env.DEBUG) {
throw err;
}
logger.error(err.toString());
process.exit(1);
}
logger.success("Installation completed.");
}
if (ctx.args.gitInit === void 0) {
ctx.args.gitInit = await logger.prompt("Initialize git repository?", {
type: "confirm",
cancel: "reject"
}).catch(() => process.exit(1));
}
if (ctx.args.gitInit) {
logger.info("Initializing git repository...\n");
try {
await x("git", ["init", template.dir], {
throwOnError: true,
nodeOptions: {
stdio: "inherit"
}
});
} catch (err) {
logger.warn(`Failed to initialize git repository: ${err}`);
}
}
const modulesToAdd = [];
if (ctx.args.modules !== void 0) {
modulesToAdd.push(
...(ctx.args.modules || "").split(",").map((module) => module.trim()).filter(Boolean)
);
} else if (!ctx.args.offline && !ctx.args.preferOffline) {
const modulesPromise = $fetch("https://api.nuxt.com/modules");
const wantsUserModules = await logger.prompt(
`Would you like to install any of the official modules?`,
{
type: "confirm",
cancel: "reject"
}
).catch(() => process.exit(1));
if (wantsUserModules) {
const [response, templateDeps] = await Promise.all([
modulesPromise,
getTemplateDependencies(template.dir)
]);
const officialModules = response.modules.filter((module) => module.type === "official" && module.npm !== "@nuxt/devtools").filter((module) => !templateDeps.includes(module.npm));
if (officialModules.length === 0) {
logger.info("All official modules are already included in this template.");
} else {
const selectedOfficialModules = await logger.prompt(
"Pick the modules to install:",
{
type: "multiselect",
options: officialModules.map((module) => ({
label: `${colors.bold(colors.greenBright(module.npm))} \u2013 ${module.description.replace(/\.$/, "")}`,
value: module.npm
})),
required: false
}
);
if (selectedOfficialModules === void 0) {
process.exit(1);
}
if (selectedOfficialModules.length > 0) {
const modules = selectedOfficialModules;
const allDependencies = Object.fromEntries(
await Promise.all(modules.map(
async (module) => [module, await getModuleDependencies(module)]
))
);
const { toInstall, skipped } = filterModules(modules, allDependencies);
if (skipped.length) {
logger.info(`The following modules are already included as dependencies of another module and will not be installed: ${skipped.map((m) => colors.cyan(m)).join(", ")}`);
}
modulesToAdd.push(...toInstall);
}
}
}
}
if (modulesToAdd.length > 0) {
const args = [
"add",
...modulesToAdd,
`--cwd=${templateDownloadPath}`,
ctx.args.install ? "" : "--skipInstall",
ctx.args.logLevel ? `--logLevel=${ctx.args.logLevel}` : ""
].filter(Boolean);
await runCommand("module", args);
}
logger.log(
`
\u2728 Nuxt project has been created with the \`${template.name}\` template. Next steps:`
);
const relativeTemplateDir = relative(process.cwd(), template.dir) || ".";
const runCmd = selectedPackageManager === "deno" ? "task" : "run";
const nextSteps = [
!ctx.args.shell && relativeTemplateDir.length > 1 && `\`cd ${relativeTemplateDir}\``,
`Start development server with \`${selectedPackageManager} ${runCmd} dev\``
].filter(Boolean);
for (const step of nextSteps) {
logger.log(` \u203A ${step}`);
}
if (ctx.args.shell) {
startShell(template.dir);
}
}
});
export { init as default };