UNPKG

@vivliostyle/cli

Version:

Save the pdf file via headless browser and Vivliostyle.

1,141 lines (1,131 loc) 34.9 kB
import { GlobMatcher, Logger, cwd, getDefaultBrowserTag, getOsLocale, isUnicodeSupported, registerExitHandler, toTitleCase, whichPm } from "./chunk-DBK27BAR.js"; import { ValidString, VivliostyleInlineConfigWithoutChecks, VivliostylePackageMetadata } from "./chunk-7GIJVX4M.js"; import { DEFAULT_CONFIG_FILENAME, DEFAULT_PROJECT_AUTHOR, DEFAULT_PROJECT_TITLE, TEMPLATE_DEFAULT_FILES, TEMPLATE_SETTINGS, cliVersion, coreVersion, languages } from "./chunk-OAFXM4ES.js"; import { __callDispose, __using } from "./chunk-I7BWSAN6.js"; // src/core/create.ts import { downloadTemplate } from "@bluwy/giget-core"; import { copy } from "fs-extra/esm"; import { isUtf8 } from "node:buffer"; import fs from "node:fs"; import { pathToFileURL } from "node:url"; import terminalLink from "terminal-link"; import { x } from "tinyexec"; import upath from "upath"; import * as v2 from "valibot"; import { cyan, dim as dim2, gray as gray2, green as green2, yellow as yellow2 } from "yoctocolors"; // src/create-template.ts import { camelCase, capitalCase, kebabCase, snakeCase } from "change-case"; import Handlebars from "handlebars"; import { titleCase } from "title-case"; function upper(text) { return text && text.toUpperCase(); } Handlebars.registerHelper("upper", upper); function lower(text) { return text && text.toLowerCase(); } Handlebars.registerHelper("lower", lower); function capital(text) { return text && capitalCase(text); } Handlebars.registerHelper("capital", capital); function camel(text) { return text && camelCase(text); } Handlebars.registerHelper("camel", camel); function snake(text) { return text && snakeCase(text); } Handlebars.registerHelper("snake", snake); function kebab(text) { return text && kebabCase(text); } Handlebars.registerHelper("kebab", kebab); function proper(text) { return text && titleCase(text); } Handlebars.registerHelper("proper", proper); function lorem() { return "Lorem ipsum dolor sit amet consectetur adipisicing elit. Odio, maxime et saepe facilis dolor aut maiores cupiditate rem voluptatem placeat accusamus voluptates laborum ratione enim blanditiis nisi voluptas non mollitia."; } Handlebars.registerHelper("lorem", lorem); function json(data) { return JSON.stringify(data); } Handlebars.registerHelper("json", json); function format(text, context) { const template = Handlebars.compile(text.toString(), { noEscape: true }); return template(context); } // src/interactive.ts import { AutocompletePrompt, getColumns, isCancel, MultiSelectPrompt, SelectPrompt, TextPrompt } from "@clack/core"; import { limitOptions } from "@clack/prompts"; import { wrapAnsi } from "fast-wrap-ansi"; import { cursor, erase } from "sisteransi"; import * as v from "valibot"; import { blueBright, dim, gray, green, hidden, inverse, redBright, strikethrough, yellow, yellowBright } from "yoctocolors"; async function askQuestion({ question: questions, interactiveLogger, schema, validateProgressMessage }) { const response = {}; while (true) { for (const [name, question] of Object.entries(questions)) { let result2; const maxItems = 10; const normalizeOptions = (options) => options.map((v3) => typeof v3 === "string" ? { value: v3 } : v3); const validate = (value = "") => { if (!question.required || "defaultValue" in question) { return; } const { success: success2, issues: issues2 } = v.safeParse(ValidString, value); return success2 ? void 0 : issues2[0].message; }; if (import.meta.env?.VITEST) { question.name = name; } if (question.type === "text") { result2 = await textPrompt({ ...question, validate }); } else if (question.type === "select") { result2 = await selectPrompt({ ...question, options: normalizeOptions(question.options), maxItems }); } else if (question.type === "multiSelect") { result2 = await multiSelectPrompt({ ...question, options: normalizeOptions(question.options), maxItems }); } else if (question.type === "autocomplete") { result2 = await autocompletePrompt({ ...question, options: normalizeOptions(question.options), maxItems, validate }); } else if (question.type === "autocompleteMultiSelect") { result2 = await autocompleteMultiSelectPrompt({ ...question, options: normalizeOptions(question.options), maxItems }); } else { result2 = question; } if (isCancel(result2)) { process.exit(0); } response[name] = result2; interactiveLogger.messageHistory.push({ type: "question", message: question.message, answer: result2 }); } let result; if (schema && schema.async) { result = await interactiveLogger?.logLoading( validateProgressMessage ?? "", () => v.safeParseAsync(schema, response) ); } else if (schema) { result = v.safeParse(schema, response); } else { return response; } const { success, output, issues } = result; if (success) { return output; } interactiveLogger.logWarn(issues[0].message); } } var promptStateSymbol = { initial: isUnicodeSupported ? "\u25C6" : "*", active: isUnicodeSupported ? "\u25C6" : "*", cancel: isUnicodeSupported ? "\u25A0" : "x", submit: isUnicodeSupported ? "\u25C7" : "o", error: isUnicodeSupported ? "\u25B2" : "x" }; var radioActiveSymbol = isUnicodeSupported ? "\u25C9" : ">"; var radioInactiveSymbol = isUnicodeSupported ? "\u25EF" : " "; var checkboxActiveSymbol = isUnicodeSupported ? "\u25FC" : "[+]"; var checkboxInactiveSymbol = isUnicodeSupported ? "\u25FB" : "[ ]"; var labelToString = (option) => option.label ?? String(option.value ?? ""); var renderListOption = (multiSelect, selectedValues) => (option, active) => { const Y = multiSelect ? checkboxActiveSymbol : radioActiveSymbol; const N = multiSelect ? checkboxInactiveSymbol : radioInactiveSymbol; if (option.disabled) { return `${gray(N)} ${gray(labelToString(option))} ${gray("(disabled)")}`; } return `${multiSelect && selectedValues?.includes(option.value) || !multiSelect && active ? green(Y) : dim(N)} ${active ? `${labelToString(option)}${option.hint ? ` ${dim(`(${option.hint})`)}` : ""}` : dim(labelToString(option))}`; }; function textPrompt(opts) { return new TextPrompt({ ...opts, input: Logger.stdin, output: Logger.stdout, signal: Logger.signal, render() { const symbol = promptStateSymbol[this.state]; const placeholder = opts.placeholder ? inverse(opts.placeholder[0]) + dim(opts.placeholder.slice(1)) : inverse(hidden("_")); const userInput = !this.userInput ? placeholder : this.userInputWithCursor; const value = this.value ?? ""; switch (this.state) { case "error": { const errorText = this.error ? ` ${yellow(this.error)}` : ""; return `${yellow("\u2551")} ${yellow(`${symbol}\u2500`)} ${opts.message} ${userInput} ${errorText}`; } case "submit": { const valueText = value ? ` ${dim(value)}` : ""; return `${blueBright("\u2551")} ${blueBright(`${symbol}\u2500`)} ${opts.message} ${blueBright("\u2551")}${valueText}`; } case "cancel": { const valueText = value ? ` ${strikethrough(dim(value))}` : ""; return `${gray("\u2551")} ${gray(`${symbol}\u2500`)} ${opts.message} ${valueText}`; } default: return `${blueBright("\u2551")} ${blueBright(`${symbol}\u2500`)} ${opts.message} ${userInput} `; } } }).prompt(); } function selectPrompt(opts, multiple = false) { return new (multiple ? MultiSelectPrompt : SelectPrompt)({ ...opts, input: Logger.stdin, output: Logger.stdout, signal: Logger.signal, render() { const symbol = promptStateSymbol[this.state]; const values = [this.value].flat(); const selected = this.options.filter((o) => values.includes(o.value)); const label = selected.length > 0 ? selected.map(labelToString).join(", ") : "none"; switch (this.state) { case "submit": { return `${blueBright("\u2551")} ${blueBright(`${symbol}\u2500`)} ${opts.message} ${blueBright("\u2551")} ${dim(label)}`; } case "cancel": { return `${gray("\u2551")} ${gray(`${symbol}\u2500`)} ${opts.message} ${strikethrough(dim(label))}`; } default: { const indents = " "; const displayingOptions = limitOptions({ output: opts.output, cursor: this.cursor, options: this.options, maxItems: opts.maxItems, columnPadding: indents.length, style: renderListOption( multiple, Array.isArray(this.value) ? this.value : [] ) }); return `${blueBright("\u2551")} ${blueBright(`${symbol}\u2500`)} ${opts.message} ${displayingOptions.join(` ${indents}`)} `; } } } }).prompt(); } function multiSelectPrompt(opts) { return selectPrompt(opts, true); } function autocompletePrompt(opts, multiple = false) { return new AutocompletePrompt({ ...opts, multiple, initialValue: opts.initialValue ? [opts.initialValue] : void 0, filter: (searchText, option) => { if (!searchText) { return true; } const label = (option.label ?? String(option.value ?? "")).toLowerCase(); const hint = (option.hint ?? "").toLowerCase(); const value = String(option.value).toLowerCase(); const term = searchText.toLowerCase(); return label.includes(term) || hint.includes(term) || value.includes(term); }, input: Logger.stdin, output: Logger.stdout, signal: Logger.signal, render() { const symbol = promptStateSymbol[this.state]; const selected = this.options.filter( (o) => this.selectedValues.includes(o.value) ); const label = selected.length > 0 ? selected.map(labelToString).join(", ") : "none"; switch (this.state) { case "submit": { return `${blueBright("\u2551")} ${blueBright(`${symbol}\u2500`)} ${opts.message} ${blueBright("\u2551")} ${dim(label)}`; } case "cancel": { const userInputText = this.userInput ? ` ${strikethrough(dim(this.userInput))}` : ""; return `${gray("\u2551")} ${gray(`${symbol}\u2500`)} ${opts.message} ${userInputText}`; } default: { const indents = " "; let searchText = ""; if (this.isNavigating || opts.placeholder && !this.userInput) { const searchTextValue = opts.placeholder ?? this.userInput; searchText = searchTextValue ? ` ${dim(searchTextValue)}` : ""; } else { searchText = ` ${this.userInputWithCursor}`; } const matches = this.filteredOptions.length !== this.options.length ? dim( ` (${this.filteredOptions.length} match${this.filteredOptions.length === 1 ? "" : "es"})` ) : ""; const headings = [ blueBright("\u2551"), `${blueBright(`${symbol}\u2500`)} ${opts.message}`, `${indents}${dim("Search:")}${searchText}${matches}` ]; if (this.filteredOptions.length === 0 && this.userInput) { headings.push(`${indents}${yellow("No matches found")}`); } if (this.state === "error") { headings.push(`${indents}${yellow(`Error: ${this.error ?? ""}`)}`); } const footers = [ multiple ? `${indents}${dim("\u2191/\u2193 to navigate \u2022 Space/Tab: select \u2022 Enter: confirm \u2022 Type: to search")}` : `${indents}${dim("\u2191/\u2193 to select \u2022 Enter: confirm \u2022 Type: to search")}` ]; const displayingOptions = limitOptions({ output: opts.output, cursor: this.cursor, options: this.filteredOptions, maxItems: opts.maxItems, columnPadding: indents.length, rowPadding: headings.length + footers.length, style: renderListOption(multiple, this.selectedValues) }); return [ ...headings, ...displayingOptions.map((s) => `${indents}${s}`), ...footers ].join("\n"); } } } }).prompt(); } function autocompleteMultiSelectPrompt(opts) { return autocompletePrompt(opts, true); } var InteractiveLogger = class { messageHistory = []; async logLoading(message, fn, deferredTimeMs = 300) { this.messageHistory.push({ type: "loading", message }); if (!Logger.isInteractive || import.meta.env?.VITEST) { return await fn(); } const output = Logger.stdout; const columns = getColumns(output); const showMessage = (msg) => { const wrapped = wrapAnsi(msg, columns, { hard: true, trim: false }); output.write(wrapped); return () => { const prevLines = wrapped.split("\n"); if (prevLines.length > 1) { output.write(cursor.up(prevLines.length - 1)); } output.write(cursor.to(0)); output.write(erase.down()); }; }; let timer; let clearMessage; const promise = new Promise((resolve) => { timer = setTimeout(() => { output.write(`${blueBright("\u2551")} `); clearMessage = showMessage( `${blueBright(`${promptStateSymbol.active}\u2500`)} ${dim(message)} ` ); resolve(); }, deferredTimeMs); }); const result = await fn().then((r) => { if (!clearMessage) { return r; } return new Promise( (resolve) => setTimeout(() => resolve(r), deferredTimeMs) ); }).catch(async (e) => { await promise; clearMessage?.(); showMessage( `${redBright(`${promptStateSymbol.error}\u2500`)} ${dim(message)} ` ); throw e; }); clearTimeout(timer); if (clearMessage) { clearMessage(); showMessage( `${blueBright(`${promptStateSymbol.submit}\u2500`)} ${dim(message)} ` ); } return result; } logInfo(message) { this.messageHistory.push({ type: "info", message }); if (import.meta.env?.VITEST) { return; } Logger.stdout.write( `${blueBright(`${promptStateSymbol.submit}\u2500`)} ${message.split("\n").join(` ${blueBright("\u2551")} `)} ` ); } logWarn(message) { this.messageHistory.push({ type: "warn", message }); if (import.meta.env?.VITEST) { return; } Logger.stdout.write( `${yellowBright(`${promptStateSymbol.error}\u2500`)} ${yellowBright(message.split("\n").join(` ${yellowBright("\u2551")} `))} ` ); } logOutro(message) { this.messageHistory.push({ type: "outro", message }); if (import.meta.env?.VITEST) { return; } Logger.stdout.write( `${blueBright("\u2551")} ${blueBright("\u2559\u2500")} ${message} ` ); } }; // src/npm.ts import { fetch as _fetch } from "node-fetch-native"; import { createProxy } from "node-fetch-native/proxy"; function createFetch(options) { const url = options.proxyServer ?? process.env.HTTP_PROXY; const proxy = url ? createProxy({ url, noProxy: options.proxyBypass ?? process.env.NO_PROXY ?? void 0 }) : void 0; const token = options.proxyUser && options.proxyPass ? `Basic ${Buffer.from( `${options.proxyUser}:${options.proxyPass}` ).toString("base64")}` : void 0; if (proxy && token) { const proxyHeadersKey = Object.getOwnPropertySymbols(proxy.dispatcher).find( (symbol) => symbol.description === "proxy headers" ); if (proxyHeadersKey) { proxy.dispatcher[proxyHeadersKey]["proxy-authorization"] = token; } } return (url2, fetchOptions) => _fetch(url2, { ...proxy ?? {}, ...fetchOptions }).then((response) => { if (!response.ok) { throw new Error( `Failed to fetch ${url2}: ${response.status} ${response.statusText}` ); } return response; }); } async function listVivliostyleThemes({ fetch }) { const keyword = "vivliostyle-theme"; return await fetch( `https://registry.npmjs.org/-/v1/search?text=keywords:${keyword}&size=250` ).then((response) => response.json()); } async function fetchPackageMetadata({ fetch, packageName, version }) { return fetch(`https://registry.npmjs.org/${packageName}/${version}`).then( (response) => response.json() ); } // src/core/create.ts async function create(inlineConfig) { Logger.setLogOptions(inlineConfig); Logger.debug("create > inlineConfig %O", inlineConfig); const fetch = createFetch(inlineConfig); const interactiveLogger = new InteractiveLogger(); let { projectPath, cwd: cwd2 = cwd, title, author, language, theme, template, installDependencies, createConfigFileOnly = false } = inlineConfig; let extraTemplateVariables = {}; let themePackage; let useLocalTemplate = false; if (template && !/^([\w-.]+):/.test(template)) { const absTemplatePath = upath.resolve(cwd2, template); useLocalTemplate = fs.existsSync(upath.resolve(cwd2, template)) && fs.statSync(upath.resolve(cwd2, template)).isDirectory(); const usingPresetTemplate = TEMPLATE_SETTINGS.find( (t) => t.value === template ); if (useLocalTemplate) { template = absTemplatePath; interactiveLogger.logInfo( `Using the specified local template directory ${dim2(upath.relative(cwd2, absTemplatePath) || ".")}` ); } else if (usingPresetTemplate) { template = usingPresetTemplate.template; } else { interactiveLogger.logWarn( `The specified theme ${green2(template)} was not found as a local directory. Proceeding to fetch it from GitHub repository.` ); } } if (!projectPath) { ({ projectPath } = await askProjectPath({ interactiveLogger })); } const dist = upath.join(cwd2, projectPath); if (createConfigFileOnly) { if (fs.existsSync(upath.join(dist, DEFAULT_CONFIG_FILENAME))) { throw new Error(`${DEFAULT_CONFIG_FILENAME} already exists. Aborting.`); } } else if (projectPath === "." && fs.readdirSync(dist).filter((n) => !n.startsWith(".")).length > 0 || projectPath !== "." && fs.existsSync(dist)) { throw new Error(`Destination ${dist} is not empty.`); } if (!title) { ({ title } = createConfigFileOnly ? { title: DEFAULT_PROJECT_TITLE } : await askTitle({ projectPath, interactiveLogger })); } if (!author) { ({ author } = createConfigFileOnly ? { author: DEFAULT_PROJECT_AUTHOR } : await askAuthor({ interactiveLogger })); } if (!language) { ({ language } = createConfigFileOnly ? { language: await getOsLocale() } : await askLanguage({ interactiveLogger })); } if (!createConfigFileOnly) { let presetTemplate; if (!template) { ({ presetTemplate } = await askPresetTemplate({ interactiveLogger })); if (presetTemplate) { template = presetTemplate.template; } } if (!theme && theme !== false) { ({ theme, themePackage } = await askTheme({ presetTemplate, template, fetch, interactiveLogger })); } if (!template) { ({ template, extraTemplateVariables } = await askThemeTemplate({ themeMetadata: themePackage?.vivliostyle, interactiveLogger })); } if (typeof installDependencies !== "boolean") { ({ installDependencies } = await askInstallDependencies({ interactiveLogger })); } } const browserType = "chrome"; const browserTag = getDefaultBrowserTag(browserType); const explicitTemplateVariables = { ...extraTemplateVariables, projectPath, title, author, language, theme: (() => { if (!theme) { return; } const arr = theme.map((t) => t.import ? t : t.specifier); return arr.length === 1 ? arr[0] : arr; })(), themePackage, template, cliVersion, coreVersion, browser: { type: browserType, tag: browserTag } }; Logger.debug( "create > explicitTemplateVariables %O", explicitTemplateVariables ); if (createConfigFileOnly) { setupConfigFile({ projectPath, cwd: cwd2, templateVariables: { ...inlineConfig, ...explicitTemplateVariables } }); } else { var _stack2 = []; try { if (interactiveLogger.messageHistory.length > 0) { interactiveLogger.logOutro( "All configurations are set! Creating your project..." ); } const _3 = __using(_stack2, Logger.startLogging( useLocalTemplate ? "Copying a local template" : "Downloading a template" )); await setupTemplate({ projectPath, cwd: cwd2, template, templateVariables: { ...inlineConfig, ...explicitTemplateVariables }, useLocalTemplate }); if (installDependencies) { var _stack = []; try { const pm = whichPm(); const _4 = __using(_stack, Logger.suspendLogging(`Installing dependencies with ${pm}`)); await performInstallDependencies({ projectPath, cwd: cwd2, pm }); } catch (_) { var _error = _, _hasError = true; } finally { __callDispose(_stack, _error, _hasError); } } } catch (_2) { var _error2 = _2, _hasError2 = true; } finally { __callDispose(_stack2, _error2, _hasError2); } } const output = createConfigFileOnly ? upath.join(dist, DEFAULT_CONFIG_FILENAME) : dist; const relativeOutput = upath.relative(cwd2, output) || "."; const formattedOutput = terminalLink( cyan(relativeOutput), pathToFileURL(output).href, { fallback: (text) => text } ); if (createConfigFileOnly) { Logger.logSuccess( `Successfully created a config file at ${formattedOutput}` ); } else { caveat(`Successfully created a project at ${formattedOutput}`, { relativeOutput, installDependencies: Boolean(installDependencies) }); } } async function askProjectPath({ interactiveLogger }) { return await askQuestion({ question: { projectPath: { type: "text", message: "Where should we create your project?", placeholder: 'Specify "." to create files in the current directory.', required: true } }, schema: v2.required( v2.pick(VivliostyleInlineConfigWithoutChecks, ["projectPath"]) ), interactiveLogger }); } async function askTitle({ projectPath, interactiveLogger }) { return await askQuestion({ question: { title: { type: "text", message: "What's the title of your publication?", defaultValue: toTitleCase(projectPath) || DEFAULT_PROJECT_TITLE, placeholder: toTitleCase(projectPath) || DEFAULT_PROJECT_TITLE } }, schema: v2.required(v2.pick(VivliostyleInlineConfigWithoutChecks, ["title"])), interactiveLogger }); } async function askAuthor({ interactiveLogger }) { return await askQuestion({ question: { author: { type: "text", message: "What's the author name?", defaultValue: DEFAULT_PROJECT_AUTHOR, placeholder: DEFAULT_PROJECT_AUTHOR } }, schema: v2.required( v2.pick(VivliostyleInlineConfigWithoutChecks, ["author"]) ), interactiveLogger }); } async function askLanguage({ interactiveLogger }) { const initialValue = await getOsLocale(); return await askQuestion({ question: { language: { type: "autocomplete", message: "What's the language?", options: Object.entries(languages).map(([value, displayName]) => ({ value, label: displayName, hint: value })), initialValue } }, schema: v2.required( v2.pick(VivliostyleInlineConfigWithoutChecks, ["language"]) ), interactiveLogger }); } var PRESET_TEMPLATE_NOT_USE = "Use templates from the community theme"; async function askPresetTemplate({ interactiveLogger }) { const { presetTemplate } = await askQuestion({ question: { presetTemplate: { type: "select", message: "What's the project template?", options: [ ...TEMPLATE_SETTINGS, { value: PRESET_TEMPLATE_NOT_USE, label: PRESET_TEMPLATE_NOT_USE, hint: "If a theme includes a template, you can choose it in the next step." } ] } }, schema: v2.object({ presetTemplate: v2.pipe( v2.string(), v2.transform( (value) => value === PRESET_TEMPLATE_NOT_USE ? void 0 : TEMPLATE_SETTINGS.find((t) => t.value === value) ) ) }), interactiveLogger }); return { presetTemplate }; } var TRUNCATE_LENGTH = 60; var truncateString = (str) => { const trimmed = str.replace(/\s+/g, " "); return trimmed.length > TRUNCATE_LENGTH ? trimmed.slice(0, TRUNCATE_LENGTH) + "\u2026" : trimmed; }; var THEME_ANSWER_NOT_USE = "Not use Vivliostyle theme"; var THEME_ANSWER_MANUAL = "Install other themes from npm"; async function askTheme({ template, presetTemplate, fetch, interactiveLogger }) { const useCommunityThemes = !presetTemplate && !template; const themePackages = await interactiveLogger.logLoading( "Fetching a list of Vivliostyle themes...", async () => { let themes = (await listVivliostyleThemes({ fetch })).objects; if (useCommunityThemes) { themes = themes.filter( (theme2) => !theme2.package.name.startsWith("@vivliostyle/") ); } themes.sort((a, b) => { const aIsOfficial = a.package.name.startsWith("@vivliostyle/") ? 1 : 0; const bIsOfficial = b.package.name.startsWith("@vivliostyle/") ? 1 : 0; return aIsOfficial ^ bIsOfficial ? bIsOfficial - aIsOfficial : b.downloads.monthly - a.downloads.monthly; }); return themes.map((theme2) => theme2.package); } ); let themePackage; const fetchedPackages = {}; const validateThemeMetadataSchema = v2.customAsync(async (value) => { if (value === THEME_ANSWER_NOT_USE || value === THEME_ANSWER_MANUAL) { return true; } if (typeof value !== "string") { return false; } fetchedPackages[value] ??= await fetchPackageMetadata({ fetch, packageName: value, version: "latest" }); const pkg = fetchedPackages[value]; const ret = v2.safeParse( v2.object({ name: v2.string(), version: v2.string(), vivliostyle: v2.optional(VivliostylePackageMetadata) }), pkg ); if (ret.success) { themePackage = ret.output; } return ret.success; }, "Invalid theme package. Please check the schema of the `vivliostyle` field."); let { theme } = await askQuestion({ question: { theme: { type: "autocomplete", message: "What's the project theme?", options: [ ...!useCommunityThemes ? [{ label: THEME_ANSWER_NOT_USE, value: THEME_ANSWER_NOT_USE }] : [], { label: THEME_ANSWER_MANUAL, value: THEME_ANSWER_MANUAL }, ...themePackages.map((pkg) => ({ label: pkg.name, value: pkg.name, hint: truncateString(pkg.description || "") })) ], initialValue: themePackages[0].name } }, schema: v2.objectAsync({ theme: validateThemeMetadataSchema }), validateProgressMessage: "Fetching package metadata...", interactiveLogger }); if (theme === THEME_ANSWER_NOT_USE) { return { theme: void 0, themePackage: void 0 }; } if (theme === THEME_ANSWER_MANUAL) { theme = await askQuestion({ question: { themeManualInput: { type: "text", message: "Input npm package name:", required: true } }, schema: v2.objectAsync({ themeManualInput: v2.pipeAsync( v2.string(), v2.customAsync(async (packageName) => { if (typeof packageName !== "string") { return false; } try { fetchedPackages[packageName] = await fetchPackageMetadata({ fetch, packageName, version: "latest" }); return true; } catch (error) { return false; } }, "Package not found"), validateThemeMetadataSchema ) }), validateProgressMessage: "Fetching package metadata...", interactiveLogger }).then((ret) => ret.themeManualInput); } return { theme: [ { specifier: themePackage ? `${theme}@^${themePackage.version}` : theme } ], themePackage }; } async function askThemeTemplate({ interactiveLogger, themeMetadata }) { const themeTemplate = themeMetadata?.template; const options = Object.entries(themeTemplate || {}).map(([value, tmpl]) => ({ label: tmpl.name || value, value, hint: truncateString(tmpl.description || "") })); if (!themeTemplate || options.length === 0) { interactiveLogger.logWarn( "The chosen theme does not set template settings. Applying the minimal template." ); return { template: TEMPLATE_SETTINGS.find((t) => t.value === "minimal").template, extraTemplateVariables: {} }; } const { usingTemplate } = await askQuestion({ question: { usingTemplate: { type: "autocomplete", message: "Which template do you want to use?", options } }, schema: v2.object({ usingTemplate: v2.pipe( v2.string(), v2.transform((input) => themeTemplate[input]) ) }), interactiveLogger }); let extraTemplateVariables = {}; if (usingTemplate.prompt?.length) { extraTemplateVariables = await askQuestion({ question: Object.fromEntries( usingTemplate.prompt.map((q) => [q.name, q]) ), interactiveLogger }); } return { template: usingTemplate.source, extraTemplateVariables }; } async function askInstallDependencies({ interactiveLogger }) { return await askQuestion({ question: { installDependencies: { type: "select", message: "Should we install dependencies? (You can install them later.)", options: [ { label: "Yes", value: true }, { label: "No", value: false } ] } }, schema: v2.object({ installDependencies: v2.boolean() }), interactiveLogger }); } async function setupTemplate({ cwd: cwd2, projectPath, template, templateVariables, useLocalTemplate }) { if (useLocalTemplate) { const matcher = new GlobMatcher([ { patterns: ["**"], ignore: ["**/node_modules/**", "**/.git/**"], dot: true, cwd: template } ]); const files = await matcher.glob({ followSymbolicLinks: true }); Logger.debug("setupTemplate > files from local template %O", files); for (const file of files) { const targetPath = upath.join(cwd2, projectPath, file); fs.mkdirSync(upath.dirname(targetPath), { recursive: true }); await copy(upath.join(template, file), targetPath); } } else { const tmpDownloadDir = upath.join( cwd2, projectPath, `.vs-template-${Date.now()}` ); Logger.debug("setupTemplate > tmpDownloadDir %s", tmpDownloadDir); const cleanupExitHandler = registerExitHandler( `Removing the temporary directory: ${tmpDownloadDir}`, () => { fs.rmSync(tmpDownloadDir, { recursive: true, force: true }); } ); await downloadTemplate(template, { dir: tmpDownloadDir }); for (const entry of fs.readdirSync(tmpDownloadDir)) { fs.renameSync( upath.join(tmpDownloadDir, entry), upath.join(cwd2, projectPath, entry) ); } cleanupExitHandler()?.(); } for (const [file, content] of Object.entries(TEMPLATE_DEFAULT_FILES)) { const targetPath = upath.join(cwd2, projectPath, file); if (fs.existsSync(targetPath)) { continue; } fs.mkdirSync(upath.dirname(targetPath), { recursive: true }); fs.writeFileSync(targetPath, content, "utf8"); } const replaceTemplateVariable = (dir) => { for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { const entryPath = upath.join(dir, entry.name); if (entry.isDirectory()) { replaceTemplateVariable(entryPath); } else { const buf = fs.readFileSync(entryPath); if (!isUtf8(buf)) { continue; } fs.writeFileSync( entryPath, format(buf.toString(), templateVariables), "utf8" ); } } }; replaceTemplateVariable(upath.join(cwd2, projectPath)); } function setupConfigFile({ cwd: cwd2, projectPath, templateVariables }) { const targetPath = upath.join(cwd2, projectPath, DEFAULT_CONFIG_FILENAME); const content = TEMPLATE_DEFAULT_FILES[DEFAULT_CONFIG_FILENAME]; fs.mkdirSync(upath.dirname(targetPath), { recursive: true }); fs.writeFileSync(targetPath, format(content, templateVariables), "utf8"); } async function performInstallDependencies({ pm, cwd: cwd2, projectPath }) { const proc = x(pm, ["install"], { throwOnError: true, nodeOptions: { cwd: upath.join(cwd2, projectPath), stdio: Logger.isInteractive ? "inherit" : void 0 } }); if (Logger.isInteractive) { await proc; } else { for await (const line of proc) { Logger.log(line); } } } function caveat(message, { relativeOutput, installDependencies }) { const steps = []; if (relativeOutput !== ".") { steps.push(`Navigate to ${green2(relativeOutput)}`); } if (!installDependencies) { steps.push(`${cyan("npm install")} to install dependencies`); } steps.push("Create and edit Markdown files"); steps.push( `Modify the ${cyan("entry")} field in ${green2("vivliostyle.config.js")}` ); steps.push(`${cyan("npm run preview")} to open a preview browser window`); steps.push(`${cyan("npm run build")} to generate the output file`); Logger.logSuccess(message); Logger.log( ` Next steps: ${steps.map((s, i) => gray2(`${i + 1}. `) + s).join("\n")} For more information, visit ${terminalLink(yellow2("https://docs.vivliostyle.org"), "https://docs.vivliostyle.org", { fallback: (text) => text })}. \u{1F58B} Happy writing!` ); } export { create }; //# sourceMappingURL=chunk-PYPAYBFL.js.map