UNPKG

@growing-web/web-builder-kit

Version:

@growing-web/web-builder-kit for Growing Web Guidelines.

458 lines (443 loc) 13.7 kB
import { BUILDER_NAME, DEFAULT_CACHE_CONFIG_FILE } from '@growing-web/web-builder-constants'; import colors from 'picocolors'; export { default as colors } from 'picocolors'; export { default as clear } from 'clear'; import consola from 'consola'; import { readPackageJSON as readPackageJSON$1 } from 'pkg-types'; import updateNotifier from 'update-notifier'; import fs from 'fs-extra'; export { default as fs } from 'fs-extra'; import * as path from 'pathe'; export { path }; export { defu as merge } from 'defu'; import semver from 'semver'; export { default as semver } from 'semver'; export { default as minimatch } from 'minimatch'; export { get, isFunction, isObject, isString, isUndefined, union } from 'lodash-es'; import path$1 from 'path'; import { exec } from 'child_process'; import { execaSync } from 'execa'; import fs$1 from 'fs'; import { getContext } from 'unctx'; import fg from 'fast-glob'; import readYamlFile from 'read-yaml-file'; import stripJsonComments from 'strip-json-comments'; import { createUnplugin } from 'unplugin'; import cmdShim from 'cmd-shim'; import { URL } from 'url'; consola.wrapConsole(); function createLogger(logLevel = 3, { allowClearScreen } = {}) { const logger = consola.create({ level: logLevel, defaults: {} }); const output = (type, msg, options) => { const prefix = `[${BUILDER_NAME}]`; const tag = type === "info" ? colors.cyan(colors.bold(prefix)) : type === "warn" ? colors.yellow(colors.bold(prefix)) : colors.red(colors.bold(prefix)); const message = `${options?.timestamp ? colors.dim(new Date().toLocaleTimeString()) + " " : ""}${tag} ${msg}`; if (options?.clear) { logger.clear(); } logger[type](message); }; const loggerInstance = { error(msg, options) { output("error", msg, options); }, warn(msg, options) { output("warn", msg, options); }, info(msg, options) { output("info", msg, options); }, debug(msg, options) { output("debug", msg, options); }, success(msg, options) { output("success", msg, options); }, ready(msg, options) { output("ready", msg, options); }, fatal(msg, options) { output("fatal", msg, options); }, start(msg, options) { output("start", msg, options); }, log(msg, options) { output("log", msg, options); }, trace(msg, options) { output("trace", msg, options); }, clear() { const canClearScreen = allowClearScreen && process.stdout.isTTY && !process.env.CI; canClearScreen && logger.clear(); } }; return loggerInstance; } async function readPackageJSON(cwd = process.cwd()) { const pkgJSON = await readPackageJSON$1(cwd); return pkgJSON; } async function getDeps(cwd = process.cwd()) { const { dependencies = {}, devDependencies = {} } = await readPackageJSON(cwd); const deps = { ...devDependencies, ...dependencies }; const keys = Array.from(/* @__PURE__ */ new Set([...Object.keys(devDependencies), ...Object.keys(dependencies)])); return { keys, deps }; } function findup(rootDir, fn) { let dir = rootDir; while (dir !== path$1.dirname(dir)) { const res = fn(dir); if (res) { return res; } dir = path$1.dirname(dir); } return null; } function lookupFile(dir, formats, pathOnly = false) { for (const format of formats) { const fullPath = path$1.join(dir, format); if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) { return pathOnly ? fullPath : fs.readFileSync(fullPath, "utf-8"); } } const parentDir = path$1.dirname(dir); if (parentDir !== dir) { return lookupFile(parentDir, formats, pathOnly); } } const PACKAGE_MANAGER_LOCKS = { yarn: "yarn.lock", npm: "package-lock.json", pnpm: "pnpm-lock.yaml" }; function getNpmLatestVersion(name) { return new Promise((resolve, reject) => { exec(`npm view ${name} version`, (err, stdout) => { if (err) { reject(err); } resolve(stdout); }); }); } function getNpmPackageManager(rootDir = process.cwd()) { return findup(rootDir, (dir) => { for (const key of Object.keys(PACKAGE_MANAGER_LOCKS)) { const _key = key; if (fs$1.existsSync(path$1.join(dir, PACKAGE_MANAGER_LOCKS[_key]))) { if (canUseNpmPackageManager(_key)) { return _key; } return "npm"; } } }); } function createNpmInstallMessage({ dev, monorepoRoot = false, packageName, global = true, client }) { const npmClient = client || getNpmPackageManager(); const suffix = `${packageName} ${dev ? "-D" : ""} ${monorepoRoot ? "-W" : ""}`; const npmMessage = `npm install ${global ? "-g" : " "} ${suffix}`; const yarnMessage = `yarn ${global ? "global" : ""} add ${suffix}`; const pnpmMessage = `pnpm add ${global ? "-g" : ""} ${suffix}`; const messageMap = { npm: npmMessage, pnpm: pnpmMessage, yarn: yarnMessage }; return messageMap[npmClient].replace(/\s+/g, " ").trimEnd().trimStart(); } function npmUpdateNotify({ name, version }) { const notifier = updateNotifier({ pkg: { name, version }, shouldNotifyInNpmScript: true }); notifier?.notify({ message: `Update available ${colors.red("{currentVersion}")} \u2192 ${colors.green("{latestVersion}")}. Run ${colors.cyan(`${createNpmInstallMessage({ dev: true, global: false, packageName: name })}`)} to update.` }); } function canUseNpmPackageManager(packageManager) { try { execaSync(packageManager, ["--version"], { env: process.env }); return true; } catch (e) { return false; } } const FRAMEWORK_LIST = ["vue", "react", "svelte", "lit"]; async function loadFrameworkTypeAndVersion(cwd = process.cwd()) { const { deps } = await getDeps(cwd); const versionMap = {}; FRAMEWORK_LIST.forEach((key) => { versionMap[key] = null; }); for (const _key of Object.keys(deps)) { const key = _key; if (FRAMEWORK_LIST.includes(key)) { let version = deps[key].replace(/^[\^~]/, ""); if (version === "latest") { version = await getNpmLatestVersion(key); } const majorVersion = semver.major(version); if (key === "vue") { if (majorVersion < 3) { const minorVersion = semver.minor(version); versionMap[key] = `${majorVersion}.${minorVersion}`; } else { versionMap[key] = majorVersion; } } else { versionMap[key] = majorVersion; } } } const frameValues = Object.values(versionMap); if (frameValues.every((item) => !item)) { return { framework: "vanilla", version: 0 }; } const installFramework = []; Object.entries(versionMap).forEach(([key, value]) => { if (value !== null) { installFramework.push(key); } }); if (installFramework.length > 1) { throw new Error(`The current project has both ${installFramework.toString()} dependencies installed, please install one of them according to the project type, delete the other frameworks, and try again.`); } for (const [key, version] of Object.entries(versionMap)) { if (versionMap[key] !== null) { return { framework: key, version }; } } return { framework: "vanilla", version: 0 }; } const webBuilderCtx = getContext("web-builder"); const useWebBuilder = () => webBuilderCtx.use(); async function checkNodeEngines(engines) { const currentNode = process.versions.node; const nodeRange = engines?.node ?? ""; const logger = createLogger(); if (!semver.satisfies(currentNode, nodeRange)) { logger.warn(`Current version of Node.js (\`${currentNode}\`) is unsupported and might cause issues. Please upgrade to a compatible version (${nodeRange}).`); } } function jsoncParse(data) { try { return new Function("return " + stripJsonComments(data).trim())(); } catch { return {}; } } function JSONReader(filename, silent = true) { try { return fs.readJSONSync(filename, { encoding: "utf-8" }); } catch (error) { if (silent) { return {}; } else { throw new Error(error); } } } const WORK_SPACE_LERNA_FILE = "lerna.json"; const WORK_SPACE_YARN_FILE = "package.json"; const WORK_SPACE_PNPM_FILE = "pnpm-workspace.yaml"; function isLerna(root) { return fs.existsSync(path$1.join(root, WORK_SPACE_LERNA_FILE)); } function isYarnWorkspace(root) { const pkgPath = path$1.join(root, WORK_SPACE_YARN_FILE); if (!fs.existsSync(pkgPath)) { return false; } const json = JSONReader(pkgPath, true); return Boolean(json.workspaces); } function isPnpmWorkspace(root) { return fs.existsSync(path$1.join(root, WORK_SPACE_PNPM_FILE)); } function isMonorepo(root) { return isPnpmWorkspace(root) || isLerna(root) || isYarnWorkspace(root); } async function findMonorepoRoot(root) { return findup(root, (dir) => { if (isMonorepo(dir)) { return dir; } }); } async function findWorkspacePackages(root) { if (!isMonorepo(root)) { return []; } let resultPkgs = []; if (isPnpmWorkspace(root)) { const yaml = await resolvePackagesManifest(root); if (yaml?.packages) { resultPkgs = findPackages(yaml.packages, root); } } if (isLerna(root) && !resultPkgs.length) { const lernaPath = path$1.join(root, WORK_SPACE_LERNA_FILE); const lernaJson = JSONReader(lernaPath); if (!lernaJson.useWorkspaces) { resultPkgs = findPackages(lernaJson.packages, root); } } if (isYarnWorkspace(root) && !resultPkgs.length) { const pkgPath = path$1.join(root, WORK_SPACE_YARN_FILE); const pkgJson = JSONReader(pkgPath); const workspaces = pkgJson.workspaces; if (workspaces) { if (Array.isArray(workspaces)) { resultPkgs = findPackages(workspaces, root); } else if (Array.isArray(workspaces.packages)) { resultPkgs = findPackages(workspaces.packages, root); } } } return resultPkgs; } function findPackages(packageSpecs, rootDirectory) { return packageSpecs.reduce((pkgDirs, pkgGlob) => { return [ ...pkgDirs, ...fg.isDynamicPattern(pkgGlob) ? fg.sync(path$1.join(rootDirectory, pkgGlob), { onlyDirectories: true, onlyFiles: false }) : [path$1.join(rootDirectory, pkgGlob)] ]; }, []); } async function resolvePackagesManifest(dir) { try { return await readYamlFile(path$1.join(dir, WORK_SPACE_PNPM_FILE)); } catch (err) { if (err.code === "ENOENT") { return null; } throw err; } } function arraify(target) { return Array.isArray(target) ? target : [target]; } function tryResolvePaths(paths) { for (const path of paths) { if (fs.existsSync(path)) { return path; } } } function createPlugins(fn) { return createUnplugin(fn); } function createSymlink(src, dest, type) { if (process.platform === "win32") { return createWindowsSymlink(src, dest, type); } return createPosixSymlink(src, dest, type); } function createSymbolicLink(src, dest, type) { return fs.lstat(dest).then(() => fs.unlink(dest)).catch(() => { }).then(() => fs.symlink(src, dest, type)); } function createPosixSymlink(src, dest, _type) { const type = _type === "exec" ? "file" : _type; const relativeSymlink = path.relative(path.dirname(dest), src); if (_type === "exec") { return fs.pathExists(src).then((exists) => { if (exists) { return createSymbolicLink(relativeSymlink, dest, type).then(() => fs.chmod(src, 493)); } return shShim(src, dest).then(() => fs.chmod(dest, 493)); }); } return createSymbolicLink(relativeSymlink, dest, type); } function createWindowsSymlink(src, dest, type) { if (type === "exec") { return fs.pathExists(src).then((exists) => { if (exists) { return cmdShim(src, dest); } return fs.outputFile(src, "").then(() => cmdShim(src, dest)).then(() => fs.remove(src), (err) => fs.remove(src).then(() => { throw err; })); }); } return createSymbolicLink(src, dest, type); } function shShim(src, dest, _type) { const absTarget = path.resolve(path.dirname(dest), src); const scriptLines = [ "#!/bin/sh", `chmod +x ${absTarget} && exec ${absTarget} "$@"` ]; return fs.writeFile(dest, scriptLines.join("\n")); } function resolveProxy(proxyList = []) { const proxyMap = {}; for (const proxy of proxyList) { const { url, target, secure, changeOrigin = true, pathRewrite = [] } = proxy; proxyMap[url] = { target, secure, changeOrigin, rewrite: (rewritePath) => { if (!pathRewrite) { return rewritePath; } const _path = rewritePath.startsWith("http") ? rewritePath : `http://localhost/${rewritePath}`; const { pathname = "", search = "" } = new URL(_path); let _pathname = pathname; pathRewrite.forEach(({ regular, replacement }) => { _pathname = _pathname.replace(new RegExp(regular, "g"), replacement); }); return path.join(_pathname, search); } }; } return proxyMap; } async function persistencePort(port) { await fs.outputJSON(path.resolve(process.cwd(), DEFAULT_CACHE_CONFIG_FILE), { port }); } export { JSONReader, arraify, canUseNpmPackageManager, checkNodeEngines, createLogger, createNpmInstallMessage, createPlugins, createSymlink, findMonorepoRoot, findWorkspacePackages, findup, getDeps, getNpmLatestVersion, getNpmPackageManager, isLerna, isMonorepo, isPnpmWorkspace, isYarnWorkspace, jsoncParse, loadFrameworkTypeAndVersion, lookupFile, npmUpdateNotify, persistencePort, readPackageJSON, resolveProxy, tryResolvePaths, useWebBuilder, webBuilderCtx };