UNPKG

@strapi/pack-up

Version:

Simple tools for creating interoperable CJS & ESM packages.

563 lines (552 loc) 18.3 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const path = require("path"); const errors = require("./errors-BG4CMlQA.js"); const files = require("./files-5NgqsTut.js"); const fs = require("fs/promises"); const os = require("os"); const prompts = require("prompts"); const ini = require("ini"); const getLatestVersion = require("get-latest-version"); const gitUrlParse = require("git-url-parse"); const outdent = require("outdent"); const node = require("esbuild-register/dist/node"); const fs$1 = require("fs"); const _interopDefault = (e) => e && e.__esModule ? e : { default: e }; const path__default = /* @__PURE__ */ _interopDefault(path); const fs__default = /* @__PURE__ */ _interopDefault(fs); const os__default = /* @__PURE__ */ _interopDefault(os); const prompts__default = /* @__PURE__ */ _interopDefault(prompts); const ini__default = /* @__PURE__ */ _interopDefault(ini); const getLatestVersion__default = /* @__PURE__ */ _interopDefault(getLatestVersion); const gitUrlParse__default = /* @__PURE__ */ _interopDefault(gitUrlParse); const resolveConfigPath = async ({ cwd }) => { const configPath = path__default.default.join(os__default.default.homedir(), ".gitconfig"); try { await fs__default.default.access(configPath); return path__default.default.resolve(cwd, configPath); } catch (err) { return null; } }; const parseIni = (str) => { const normalisedString = str.replace(/\[(\S+) "(.*)"\]/g, (m, $1, $2) => { return $1 && $2 ? `[${$1} "${$2.split(".").join("\\.")}"]` : m; }); return ini__default.default.parse(normalisedString); }; const parseGlobalGitConfig = async () => { const cwd = process.cwd(); const filepath = await resolveConfigPath({ cwd }); if (!filepath) { return null; } const file = await fs__default.default.stat(filepath).then(() => fs__default.default.readFile(filepath, "utf8")); return parseIni(file); }; const createPackageFromTemplate = async (packagePath, opts) => { const { cwd, logger, template: templateOrResolver } = opts; const prettier = await import("prettier"); const gitConfig = await parseGlobalGitConfig(); const template = typeof templateOrResolver === "function" ? await templateOrResolver({ cwd, logger, packagePath, gitConfig }) : templateOrResolver; logger.info("Creating a new package at: ", path.relative(cwd, packagePath)); logger.debug("Loaded template:", os__default.default.EOL, template); const answers = []; if (Array.isArray(template.prompts)) { for (const prompt of template.prompts) { if ("type" in prompt) { const res = await prompts__default.default(prompt, { onCancel() { process.exit(0); } }); answers.push({ name: prompt.name, answer: res[prompt.name] }); } else { const res = prompt.optional ? await prompts__default.default({ type: "confirm", name: "confirm", message: `use ${prompt.name}?`, initial: prompt.initial }) : null; answers.push({ name: prompt.name, answer: res?.confirm ?? !prompt.optional }); } } logger.debug( [ "User answers: ", ...answers.map((ans) => ` ${ans.name}: ${JSON.stringify(ans.answer)}`) ].join(os__default.default.EOL) ); } const files2 = await template.getFiles(answers); logger.debug( ["Files to write: ", ...files2.map((f) => ` ${f.name}: ${f.contents}`)].join(os__default.default.EOL) ); files2.sort((a, b) => { return a.name.localeCompare(b.name); }); for (const file of files2) { const filePath = path.resolve(packagePath, file.name); await fs.mkdir(path.dirname(filePath), { recursive: true }); const defaultPrettierConfig = { endOfLine: "lf", tabWidth: 2, printWidth: 100, singleQuote: true, trailingComma: "es5" }; try { const formattedContents = await prettier.format(file.contents, { ...defaultPrettierConfig, filepath: filePath }); await fs.writeFile(filePath, `${formattedContents.trim()}${os__default.default.EOL}`); } catch (err) { if (errors.isError(err)) { logger.debug(err.message); } await fs.writeFile(filePath, `${file.contents.trim()}${os__default.default.EOL}`); } logger.success(`Wrote ${path.relative(cwd, filePath)}`); } }; const defineTemplate = (template) => template; const definePackageOption = (option) => option; const definePackageFeature = (feature) => feature; const editorConfigFile = { name: ".editorconfig", contents: outdent.outdent` root = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [{package.json,*.yml}] indent_style = space indent_size = 2 [*.md] trim_trailing_whitespace = false ` }; const gitIgnoreFile = { name: ".gitignore", contents: outdent.outdent` # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies node_modules .pnp .pnp.js # testing coverage # production dist # misc .DS_Store *.pem # debug npm-debug.log* yarn-debug.log* yarn-error.log* # local env files .env .env.local .env.development.local .env.test.local .env.production.local ` }; const prettierFile = { name: ".prettierrc", contents: outdent.outdent` { "endOfLine": 'lf', "tabWidth": 2, "printWidth": 100, "singleQuote": true, "trailingComma": 'es5', } ` }; const prettierIgnoreFile = { name: ".prettierignore", contents: outdent.outdent` dist coverage ` }; const PACKAGE_NAME_REGEXP = /^(?:@(?:[a-z0-9-*~][a-z0-9-*._~]*)\/)?[a-z0-9-~][a-z0-9-._~]*$/i; const defaultTemplate = defineTemplate(async ({ logger, gitConfig }) => { let repo; return { prompts: [ definePackageOption({ name: "repo", type: "text", message: "git url", validate(v) { if (!v) { return true; } try { const result = gitUrlParse__default.default(v); repo = { source: result.source, owner: result.owner, name: result.name }; return true; } catch (err) { return "invalid git url"; } } }), definePackageOption({ name: "pkgName", type: "text", message: "package name", initial: () => repo?.name ?? "", validate(v) { if (!v) { return "package name is required"; } const match = PACKAGE_NAME_REGEXP.exec(v); if (!match) { return "invalid package name"; } return true; } }), definePackageOption({ name: "description", type: "text", message: "package description" }), definePackageOption({ name: "authorName", type: "text", message: "package author name", initial: gitConfig?.user?.name }), definePackageOption({ name: "authorEmail", type: "text", message: "package author email", initial: gitConfig?.user?.email }), definePackageOption({ name: "license", type: "text", message: "package license", initial: "MIT", validate(v) { if (!v) { return "license is required"; } return true; } }), definePackageFeature({ name: "typescript", initial: true, optional: true }), definePackageFeature({ name: "eslint", initial: true, optional: true }) ], async getFiles(answers) { const devDepsToInstall = []; const author = []; let isTypescript = false; const files2 = []; const pkgJson = { version: "0.0.0", keywords: [], type: "commonjs", exports: { // @ts-expect-error yup typings are a bit weak. ".": { require: "./dist/index.js", import: "./dist/index.mjs", source: "", default: "./dist/index.js" }, "./package.json": "./package.json" }, main: "./dist/index.js", module: "./dist/index.mjs", files: ["dist"], scripts: { check: "pack-up check", build: "pack-up build", watch: "pack-up watch" }, dependencies: {}, devDependencies: { /** * We set * as a default version, but further down * we try to resolve each package to their latest * version, failing that we leave the fallback of *. */ "@strapi/pack-up": "*", prettier: "*" } }; if (Array.isArray(answers)) { for (const ans of answers) { const { name, answer } = ans; switch (name) { case "pkgName": { pkgJson.name = String(answer); break; } case "description": { pkgJson.description = String(answer) ?? void 0; break; } case "authorName": { author.push(String(answer)); break; } case "authorEmail": { if (answer) { author.push(`<${answer}>`); } break; } case "license": { pkgJson.license = String(answer); break; } case "typescript": { isTypescript = Boolean(answer); pkgJson.source = isTypescript ? "./src/index.ts" : "./src/index.js"; if (isRecord(pkgJson.exports["."])) { pkgJson.exports["."].source = isTypescript ? "./src/index.ts" : "./src/index.js"; } if (isTypescript) { pkgJson.types = "./dist/index.d.ts"; if (isRecord(pkgJson.exports["."])) { pkgJson.exports["."] = { // @ts-expect-error it won't be overwritten. types: "./dist/index.d.ts", ...pkgJson.exports["."] }; } pkgJson.scripts = { ...pkgJson.scripts, "test:ts": "tsc --build" }; devDepsToInstall.push("typescript"); const { tsconfigBuildFile, tsconfigFile } = await Promise.resolve().then(() => require("./typescript-CrwCQpra.js")); files2.push(tsconfigFile, tsconfigBuildFile); } files2.push({ name: isTypescript ? "src/index.ts" : "src/index.js", contents: outdent.outdent` /** * @public */ const main = () => { // silence is golden } export { main } ` }); break; } case "eslint": { if (answer) { const eslintConfig = { root: true, env: { browser: true, es6: true, node: true }, extends: ["eslint:recommended", "plugin:prettier/recommended"], parserOptions: { ecmaVersion: "latest", sourceType: "module" }, plugins: ["prettier"] }; if (isTypescript) { eslintConfig.overrides = [ { files: ["**/*.ts", "**/*.tsx"], parser: "@typescript-eslint/parser", parserOptions: { project: ["./tsconfig.eslint.json"] }, extends: [ "eslint:recommended", "plugin:prettier/recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended" ], plugins: ["@typescript-eslint", "prettier"] } ]; const { tsconfigEslintFile } = await Promise.resolve().then(() => require("./typescript-CrwCQpra.js")); files2.push(tsconfigEslintFile); } pkgJson.scripts = { ...pkgJson.scripts, lint: isTypescript ? "eslint . --ext .cjs,.js,.ts,.tsx" : "eslint . --ext .cjs,.js" }; devDepsToInstall.push("eslint", "eslint-config-prettier", "eslint-plugin-prettier"); if (isTypescript) { devDepsToInstall.push( "@typescript-eslint/eslint-plugin", "@typescript-eslint/parser" ); } const { eslintIgnoreFile } = await Promise.resolve().then(() => require("./eslint-CMT-ywGG.js")); files2.push( { name: ".eslintrc", contents: outdent.outdent` ${JSON.stringify(eslintConfig, null, 2)} ` }, eslintIgnoreFile ); } break; } } } } if (repo) { pkgJson.repository = { type: "git", url: `git+ssh://git@${repo.source}/${repo.owner}/${repo.name}.git` }; pkgJson.bugs = { url: `https://${repo.source}/${repo.owner}/${repo.name}/issues` }; pkgJson.homepage = `https://${repo.source}/${repo.owner}/${repo.name}#readme`; } pkgJson.author = author.filter(Boolean).join(" ") ?? void 0; try { pkgJson.devDependencies = await resolveLatestVerisonOfDeps([ ...devDepsToInstall, ...Object.keys(pkgJson.devDependencies) ]); } catch (err) { if (errors.isError(err)) { logger.error(err.message); } else { logger.error(err); } } files2.push({ name: "package.json", contents: outdent.outdent` ${JSON.stringify(pkgJson, null, 2)} ` }); files2.push(prettierFile, prettierIgnoreFile, editorConfigFile, gitIgnoreFile); return files2; } }; }); const isRecord = (value) => Boolean(value) && !Array.isArray(value) && typeof value === "object"; const resolveLatestVerisonOfDeps = async (deps) => { const latestDeps = {}; for (const name of deps) { try { const latestVersion = await getLatestVersion__default.default(name, "*"); latestDeps[name] = latestVersion ? `^${latestVersion}` : "*"; } catch (err) { latestDeps[name] = "*"; } } return latestDeps; }; const loadTemplate = (path$1, { logger }) => { const configPath = path.resolve(path$1); const exists = fs$1.existsSync(configPath); if (exists) { const esbuildOptions = { extensions: [".js", ".mjs", ".ts"] }; const { unregister } = node.register(esbuildOptions); const mod = require(configPath); unregister(); if (!mod) { logger.warn(`Could not find template at: ${path$1}. Are you sure it exists?`); return void 0; } logger.debug("Loaded user provided template from: ", path$1); return mod?.default || mod; } logger.warn(`Could not find template at: ${path$1}. Are you sure it exists?`); return void 0; }; const init$1 = async (opts) => { const { silent, debug, cwd = process.cwd(), path: path$1 } = opts; let { template = defaultTemplate } = opts; const logger = errors.createLogger({ silent, debug }); if (!path$1) { logger.error("Path is a required option"); process.exit(1); } const packageRoot = path.resolve(cwd, path$1); logger.debug("Package is: ", packageRoot); if (typeof template === "string") { const templatePath = path.resolve(cwd, template); const userTemplate = loadTemplate(templatePath, { logger }); if (userTemplate) { template = userTemplate; } else { template = defaultTemplate; } } await files.ensurePackagePathIsViable(packageRoot).catch((err) => { if (errors.isError(err)) { logger.error(err.message); } process.exit(1); }); logger.debug("Package path is viable"); await createPackageFromTemplate(packageRoot, { logger, cwd, template }); }; const init = async (options) => { try { await init$1(options); } catch (err) { errors.handleError(err); } }; exports.init = init; //# sourceMappingURL=init-DF467gFw.js.map