Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unify code-base as ts and transpile to cjs for publish #153

Merged
merged 1 commit into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .config/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use strict'

const { isEsmId } = require('../scripts/packages')

module.exports = {
plugins: [
'@babel/plugin-proposal-export-default-from',
'@babel/plugin-transform-export-namespace-from',
[
'@babel/plugin-transform-runtime',
{
absoluteRuntime: false,
corejs: false,
helpers: true,
regenerator: false,
version: '^7.24.6'
}
],
[
'@babel/plugin-transform-modules-commonjs',
{
allowTopLevelThis: true,
importInterop: (specifier, requestingFilename) => {
if (requestingFilename) {
const specIsEsm = isEsmId(specifier, requestingFilename)
const parentIsEsm = isEsmId(requestingFilename)
if (specIsEsm && parentIsEsm) {
return 'none'
}
if (specIsEsm) {
return 'babel'
}
}
return 'node'
}
}
]
]
}
229 changes: 229 additions & 0 deletions .config/rollup.base.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
import { builtinModules, createRequire } from 'node:module'
import path from 'node:path'
import { fileURLToPath } from 'node:url'

import commonjs from '@rollup/plugin-commonjs'
import replace from '@rollup/plugin-replace'
import { nodeResolve } from '@rollup/plugin-node-resolve'
import rangesIntersect from 'semver/ranges/intersects.js'
import { readPackageUpSync } from 'read-package-up'

import { loadJSON } from '../scripts/files.js'
import {
getPackageName,
getPackageNameEnd,
isEsmId,
normalizeId,
isPackageName,
isBuiltin,
resolveId
} from '../scripts/packages.js'
import { escapeRegExp } from '../scripts/strings.js'
import socketModifyPlugin from '../scripts/rollup/socket-modify-plugin.js'

const __dirname = fileURLToPath(new URL('.', import.meta.url))
const require = createRequire(import.meta.url)

const ts = require('rollup-plugin-ts')

const ENTRY_SUFFIX = '?commonjs-entry'
const EXTERNAL_SUFFIX = '?commonjs-external'

const builtinAliases = builtinModules.reduce((o, n) => {
o[n] = `node:${n}`
return o
}, {})

const rootPath = path.resolve(__dirname, '..')
const babelConfigPath = path.join(__dirname, 'babel.config.js')
const tsconfigPath = path.join(__dirname, 'tsconfig.rollup.json')

const babelConfig = require(babelConfigPath)
const { dependencies: pkgDeps, devDependencies: pkgDevDeps } = loadJSON(
path.resolve(rootPath, 'package.json')
)

const customResolver = nodeResolve({
exportConditions: ['node'],
preferBuiltins: true
})

export default (extendConfig = {}) => {
const depStats = {
dependencies: { __proto__: null },
devDependencies: { __proto__: null },
esm: { __proto__: null },
external: { __proto__: null },
transitives: { __proto__: null }
}

const config = {
__proto__: {
meta: {
depStats
}
},
external(id_, parentId_) {
if (id_.endsWith(EXTERNAL_SUFFIX) || isBuiltin(id_)) {
return true
}
const id = normalizeId(id_)
if (id.endsWith('.cjs') || id.endsWith('.json')) {
return true
}
if (
id.endsWith('.mjs') ||
id.endsWith('.mts') ||
id.endsWith('.ts') ||
!isPackageName(id)
) {
return false
}
const name = getPackageName(id)
const parentId = parentId_ ? resolveId(parentId_) : undefined
const resolvedId = resolveId(id, parentId)
if (isEsmId(resolvedId, parentId)) {
const parentPkg = parentId
? readPackageUpSync({ cwd: path.dirname(parentId) })?.packageJson
: undefined
depStats.esm[name] =
pkgDeps[name] ??
pkgDevDeps[name] ??
parentPkg?.dependencies?.[name] ??
parentPkg?.optionalDependencies?.[name] ??
parentPkg?.peerDependencies?.[name] ??
readPackageUpSync({ cwd: path.dirname(resolvedId) })?.packageJson
?.version ??
''
return false
}
const parentNodeModulesIndex = parentId.lastIndexOf('/node_modules/')
if (parentNodeModulesIndex !== -1) {
const parentNameStart = parentNodeModulesIndex + 14
const parentNameEnd = getPackageNameEnd(parentId, parentNameStart)
const {
version,
dependencies = {},
optionalDependencies = {},
peerDependencies = {}
} = loadJSON(`${parentId.slice(0, parentNameEnd)}/package.json`)
const curRange =
dependencies[name] ??
optionalDependencies[name] ??
peerDependencies[name] ??
version
const seenRange = pkgDeps[name] ?? depStats.external[name]
if (seenRange) {
return rangesIntersect(seenRange, curRange)
}
depStats.external[name] = curRange
depStats.transitives[name] = curRange
} else if (pkgDeps[name]) {
depStats.external[name] = pkgDeps[name]
depStats.dependencies[name] = pkgDeps[name]
} else if (pkgDevDeps[name]) {
depStats.devDependencies[name] = pkgDevDeps[name]
}
return true
},
...extendConfig,
plugins: [
customResolver,
ts({
transpiler: 'babel',
browserslist: false,
transpileOnly: true,
exclude: ['**/*.json'],
babelConfig,
tsconfig: tsconfigPath
}),
// Convert un-prefixed built-in imports into "node:"" prefixed forms.
replace({
delimiters: ['(?<=(?:require\\(|from\\s*)["\'])', '(?=["\'])'],
preventAssignment: false,
values: builtinAliases
}),
// Convert `require('u' + 'rl')` into something like `require$$2$3`.
socketModifyPlugin({
find: /require\('u' \+ 'rl'\)/g,
replace(match) {
return (
/(?<=var +)[$\w]+(?= *= *require\('node:url'\))/.exec(
this.input
)?.[0] ?? match
)
}
}),
// Remove bare require calls, e.g. require calls not associated with an
// import binding:
// require('node:util')
// require('graceful-fs')
socketModifyPlugin({
find: /^\s*require\(["'].+?["']\);?\r?\n/gm,
replace: ''
}),
// Fix incorrectly set "spinners" binding caused by a transpilation bug
// https://github.com/sindresorhus/ora/blob/main/index.js#L416C2-L416C50
// export {default as spinners} from 'cli-spinners'
socketModifyPlugin({
find: /(?<=ora[^.]+\.spinners\s*=\s*)[$\w]+/g,
replace(match) {
return (
new RegExp(`(?<=${escapeRegExp(match)}\\s*=\\s*)[$\\w]+`).exec(
this.input
)?.[0] ?? match
)
}
}),
commonjs({
ignoreDynamicRequires: true,
ignoreGlobal: true,
ignoreTryCatch: true,
defaultIsModuleExports: true,
transformMixedEsModules: true,
extensions: ['.cjs', '.js', '.ts', `.ts${ENTRY_SUFFIX}`]
}),
...(extendConfig.plugins ?? [])
]
}

const output = (
Array.isArray(config.output)
? config.output
: config.output
? [config.output]
: []
).map(o => ({
...o,
chunkFileNames: '[name].js',
manualChunks(id) {
if (id.includes('/node_modules/')) {
return 'vendor'
}
}
}))

// Replace hard-coded absolute paths in source with hard-coded relative paths.
const replacePlugin = replace({
delimiters: ['(?<=["\'])', '/'],
preventAssignment: false,
values: {
[rootPath]: '../'
}
})

const replaceOutputPlugin = {
name: replacePlugin.name,
renderChunk: replacePlugin.renderChunk
}

for (const o of output) {
o.plugins = [
...(Array.isArray(o.plugins) ? o.plugins : []),
replaceOutputPlugin
]
}

config.output = output
return config
}
102 changes: 102 additions & 0 deletions .config/rollup.dist.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { chmodSync, readFileSync, writeFileSync } from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'

import { loadJSON } from '../scripts/files.js'
import { hasKeys } from '../scripts/objects.js'
import { toSortedObject } from '../scripts/sorts.js'
import { formatObject } from '../scripts/strings.js'

import baseConfig from './rollup.base.config.mjs'

const __dirname = fileURLToPath(new URL('.', import.meta.url))

const rootPath = path.resolve(__dirname, '..')
const depStatsPath = path.join(rootPath, '.dep-stats.json')
const distPath = path.join(rootPath, 'dist')
const srcPath = path.join(rootPath, 'src')

const pkgJSONPath = path.resolve(rootPath, 'package.json')
const pkgJSON = loadJSON(pkgJSONPath)

export default () => {
const config = baseConfig({
input: {
cli: `${srcPath}/cli.ts`,
'npm-cli': `${srcPath}/shadow/npm-cli.ts`,
'npx-cli': `${srcPath}/shadow/npx-cli.ts`,
'npm-injection': `${srcPath}/shadow/npm-injection.ts`
},
output: [
{
dir: 'dist',
entryFileNames: '[name].js',
format: 'cjs',
exports: 'auto',
externalLiveBindings: false,
freeze: false
}
],
plugins: [
{
writeBundle() {
const { '@cyclonedx/cdxgen': cdxgenRange, synp: synpRange } =
pkgJSON.dependencies
const { depStats } = config.meta

// Manually add @cyclonedx/cdxgen and synp as they are not directly
// referenced in the code but used through spawned processes.
depStats.dependencies['@cyclonedx/cdxgen'] = cdxgenRange
depStats.dependencies.synp = synpRange
depStats.external['@cyclonedx/cdxgen'] = cdxgenRange
depStats.external.synp = synpRange

try {
// Remove transitives from dependencies
const oldDepStats = loadJSON(depStatsPath)
for (const key of Object.keys(oldDepStats.transitives)) {
if (pkgJSON.dependencies[key]) {
depStats.transitives[key] = pkgJSON.dependencies[key]
depStats.external[key] = pkgJSON.dependencies[key]
delete depStats.dependencies[key]
}
}
} catch {}

depStats.dependencies = toSortedObject(depStats.dependencies)
depStats.devDependencies = toSortedObject(depStats.devDependencies)
depStats.esm = toSortedObject(depStats.esm)
depStats.external = toSortedObject(depStats.external)
depStats.transitives = toSortedObject(depStats.transitives)

// Write dep stats
writeFileSync(depStatsPath, `${formatObject(depStats)}\n`, 'utf8')

// Make dist files chmod +x
chmodSync(path.join(distPath, 'cli.js'), 0o755)
chmodSync(path.join(distPath, 'npm-cli.js'), 0o755)
chmodSync(path.join(distPath, 'npx-cli.js'), 0o755)

// Update dependencies with additional inlined modules
writeFileSync(
pkgJSONPath,
readFileSync(pkgJSONPath, 'utf8').replace(
/(?<="dependencies":\s*)\{[^\}]*\}/,
() => {
const deps = {
...depStats.dependencies,
...depStats.transitives
}
const formatted = formatObject(deps, 4)
return hasKeys(deps) ? formatted.replace('}', ' }') : formatted
}
),
'utf8'
)
}
}
]
})

return config
}
26 changes: 26 additions & 0 deletions .config/rollup.test.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import path from 'node:path'
import { fileURLToPath } from 'node:url'

import baseConfig from './rollup.base.config.mjs'

const __dirname = fileURLToPath(new URL('.', import.meta.url))

const rootPath = path.resolve(__dirname, '..')
const srcPath = path.join(rootPath, 'src')

export default () =>
baseConfig({
input: {
'path-resolve': `${srcPath}/utils/path-resolve.ts`
},
output: [
{
dir: 'test/dist',
entryFileNames: '[name].js',
format: 'cjs',
exports: 'auto',
externalLiveBindings: false,
freeze: false
}
]
})
Loading
Loading