490 lines
No EOL
16 KiB
JavaScript
490 lines
No EOL
16 KiB
JavaScript
import { o as logLevelArgs, t as cwdArgs } from "./_shared-BCYCnX0T.mjs";
|
||
import { n as logger } from "./logger-B4ge7MhP.mjs";
|
||
import { r as relativeToProcess } from "./kit-B3S8uoS_.mjs";
|
||
import { t as getNuxtVersion } from "./versions-Bly87QYZ.mjs";
|
||
import "./fs-CQH7NJn6.mjs";
|
||
import { n as runCommand$1, t as add_default } from "./add-cOz5A42V.mjs";
|
||
import { n as fetchModules, t as checkNuxtCompatibility } from "./_utils-NB3Cn3-G.mjs";
|
||
import "./prepare-CUaf6Joj.mjs";
|
||
import process from "node:process";
|
||
import { defineCommand } from "citty";
|
||
import { hasTTY } from "std-env";
|
||
import { colors } from "consola/utils";
|
||
import { box, cancel, confirm, intro, isCancel, multiselect, outro, select, spinner, tasks, text } from "@clack/prompts";
|
||
import { existsSync } from "node:fs";
|
||
import { basename, join, relative, resolve } from "pathe";
|
||
import { findFile, readPackageJSON, writePackageJSON } from "pkg-types";
|
||
import { x } from "tinyexec";
|
||
import { installDependencies } from "nypm";
|
||
import { downloadTemplate, startShell } from "giget";
|
||
import { $fetch } from "ofetch";
|
||
|
||
//#region ../nuxi/src/utils/ascii.ts
|
||
/**
|
||
* Thank you to IndyJoenz for this ASCII art
|
||
* https://bsky.app/profile/durdraw.org/post/3liadod3gv22a
|
||
*/
|
||
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");
|
||
|
||
//#endregion
|
||
//#region ../nuxi/src/utils/starter-templates.ts
|
||
const hiddenTemplates = [
|
||
"doc-driven",
|
||
"v4",
|
||
"v4-compat",
|
||
"v2-bridge",
|
||
"v3",
|
||
"ui-vue",
|
||
"module-devtools",
|
||
"layer",
|
||
"hub"
|
||
];
|
||
const fetchOptions = {
|
||
timeout: 3e3,
|
||
responseType: "json",
|
||
headers: {
|
||
"user-agent": "@nuxt/cli",
|
||
...process.env.GITHUB_TOKEN ? { authorization: `token ${process.env.GITHUB_TOKEN}` } : {}
|
||
}
|
||
};
|
||
let templatesCache = null;
|
||
async function getTemplates() {
|
||
templatesCache ||= fetchTemplates();
|
||
return templatesCache;
|
||
}
|
||
async function fetchTemplates() {
|
||
const templates = {};
|
||
const files = await $fetch("https://api.github.com/repos/nuxt/starter/contents/templates?ref=templates", fetchOptions);
|
||
await Promise.all(files.map(async (file) => {
|
||
if (!file.download_url || file.type !== "file" || !file.name.endsWith(".json")) return;
|
||
const templateName = file.name.replace(".json", "");
|
||
if (hiddenTemplates.includes(templateName)) return;
|
||
templates[templateName] = void 0;
|
||
templates[templateName] = await $fetch(file.download_url, fetchOptions);
|
||
}));
|
||
return templates;
|
||
}
|
||
|
||
//#endregion
|
||
//#region ../nuxi/src/commands/init.ts
|
||
const DEFAULT_REGISTRY = "https://raw.githubusercontent.com/nuxt/starter/templates/templates";
|
||
const DEFAULT_TEMPLATE_NAME = "minimal";
|
||
const packageManagerOptions = Object.keys({
|
||
npm: void 0,
|
||
pnpm: void 0,
|
||
yarn: void 0,
|
||
bun: void 0,
|
||
deno: void 0
|
||
});
|
||
var init_default = 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 (!ctx.args.offline && !ctx.args.preferOffline && !ctx.args.template) getTemplates().catch(() => null);
|
||
if (hasTTY) process.stdout.write(`\n${nuxtIcon}\n\n`);
|
||
intro(colors.bold(`Welcome to Nuxt!`.split("").map((m) => `${themeColor}${m}`).join("")));
|
||
let availableTemplates = {};
|
||
if (!ctx.args.template || !ctx.args.dir) {
|
||
const defaultTemplates = await import("./templates-FddTjK4U.mjs").then((r) => r.templates);
|
||
if (ctx.args.offline || ctx.args.preferOffline) availableTemplates = defaultTemplates;
|
||
else {
|
||
const templatesSpinner = spinner();
|
||
templatesSpinner.start("Loading available templates");
|
||
try {
|
||
availableTemplates = await getTemplates();
|
||
templatesSpinner.stop("Templates loaded");
|
||
} catch {
|
||
availableTemplates = defaultTemplates;
|
||
templatesSpinner.stop("Templates loaded from cache");
|
||
}
|
||
}
|
||
}
|
||
let templateName = ctx.args.template;
|
||
if (!templateName) {
|
||
const result = await select({
|
||
message: "Which template would you like to use?",
|
||
options: Object.entries(availableTemplates).map(([name, data]) => {
|
||
return {
|
||
value: name,
|
||
label: data ? `${colors.whiteBright(name)} – ${data.description}` : name,
|
||
hint: name === DEFAULT_TEMPLATE_NAME ? "recommended" : void 0
|
||
};
|
||
}),
|
||
initialValue: DEFAULT_TEMPLATE_NAME
|
||
});
|
||
if (isCancel(result)) {
|
||
cancel("Operation cancelled.");
|
||
process.exit(1);
|
||
}
|
||
templateName = result;
|
||
}
|
||
templateName ||= DEFAULT_TEMPLATE_NAME;
|
||
if (typeof templateName !== "string") {
|
||
logger.error("Please specify a template!");
|
||
process.exit(1);
|
||
}
|
||
if (ctx.args.dir === "") {
|
||
const defaultDir = availableTemplates[templateName]?.defaultDir || "nuxt-app";
|
||
const result = await text({
|
||
message: "Where would you like to create your project?",
|
||
placeholder: `./${defaultDir}`,
|
||
defaultValue: defaultDir
|
||
});
|
||
if (isCancel(result)) {
|
||
cancel("Operation cancelled.");
|
||
process.exit(1);
|
||
}
|
||
ctx.args.dir = result;
|
||
}
|
||
const cwd = resolve(ctx.args.cwd);
|
||
let templateDownloadPath = resolve(cwd, ctx.args.dir);
|
||
logger.step(`Creating project in ${colors.cyan(relativeToProcess(templateDownloadPath))}`);
|
||
let shouldForce = Boolean(ctx.args.force);
|
||
if (!shouldForce && existsSync(templateDownloadPath)) {
|
||
const selectedAction = await select({
|
||
message: `The directory ${colors.cyan(relativeToProcess(templateDownloadPath))} already exists. What would you like to do?`,
|
||
options: [
|
||
{
|
||
value: "override",
|
||
label: "Override its contents"
|
||
},
|
||
{
|
||
value: "different",
|
||
label: "Select different directory"
|
||
},
|
||
{
|
||
value: "abort",
|
||
label: "Abort"
|
||
}
|
||
]
|
||
});
|
||
if (isCancel(selectedAction)) {
|
||
cancel("Operation cancelled.");
|
||
process.exit(1);
|
||
}
|
||
switch (selectedAction) {
|
||
case "override":
|
||
shouldForce = true;
|
||
break;
|
||
case "different": {
|
||
const result = await text({ message: "Please specify a different directory:" });
|
||
if (isCancel(result)) {
|
||
cancel("Operation cancelled.");
|
||
process.exit(1);
|
||
}
|
||
templateDownloadPath = resolve(cwd, result);
|
||
break;
|
||
}
|
||
case "abort":
|
||
default: process.exit(1);
|
||
}
|
||
}
|
||
let template;
|
||
const downloadSpinner = spinner();
|
||
downloadSpinner.start(`Downloading ${colors.cyan(templateName)} 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
|
||
});
|
||
if (ctx.args.dir.length > 0) {
|
||
const path = await findFile("package.json", {
|
||
startingFrom: join(templateDownloadPath, "package.json"),
|
||
reverse: true
|
||
});
|
||
if (path) {
|
||
const pkg = await readPackageJSON(path, { try: true });
|
||
if (pkg && pkg.name) {
|
||
const slug = basename(templateDownloadPath).replace(/[^\w-]/g, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "");
|
||
if (slug) {
|
||
pkg.name = slug;
|
||
await writePackageJSON(path, pkg);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
downloadSpinner.stop(`Downloaded ${colors.cyan(template.name)} template`);
|
||
} catch (err) {
|
||
downloadSpinner.error("Template download failed");
|
||
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 nightlySpinner = spinner();
|
||
nightlySpinner.start("Fetching nightly version info");
|
||
const response = await $fetch("https://registry.npmjs.org/nuxt-nightly");
|
||
const nightlyChannelTag = ctx.args.nightly || "latest";
|
||
if (!nightlyChannelTag) {
|
||
nightlySpinner.error("Failed to get nightly channel tag");
|
||
logger.error(`Error getting nightly channel tag.`);
|
||
process.exit(1);
|
||
}
|
||
const nightlyChannelVersion = response["dist-tags"][nightlyChannelTag];
|
||
if (!nightlyChannelVersion) {
|
||
nightlySpinner.error("Nightly version not found");
|
||
logger.error(`Nightly channel version for tag ${colors.cyan(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);
|
||
nightlySpinner.stop(`Updated to nightly version ${colors.cyan(nightlyChannelVersion)}`);
|
||
}
|
||
const currentPackageManager = detectCurrentPackageManager();
|
||
const packageManagerArg = ctx.args.packageManager;
|
||
const packageManagerSelectOptions = packageManagerOptions.map((pm) => ({
|
||
label: pm,
|
||
value: pm,
|
||
hint: currentPackageManager === pm ? "current" : void 0
|
||
}));
|
||
let selectedPackageManager;
|
||
if (packageManagerOptions.includes(packageManagerArg)) selectedPackageManager = packageManagerArg;
|
||
else {
|
||
const result = await select({
|
||
message: "Which package manager would you like to use?",
|
||
options: packageManagerSelectOptions,
|
||
initialValue: currentPackageManager
|
||
});
|
||
if (isCancel(result)) {
|
||
cancel("Operation cancelled.");
|
||
process.exit(1);
|
||
}
|
||
selectedPackageManager = result;
|
||
}
|
||
if (ctx.args.gitInit === void 0) {
|
||
const result = await confirm({ message: "Initialize git repository?" });
|
||
if (isCancel(result)) {
|
||
cancel("Operation cancelled.");
|
||
process.exit(1);
|
||
}
|
||
ctx.args.gitInit = result;
|
||
}
|
||
if (ctx.args.install === false) logger.info("Skipping install dependencies step.");
|
||
else {
|
||
const setupTasks = [{
|
||
title: `Installing dependencies with ${colors.cyan(selectedPackageManager)}`,
|
||
task: async () => {
|
||
await installDependencies({
|
||
cwd: template.dir,
|
||
packageManager: {
|
||
name: selectedPackageManager,
|
||
command: selectedPackageManager
|
||
}
|
||
});
|
||
return "Dependencies installed";
|
||
}
|
||
}];
|
||
if (ctx.args.gitInit) setupTasks.push({
|
||
title: "Initializing git repository",
|
||
task: async () => {
|
||
try {
|
||
await x("git", ["init", template.dir], {
|
||
throwOnError: true,
|
||
nodeOptions: { stdio: "inherit" }
|
||
});
|
||
return "Git repository initialized";
|
||
} catch (err) {
|
||
return `Git initialization failed: ${err}`;
|
||
}
|
||
}
|
||
});
|
||
try {
|
||
await tasks(setupTasks);
|
||
} catch (err) {
|
||
if (process.env.DEBUG) throw err;
|
||
logger.error(err.toString());
|
||
process.exit(1);
|
||
}
|
||
}
|
||
const modulesToAdd = [];
|
||
if (ctx.args.modules !== void 0) for (const segment of (ctx.args.modules || "").split(",")) {
|
||
const mod = segment.trim();
|
||
if (mod) modulesToAdd.push(mod);
|
||
}
|
||
else if (!ctx.args.offline && !ctx.args.preferOffline) {
|
||
const modulesPromise = fetchModules();
|
||
const wantsUserModules = await confirm({
|
||
message: `Would you like to install any of the official modules?`,
|
||
initialValue: false
|
||
});
|
||
if (isCancel(wantsUserModules)) {
|
||
cancel("Operation cancelled.");
|
||
process.exit(1);
|
||
}
|
||
if (wantsUserModules) {
|
||
const modulesSpinner = spinner();
|
||
modulesSpinner.start("Fetching available modules");
|
||
const [response, templateDeps, nuxtVersion] = await Promise.all([
|
||
modulesPromise,
|
||
getTemplateDependencies(template.dir),
|
||
getNuxtVersion(template.dir)
|
||
]);
|
||
modulesSpinner.stop("Modules loaded");
|
||
const officialModules = response.filter((module) => module.type === "official" && module.npm !== "@nuxt/devtools" && !templateDeps.includes(module.npm) && (!module.compatibility.nuxt || checkNuxtCompatibility(module, nuxtVersion)));
|
||
if (officialModules.length === 0) logger.info("All official modules are already included in this template.");
|
||
else {
|
||
const selectedOfficialModules = await multiselect({
|
||
message: "Pick the modules to install:",
|
||
options: officialModules.map((module) => ({
|
||
label: `${colors.bold(colors.greenBright(module.npm))} – ${module.description.replace(/\.$/, "")}`,
|
||
value: module.npm
|
||
})),
|
||
required: false
|
||
});
|
||
if (isCancel(selectedOfficialModules)) process.exit(1);
|
||
if (selectedOfficialModules.length > 0) {
|
||
const modules = selectedOfficialModules;
|
||
const { toInstall, skipped } = filterModules(modules, Object.fromEntries(await Promise.all(modules.map(async (module) => [module, await getModuleDependencies(module)]))));
|
||
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) await runCommand$1(add_default, [
|
||
...modulesToAdd,
|
||
`--cwd=${templateDownloadPath}`,
|
||
ctx.args.install ? "" : "--skipInstall",
|
||
ctx.args.logLevel ? `--logLevel=${ctx.args.logLevel}` : ""
|
||
].filter(Boolean));
|
||
outro(`✨ Nuxt project has been created with the ${colors.cyan(template.name)} template.`);
|
||
const relativeTemplateDir = relative(process.cwd(), template.dir) || ".";
|
||
const runCmd = selectedPackageManager === "deno" ? "task" : "run";
|
||
box(`\n${[!ctx.args.shell && relativeTemplateDir.length > 1 && colors.cyan(`cd ${relativeTemplateDir}`), colors.cyan(`${selectedPackageManager} ${runCmd} dev`)].filter(Boolean).map((step) => ` › ${step}`).join("\n")}\n`, ` 👉 Next steps `, {
|
||
contentAlign: "left",
|
||
titleAlign: "left",
|
||
width: "auto",
|
||
titlePadding: 2,
|
||
contentPadding: 2,
|
||
rounded: true,
|
||
withGuide: false,
|
||
formatBorder: (text$1) => `${themeColor + text$1}\x1B[0m`
|
||
});
|
||
if (ctx.args.shell) startShell(template.dir);
|
||
}
|
||
});
|
||
async function getModuleDependencies(moduleName) {
|
||
try {
|
||
const dependencies = (await $fetch(`https://registry.npmjs.org/${moduleName}/latest`)).dependencies || {};
|
||
return Object.keys(dependencies);
|
||
} catch (err) {
|
||
logger.warn(`Could not get dependencies for ${colors.cyan(moduleName)}: ${err}`);
|
||
return [];
|
||
}
|
||
}
|
||
function filterModules(modules, allDependencies) {
|
||
const result = {
|
||
toInstall: [],
|
||
skipped: []
|
||
};
|
||
for (const module of modules) if (modules.some((otherModule) => {
|
||
if (otherModule === module) return false;
|
||
return (allDependencies[otherModule] || []).includes(module);
|
||
})) 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 readPackageJSON(packageJsonPath);
|
||
const directDeps = {
|
||
...packageJson.dependencies,
|
||
...packageJson.devDependencies
|
||
};
|
||
const directDepNames = Object.keys(directDeps);
|
||
const allDeps = new Set(directDepNames);
|
||
(await Promise.all(directDepNames.map((dep) => getModuleDependencies(dep)))).forEach((deps) => {
|
||
deps.forEach((dep) => allDeps.add(dep));
|
||
});
|
||
return Array.from(allDeps);
|
||
} catch (err) {
|
||
logger.warn(`Could not read template dependencies: ${err}`);
|
||
return [];
|
||
}
|
||
}
|
||
function detectCurrentPackageManager() {
|
||
const userAgent = process.env.npm_config_user_agent;
|
||
if (!userAgent) return;
|
||
const [name] = userAgent.split("/");
|
||
if (packageManagerOptions.includes(name)) return name;
|
||
}
|
||
|
||
//#endregion
|
||
export { init_default as default }; |