@growing-web/web-builder-kit
Version:
@growing-web/web-builder-kit for Growing Web Guidelines.
458 lines (443 loc) • 13.7 kB
JavaScript
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 };