UNPKG

@strapi/pack-up

Version:

Simple tools for creating interoperable CJS & ESM packages.

1 lines 138 kB
{"version":3,"file":"index.mjs","sources":["../src/node/core/config.ts","../src/node/core/errors.ts","../src/node/core/exports.ts","../src/node/core/logger.ts","../src/node/core/pkg.ts","../src/node/core/tsconfig.ts","../src/node/createBuildContext.ts","../src/node/createTasks.ts","../src/node/tasks/dts/diagnostic.ts","../src/node/tasks/dts/build.ts","../src/node/tasks/dts/watch.ts","../src/node/tasks/vite/config.ts","../src/node/tasks/vite/build.ts","../src/node/tasks/vite/watch.ts","../src/node/tasks/index.ts","../src/node/build.ts","../src/node/watch.ts","../src/node/core/files.ts","../src/node/check.ts","../src/node/core/git.ts","../src/node/templates/create.ts","../src/node/templates/internal/files/editorConfig.ts","../src/node/templates/internal/files/gitIgnore.ts","../src/node/templates/internal/files/prettier.ts","../src/node/templates/internal/default.ts","../src/node/templates/load.ts","../src/node/init.ts"],"sourcesContent":["import { register } from 'esbuild-register/dist/node';\nimport * as fs from 'fs';\nimport os from 'os';\nimport * as path from 'path';\nimport pkgUp from 'pkg-up';\n\nimport type { Export } from './exports';\nimport type { Logger } from './logger';\nimport type { Runtime } from '../createBuildContext';\nimport type { InlineConfig, PluginOption } from 'vite';\n\ninterface LoadConfigOptions {\n cwd: string;\n logger: Logger;\n}\n\nconst CONFIG_FILE_NAMES = [\n 'packup.config.ts',\n 'packup.config.js',\n 'packup.config.cjs',\n 'packup.config.mjs',\n];\n\nconst loadConfig = async ({ cwd, logger }: LoadConfigOptions): Promise<Config | undefined> => {\n const pkgPath = await pkgUp({ cwd });\n\n if (!pkgPath) {\n logger.debug(\n 'Could not find a package.json in the current directory, therefore no config was loaded'\n );\n\n return undefined;\n }\n\n const root = path.dirname(pkgPath);\n\n for (const fileName of CONFIG_FILE_NAMES) {\n const configPath = path.resolve(root, fileName);\n\n const exists = fs.existsSync(configPath);\n\n if (exists) {\n const esbuildOptions = { extensions: ['.js', '.mjs', '.ts'] };\n\n const { unregister } = register(esbuildOptions);\n\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const mod = require(configPath);\n\n unregister();\n\n /**\n * handles esm or cjs exporting.\n */\n const config = mod?.default || mod || undefined;\n\n if (config) {\n logger.debug('Loaded configuration:', os.EOL, config);\n }\n\n return config;\n }\n }\n\n return undefined;\n};\n\ninterface ConfigBundle {\n source: string;\n import?: string;\n require?: string;\n runtime?: Runtime;\n tsconfig?: string;\n types?: string;\n}\n\ninterface ConfigOptions {\n bundles?: ConfigBundle[];\n /**\n * @description the directory to output the bundle to.\n */\n dist?: string;\n /**\n * @description Overwrite the default exports.\n */\n exports?: ConfigProperty<Record<string, Export>>;\n /**\n * @description a list of external dependencies to exclude from the bundle.\n * We already collect the dependencies & peerDeps from the package.json.\n */\n externals?: string[];\n minify?: boolean;\n plugins?: PluginOption[] | (({ runtime }: { runtime: Runtime }) => PluginOption[]);\n /**\n * @alpha\n *\n * @description Instead of creating as few chunks as possible, this mode\n * will create separate chunks for all modules using the original module\n * names as file names\n */\n preserveModules?: boolean;\n sourcemap?: boolean;\n runtime?: Runtime;\n /**\n * @description path to the tsconfig file to use for the bundle.\n *\n * @default tsconfig.build.json\n */\n tsconfig?: string;\n\n /**\n * @experimental\n * @description option to overwrite vite's config\n */\n unstable_viteConfig?: InlineConfig;\n}\n\n/**\n * @public\n *\n * @description a helper function to define your config in a typesafe manner.\n */\nconst defineConfig = (configOptions: ConfigOptions): ConfigOptions => configOptions;\n\ntype Config = ConfigOptions;\n\ntype ConfigPropertyResolver<T> = (currentValue: T) => T;\n\ntype ConfigProperty<T> = T | ConfigPropertyResolver<T>;\n\n/** @internal */\nexport function resolveConfigProperty<T>(prop: ConfigProperty<T> | undefined, initialValue: T): T {\n if (prop === undefined || prop === null) {\n return initialValue;\n }\n\n if (typeof prop === 'function') {\n return (prop as ConfigPropertyResolver<T>)(initialValue);\n }\n\n return prop;\n}\n\nexport { loadConfig, defineConfig, CONFIG_FILE_NAMES };\nexport type {\n Config,\n ConfigOptions,\n ConfigBundle,\n ConfigPropertyResolver,\n ConfigProperty,\n PluginOption,\n Runtime,\n};\n","const isError = (err: unknown): err is Error => err instanceof Error;\n\nexport { isError };\n","import os from 'os';\n\nimport type { Logger } from './logger';\nimport type { PackageJson } from './pkg';\n\n/**\n * @description validate the `exports` property of the package.json against a set of rules.\n * If the validation fails, the process will throw with an appropriate error message. If\n * there is no `exports` property we check the standard export-like properties on the root\n * of the package.json.\n */\nconst validateExportsOrdering = async ({\n pkg,\n logger,\n}: {\n pkg: PackageJson;\n logger: Logger;\n}): Promise<PackageJson> => {\n if (pkg.exports) {\n const exports = Object.entries(pkg.exports);\n\n for (const [expPath, exp] of exports) {\n if (typeof exp === 'string') {\n // eslint-disable-next-line no-continue\n continue;\n }\n\n const keys = Object.keys(exp);\n\n if (!assertFirst('types', keys)) {\n throw new Error(`exports[\"${expPath}\"]: the 'types' property should be the first property`);\n }\n\n if (exp.node) {\n const nodeKeys = Object.keys(exp.node);\n\n if (!assertOrder('module', 'import', nodeKeys)) {\n throw new Error(\n `exports[\"${expPath}\"]: the 'node.module' property should come before the 'node.import' property`\n );\n }\n\n if (!assertOrder('import', 'require', nodeKeys)) {\n logger.warn(\n `exports[\"${expPath}\"]: the 'node.import' property should come before the 'node.require' property`\n );\n }\n\n if (!assertOrder('module', 'require', nodeKeys)) {\n logger.warn(\n `exports[\"${expPath}\"]: the 'node.module' property should come before 'node.require' property`\n );\n }\n\n if (exp.import && exp.node.import && !assertOrder('node', 'import', keys)) {\n throw new Error(\n `exports[\"${expPath}\"]: the 'node' property should come before the 'import' property`\n );\n }\n\n if (exp.module && exp.node.module && !assertOrder('node', 'module', keys)) {\n throw new Error(\n `exports[\"${expPath}\"]: the 'node' property should come before the 'module' property`\n );\n }\n\n /**\n * If there's a `node.import` property but not a `node.require` we can assume `node.import`\n * is wrapping `import` and `node.module` should be added for bundlers.\n */\n if (\n exp.node.import &&\n (!exp.node.require || exp.require === exp.node.require) &&\n !exp.node.module\n ) {\n logger.warn(\n `exports[\"${expPath}\"]: the 'node.module' property should be added so bundlers don't unintentionally try to bundle 'node.import'. Its value should be '\"module\": \"${exp.import}\"'`\n );\n }\n\n if (\n exp.node.import &&\n !exp.node.require &&\n exp.node.module &&\n exp.import &&\n exp.node.module !== exp.import\n ) {\n throw new Error(\n `exports[\"${expPath}\"]: the 'node.module' property should match 'import'`\n );\n }\n\n if (exp.require && exp.node.require && exp.require === exp.node.require) {\n throw new Error(\n `exports[\"${expPath}\"]: the 'node.require' property isn't necessary as it's identical to 'require'`\n );\n } else if (exp.require && exp.node.require && !assertOrder('node', 'require', keys)) {\n throw new Error(\n `exports[\"${expPath}\"]: the 'node' property should come before the 'require' property`\n );\n }\n } else {\n if (!assertOrder('import', 'require', keys)) {\n logger.warn(\n `exports[\"${expPath}\"]: the 'import' property should come before the 'require' property`\n );\n }\n\n if (!assertOrder('module', 'import', keys)) {\n logger.warn(\n `exports[\"${expPath}\"]: the 'module' property should come before 'import' property`\n );\n }\n }\n if (!assertLast('default', keys)) {\n throw new Error(\n `exports[\"${expPath}\"]: the 'default' property should be the last property`\n );\n }\n }\n } else if (!['main', 'module'].some((key) => Object.prototype.hasOwnProperty.call(pkg, key))) {\n throw new Error(\"'package.json' must contain a 'main' and 'module' property\");\n }\n\n return pkg;\n};\n\n/** @internal */\nfunction assertFirst(key: string, arr: string[]) {\n const aIdx = arr.indexOf(key);\n\n // if not found, then we don't care\n if (aIdx === -1) {\n return true;\n }\n\n return aIdx === 0;\n}\n\n/** @internal */\nfunction assertLast(key: string, arr: string[]) {\n const aIdx = arr.indexOf(key);\n\n // if not found, then we don't care\n if (aIdx === -1) {\n return true;\n }\n\n return aIdx === arr.length - 1;\n}\n\n/** @internal */\nfunction assertOrder(keyA: string, keyB: string, arr: string[]) {\n const aIdx = arr.indexOf(keyA);\n const bIdx = arr.indexOf(keyB);\n\n // if either is not found, then we don't care\n if (aIdx === -1 || bIdx === -1) {\n return true;\n }\n\n return aIdx < bIdx;\n}\n\ntype Extensions = 'cjs' | 'es';\n\ninterface ExtMap {\n commonjs: Record<Extensions, string>;\n module: Record<Extensions, string>;\n}\n\n/**\n * @internal\n */\nconst DEFAULT_PKG_EXT_MAP = {\n // pkg.type: \"commonjs\"\n commonjs: {\n cjs: '.js',\n es: '.mjs',\n },\n\n // pkg.type: \"module\"\n module: {\n cjs: '.cjs',\n es: '.js',\n },\n} satisfies ExtMap;\n\n/**\n * We potentially might need to support legacy exports or as package\n * development continues we have space to tweak this.\n */\nconst getExportExtensionMap = (): ExtMap => {\n return DEFAULT_PKG_EXT_MAP;\n};\n\n/**\n * @internal\n *\n * @description validate the `require` and `import` properties of a given exports maps from the package.json\n * returning if any errors are found.\n */\nconst validateExports = (\n _exports: Array<Export & { _path: string }>,\n options: { extMap: ExtMap; pkg: PackageJson }\n) => {\n const { extMap, pkg } = options;\n const ext = extMap[pkg.type || 'commonjs'];\n\n const errors = [];\n\n for (const exp of _exports) {\n if (exp.require && !exp.require.endsWith(ext.cjs)) {\n errors.push(\n `package.json with 'type: \"${pkg.type}\"' - 'exports[\"${exp._path}\"].require' must end with \"${ext.cjs}\"`\n );\n }\n\n if (exp.import && !exp.import.endsWith(ext.es)) {\n errors.push(\n `package.json with 'type: \"${pkg.type}\"' - 'exports[\"${exp._path}\"].import' must end with \"${ext.es}\"`\n );\n }\n }\n\n return errors;\n};\n\ninterface Export {\n types?: string;\n source: string;\n browser?: {\n source: string;\n import?: string;\n require?: string;\n };\n node?: {\n source?: string;\n module?: string;\n import?: string;\n require?: string;\n };\n module?: string;\n import?: string;\n require?: string;\n default: string;\n}\n\n/**\n * @description parse the exports map from the package.json into a standardised\n * format that we can use to generate build tasks from.\n */\nconst parseExports = ({ extMap, pkg }: { extMap: ExtMap; pkg: PackageJson }) => {\n const rootExport = {\n _path: '.',\n types: pkg.types,\n source: pkg.source || '',\n require: pkg.main,\n import: pkg.module,\n default: pkg.module || pkg.main || '',\n } satisfies Export & { _path: string };\n\n const extraExports: Export[] = [];\n\n const errors: string[] = [];\n\n if (pkg.exports) {\n if (!pkg.exports['./package.json']) {\n errors.push('package.json: `exports[\"./package.json\"] must be declared.');\n }\n\n Object.entries(pkg.exports).forEach(([path, entry]) => {\n if (path.endsWith('.json')) {\n if (path === './package.json' && entry !== './package.json') {\n errors.push(\"package.json: 'exports[\\\"./package.json\\\"]' must be './package.json'.\");\n }\n } else if (Boolean(entry) && typeof entry === 'object' && !Array.isArray(entry)) {\n if (path === '.') {\n if (entry.require && rootExport.require && entry.require !== rootExport.require) {\n errors.push(\n \"package.json: mismatch between 'main' and 'exports.require'. These must be equal.\"\n );\n }\n\n if (entry.import && rootExport.import && entry.import !== rootExport.import) {\n errors.push(\n \"package.json: mismatch between 'module' and 'exports.import' These must be equal.\"\n );\n }\n\n if (entry.types && rootExport.types && entry.types !== rootExport.types) {\n errors.push(\n \"package.json: mismatch between 'types' and 'exports.types'. These must be equal.\"\n );\n }\n\n if (entry.source && rootExport.source && entry.source !== rootExport.source) {\n errors.push(\n \"package.json: mismatch between 'source' and 'exports.source'. These must be equal.\"\n );\n }\n\n Object.assign(rootExport, entry);\n } else {\n const extraExport = {\n _exported: true,\n _path: path,\n ...entry,\n };\n\n extraExports.push(extraExport);\n }\n } else if (!['string', 'object'].includes(typeof entry)) {\n errors.push('package.json: exports must be an object or string');\n }\n });\n }\n\n // eslint-disable-next-line @typescript-eslint/naming-convention\n const _exports = [\n /**\n * In the case of strapi plugins, we don't have a root export because we\n * ship a server side and client side package. So this can be completely omitted.\n */\n Object.values(rootExport).some((exp) => exp !== rootExport._path && Boolean(exp)) && rootExport,\n ...extraExports,\n ].filter((exp) => Boolean(exp)) as Array<Export & { _path: string }>;\n\n errors.push(...validateExports(_exports, { extMap, pkg }));\n\n if (errors.length) {\n throw new Error(`${os.EOL}- ${errors.join(`${os.EOL}- `)}`);\n }\n\n return _exports;\n};\n\nexport { validateExportsOrdering, getExportExtensionMap, parseExports };\nexport type { ExtMap, Export, Extensions };\n","import chalk from 'chalk';\n\ninterface LoggerOptions {\n silent?: boolean;\n debug?: boolean;\n}\n\nexport interface Logger {\n warnings: number;\n errors: number;\n debug: (...args: any[]) => void;\n info: (...args: any[]) => void;\n warn: (...args: any[]) => void;\n error: (...args: any[]) => void;\n log: (...args: any[]) => void;\n success: (...args: any[]) => void;\n}\n\nexport const createLogger = (options: LoggerOptions = {}): Logger => {\n const { silent = false, debug = false } = options;\n\n const state = { errors: 0, warning: 0 };\n\n return {\n get warnings() {\n return state.warning;\n },\n\n get errors() {\n return state.errors;\n },\n\n debug(...args) {\n if (silent || !debug) {\n return;\n }\n\n console.debug(chalk.cyan('[DEBUG] '), ...args);\n },\n\n info(...args) {\n if (silent) {\n return;\n }\n\n console.info(chalk.blue('[INFO] '), ...args);\n },\n\n log(...args) {\n if (silent) {\n return;\n }\n\n console.log(...args);\n },\n\n warn(...args) {\n state.warning += 1;\n\n if (silent) {\n return;\n }\n\n console.warn(chalk.yellow('[WARN] '), ...args);\n },\n\n error(...args) {\n state.errors += 1;\n\n if (silent) {\n return;\n }\n\n console.error(chalk.red('[ERROR] '), ...args);\n },\n\n success(...args) {\n if (silent) {\n return;\n }\n\n console.info(chalk.green('[SUCCESS] '), ...args);\n },\n };\n};\n","/**\n * Utility functions for loading and validating package.json\n * this includes the specific validation of specific parts of\n * the package.json.\n */\nimport chalk from 'chalk';\nimport fs from 'fs/promises';\nimport os from 'os';\nimport pkgUp from 'pkg-up';\nimport * as yup from 'yup';\n\nimport type { Export } from './exports';\nimport type { Logger } from './logger';\n\nconst record = (value: unknown) =>\n yup\n .object(\n typeof value === 'object' && value\n ? Object.entries(value).reduce<Record<string, yup.SchemaOf<string>>>((acc, [key]) => {\n acc[key] = yup.string().required();\n\n return acc;\n }, {})\n : {}\n )\n .optional();\n\n/**\n * The schema for the package.json that we expect,\n * currently pretty loose.\n */\nconst packageJsonSchema = yup.object({\n name: yup.string().required(),\n version: yup.string().required(),\n description: yup.string().optional(),\n author: yup.lazy((value) => {\n if (typeof value === 'object') {\n return yup\n .object({\n name: yup.string().required(),\n email: yup.string().optional(),\n url: yup.string().optional(),\n })\n .optional();\n }\n\n return yup.string().optional();\n }),\n keywords: yup.array(yup.string()).optional(),\n type: yup.mixed().oneOf(['commonjs', 'module']).optional(),\n license: yup.string().optional(),\n repository: yup\n .object({\n type: yup.string().required(),\n url: yup.string().required(),\n })\n .optional(),\n bugs: yup\n .object({\n url: yup.string().required(),\n })\n .optional(),\n homepage: yup.string().optional(),\n // TODO: be nice just to make this either a string or a record of strings.\n bin: yup.lazy((value) => {\n if (typeof value === 'object') {\n return record(value);\n }\n\n return yup.string().optional();\n }),\n // TODO: be nice just to make this either a string or a record of strings.\n browser: yup.lazy((value) => {\n if (typeof value === 'object') {\n return record(value);\n }\n\n return yup.string().optional();\n }),\n main: yup.string().optional(),\n module: yup.string().optional(),\n source: yup.string().optional(),\n types: yup.string().optional(),\n exports: yup.lazy((value) =>\n yup\n .object(\n typeof value === 'object'\n ? Object.entries(value).reduce(\n (acc, [key, v]) => {\n if (typeof v === 'object') {\n // @ts-expect-error yup is not typed correctly\n acc[key] = yup\n .object({\n types: yup.string().optional(),\n source: yup.string().required(),\n browser: yup\n .object({\n source: yup.string().required(),\n import: yup.string().optional(),\n require: yup.string().optional(),\n })\n .optional(),\n node: yup\n .object({\n source: yup.string().optional(),\n module: yup.string().optional(),\n import: yup.string().optional(),\n require: yup.string().optional(),\n })\n .optional(),\n module: yup.string().optional(),\n import: yup.string().optional(),\n require: yup.string().optional(),\n default: yup.string().required(),\n })\n .noUnknown(true);\n } else {\n acc[key] = yup.string().required();\n }\n\n return acc;\n },\n {} as Record<string, yup.SchemaOf<string> | yup.SchemaOf<Export>>\n )\n : undefined\n )\n .optional()\n ),\n files: yup.array(yup.string()).optional(),\n scripts: yup.lazy(record),\n dependencies: yup.lazy(record),\n devDependencies: yup.lazy(record),\n peerDependencies: yup.lazy(record),\n engines: yup.lazy(record),\n browserslist: yup.array(yup.string().required()).optional(),\n});\n\n/**\n * @description being a task to load the package.json starting from the current working directory\n * using a shallow find for the package.json and `fs` to read the file. If no package.json is found,\n * the process will throw with an appropriate error message.\n */\nconst loadPkg = async ({ cwd, logger }: { cwd: string; logger: Logger }): Promise<object> => {\n const pkgPath = await pkgUp({ cwd });\n\n if (!pkgPath) {\n throw new Error('Could not find a package.json in the current directory');\n }\n\n const buffer = await fs.readFile(pkgPath);\n\n const pkg = JSON.parse(buffer.toString());\n\n logger.debug('Loaded package.json:', os.EOL, pkg);\n\n return pkg;\n};\n\ninterface PackageJson extends Omit<yup.Asserts<typeof packageJsonSchema>, 'type'> {\n type?: 'commonjs' | 'module';\n}\n\n/**\n * @description validate the package.json against a standardised schema using `yup`.\n * If the validation fails, the process will throw with an appropriate error message.\n */\nconst validatePkg = async ({ pkg }: { pkg: object }): Promise<PackageJson> => {\n try {\n const validatedPkg = await packageJsonSchema.validate(pkg, {\n strict: true,\n });\n\n return validatedPkg;\n } catch (err) {\n if (err instanceof yup.ValidationError) {\n switch (err.type) {\n case 'required':\n if (err.path) {\n throw new Error(\n `'${err.path}' in 'package.json' is required as type '${chalk.magenta(\n yup.reach(packageJsonSchema, err.path).type\n )}'`\n );\n }\n break;\n case 'matches':\n if (err.params && err.path && 'value' in err.params && 'regex' in err.params) {\n throw new Error(\n `'${err.path}' in 'package.json' must be of type '${chalk.magenta(\n err.params.regex\n )}' (recieved the value '${chalk.magenta(err.params.value)}')`\n );\n }\n break;\n /**\n * This will only be thrown if there are keys in the export map\n * that we don't expect so we can therefore make some assumptions\n */\n case 'noUnknown':\n if (err.path && err.params && 'unknown' in err.params) {\n throw new Error(\n `'${err.path}' in 'package.json' contains the unknown key ${chalk.magenta(\n err.params.unknown\n )}, for compatability only the following keys are allowed: ${chalk.magenta(\n \"['types', 'source', 'import', 'require', 'default']\"\n )}`\n );\n }\n break;\n default:\n if (err.path && err.params && 'type' in err.params && 'value' in err.params) {\n throw new Error(\n `'${err.path}' in 'package.json' must be of type '${chalk.magenta(\n err.params.type\n )}' (recieved '${chalk.magenta(typeof err.params.value)}')`\n );\n }\n }\n }\n\n throw err;\n }\n};\n\nexport { loadPkg, validatePkg };\nexport type { PackageJson };\n","import os from 'os';\nimport nodePath from 'path';\nimport ts from 'typescript';\n\nimport type { Logger } from './logger';\n\n/**\n * @description Load a tsconfig.json file and return the parsed config\n * after injecting some required defaults for producing types.\n *\n * @internal\n */\nconst loadTsConfig = ({\n cwd,\n path,\n logger,\n}: {\n cwd: string;\n path: string;\n logger: Logger;\n}):\n | {\n config: ts.ParsedCommandLine;\n path: string;\n }\n | undefined => {\n const providedPath = path.split(nodePath.sep);\n const [configFileName] = providedPath.slice(-1);\n const pathToConfig = nodePath.join(cwd, providedPath.slice(0, -1).join(nodePath.sep));\n\n const configPath = ts.findConfigFile(pathToConfig, ts.sys.fileExists, configFileName);\n\n if (!configPath) {\n return undefined;\n }\n\n const configFile = ts.readConfigFile(configPath, ts.sys.readFile);\n\n const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, pathToConfig);\n\n logger.debug('Loaded user TS config:', os.EOL, parsedConfig);\n\n const { outDir } = parsedConfig.raw.compilerOptions;\n\n if (!outDir) {\n throw new Error(\"tsconfig.json is missing 'compilerOptions.outDir'\");\n }\n\n parsedConfig.options = {\n ...parsedConfig.options,\n declaration: true,\n declarationDir: outDir,\n emitDeclarationOnly: true,\n noEmit: false,\n outDir,\n };\n\n logger.debug('Using TS config:', os.EOL, parsedConfig);\n\n return {\n config: parsedConfig,\n path: configPath,\n };\n};\n\nexport { loadTsConfig };\n","import browserslistToEsbuild from 'browserslist-to-esbuild';\nimport path from 'path';\n\nimport { resolveConfigProperty } from './core/config';\nimport { parseExports } from './core/exports';\nimport { loadTsConfig } from './core/tsconfig';\n\nimport type { Config } from './core/config';\nimport type { ExtMap, Export } from './core/exports';\nimport type { Logger } from './core/logger';\nimport type { PackageJson } from './core/pkg';\nimport type { ParsedCommandLine } from 'typescript';\n\ninterface BuildContextArgs {\n config: Config;\n cwd: string;\n extMap: ExtMap;\n logger: Logger;\n pkg: PackageJson;\n}\n\ninterface Targets {\n node: string[];\n web: string[];\n '*': string[];\n}\n\ntype Runtime = '*' | 'node' | 'web';\n\ninterface BuildContext {\n config: Config;\n cwd: string;\n distPath: string;\n exports: Record<string, Export>;\n external: string[];\n extMap: ExtMap;\n logger: Logger;\n pkg: PackageJson;\n runtime?: Runtime;\n targets: Targets;\n ts?: {\n config: ParsedCommandLine;\n path: string;\n };\n}\n\nconst DEFAULT_BROWSERS_LIST_CONFIG = [\n 'last 3 major versions',\n 'Firefox ESR',\n 'last 2 Opera versions',\n 'not dead',\n 'node 18.0.0',\n];\n\n/**\n * @description Create a build context for the pipeline we're creating,\n * this is shared among tasks so they all use the same settings for core pieces\n * such as a target, distPath, externals etc.\n */\nconst createBuildContext = async ({\n config,\n cwd,\n extMap,\n logger,\n pkg,\n}: BuildContextArgs): Promise<BuildContext> => {\n const tsConfig = loadTsConfig({\n cwd,\n path: resolveConfigProperty(config.tsconfig, 'tsconfig.build.json'),\n logger,\n });\n\n const targets = {\n '*': browserslistToEsbuild(pkg.browserslist ?? DEFAULT_BROWSERS_LIST_CONFIG),\n node: browserslistToEsbuild(['node 18.0.0']),\n web: ['esnext'],\n };\n\n const parsedExports = parseExports({ extMap, pkg }).reduce(\n (acc, x) => {\n const { _path: exportPath, ...exportEntry } = x;\n\n return { ...acc, [exportPath]: exportEntry };\n },\n {} as Record<string, Export>\n );\n\n const exports = resolveConfigProperty(config.exports, parsedExports);\n\n const parsedExternals = [\n ...(pkg.dependencies ? Object.keys(pkg.dependencies) : []),\n ...(pkg.peerDependencies ? Object.keys(pkg.peerDependencies) : []),\n ];\n\n const external =\n config && Array.isArray(config.externals)\n ? [...parsedExternals, ...config.externals]\n : parsedExternals;\n\n const outputPaths = Object.values(exports)\n .flatMap((exportEntry) => {\n return [\n exportEntry.import,\n exportEntry.require,\n exportEntry.browser?.import,\n exportEntry.browser?.require,\n exportEntry.node?.source && exportEntry.node.import,\n exportEntry.node?.source && exportEntry.node.require,\n ].filter(Boolean) as string[];\n })\n .map((p) => path.resolve(cwd, p));\n\n const commonDistPath = findCommonDirPath(outputPaths);\n\n if (commonDistPath === cwd) {\n throw new Error(\n 'all output files must share a common parent directory which is not the root package directory'\n );\n }\n\n if (commonDistPath && !pathContains(cwd, commonDistPath)) {\n throw new Error('all output files must be located within the package');\n }\n\n const configDistPath = config?.dist ? path.resolve(cwd, config.dist) : undefined;\n\n const distPath = configDistPath || commonDistPath;\n\n if (!distPath) {\n throw new Error(\"could not detect 'dist' path\");\n }\n\n return {\n config,\n cwd,\n distPath,\n exports,\n external,\n extMap,\n logger,\n pkg,\n runtime: config?.runtime,\n targets,\n ts: tsConfig,\n };\n};\n\n/**\n * @internal\n */\nconst pathContains = (containerPath: string, itemPath: string): boolean => {\n return !path.relative(containerPath, itemPath).startsWith('..');\n};\n\n/**\n * @internal\n */\nconst findCommonDirPath = (filePaths: string[]): string | undefined => {\n let commonPath: string | undefined;\n\n for (const filePath of filePaths) {\n let dirPath = path.dirname(filePath);\n\n if (!commonPath) {\n commonPath = dirPath;\n // eslint-disable-next-line no-continue\n continue;\n }\n\n while (dirPath !== commonPath) {\n dirPath = path.dirname(dirPath);\n\n if (dirPath === commonPath) {\n break;\n }\n\n if (pathContains(dirPath, commonPath)) {\n commonPath = dirPath;\n break;\n }\n\n if (dirPath === '.') {\n return undefined;\n }\n }\n }\n\n return commonPath;\n};\n\nexport { createBuildContext };\nexport type { BuildContext, Targets, Runtime };\n","import path from 'path';\n\nimport type { Extensions } from './core/exports';\nimport type { BuildContext, Runtime } from './createBuildContext';\nimport type { DtsBuildTask } from './tasks/dts/build';\nimport type { DtsBaseTask } from './tasks/dts/types';\nimport type { DtsWatchTask } from './tasks/dts/watch';\nimport type { ViteBuildTask } from './tasks/vite/build';\nimport type { ViteBaseTask, ViteTaskEntry } from './tasks/vite/types';\nimport type { ViteWatchTask } from './tasks/vite/watch';\n\ntype BuildTask = DtsBuildTask | ViteBuildTask;\ntype WatchTask = ViteWatchTask | DtsWatchTask;\n\ntype BaseTask = ViteBaseTask | DtsBaseTask;\n\n/**\n * @description Create the build tasks for the pipeline, this\n * comes from the exports map we've created in the build context.\n * But handles each export line uniquely with space to add more\n * as the standard develops.\n */\nconst createTasks =\n <TMode extends 'build' | 'watch'>(mode: TMode) =>\n async (ctx: BuildContext): Promise<TMode extends 'build' ? BuildTask[] : WatchTask[]> => {\n const tasks: Array<BaseTask> = [];\n\n const dtsTask: DtsBaseTask = {\n type: `${mode}:dts`,\n entries: [],\n };\n\n const viteTasks: Record<string, ViteBaseTask> = {};\n\n const createViteTask = (\n format: Extensions,\n runtime: Runtime,\n { output, ...restEntry }: ViteTaskEntry & Pick<ViteWatchTask | ViteBuildTask, 'output'>\n ) => {\n const buildId = `${format}:${output}`;\n\n if (viteTasks[buildId]) {\n viteTasks[buildId]?.entries.push(restEntry);\n\n if (output !== viteTasks[buildId]?.output) {\n ctx.logger.warn(\n 'Multiple entries with different outputs for the same format are not supported. The first output will be used.'\n );\n }\n } else {\n viteTasks[buildId] = {\n type: `${mode}:js`,\n format,\n output,\n runtime,\n entries: [restEntry],\n };\n }\n };\n\n const exps = Object.entries(ctx.exports).map(([exportPath, exportEntry]) => ({\n ...exportEntry,\n _path: exportPath,\n }));\n\n for (const exp of exps) {\n if (exp.types) {\n const importId = path.join(ctx.pkg.name, exp._path);\n\n dtsTask.entries.push({\n importId,\n exportPath: exp._path,\n sourcePath: exp.source,\n targetPath: exp.types,\n });\n }\n\n if (exp.require) {\n /**\n * register CJS task\n */\n createViteTask('cjs', ctx.runtime ?? '*', {\n path: exp._path,\n entry: exp.source,\n output: exp.require,\n });\n }\n\n if (exp.import) {\n /**\n * register ESM task\n */\n createViteTask('es', ctx.runtime ?? '*', {\n path: exp._path,\n entry: exp.source,\n output: exp.import,\n });\n }\n\n if (exp.browser?.require) {\n createViteTask('cjs', 'web', {\n path: exp._path,\n entry: exp.browser?.source || exp.source,\n output: exp.browser.require,\n });\n }\n\n if (exp.browser?.import) {\n createViteTask('cjs', 'web', {\n path: exp._path,\n entry: exp.browser?.source || exp.source,\n output: exp.browser.import,\n });\n }\n }\n\n const bundles = ctx.config.bundles ?? [];\n\n for (const bundle of bundles) {\n const idx = bundles.indexOf(bundle);\n\n if (bundle.require) {\n createViteTask('cjs', (bundle.runtime || ctx.runtime) ?? '*', {\n path: `bundle_cjs_${idx}`,\n entry: bundle.source,\n output: bundle.require,\n });\n }\n\n if (bundle.import) {\n createViteTask('es', (bundle.runtime || ctx.runtime) ?? '*', {\n path: `bundle_esm_${idx}`,\n entry: bundle.source,\n output: bundle.import,\n });\n }\n\n if (bundle.types) {\n const importId = path.join(ctx.pkg.name, bundle.source);\n\n dtsTask.entries.push({\n importId,\n exportPath: bundle.source,\n sourcePath: bundle.source,\n targetPath: bundle.types,\n tsconfig: bundle.tsconfig,\n });\n }\n }\n\n if (dtsTask.entries.length) {\n tasks.push(dtsTask);\n }\n if (Object.values(viteTasks).length) {\n tasks.push(...Object.values(viteTasks));\n }\n\n return tasks as TMode extends 'build' ? BuildTask[] : WatchTask[];\n };\n\nconst createBuildTasks = createTasks('build');\nconst createWatchTasks = createTasks('watch');\n\nexport { createBuildTasks, createWatchTasks };\nexport type { BuildTask, WatchTask, BaseTask };\n","import chalk from 'chalk';\nimport path from 'path';\nimport ts from 'typescript';\n\nimport type { Logger } from '../../core/logger';\n\nconst printDiagnostic = (\n diagnostic: ts.Diagnostic,\n { logger, cwd }: { logger: Logger; cwd: string }\n) => {\n let output = ts.flattenDiagnosticMessageText(diagnostic.messageText, ts.sys.newLine);\n\n if (diagnostic.file && diagnostic.start) {\n const { line, character } = ts.getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start);\n const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, ts.sys.newLine);\n\n const file = path.relative(cwd, diagnostic.file.fileName);\n\n output = [\n `${chalk.cyan(file)}:${chalk.cyan(line + 1)}:${chalk.cyan(character + 1)} - `,\n `${chalk.gray(`TS${diagnostic.code}:`)} ${message}`,\n ].join('');\n }\n\n switch (diagnostic.category) {\n case ts.DiagnosticCategory.Error:\n logger.error(output);\n break;\n case ts.DiagnosticCategory.Warning:\n logger.warn(output);\n break;\n case ts.DiagnosticCategory.Message:\n logger.info(output);\n break;\n case ts.DiagnosticCategory.Suggestion:\n logger.info(output);\n break;\n default:\n break;\n }\n};\n\nexport { printDiagnostic };\n","import chalk from 'chalk';\nimport os from 'os';\nimport { Observable } from 'rxjs';\nimport ts from 'typescript';\n\nimport { isError } from '../../core/errors';\nimport { loadTsConfig } from '../../core/tsconfig';\n\nimport { printDiagnostic } from './diagnostic';\n\nimport type { DtsBaseTask } from './types';\nimport type { TaskHandler } from '../index';\n\ninterface DtsBuildTask extends DtsBaseTask {\n type: 'build:dts';\n}\n\nconst dtsBuildTask: TaskHandler<DtsBuildTask> = {\n print(ctx, task) {\n const entries = [\n ' entries:',\n ...task.entries.map((entry) =>\n [\n ' – ',\n chalk.green(`${entry.importId} `),\n `${chalk.cyan(entry.sourcePath)} ${chalk.gray('->')} ${chalk.cyan(entry.targetPath)}`,\n ].join('')\n ),\n ];\n\n ctx.logger.log(['Building type files:', ...entries].join(os.EOL));\n },\n run$(ctx, task) {\n return new Observable((subscriber) => {\n Promise.all(\n task.entries.map(async (entry) => {\n /**\n * Entry level tsconfig's take precedence\n */\n const tsconfig = entry.tsconfig\n ? loadTsConfig({\n cwd: ctx.cwd,\n path: entry.tsconfig,\n logger: ctx.logger,\n })\n : ctx.ts;\n\n if (!tsconfig) {\n ctx.logger.warn(\n `You've added a types entry but no tsconfig.json was found for ${entry.targetPath}. Skipping...`\n );\n\n return;\n }\n\n const program = ts.createProgram(tsconfig.config.fileNames, tsconfig.config.options);\n\n const emitResult = program.emit();\n\n const allDiagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics);\n\n for (const diagnostic of allDiagnostics) {\n printDiagnostic(diagnostic, { logger: ctx.logger, cwd: ctx.cwd });\n }\n\n const errors = allDiagnostics.filter(\n (diag) => diag.category === ts.DiagnosticCategory.Error\n );\n\n if (errors.length) {\n throw new Error('Failed to compile TypeScript definitions');\n }\n })\n )\n .then(() => {\n subscriber.complete();\n })\n .catch((err) => {\n subscriber.error(err);\n });\n });\n },\n async success(ctx, task) {\n const msg = [\n 'Built types, entries:',\n task.entries\n .map(\n (entry) =>\n ` ${chalk.blue(`${entry.importId}`)}: ${entry.sourcePath} -> ${entry.targetPath}`\n )\n .join(os.EOL),\n ];\n\n ctx.logger.success(msg.join(os.EOL));\n },\n async fail(ctx, task, err) {\n if (isError(err)) {\n ctx.logger.error(err.message);\n }\n },\n};\n\nexport { dtsBuildTask };\n\nexport type { DtsBuildTask };\n","import chalk from 'chalk';\nimport os from 'os';\nimport { Observable } from 'rxjs';\nimport ts from 'typescript';\n\nimport { isError } from '../../core/errors';\nimport { loadTsConfig } from '../../core/tsconfig';\n\nimport { printDiagnostic } from './diagnostic';\n\nimport type { DtsBaseTask } from './types';\nimport type { TaskHandler } from '../index';\n\ninterface DtsWatchTask extends DtsBaseTask {\n type: 'watch:dts';\n}\n\nconst dtsWatchTask: TaskHandler<DtsWatchTask, ts.Diagnostic> = {\n print(ctx, task) {\n const msg = [\n 'Building Types, entries:',\n task.entries\n .map(\n (entry) =>\n ` ${chalk.blue(`${entry.importId}`)}: ${entry.sourcePath} -> ${entry.targetPath}`\n )\n .join(os.EOL),\n ];\n\n ctx.logger.success(msg.join(os.EOL));\n },\n run$(ctx, task) {\n let programs: Array<\n ts.WatchOfConfigFile<ts.EmitAndSemanticDiagnosticsBuilderProgram> | undefined\n > = [];\n\n return new Observable((subscriber) => {\n Promise.all(\n task.entries.map(async (entry) => {\n /**\n * Entry level tsconfig's take precedence\n */\n const tsconfig = entry.tsconfig\n ? loadTsConfig({\n cwd: ctx.cwd,\n path: entry.tsconfig,\n logger: ctx.logger,\n })\n : ctx.ts;\n\n if (!tsconfig) {\n ctx.logger.warn(\n `You've added a types entry but no tsconfig.json was found for ${entry.targetPath}. Skipping...`\n );\n\n return;\n }\n\n const compilerHost = ts.createWatchCompilerHost(\n tsconfig.path,\n tsconfig.config.options,\n ts.sys,\n ts.createEmitAndSemanticDiagnosticsBuilderProgram,\n (diagnostic) => {\n subscriber.next(diagnostic);\n },\n (diagnostic) => {\n subscriber.next(diagnostic);\n }\n );\n\n return ts.createWatchProgram(compilerHost);\n })\n )\n .then((progs) => {\n programs = progs;\n })\n .catch((err) => {\n subscriber.error(err);\n });\n\n return () => {\n programs.forEach((prog) => {\n prog?.close();\n });\n };\n });\n },\n async success(ctx, task, diagnostic) {\n const { logger, cwd } = ctx;\n\n /**\n * This code is \"Found 0 errors. Watching for file changes.\"\n * which is equivalent to \"BUNDLE_END\" code with rollup/vite.\n *\n * So we use this to say, hey we've built your types again!\n */\n if (diagnostic.code === 6194) {\n this.print(ctx, task);\n }\n\n /**\n * We don't want to print messages or suggestions.\n * Only errors and warnings in watch mode.\n */\n if (\n diagnostic.category === ts.DiagnosticCategory.Message ||\n diagnostic.category === ts.DiagnosticCategory.Suggestion\n ) {\n return;\n }\n\n printDiagnostic(diagnostic, { logger, cwd });\n },\n async fail(ctx, task, err) {\n if (isError(err)) {\n ctx.logger.error(err);\n }\n },\n};\n\nexport { dtsWatchTask };\n\nexport type { DtsWatchTask };\n","/* eslint-disable no-nested-ternary */\nimport react from '@vitejs/plugin-react-swc';\nimport { builtinModules } from 'node:module';\nimport path from 'path';\n\nimport { resolveConfigProperty } from '../../core/config';\n\nimport type { ViteBaseTask } from './types';\nimport type { BuildContext } from '../../createBuildContext';\nimport type { InlineConfig } from 'vite';\n\n/**\n * @internal\n */\nconst resolveViteConfig = async (ctx: BuildContext, task: ViteBaseTask) => {\n const { cwd, distPath, targets, external, extMap, pkg, exports: exportMap } = ctx;\n const { entries, format, output, runtime } = task;\n const outputExt = extMap[pkg.type || 'commonjs'][format];\n const outDir = path.relative(cwd, distPath);\n\n const { createLogger } = await import('vite');\n const customLogger = createLogger();\n customLogger.warn = (msg) => ctx.logger.warn(msg);\n customLogger.warnOnce = (msg) => ctx.logger.warn(msg);\n customLogger.error = (msg) => ctx.logger.error(msg);\n customLogger.info = () => {};\n\n const exportIds = Object.keys(exportMap).map((exportPath) => path.join(pkg.name, exportPath));\n const sourcePaths = Object.values(exportMap).map((exp) => path.resolve(cwd, exp.source));\n\n const basePlugins = runtime === 'node' ? [] : [react()];\n\n const plugins = ctx.config.plugins\n ? typeof ctx.config.plugins === 'function'\n ? ctx.config.plugins({ runtime })\n : ctx.config.plugins\n : [];\n\n const config = {\n configFile: false,\n root: cwd,\n mode: 'production',\n logLevel: 'warn',\n clearScreen: false,\n customLogger,\n build: {\n minify: resolveConfigProperty(ctx.config.minify, false),\n sourcemap: resolveConfigProperty(ctx.config.sourcemap, true),\n /**\n * The task runner will clear this for us\n */\n emptyOutDir: false,\n target: targets[runtime],\n outDir,\n lib: {\n entry: entries.map((e) => e.entry),\n formats: [format],\n /**\n * this enforces the file name to match what the output we've\n * determined from the package.json exports. However, when preserving modules\n * we want to let Rollup handle the file names.\n */\n fileName: resolveConfigProperty(ctx.config.preserveModules, false)\n ? undefined\n : () => {\n return `${path.relative(outDir, output).replace(/\\.[^/.]+$/, '')}${outputExt}`;\n },\n },\n rollupOptions: {\n external(id, importer) {\n // Check if the id is a self-referencing import\n if (exportIds?.includes(id)) {\n return true;\n }\n\n // Check if the id is a file path that points to an exported source file\n if (importer && (id.startsWith('.') || id.startsWith('/'))) {\n const idPath = path.resolve(path.dirname(importer), id);\n\n if (sourcePaths?.includes(idPath)) {\n ctx.logger.warn(\n `detected self-referencing import – treating as external: ${path.relative(\n cwd,\n idPath\n )}`\n );\n\n return true;\n }\n }\n\n const idParts = id.split('/');\n\n const name = idParts[0]?.startsWith('@') ? `${idParts[0]}/${idParts[1]}` : idParts[0];\n\n const builtinModulesWithNodePrefix = [\n ...builtinModules,\n ...builtinModules.map((modName) => `node:${modName}`),\n ];\n\n if (\n (name && external.includes(name)) ||\n (name && builtinModulesWithNodePrefix.includes(name))\n ) {\n return true;\n }\n\n return false;\n },\n output: {\n preserveModules: resolveConfigProperty(ctx.config.preserveModules, false),\n /**\n * Mimic TypeScript's behavior, by setting the value to \"auto\" to control\n * how Rollup handles default, namespace and dynamic imports from external\n * dependencies in formats like CommonJS that do not natively support\n * these concepts. Mainly styled-components@5\n *\n * For more info see https://rollupjs.org/configuration-options/#output-interop\n */\n interop: 'auto',\n chunkFileNames() {\n const parts = outputExt.split('.');\n\n if (parts.length === 3) {\n return `_chunks/[name]-[hash].${parts[2]}`;\n }\n\n return `_chunks/[name]-[hash]${outputExt}`;\n },\n },\n },\n },\n plugins: [...basePlugins, ...plugins],\n } satisfies InlineConfig;\n\n return import('vite').then(({ mergeConfig }) =>\n mergeConfig(config, ctx.config.unstable_viteConfig ?? {})\n );\n};\n\nexport { resolveViteConfig };\n","import chalk from 'chalk';\nimport os from 'os';\nimport path from 'path';\nimport { Observable } from 'rxjs';\n\nimport { isError } from '../../core/errors';\n\nimport { resolveViteConfig } from './config';\n\nimport type { ViteBaseTask } from './types';\nimport type { TaskHandler } from '../index';\n\ninterface ViteBuildTask extends ViteBaseTask {\n type: 'build:js';\n}\n\nconst viteBuildTask: TaskHandler<ViteBuildTask> = {\n print(ctx, task) {\n const targetLines = [\n ' target:',\n ...ctx.targets[task.runtime].map((t) => chalk.cyan(` - ${t}`)),\n ];\n const entries = [\n ' entries:',\n ...task.entries.map((entry) =>\n [\n ' – ',\n chalk.green(`${path.join(ctx.pkg.name, entry.path)}: `),\n `${chalk.cyan(entry.entry)} ${chalk.gray('→')} ${chalk.cyan(task.output)}`,\n ].join('')\n ),\n ];\n\n ctx.logger.log(\n ['Building javascript files:', ` format: ${task.format}`, ...targetLines, ...entries].join(\n os.EOL\n )\n );\n },\n run$(ctx, task) {\n return new Observable((subscriber) => {\n resolveViteConfig(ctx, task).then((config) => {\n ctx.logger.debug('Vite config:', os.EOL, config);\n import('vite').then(({ build }) => {\n build(config)\n .then(() => {\n subscriber.complete();\n })\n .catch((err) => {\n subscriber.error(err);\n });\n });\n });\n });\n },\n async success(ctx, task) {\n const msg = [\n `Built javascript (runtime: ${task.runtime} – target: ${task.format})`,\n task.entries\n .map(\n (e) => ` ${chalk.blue(path.join(ctx.pkg.name, e.path))}: ${e.entry} -> ${task.output}`\n )\n .join(os.EOL),\n ];\n\n ctx.logger.success(msg.join(os.EOL));\n },\n async fail(ctx, task, err) {\n if (isError(err)) {\n ctx.logger.error(err.message);\n }\n },\n};\n\nexport { viteBuildTask };\nexport type { ViteBuildTask };\n","import chalk from 'chalk';\nimport os from 'os';\nimport path from 'path';\nimport { Observable } from 'rxjs';\n\nimport { isError } from '../../core/errors';\n\nimport { resolveViteConfig } from './config';\n\nimport type { ViteBaseTask } from './types';\nimport type { TaskHandler } from '../index';\n\nexport type InputOption = string | string[] | { [entryAlias: string]: stri