UNPKG

nuxi

Version:
962 lines (961 loc) 33.8 kB
import { t as defineCommand } from "./dist-B03QHgrC.mjs"; import { n as colors } from "./consola.DXBYu-KD-qSGefJ79.mjs"; import "./utils-MaFlCoS1.mjs"; import { i as h } from "./dist-BkNIIgFa.mjs"; import { o as logLevelArgs, t as cwdArgs } from "./_shared-D6pJgr6t.mjs"; import { t as runCommand } from "./run-CwixkrKi.mjs"; import { _ as q, a as Ee, f as me, g as ye, h as xe, l as ft, m as ue, n as logger, p as oe, s as Re, u as ge } from "./logger-CtlB9piy.mjs"; import { a as join$1, c as resolve$1, s as relative, t as basename$1 } from "./pathe.M-eThtNZ-BfnU2wdd.mjs"; import { n as themeColor, t as nuxtIcon } from "./ascii-ByocWiRd.mjs"; import { r as relativeToProcess } from "./kit-Bx45zdA5.mjs"; import { a as writePackageJSON, r as readPackageJSON, t as findFile } from "./dist-BPzTdxaO.mjs"; import { t as getNuxtVersion } from "./versions-Bq8QDcwV.mjs"; import { a as y, n as S, r as ne, t as C } from "./nypm-Bt3fv74t.mjs"; import { t as V } from "./main-BD07ngkv.mjs"; import { i as installDependencies } from "./dist-bPNZE3kQ.mjs"; import { i as $fetch, n as fetchModules, t as checkNuxtCompatibility } from "./_utils-CpQw4j1K.mjs"; import add_default, { t as selectModulesAutocomplete } from "./add-C1fhsUXS.mjs"; import { join } from "node:path"; import process$1 from "node:process"; import { promisify } from "node:util"; import { createWriteStream, existsSync, readdirSync, renameSync } from "node:fs"; import { homedir, tmpdir } from "node:os"; import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { spawn, spawnSync } from "node:child_process"; import { PassThrough, Readable, pipeline } from "node:stream"; import { pipeline as pipeline$1 } from "node:stream/promises"; //#region ../../node_modules/.pnpm/giget@3.2.0/node_modules/giget/dist/_chunks/giget.mjs async function download(url, filePath, options = {}) { const infoPath = filePath + ".json"; const info = JSON.parse(await readFile(infoPath, "utf8").catch(() => "{}")); const etag = (await sendFetch(url, { method: "HEAD", headers: options.headers }).catch(() => void 0))?.headers.get("etag"); if (info.etag === etag && existsSync(filePath)) return; if (typeof etag === "string") info.etag = etag; const response = await sendFetch(url, { headers: options.headers }); if (response.status >= 400) throw new Error(`Failed to download ${url}: ${response.status} ${response.statusText}`); const stream = createWriteStream(filePath); await promisify(pipeline)(response.body, stream); await writeFile(infoPath, JSON.stringify(info), "utf8"); } const inputRegex = /^(?<repo>[-\w.]+\/[-\w.]+)(?<subdir>[^#]+)?(?<ref>#[-\w./@]+)?/; const expandedInputRegex = /^(?<repo>[-\w.]+(?:\/[-\w.]+)+?)(?:::(?<subdir>[^#]*))?(?<ref>#[-\w./@]+)?$/; function parseGitURI(input, options) { const useExpanded = options?.expandRepo || input.includes("::"); const m = input.match(useExpanded ? expandedInputRegex : inputRegex)?.groups || {}; const subdir = useExpanded ? m.subdir ? "/" + m.subdir : "/" : m.subdir || "/"; return { repo: m.repo || "", subdir, ref: m.ref ? m.ref.slice(1) : "main" }; } function debug(...args) { if (process.env.DEBUG) console.debug("[giget]", ...args); } async function sendFetch(url, options = {}) { if (options.headers?.["sec-fetch-mode"]) options.mode = options.headers["sec-fetch-mode"]; const res = await fetch(url, { ...options, headers: normalizeHeaders(options.headers) }).catch((error) => { throw new Error(`Failed to download ${url}: ${error}`, { cause: error }); }); if (options.validateStatus && res.status >= 400) throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`); return res; } function cacheDirectory() { const cacheDir = process.env.XDG_CACHE_HOME ? y(process.env.XDG_CACHE_HOME, "giget") : y(homedir(), ".cache/giget"); if (process.platform === "win32") { const windowsCacheDir = y(tmpdir(), "giget"); if (!existsSync(windowsCacheDir) && existsSync(cacheDir)) try { renameSync(cacheDir, windowsCacheDir); } catch {} return windowsCacheDir; } return cacheDir; } function normalizeHeaders(headers = {}) { const normalized = {}; for (const [key, value] of Object.entries(headers)) { if (!value) continue; normalized[key.toLowerCase()] = value; } return normalized; } function currentShell() { if (process.env.SHELL) return process.env.SHELL; if (process.platform === "win32") return "cmd.exe"; return "/bin/bash"; } function startShell(cwd) { cwd = y(cwd); const shell = currentShell(); console.info(`(experimental) Opening shell in ${S(process.cwd(), cwd)}...`); spawnSync(shell, [], { cwd, shell: true, stdio: "inherit" }); } const git = (input, options) => { const parsed = parseGitCloneURI(input); return { name: parsed.name, version: parsed.subdir ? `${parsed.version || "default"}-${parsed.subdir.replaceAll("/", "-")}` : parsed.version, tar: ({ auth } = {}) => _cloneAndTar(parsed, auth ?? options.auth) }; }; function parseGitCloneURI(input, opts = {}) { const cwd = opts.cwd ?? process.cwd(); let uri = input.replace(/#.*$/, ""); let pathSubdir; if (/^[./]/.test(input)) uri = y(cwd, uri); else if (/^https?:\/\//.test(uri)) { const httpMatch = /^(https?:\/\/[^/]+)\/([\w.-]+\/[\w.-]+?)(?:\.git)?(?:\/(.+))?$/.exec(uri); if (httpMatch) { const [, origin, repo, rest] = httpMatch; uri = `${origin}/${repo}`; if (rest) pathSubdir = rest; } } else if (uri.includes("@")) { const sshMatch = /^(.*?:[\w.-]+\/[\w.-]+?)(?:\.git)?(?:\/(.+))?$/.exec(uri); if (sshMatch) { const [, repoUri, rest] = sshMatch; uri = repoUri; if (rest) pathSubdir = rest; } } else { const hostMap = { "github:": "https://github.com/", "gh:": "https://github.com/", "gitlab:": "https://gitlab.com/", "bitbucket:": "https://bitbucket.org/", "sourcehut:": "https://git.sr.ht/~" }; const host = /^(.+?:)/.exec(uri)?.at(1); if (host && hostMap[host]) uri = uri.replace(host, hostMap[host]); else if (!host) uri = `${(process.env.GIGET_GIT_HOST || "https://github.com/").replace(/\/$/, "")}/${uri}`; const httpMatch = /^(https?:\/\/[^/]+\/~?[\w.-]+\/[\w.-]+?)(?:\.git)?(?:\/(.+))?$/.exec(uri); if (httpMatch) { const [, repoUri, rest] = httpMatch; uri = repoUri; if (rest) pathSubdir = rest; } } const name = uri.replace(/^https?:\/\//, "").replace(/^.+@/, "").replace(/(\.git)?(#.*)?$/, "").replace(/^\W+/, "").replaceAll(/[:/]/g, "-"); const [version, hashSubdir] = /#(.+)$/.exec(input)?.at(1)?.split(":") ?? []; const resolvedVersion = version || void 0; const subdir = hashSubdir || pathSubdir; return { uri, name, ...resolvedVersion && { version: resolvedVersion }, ...subdir && { subdir } }; } async function _cloneAndTar(parsed, token) { const tmpDir = await mkdtemp(join(tmpdir(), "giget-git-")); if (token && /[\r\n]/.test(token)) throw new Error("Auth token must not contain newline characters"); const execEnv = { ...process.env, GIT_TERMINAL_PROMPT: "0" }; if (token) { execEnv.GIT_CONFIG_COUNT = "1"; execEnv.GIT_CONFIG_KEY_0 = "http.extraHeader"; execEnv.GIT_CONFIG_VALUE_0 = `Authorization: Bearer ${token}`; } const execOpts = { env: execEnv, timeout: 6e4 }; const status = _createStatus(); const gitExec = (args) => _gitSpawn(args, execOpts, status); const gitExecIn = (args) => _gitSpawn(args, { ...execOpts, cwd: tmpDir }, status); try { const cloneArgs = [ "clone", "--progress", "--depth", "1" ]; if (parsed.subdir) cloneArgs.push("--filter=blob:none", "--sparse", "--no-checkout"); if (parsed.version) cloneArgs.push("--branch", parsed.version); cloneArgs.push("--", parsed.uri, tmpDir); try { status.update("Cloning..."); await gitExec(cloneArgs); status.update("Cloned."); } catch (cloneError) { if (!parsed.version) throw cloneError; debug("Shallow clone failed, falling back to full clone:", cloneError); status.update("Shallow clone failed, cloning..."); await rm(tmpDir, { recursive: true, force: true }); await mkdir(tmpDir, { recursive: true }); await gitExecIn(["init"]); await gitExecIn([ "remote", "add", "origin", parsed.uri ]); await gitExecIn(["fetch", "origin"]); await gitExecIn(["checkout", parsed.version]); status.update("Fetched."); } if (parsed.subdir) { status.update(`Sparse checkout ${parsed.subdir}...`); await gitExecIn([ "sparse-checkout", "set", parsed.subdir ]); await gitExecIn(["checkout"]); } status.update("Packing..."); const tarDir = parsed.subdir ? join(tmpDir, parsed.subdir) : tmpDir; const { create } = await import("./tar-MSmnQMH9.mjs").then((n) => n.t); status.done(); const stream = create({ gzip: true, cwd: tarDir, filter: (path) => !path.startsWith(".git/") && path !== ".git" && !path.startsWith("./.git/") && path !== "./.git" }, ["."]).pipe(new PassThrough()); let cleaned = false; const cleanup = () => { if (cleaned) return; cleaned = true; rm(tmpDir, { recursive: true, force: true }); }; stream.on("end", cleanup); stream.on("error", cleanup); stream.on("close", cleanup); return stream; } catch (error) { status.done(); await rm(tmpDir, { recursive: true, force: true }); throw error; } } const _spinnerFrames = [ "⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏" ]; function _gitSpawn(args, opts, status) { return new Promise((resolve, reject) => { const proc = spawn("git", args, { ...opts, stdio: [ "ignore", "pipe", "pipe" ] }); proc.stdout.resume(); let lastLine = ""; proc.stderr?.on("data", (chunk) => { const str = chunk.toString(); for (const line of str.split(/[\r\n]/)) { const clean = line.trim(); if (clean) lastLine = clean; } if (status) status.update(lastLine); }); proc.on("close", (code) => { if (code === 0) resolve(lastLine); else reject(/* @__PURE__ */ new Error(`git ${args[0]} exited with code ${code}. Is git installed?`)); }); proc.on("error", (err) => { if (err.code === "ENOENT") reject(/* @__PURE__ */ new Error("git is not installed or not found in PATH")); else reject(err); }); }); } function _createStatus() { if (!process.stderr.isTTY) return { update(_text) {}, done() {} }; let msg = ""; let frame = 0; const render = () => { const spinner = _spinnerFrames[frame % _spinnerFrames.length]; frame++; process.stderr.write(`\x1B[2K\r\x1B[2m${spinner} ${msg}\x1B[0m`); }; const interval = setInterval(render, 80); return { update(text) { msg = text; render(); }, done() { clearInterval(interval); process.stderr.write("\x1B[2K\r"); } }; } const http = async (input, options) => { if (input.endsWith(".json")) return await _httpJSON(input, options); const url = new URL(input); let name = ne(url.pathname); try { const head = await sendFetch(url.href, { method: "HEAD", validateStatus: true, headers: { authorization: options.auth ? `Bearer ${options.auth}` : void 0 } }); if ((head.headers.get("content-type") || "").includes("application/json")) return await _httpJSON(input, options); const filename = head.headers.get("content-disposition")?.match(/filename="?(.+)"?/)?.[1]; if (filename) name = filename.split(".")[0]; } catch (error) { debug(`Failed to fetch HEAD for ${url.href}:`, error); } return { name: `${name}-${url.href.slice(0, 8)}`, version: "", subdir: "", tar: url.href, defaultDir: name, headers: { Authorization: options.auth ? `Bearer ${options.auth}` : void 0 } }; }; const _httpJSON = async (input, options) => { const info = await (await sendFetch(input, { validateStatus: true, headers: { authorization: options.auth ? `Bearer ${options.auth}` : void 0 } })).json(); if (!info.tar || !info.name) throw new Error(`Invalid template info from ${input}. name or tar fields are missing!`); return info; }; const github = (input, options) => { const parsed = parseGitURI(input); const githubAPIURL = process.env.GIGET_GITHUB_URL || "https://api.github.com"; return { name: parsed.repo.replace("/", "-"), version: parsed.ref, subdir: parsed.subdir, headers: { Authorization: options.auth ? `Bearer ${options.auth}` : void 0, Accept: "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28" }, url: `${githubAPIURL.replace("api.github.com", "github.com")}/${parsed.repo}/tree/${parsed.ref}${parsed.subdir}`, tar: `${githubAPIURL}/repos/${parsed.repo}/tarball/${parsed.ref}` }; }; const gitlab = (input, options) => { const parsed = parseGitURI(input, { expandRepo: true }); const gitlab = process.env.GIGET_GITLAB_URL || "https://gitlab.com"; return { name: parsed.repo.replace("/", "-"), version: parsed.ref, subdir: parsed.subdir, headers: { authorization: options.auth ? `Bearer ${options.auth}` : void 0, "sec-fetch-mode": "same-origin" }, url: `${gitlab}/${parsed.repo}/tree/${parsed.ref}${parsed.subdir}`, tar: `${gitlab}/${parsed.repo}/-/archive/${parsed.ref}.tar.gz` }; }; const bitbucket = (input, options) => { const parsed = parseGitURI(input); return { name: parsed.repo.replace("/", "-"), version: parsed.ref, subdir: parsed.subdir, headers: { authorization: options.auth ? `Bearer ${options.auth}` : void 0 }, url: `https://bitbucket.com/${parsed.repo}/src/${parsed.ref}${parsed.subdir}`, tar: `https://bitbucket.org/${parsed.repo}/get/${parsed.ref}.tar.gz` }; }; const sourcehut = (input, options) => { const parsed = parseGitURI(input); return { name: parsed.repo.replace("/", "-"), version: parsed.ref, subdir: parsed.subdir, headers: { authorization: options.auth ? `Bearer ${options.auth}` : void 0 }, url: `https://git.sr.ht/~${parsed.repo}/tree/${parsed.ref}/item${parsed.subdir}`, tar: `https://git.sr.ht/~${parsed.repo}/archive/${parsed.ref}.tar.gz` }; }; const providers = { http, https: http, git, github, gh: github, gitlab, bitbucket, sourcehut }; const DEFAULT_REGISTRY$1 = "https://raw.githubusercontent.com/unjs/giget/main/templates"; const registryProvider = (registryEndpoint = DEFAULT_REGISTRY$1, options = {}) => { return (async (input) => { const start = Date.now(); const registryURL = `${registryEndpoint}/${input}.json`; const result = await sendFetch(registryURL, { headers: { authorization: options.auth ? `Bearer ${options.auth}` : void 0 } }); if (result.status >= 400) throw new Error(`Failed to download ${input} template info from ${registryURL}: ${result.status} ${result.statusText}`); const info = await result.json(); if (!info.tar || !info.name) throw new Error(`Invalid template info from ${registryURL}. name or tar fields are missing!`); debug(`Fetched ${input} template info from ${registryURL} in ${Date.now() - start}ms`); return info; }); }; const sourceProtoRe = /^([\w+-.]+):/; async function downloadTemplate(input, options = {}) { options.registry = process.env.GIGET_REGISTRY ?? options.registry; options.auth = process.env.GIGET_AUTH ?? options.auth; const registry = options.registry === false ? void 0 : registryProvider(options.registry, { auth: options.auth }); let providerName = options.provider || (registry ? "registry" : "github"); let source = input; const sourceProviderMatch = input.match(sourceProtoRe); if (sourceProviderMatch) { providerName = sourceProviderMatch[1]; source = input.slice(sourceProviderMatch[0].length); if (providerName === "http" || providerName === "https") source = input; } if (providerName.endsWith("+git")) { source = `${providerName.slice(0, -4)}:${source}`; providerName = "git"; } const provider = options.providers?.[providerName] || providers[providerName] || registry; if (!provider) throw new Error(`Unsupported provider: ${providerName}`); const template = await Promise.resolve().then(() => provider(source, { auth: options.auth })).catch((error) => { throw new Error(`Failed to download template from ${providerName}: ${error.message}`); }); if (!template) throw new Error(`Failed to resolve template from ${providerName}`); template.name = (template.name || "template").replace(/[^\da-z-]/gi, "-"); template.defaultDir = (template.defaultDir || template.name).replace(/[^\da-z-]/gi, "-"); const tarPath = y(y(cacheDirectory(), providerName, template.name), (template.version || template.name) + ".tar.gz"); if (options.preferOffline && existsSync(tarPath)) options.offline = true; if (!options.offline) { await mkdir(C(tarPath), { recursive: true }); const s = Date.now(); if (typeof template.tar === "function") { const tarFn = template.tar; await (async () => { const stream = await tarFn({ auth: options.auth }); await pipeline$1(stream instanceof Readable ? stream : Readable.fromWeb(stream), createWriteStream(tarPath)); })().catch((error) => { if (!existsSync(tarPath)) throw error; debug("Download error. Using cached version:", error); options.offline = true; }); } else await download(template.tar, tarPath, { headers: { Authorization: options.auth ? `Bearer ${options.auth}` : void 0, ...normalizeHeaders(template.headers) } }).catch((error) => { if (!existsSync(tarPath)) throw error; debug("Download error. Using cached version:", error); options.offline = true; }); debug(`Downloaded to ${tarPath} in ${Date.now() - s}ms`); } if (!existsSync(tarPath)) throw new Error(`Tarball not found: ${tarPath} (offline: ${options.offline})`); const extractPath = y(y(options.cwd || "."), options.dir || template.defaultDir); if (options.forceClean) await rm(extractPath, { recursive: true, force: true }); if (!options.force && existsSync(extractPath) && readdirSync(extractPath).length > 0) throw new Error(`Destination ${extractPath} already exists.`); await mkdir(extractPath, { recursive: true }); const s = Date.now(); const subdir = template.subdir?.replace(/^\//, "") || ""; const { extract } = await import("./tar-MSmnQMH9.mjs").then((n) => n.t); await extract({ file: tarPath, cwd: extractPath, onReadEntry(entry) { entry.path = entry.path.split("/").splice(1).join("/"); if (subdir) if (entry.path.startsWith(subdir + "/")) entry.path = entry.path.slice(subdir.length); else entry.path = ""; } }); debug(`Extracted to ${extractPath} in ${Date.now() - s}ms`); if (options.install) { debug("Installing dependencies..."); const { installDependencies } = await import("./nypm-Bt3fv74t.mjs").then((n) => n.i).then((n) => n.t); await installDependencies({ cwd: extractPath, silent: options.silent, ...typeof options.install === "object" ? options.install : {} }); } return { ...template, source, dir: extractPath }; } //#endregion //#region 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$1.env.GITHUB_TOKEN ? { authorization: `token ${process$1.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 src/commands/init.ts const NON_WORD_RE = /[^\w-]/g; const MULTI_DASH_RE = /-{2,}/g; const LEADING_TRAILING_DASH_RE = /^-|-$/g; 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 (h) process$1.stdout.write(`\n${nuxtIcon}\n\n`); ge(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-Dh0-23V0.mjs").then((r) => r.templates); if (ctx.args.offline || ctx.args.preferOffline) availableTemplates = defaultTemplates; else { const templatesSpinner = ft(); 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 Ee({ 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 (q(result)) { me("Operation cancelled."); process$1.exit(1); } templateName = result; } templateName ||= DEFAULT_TEMPLATE_NAME; if (typeof templateName !== "string") { logger.error("Please specify a template!"); process$1.exit(1); } let dir = ctx.args.dir; if (dir === "") { const defaultDir = availableTemplates[templateName]?.defaultDir || "nuxt-app"; const result = await Re({ message: "Where would you like to create your project?", placeholder: `./${defaultDir}`, defaultValue: defaultDir }); if (q(result)) { me("Operation cancelled."); process$1.exit(1); } dir = result; } const cwd = resolve$1(ctx.args.cwd); let templateDownloadPath = resolve$1(cwd, dir); logger.step(`Creating project in ${colors.cyan(relativeToProcess(templateDownloadPath))}`); let shouldForce = Boolean(ctx.args.force); if (!shouldForce && existsSync(templateDownloadPath)) { const selectedAction = await Ee({ 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 (q(selectedAction)) { me("Operation cancelled."); process$1.exit(1); } switch (selectedAction) { case "override": shouldForce = true; break; case "different": { const result = await Re({ message: "Please specify a different directory:" }); if (q(result)) { me("Operation cancelled."); process$1.exit(1); } templateDownloadPath = resolve$1(cwd, result); break; } default: process$1.exit(1); } } let template; const downloadSpinner = ft(); 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$1.env.NUXI_INIT_REGISTRY || DEFAULT_REGISTRY }); if (dir.length > 0) { const path = await findFile("package.json", { startingFrom: join$1(templateDownloadPath, "package.json"), reverse: true }); if (path) { const pkg = await readPackageJSON(path, { try: true }); if (pkg && pkg.name) { const slug = basename$1(templateDownloadPath).replace(NON_WORD_RE, "-").replace(MULTI_DASH_RE, "-").replace(LEADING_TRAILING_DASH_RE, ""); 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$1.env.DEBUG) throw err; logger.error(err.toString()); process$1.exit(1); } if (ctx.args.nightly !== void 0 && !ctx.args.offline && !ctx.args.preferOffline) { const nightlySpinner = ft(); 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$1.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$1.exit(1); } const nightlyNuxtPackageJsonVersion = `npm:nuxt-nightly@${nightlyChannelVersion}`; const packageJsonPath = resolve$1(cwd, 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$1(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 Ee({ message: "Which package manager would you like to use?", options: packageManagerSelectOptions, initialValue: currentPackageManager }); if (q(result)) { me("Operation cancelled."); process$1.exit(1); } selectedPackageManager = result; } let gitInit = ctx.args.gitInit === "false" ? false : ctx.args.gitInit; if (gitInit === void 0) { const result = await ue({ message: "Initialize git repository?" }); if (q(result)) { me("Operation cancelled."); process$1.exit(1); } gitInit = result; } if (ctx.args.install === false || 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 }, silent: true }); return "Dependencies installed"; } }]; if (gitInit) setupTasks.push({ title: "Initializing git repository", task: async () => { try { await V("git", ["init", template.dir], { throwOnError: true, nodeOptions: { stdio: "inherit" } }); return "Git repository initialized"; } catch (err) { return `Git initialization failed: ${err}`; } } }); try { await xe(setupTasks); } catch (err) { if (process$1.env.DEBUG) throw err; logger.error(err.toString()); process$1.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 ue({ message: `Would you like to browse and install modules?`, initialValue: false }); if (q(wantsUserModules)) { me("Operation cancelled."); process$1.exit(1); } if (wantsUserModules) { const modulesSpinner = ft(); modulesSpinner.start("Fetching available modules"); const [response, templateDeps, nuxtVersion] = await Promise.all([ modulesPromise, getTemplateDependencies(template.dir), getNuxtVersion(template.dir) ]); modulesSpinner.stop("Modules loaded"); const allModules = response.filter((module) => module.npm !== "@nuxt/devtools" && !templateDeps.includes(module.npm) && (!module.compatibility.nuxt || checkNuxtCompatibility(module, nuxtVersion))); if (allModules.length === 0) logger.info("All modules are already included in this template."); else { const result = await selectModulesAutocomplete({ modules: allModules }); if (result.selected.length > 0) { const modules = result.selected; 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(add_default, [ ...modulesToAdd, `--cwd=${templateDownloadPath}`, ctx.args.install ? "" : "--skipInstall", ctx.args.logLevel ? `--logLevel=${ctx.args.logLevel}` : "" ].filter(Boolean)); ye(`✨ Nuxt project has been created with the ${colors.cyan(template.name)} template.`); const relativeTemplateDir = relative(process$1.cwd(), template.dir) || "."; const runCmd = selectedPackageManager === "deno" ? "task" : "run"; oe(`\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) => `${themeColor + text}\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$1(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 [...allDeps]; } catch (err) { logger.warn(`Could not read template dependencies: ${err}`); return []; } } function detectCurrentPackageManager() { const userAgent = process$1.env.npm_config_user_agent; if (!userAgent) return; const [name] = userAgent.split("/"); if (packageManagerOptions.includes(name)) return name; } //#endregion export { init_default as default };