Skip to content

Commit

Permalink
chore(tools): reorganize build workflow
Browse files Browse the repository at this point in the history
  • Loading branch information
unicornware committed Oct 30, 2021
1 parent e59e47a commit 90ba0ea
Show file tree
Hide file tree
Showing 17 changed files with 799 additions and 847 deletions.
22 changes: 8 additions & 14 deletions .eslintrc.base.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -227,20 +227,11 @@ module.exports = {
'unicorn/throw-new-error': 2
},
overrides: [
{
files: ['**/*.cjs', '**/*.cts', '**/*.md/*.js'],
parser: `${__dirname}/node_modules/@babel/eslint-parser/lib/index.cjs`,
parserOptions: {
requireConfigFile: false
},
rules: {
'@typescript-eslint/explicit-module-boundary-types': 0,
'@typescript-eslint/no-var-requires': 0
}
},
{
files: ['**/*.cjs', '**/*.cts'],
rules: {
'@typescript-eslint/explicit-module-boundary-types': 0,
'@typescript-eslint/no-var-requires': 0,
'unicorn/prefer-module': 0
}
},
Expand Down Expand Up @@ -301,7 +292,7 @@ module.exports = {
}
},
{
files: ['tools/loaders/env.cjs'],
files: ['tools/cli/loadenv.cjs'],
rules: {
'unicorn/no-array-reduce': 0
}
Expand All @@ -311,20 +302,23 @@ module.exports = {
settings: {
'import/parsers': {
[require.resolve('@typescript-eslint/parser')]: [
'.cjs',
'.cts',
'.d.cts',
'.d.mts',
'.d.ts',
'.mjs',
'.mts',
'.ts'
]
},
'import/resolver': {
[require.resolve('eslint-import-resolver-node')]: {
extensions: ['.cts', '.mts', '.ts']
extensions: ['.cjs', '.cts', '.mjs', '.mts', '.ts']
},
[require.resolve('eslint-import-resolver-typescript')]: {
alwaysTryTypes: true
alwaysTryTypes: true,
project: ['./tsconfig.json']
}
},
jsdoc: {
Expand Down
15 changes: 8 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
},
"main": "./cjs/log.min.cjs",
"module": "./esm/log.min.mjs",
"typings": "./types/index.d.ts",
"types": "./types/index.d.ts",
"scripts": {
"clean:build": "rimraf ./(cjs|esm|types|*.tgz)",
"clean:modules": "rimraf ./node_modules",
Expand All @@ -95,10 +95,10 @@
"fix:format": "prettier --write .",
"fix:style": "yarn check:style --fix --cache",
"test": "bash ./tools/scripts/jest.sh",
"build": "node ./tools/cli/build.ts",
"prepack": "toggle-scripts -postinstall && yarn build",
"build": "node ./tools/cli/build-pkg.ts",
"prepack": "toggle-scripts -postinstall && NODE_ENV=production yarn build",
"postpack": "toggle-scripts +postinstall",
"release": "node ./tools/cli/release.ts",
"release": "NODE_ENV=production node ./tools/cli/release.ts",
"prepublishOnly": "toggle-scripts -prepack",
"postpublish": "toggle-scripts +prepack"
},
Expand All @@ -118,7 +118,7 @@
"@commitlint/format": "13.2.0",
"@commitlint/types": "13.2.0",
"@flex-development/grease": "2.0.0",
"@flex-development/trext": "1.0.2",
"@flex-development/trext": "2.0.0",
"@jest/globals": "27.2.5",
"@jest/types": "27.2.5",
"@log/config": "link:src/config",
Expand All @@ -136,6 +136,7 @@
"@types/resolve": "1.20.1",
"@types/rimraf": "3.0.2",
"@types/shelljs": "0.8.9",
"@types/yargs": "17.0.5",
"@typescript-eslint/eslint-plugin": "4.33.0",
"@typescript-eslint/parser": "4.33.0",
"@vates/toggle-scripts": "1.0.0",
Expand Down Expand Up @@ -171,11 +172,11 @@
"prettier-plugin-sh": "0.7.1",
"read-pkg": "7.0.0",
"replace-in-file": "6.2.0",
"resolve-tspaths": "0.1.1",
"rimraf": "3.0.2",
"shelljs": "0.8.4",
"ts-jest": "27.0.5",
"ts-node": "10.2.1",
"tsc-alias": "1.3.10",
"ts-node": "10.4.0",
"tsc-prog": "2.2.1",
"tsconfig": "7.0.0",
"tsconfig-paths": "3.11.0",
Expand Down
209 changes: 209 additions & 0 deletions tools/cli/build-pkg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
#!/usr/bin/env ts-node

import LogLevel from '@flex-development/log/enums/log-level.enum'
import type { TrextOptions } from '@flex-development/trext'
import { trext } from '@flex-development/trext'
import ncc from '@vercel/ncc'
import fs from 'fs/promises'
import path from 'node:path'
import replace from 'replace-in-file'
import rimraf from 'rimraf'
import type { BuildOptions as TsBuildOptions } from 'tsc-prog'
import * as tsc from 'tsc-prog'
import { logDiagnostics } from 'tsc-prog/dist/utils/log'
import ts from 'typescript'
import type { Argv } from 'yargs'
import type { BuildOptions } from '../helpers/build'
import build, { args as bargs } from '../helpers/build'
import logger from '../helpers/logger'
import { $PACKAGE, $WNS } from '../helpers/pkg'
import type { TsRemapOptions } from '../helpers/ts-remap'
import tsRemap from '../helpers/ts-remap'
import tsconfigCascade from '../helpers/tsconfig-cascade'

/**
* @file CLI - Package Build Workflow
* @module tools/cli/build-pkg
*/

export type BuildPkgOptions = BuildOptions & {
/**
* Specify module build formats.
*
* @default ['cjs','esm','types']
*/
formats?: ('cjs' | 'esm' | 'types')[]

/** @see BuildPkgOptions.formats */
f?: BuildPkgOptions['formats']
}

export type BuildPkgArgs = Argv<BuildPkgOptions>
export type BuildPkgArgv = Exclude<BuildPkgArgs['argv'], Promise<any>>

export type BuildModuleFormatOptions = {
build: TsBuildOptions
trext: TrextOptions<'js', 'cjs' | 'mjs'>
remap: TsRemapOptions
}

/** @property {string[]} BUILD_FORMATS - Module build formats */
const BUILD_FORMATS: BuildPkgOptions['formats'] = ['cjs', 'esm', 'types']

/** @property {BuildPkgArgs} args - CLI arguments parser */
const args = bargs
.option('formats', {
alias: 'f',
choices: BUILD_FORMATS,
default: BUILD_FORMATS,
describe: 'specify module build format(s)',
type: 'array'
})
.alias('version', 'v')
.pkgConf('build-pkg') as BuildPkgArgs

/**
* Executes the package build workflow.
*
* @async
* @return {Promise<void>} Empty promise when complete
*/
async function buildPackage(): Promise<void> {
await build<BuildPkgOptions>(args.argv, async (argv, context) => {
const { dryRun, env, formats = [] } = argv
const { cwd, pwd } = context

// Build project, convert output extensions, create bundles
for (const format of formats) {
// Remove stale build directory
!dryRun && rimraf.sync(format)
logger(argv, `remove stale ${format} directory`)

// Get config file suffix
const suffix: `${string}.json` = `prod.${format}.json`

// Get output extension
const extension: 'cjs' | 'mjs' = `${format === 'cjs' ? 'c' : 'm'}js`

// Get module format build options
// See: https://github.com/jeremyben/tsc-prog
// See: https://github.com/flex-development/trext
const options: BuildModuleFormatOptions = (() => {
const tsconfig = (() => {
const { include = [] } = tsconfigCascade([[cwd, 'prod.json']])
const { compilerOptions = {}, exclude = [] } = tsconfigCascade([
[pwd, 'json'],
[pwd, 'prod.json'],
[pwd, suffix]
])

compilerOptions.outDir = format
delete compilerOptions.rootDir

return { compilerOptions, exclude, include }
})()

return {
build: { ...tsconfig, basePath: cwd, clean: { outDir: true } },
remap: {
compilerOptions: { ...tsconfig.compilerOptions, baseUrl: '../..' },
dryRun,
verbose: !!JSON.parse(`${process.env['GITHUB_ACTIONS'] || 'false'}`)
},
trext: {
absolute: /@flex-development/,
babel: { sourceMaps: 'inline' as const },
from: 'js',
pattern: /.js$/,
to: extension
}
} as BuildModuleFormatOptions
})()

// Build project
if (!dryRun) {
// Log compilation start
logger(argv, 'compilation started')

// Create TypeScript program
const program = tsc.createProgramFromConfig(options.build)

// Compile project
const emit = program.emit(
undefined,
undefined,
undefined,
format === 'types'
)

// Get all diagnostics
const diagnostics = [
...ts.getPreEmitDiagnostics(program),
...emit.diagnostics
]

// Log diagnostics
logDiagnostics(diagnostics, true)

// Throw error if files weren't emitted
if (!program.getCompilerOptions().noEmit && emit.emitSkipped) {
throw new Error('compilation failed')
}

// Log compilation result
if (diagnostics.length > 0) {
const message = `compilation done with ${diagnostics.length} errors`
logger(argv, message, [], LogLevel.WARN)
} else logger(argv, 'compilation successful')
}

// Transform paths
tsRemap(options.remap)

if (format !== 'types') {
// Create bundles
// See: https://github.com/vercel/ncc
const BUNDLES = [$WNS, `${$WNS}.min`].map(async name => {
const bundle = `${format}/${name}.${options.trext.to}`
const filename = 'src/index.ts'

if (!dryRun) {
const { code } = await ncc(`${cwd}/${filename}`, {
esm: format === 'esm',
externals: [
...Object.keys($PACKAGE?.optionalDependencies ?? {}),
...Object.keys($PACKAGE?.peerDependencies ?? {})
],
filename,
minify: path.extname(name) === '.min',
production: env === 'production',
quiet: true,
target: options.build.compilerOptions?.target,
// ! type check already performed above
transpileOnly: true
})

await fs.writeFile(bundle, code, { flag: 'wx+' })
await fs.copyFile(`${format}/index.d.ts`, `${format}/${name}.d.ts`)
await replace.replaceInFile({
files: bundle,
from: ';// CONCATENATED MODULE: ./src/',
to: ';// CONCATENATED MODULE: ../src/'
})
}

return bundle
})

// Complete bundle promises
logger(argv, `bundle ${format}`, await Promise.all(BUNDLES))

// Convert TypeScript output to .cjs or .mjs
!dryRun && (await trext<'js', typeof extension>(format, options.trext))
logger(argv, `use .${extension} extensions`)
}
}
})
}

buildPackage()
Loading

0 comments on commit 90ba0ea

Please sign in to comment.