Skip to content

Commit

Permalink
✨ feat(core): refactor, add advance configuration, config file suppor…
Browse files Browse the repository at this point in the history
…t, increase performance etc
angelespejo committed Oct 18, 2024
1 parent 8d987cc commit 1cb8dc6
Showing 29 changed files with 2,941 additions and 1,125 deletions.
8 changes: 8 additions & 0 deletions .dev/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineBuildConfig } from 'unbuild'

export default defineBuildConfig( [ {
entries : [ './src/main', './src/cli' ],
sourcemap : false,
declaration : true,
rollup : { esbuild: { minify: true } },
} ] )
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# binarium

## 0.2.0

### Minor Changes

- refactor, add advance configuration, config file support, increase performance etc

## 0.1.6

### Patch Changes
36 changes: 35 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -22,6 +22,8 @@
- [Node example](#node-example)
- [CLI example](#cli-example)
- [Options](#options)
- [Config File](#config-file)
- [Example](#example)
- [👨‍💻 Development](#-development)
- [☕ Donate](#-donate)
- [📜 License](#-license)
@@ -106,10 +108,42 @@ type BuilderParams = {
* @default 'all'
*/
type?: 'all'|'cjs'|'bin'

/**
* Config file path.
*
* @default undefined
*/
config?: string
}
```
### Config File
For more advanced configuration you can use a configuration file.
Supported formats are: `.mjs, .js, .json, .yml, .yaml, .toml, .tml`.
In the configuration file you can define your build options and configure advanced options of the build itself using the `options` key.
> The `options` configuration is only recommended for cases that require a more advanced configuration.
#### Example
```bash
binarium --config=binarium.config.js
```
```js
// binarium.config.js
import { defineConfig } from 'binarium'

export default defineConfig( {
name : 'my-app-name',
onlyOs : true,
options : { esbuild: { tsconfig: './tsconfig.builder.json' } },
} )

```

## 👨‍💻 Development

**binarium** is an open-source project and its development is open to anyone who wants to participate.
4 changes: 2 additions & 2 deletions examples/build.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import {
build,
BINARIUM_CONSTS,
} from '../dist/main'
} from 'binarium'

BINARIUM_CONSTS.name = 'binarium-test'

build( {
input : 'examples/app',
input : './examples/app',
name : 'binarium-test',
// outDir : resolve( 'build' ),
type : 'bin',
7 changes: 7 additions & 0 deletions examples/config/file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from 'binarium'

export default defineConfig( {
name : 'my-app-name',
onlyOs : true,
options : { esbuild: { tsconfig: './tsconfig.builder.json' } },
} )
8 changes: 8 additions & 0 deletions examples/config/file.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"onlyOS": true,
"options": {
"esbuild": {
"tsconfig": "./tsconfig.builder.json"
}
}
}
45 changes: 23 additions & 22 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "binarium",
"version": "0.1.6",
"version": "0.2.0",
"description": "Zero-configuration node library and CLI for building executables for all platforms and architectures",
"type": "module",
"license": "GPL-3.0",
@@ -32,17 +32,17 @@
"registry": "https://registry.npmjs.org/"
},
"bin": {
"binarium": "dist/bin.js"
"binarium": "dist/cli.mjs"
},
"files": [
"dist",
"tsconfig.builder.json"
],
"module": "dist/main.js",
"main": "dist/main.js",
"module": "dist/main.mjs",
"main": "dist/main.mjs",
"types": "dist/main.d.ts",
"exports": {
".": "./dist/main.js"
".": "./dist/main.mjs"
},
"keywords": [
"cli",
@@ -56,12 +56,13 @@
"pp"
],
"scripts": {
"dev": "pnpm build && tsx examples/build.ts",
"preview": "node dist/bin.js --input=examples/server.ts",
"build": "pnpm run --sequential \"/^build:.*/\"",
"dev": "tsx src/bin.ts",
"example": "tsx examples/build.ts",
"preview": "node dist/cli.mjs --input=examples/app.ts",
"build": "pnpm run --sequential \"/build:.*/\"",
"build:rm": "rimraf dist && rimraf build",
"build:lib": "vite build",
"test": "pnpm run --sequential \"/^test:.*/\"",
"build:lib": "unbuild -c .dev/build.js",
"test": "pnpm run --sequential \"/test:.*/\"",
"test:unit": "vitest run src --passWithNoTests",
"lint": "pnpm eslint src",
"update-version": "changeset && changeset version",
@@ -70,20 +71,20 @@
},
"devDependencies": {
"@changesets/changelog-github": "0.5.0",
"@changesets/cli": "2.27.8",
"@changesets/cli": "2.27.9",
"@commitlint/cli": "19.5.0",
"@parcel/packager-ts": "2.12.0",
"@parcel/transformer-typescript-types": "2.12.0",
"@pigeon-posse/eslint-config": "1.0.1",
"@types/archiver": "6.0.2",
"@types/node": "22.7.4",
"@types/node": "22.7.6",
"@typescript-eslint/eslint-plugin": "8.2.0",
"@typescript-eslint/parser": "8.2.0",
"commitizen": "4.3.1",
"commitlint-config-gitmoji": "2.3.1",
"cz-customizable": "7.2.1",
"cz-emoji": "1.3.2-canary.2",
"doctoc": "^2.2.1",
"doctoc": "2.2.1",
"eslint": "8.57.0",
"eslint-plugin-align-assignments": "1.1.2",
"eslint-plugin-align-import": "1.0.0",
@@ -102,20 +103,20 @@
"jsdoc": "4.0.3",
"jsonc-eslint-parser": "2.4.0",
"lint-staged": "15.2.10",
"rimraf": "^6.0.1",
"ts-node": "10.9.2",
"tslib": "2.7.0",
"tsx": "^4.19.1",
"typescript": "5.6.2",
"vite": "5.4.8",
"vite-plugin-dts": "4.2.2",
"vitest": "2.1.1"
"rimraf": "6.0.1",
"tslib": "2.8.0",
"tsx": "4.19.1",
"typescript": "5.6.3",
"unbuild": "^2.0.0",
"vitest": "2.1.3"
},
"dependencies": {
"@vercel/ncc": "0.38.2",
"@yao-pkg/pkg": "5.15.0",
"archiver": "7.0.1",
"esbuild": "0.24.0"
"esbuild": "0.24.0",
"toml": "3.0.0",
"yaml": "2.6.0"
},
"commitlint": {
"extends": [
2,475 changes: 1,843 additions & 632 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

87 changes: 87 additions & 0 deletions src/_shared/compress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import archiver from 'archiver'
import {
createWriteStream,
existsSync,
} from 'node:fs'
import {
mkdir,
readdir,
} from 'node:fs/promises'
import { cpus } from 'node:os'
import { join } from 'node:path'

// Function that handles the zipping of a single file
const zipFileWorker = ( sourceFilePath: string, zipName: string, outputDirectory: string ) => {

return new Promise<void>( ( resolve, reject ) => {

const output = createWriteStream( join( outputDirectory, zipName ) )
const archive = archiver( 'zip', { zlib: { level: 6 } } ) // Reduced compression level for speed

output.on( 'close', () => {

console.log( `Zip file [${zipName}] created successfully!` )
resolve()

} )

archive.on( 'error', err => {

console.error( `💥 Error creating ${zipName}:`, err )
reject( err )

} )

archive.pipe( output )
archive.file( sourceFilePath, { name: zipName.replace( '.zip', '' ) } )
archive.finalize()

} )

}

// Function to execute zipping in worker threads
const createZipForFileInThread = async ( sourceDirectory: string, file: string, outputDirectory: string ) => {

const sourceFilePath = join( sourceDirectory, file )
const zipName = `${file}.zip`
return zipFileWorker( sourceFilePath, zipName, outputDirectory )

}

export const zipFilesInDirectory = async ( sourceDirectory: string, outputDirectory: string ) => {

// Function to filter out invisible files
const filter = ( file: string ) => !( /(^|\/)\.[^\\/\\.]/g ).test( file )

// Ensure that the output directory exists or create it if it doesn't
if ( !existsSync( outputDirectory ) ) {

await mkdir( outputDirectory, { recursive: true } )

}

const files = await readdir( sourceDirectory )

// Filter out invisible files
const visibleFiles = files.filter( filter )

// Get available CPUs for worker threads
const cpuCount = cpus().length

// Split the files to be processed in chunks based on CPU cores
const fileChunks = []
for ( let i = 0; i < visibleFiles.length; i += cpuCount ) {

fileChunks.push( visibleFiles.slice( i, i + cpuCount ) )

}

// Process each chunk in parallel using workers
await Promise.all( fileChunks.map( async chunk => {

await Promise.all( chunk.map( file => createZipForFileInThread( sourceDirectory, file, outputDirectory ) ) )

} ) )

}
7 changes: 7 additions & 0 deletions src/_shared/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const catchError = async <T>( promise: Promise<T> ): Promise<[undefined, T] | [Error]> => {

return promise
.then( value => ( [ undefined, value ] as unknown as [undefined, T] ) )
.catch( error => ( [ error ] ) )

}
2 changes: 1 addition & 1 deletion src/logger.ts → src/_shared/logger.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

export const logger = (
{
icon = '📦',
icon,
name,
isDebug = false,
}: {
102 changes: 102 additions & 0 deletions src/_shared/process.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@

import { spawn } from 'node:child_process'
import process from 'node:process'

export const exit = process.exit

/**
* This is not recomended but is for not display `(node:31972) [DEP0040] DeprecationWarning:
* The `punycode` module is deprecated. Please use a userland alternative instead.` message.
*
* @example setNoDeprecationAlerts()
*
*/
export const setNoDeprecationAlerts = () => {

// @ts-ignore
process.noDeprecation = true

}
export const onExit = ( cb: NodeJS.ExitListener ) => {

process.on( 'exit', cb )

}

export const getFlagValue = ( key: string ) =>{

const flags = process.argv
for ( const flag of flags ) {

if ( flag.startsWith( `--${key}=` ) ) return flag.split( '=' )[ 1 ]

}
return undefined

}
export const existsFlag = ( v: string ) => process.argv.includes( `--${v}` )

export const exec = async ( cmd: string ) => {

await new Promise<void>( ( resolve, reject ) => {

const childProcess = spawn( cmd, {
shell : true,
stdio : 'inherit',
} )

childProcess.on( 'close', code => {

if ( code === 0 ) resolve()
else {

const error = new Error( `Command failed with code ${code}` )
console.error( error )
reject( error )

}

} )

} )

}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type FnDefault = () => Promise<any>
type OnWriteParams<FN extends FnDefault> = {
fn : FN

// eslint-disable-next-line no-unused-vars
on( value:string ): Promise<string | undefined>
}
export const onOutputWrite = async <FN extends FnDefault>( {
fn, on,
}: OnWriteParams<FN> ): Promise<ReturnType<FN>> => {

const originalWrite = process.stdout.write
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
stdout.write = async ( chunk, ...args ) => {

const value = chunk.toString()
const validated = await on( value )
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if ( validated ) originalWrite.call( stdout, chunk, ...args )

}

try {

// Llamamos a la función original
return await fn()

} finally {

// Restauramos process.stdout.write a su comportamiento original
process.stdout.write = originalWrite

}

}
97 changes: 53 additions & 44 deletions src/utils.ts → src/_shared/sys.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@

import { spawn } from 'node:child_process'
import {
readFile,
mkdir,
writeFile as fsWriteFile,
access,
@@ -16,11 +15,17 @@ import {
parse,
dirname,
join,
resolve,
resolve,
extname,
} from 'node:path'
import * as url from 'node:url'
import {
fileURLToPath,
pathToFileURL,
} from 'node:url'
import toml from 'toml'
import yaml from 'yaml'

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

export const joinPath = join
export const resolvePath = resolve
@@ -98,45 +103,6 @@ export const getPlatform = async () => {

}

export const getFlagValue = ( key: string ) =>{

const flags = process.argv
for ( const flag of flags ) {

if ( flag.startsWith( `--${key}=` ) ) return flag.split( '=' )[ 1 ]

}
return undefined

}
export const existsFlag = ( v: string ) => process.argv.includes( `--${v}` )

export const exec = async ( cmd: string ) => {

await new Promise<void>( ( resolve, reject ) => {

const childProcess = spawn( cmd, {
shell : true,
stdio : 'inherit',
} )

childProcess.on( 'close', code => {

if ( code === 0 ) resolve()
else {

const error = new Error( `Command failed with code ${code}` )
console.error( error )
reject( error )

}

} )

} )

}

export const removePathIfExist = async ( path: string ) => {

try {
@@ -171,3 +137,46 @@ export const removePathIfExist = async ( path: string ) => {
}

}

/**
* Reads a configuration file and returns the parsed content.
*
* @param {string} filePath - The path to the configuration file.
* @returns {Promise<object>} - The parsed content of the configuration file.
* @throws {Error} - If the file extension is not supported.
* @example ``
*
*/
export const readConfigFile = async ( filePath: string ): Promise<object> => {

const ext = extname( filePath )
const content = await readFile( filePath, 'utf8' )
let res

if ( ext === '.json' ) {

res = JSON.parse( content )

} else if ( ext === '.yml' || ext === '.yaml' ) {

res = yaml.parse( content )

} else if ( ext === '.toml' || ext === '.tml' ) {

res = toml.parse( content )

} else if ( ext === '.js' || ext === '.mjs' ) {

const modulePath = pathToFileURL( filePath ).href
res = ( await import( modulePath ) ).default

} else {

throw new Error( `Unsupported file extension: ${ext}` )

}

return res

}

9 changes: 9 additions & 0 deletions src/_shared/time.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { performance } from 'node:perf_hooks'

export const perf = () => {

const start = performance.now()
const stop = () => `${( ( performance.now() - start ) / 1000 ).toFixed( 2 )}`
return { stop }

}
File renamed without changes.
89 changes: 0 additions & 89 deletions src/compress.ts

This file was deleted.

21 changes: 16 additions & 5 deletions src/const.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import { name as pkgName } from '../package.json'

import {
name as pkgName,
version,
} from '../package.json'

export const target = 'node20'
export const name = pkgName.toUpperCase()

export { version }

export const ERROR_ID = {
NO_INPUT : 'NO_INPUT',
PLATFORM_UNKWON : 'PLATFORM_UNKWON',
ON_ESBUILD : 'ON_ESBUILD',
ON_SUCRASE : 'ON_SUCRASE',
ON_COMPRESSION : 'ON_COMPRESSION',
ON_NCC : 'ON_NCC',
ON_PKG : 'ON_PKG',
ON_CONFIG : 'ON_CONFIG',
UNEXPECTED : 'UNEXPECTED',
} as const
export const BUILDER_TYPE = {
@@ -27,8 +34,12 @@ export const BINARIUM_CONSTS: {
name?: string,
debug?: boolean,
icon?: string
onVersion?: () => void
onHelp?: () => void
} = {
icon : undefined,
name : undefined,
debug : undefined,
icon : undefined,
name : undefined,
debug : undefined,
onVersion : undefined,
onHelp : undefined,
}
215 changes: 215 additions & 0 deletions src/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import {
existsFlag,
getFlagValue,
} from './_shared/process'
import {
existsPath,
getArch,
getFilename,
getPlatform,
joinPath,
readConfigFile,
resolvePath,
} from './_shared/sys'
import {
ERROR_ID,
target,
BUILDER_TYPE,
ARCH,
} from './const'
import { BuildError } from './error'

import type { getLog } from './log'
import type {
BuilderParams,
ConfigParams,
} from './types'

type BuildConstructorParams = BuilderParams & { log: ReturnType<typeof getLog> }

const getInput = async ( path: string ) => {

const validExtensions = [
'.ts',
'.js',
'.mjs',
'.mts',
'.cjs',
'.cts',
]

if( !validExtensions.some( ext => path.endsWith( ext ) ) ){

for ( let index = 0; index < validExtensions.length; index++ ) {

const input = path + validExtensions[ index ]

const exists = await existsPath( input )
if( exists ) return input

}
return undefined

}
const exists = await existsPath( path )
if( exists ) return path
return undefined

}

const getConfigfile = async ( path?: string ): Promise<ConfigParams | undefined> => {

try {

if( !path ) return undefined

const exists = await existsPath( path )
if( !exists ) throw new Error( 'Config file does not exist' )

const config = await readConfigFile( path ) as ConfigParams

return {
input : config?.input || undefined,
name : config?.name || undefined,
outDir : config?.outDir || undefined,
onlyOs : config?.onlyOs || undefined,
type : ( config?.type && Object.values( BUILDER_TYPE ).includes( config.type ) ) ? config.type : undefined,
options : {
esbuild : config?.options?.esbuild || undefined,
ncc : config?.options?.ncc || undefined,
pkg : config?.options?.pkg || undefined,
},
}

}catch( error ){

throw new BuildError( ERROR_ID.ON_CONFIG, {
path,
error,
} )

}

}
export const getData = async ( {
input,
name,
config,
outDir = resolvePath( 'build' ),
onlyOs = false,
type = BUILDER_TYPE.ALL,
log,
}: BuildConstructorParams )=> {

const version = existsFlag( 'version' )
const help = existsFlag( 'help' )

if( help ) log.printHelp( )
if( version ) log.printVersion()

const params = Object.assign( {}, {
input,
name,
outDir,
onlyOs,
type,
config,
} )

const flags = {
input : getFlagValue( 'input' ),
onlyOs : existsFlag( 'onlyOs' ),
outDir : getFlagValue( 'outDir' ),
type : getFlagValue( 'type' ) as typeof type,
name : getFlagValue( 'name' ),
config : getFlagValue( 'config' ),
}

if( flags.config ) config = flags.config
const configfile = await getConfigfile( config )

if( configfile ){

if( configfile.name ) name = configfile.name
if( configfile.input ) input = configfile.input
if( configfile.onlyOs ) onlyOs = configfile.onlyOs
if( configfile.outDir ) outDir = configfile.outDir

}

if( flags.name ) name = flags.name
if( flags.input ) input = flags.input
if( flags.onlyOs ) onlyOs = flags.onlyOs
if( flags.outDir ) outDir = flags.outDir

if( flags.type && Object.values( BUILDER_TYPE ).includes( flags.type ) ) type = flags.type

log.info( 'Starting construction...' )
console.log()

const arch = await getArch()
const plat = await getPlatform()

const projectBuild = outDir
if( !name ) name = getFilename( input )

const opts = {
input,
name,
outDir,
onlyOs,
type,
options : configfile?.options,
}

const data = {
platform : plat,
arch,
opts,
}

const projectBuildBin = joinPath( projectBuild, 'bin' )
const projectBuildZip = joinPath( projectBuild, 'zip' )
const projectBuildCjs = joinPath( projectBuild, 'cjs' )

// GET TARGETS
const getTargets = ( arch: typeof ARCH[keyof typeof ARCH] ) => ( onlyOs ? [ `${target}-${plat}-${arch}` ] : [
`${target}-alpine-${arch}`,
`${target}-linux-${arch}`,
`${target}-linuxstatic-${arch}`,
`${target}-macos-${arch}`,
`${target}-win-${arch}`,
] )

const targets = arch === ARCH.ARM64
? [ ...getTargets( ARCH.ARM64 ), ...getTargets( ARCH.X64 ) ]
: getTargets( ARCH.X64 )

log.debug( JSON.stringify( {
message : 'Init data: function params, process flags, final options..',
data : {
params,
flags,
configfile,
...data,
targets,
},
}, null, 2 ) )

// EXIST INPUT

const exists = await getInput( input )
if( !exists ) throw new BuildError( ERROR_ID.NO_INPUT, data )
else input = exists

if( plat === 'unknown' ) throw new BuildError( ERROR_ID.PLATFORM_UNKWON, data )

return {
...data.opts,
targets,
binDir : projectBuildBin,
jsDir : projectBuildCjs,
compressDir : projectBuildZip,
}

}
17 changes: 8 additions & 9 deletions src/error.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import type {
BuilderErrors,
BuilderParams,
} from './types'
import type { BuilderErrors } from './types'

export class BuildError extends Error {

constructor(
message: BuilderErrors,
data: {
platform: string
arch: string
opts: BuilderParams
} & Record<string, unknown>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data: any,
// data: {
// platform: string
// arch: string
// opts: any
// } & Record<string, unknown>,
) {

super( message )
37 changes: 37 additions & 0 deletions src/help.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { exit } from './_shared/process'

export const printHelp = ( name: string ): void => {

name = name.toLowerCase()
console.log( `Usage: ${name} [options]
Options:
--input Input file path.
Accepted files: .ts, .js, .mjs, .mts, .cjs, .cts
The input can be provided without an extension.
--outDir Output directory path.
--name Binary name.
--type Binary type build
Supported values: all, cjs, bin
--onlyOs Build only binary for your current OS.
If is not set ${name} will build a binary for every OS.
--config Config file path.
Files supported: .mjs, .js, .json, .yml, .yaml, .toml, .tml
Global options:
--help Show help message
--version Show version
--debug Debug mode
Examples:
${name} --input src/main
${name} --input src/main.js --outDir out
${name} --input src/main.ts --name my-app
${name} --input src/main.ts --config my-config.js
` )
exit( 0 )

}
33 changes: 33 additions & 0 deletions src/log.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { logger } from './_shared/logger'
import {
existsFlag,
exit,
} from './_shared/process'
import {
version,
name,
BINARIUM_CONSTS,
} from './const'
import { printHelp } from './help'

export const getLog = () => {

const projectName = BINARIUM_CONSTS.name || name
const isDebug = BINARIUM_CONSTS.debug || existsFlag( 'debug' ) || false
return {
...logger( {
icon : BINARIUM_CONSTS.icon || '📦',
name : projectName,
isDebug,
} ),
isDebug,
printHelp : BINARIUM_CONSTS.onHelp ? () => BINARIUM_CONSTS.onHelp : () => printHelp( projectName ),
printVersion : BINARIUM_CONSTS.onVersion ? () => BINARIUM_CONSTS.onVersion : () => {

console.log( version )
exit( 0 )

},
}

}
474 changes: 187 additions & 287 deletions src/main.ts

Large diffs are not rendered by default.

43 changes: 43 additions & 0 deletions src/steps/bin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* PKG BUILD.
*
* @see https://www.npmjs.com/package/@yao-pkg/pkg
*/
import { exec as execPkg } from '@yao-pkg/pkg'

import { catchError } from '../_shared/error'
import { joinPath } from '../_shared/sys'

import type { Config } from './types'

type Opts = {
input: string,
output: string,
name: string,
targets: string[],
config?: Config['pkg']
debug: ( data: object | string ) => void
isDebug?: boolean
}

export default async ( {
input, output, name, targets, debug, config, isDebug,
}: Opts ) => {

const defConfig = [
input,
'--targets',
targets.join( ',' ),
'--output',
joinPath( output, name ),
'--compress',
'GZip',
...( isDebug ? [ '--debug' ] : [] ),
]

const buildConfig = config ? [ ...defConfig, ...config ] : defConfig

debug( { pkgOpts: buildConfig } )
return await catchError( execPkg( buildConfig ) )

}
60 changes: 60 additions & 0 deletions src/steps/cjs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* ESBUILD BUILD.
*
* @see https://esbuild.github.io/api/#build
*/
import { build } from 'esbuild'

import { catchError } from '../_shared/error'
import {
existsPath,
joinPath,
packageDir,
resolvePath,
} from '../_shared/sys'

import type { Config } from './types'

type Opts = {
input: string,
output: string,
target: string,
config?: Config['esbuild']
isDebug?: boolean
debug: ( data: object | string ) => void
}

export default async ( data: Opts ) => {

if( data.config === false ) return

const getTsConfig = async () =>{

const userTs = resolvePath( 'tsconfig.json' )
const existUserTs = await existsPath( userTs )
if( existUserTs ) return userTs
return joinPath( packageDir, 'tsconfig.builder.json' )

}

const defConfig: NonNullable<Config['esbuild']> = {
entryPoints : [ data.input ],
minify : true,
bundle : true,
format : 'cjs',
platform : 'node',
target : data.target,
outfile : data.output,
tsconfig : await getTsConfig(),
}

const buildConfig = data.config ? {
...defConfig,
...data.config,
} : defConfig

data.debug( { esbuildConfig: buildConfig } )

return await catchError( build( buildConfig ) )

}
56 changes: 56 additions & 0 deletions src/steps/compile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* NCC BUILD.
*
* @see https://github.com/vercel/ncc?tab=readme-ov-file#programmatically-from-nodejs
*/
// @ts-ignore
import ncc from '@vercel/ncc'

import { catchError } from '../_shared/error'
import {
deleteFile,
writeFile,
} from '../_shared/sys'

import type { Config } from './types'

type Opts = {
input: string,
output: string,
config?: Config['ncc']
isDebug?: boolean
debug: ( data: object | string ) => void
}

export default async ( {
input, output, debug, config, isDebug,
}: Opts ) => {

if( config === false ) return

const defConfig = {
minify : true,
cache : false,
debugLog : isDebug || false,
// target,
}

const buildConfig = config ? {
...defConfig,
...config,
} : defConfig

const build = async () => {

const { code } = await ncc( input, buildConfig )

await writeFile( output, code )
await deleteFile( input )

}

debug( { nccOpts: buildConfig } )

return await catchError( build() )

}
25 changes: 25 additions & 0 deletions src/steps/compress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

import { zipFilesInDirectory } from '../_shared/compress'
import { catchError } from '../_shared/error'

type Opts = {
input: string,
output: string,
isDebug?: boolean
debug: ( data: object | string ) => void
}

export default async ( {
input, output, debug,
}: Opts ) => {

debug( {
compressOpts : {
input,
output,
},
} )

return await catchError( zipFilesInDirectory( input, output ) )

}
46 changes: 46 additions & 0 deletions src/steps/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { BuildOptions } from 'esbuild'

export type Config = {
/**
* PKG configuration.
*
* @see https://www.npmjs.com/package/@yao-pkg/pkg
*/
pkg?: string[]
/**
* ESBUILD configuration.
*
* @see https://esbuild.github.io/api/#build
*/
esbuild?: BuildOptions | false
/**
* NCC configuration.
*
* @see https://github.com/vercel/ncc?tab=readme-ov-file#programmatically-from-nodejs
*/
ncc?: {
/**
* Provide a custom cache path or disable caching .
*/
cache?: string | false;
/**
* Externals to leave as requires of the build.
*/
externals?: string[];
/**
* Directory outside of which never to emit assets.
*/
filterAssetBase?: string;
minify?: boolean;
sourceMap?: boolean;
assetBuilds?: boolean;
sourceMapBasePrefix?: string;
// when outputting a sourcemap, automatically include
// source-map-support in the output file (increases output by 32kB).
sourceMapRegister?: boolean;
watch?: boolean;
license?: string;
target?: string;
v8cache?: boolean;
} | false
}
25 changes: 24 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
@@ -2,10 +2,15 @@ import type {
BUILDER_TYPE,
ERROR_ID,
} from './const'
import type { Config } from './steps/types'

export type Prettify<T> = {
[K in keyof T]: Prettify<T[K]>;
} & {}

export type BuilderParams = {
/**
* The app server input file.
* The app input file.
*
* The input can be provided without an extension.
* If the extension is omitted, the system will automatically look for the following extensions: `.ts`, `.js`, `.mjs`, `.mts`.
@@ -33,6 +38,24 @@ export type BuilderParams = {
* @default 'all'
*/
type?: typeof BUILDER_TYPE[keyof typeof BUILDER_TYPE]
/**
* Config file path.
*
* @default undefined
*/
config?: string
}

export type BuilderErrors = typeof ERROR_ID[keyof typeof ERROR_ID]

export type ConfigParams = Prettify<Partial<BuilderParams> & {
/**
* Custom build configuration.
*
* Override build options for different build steps.
* Use this for advanced use cases.
*
* @experimental
*/
options?: Config
}>
32 changes: 0 additions & 32 deletions vite.config.ts

This file was deleted.

0 comments on commit 1cb8dc6

Please sign in to comment.