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

build: generate js and dts bundles #722

Merged
merged 11 commits into from
Feb 24, 2024
1,440 changes: 1,277 additions & 163 deletions package-lock.json

Large diffs are not rendered by default.

49 changes: 29 additions & 20 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,39 +41,48 @@
"scripts": {
"fmt": "prettier --write .",
"fmt:check": "prettier --check .",
"build": "tsc --project tsconfig.prod.json",
"build": "npm run build:js && npm run build:dts",
"build:check": "tsc",
"build:js": "node scripts/build-js.mjs --format=esm --entry=src/*.ts && npm run build:vendor",
"build:vendor": "node scripts/build-js.mjs --format=esm --entry=src/vendor.ts --bundle=all --banner",
"build:dts": "tsc --project tsconfig.prod.json && node scripts/build-dts.mjs",
"test": "npm run build && node ./test/all.test.js",
"test:types": "tsd",
"coverage": "c8 --check-coverage npm test",
"coverage": "c8 -x build/vendor.js -x 'test/**' -x scripts --check-coverage npm test",
"mutation": "stryker run",
"circular": "madge --circular src/*",
"version": "cat package.json | fx .version"
},
"dependencies": {
"@types/fs-extra": "^11.0.1",
"@types/minimist": "^1.2.2",
"@types/node": "^18.16.3",
"@types/ps-tree": "^1.1.2",
"@types/which": "^3.0.0",
"chalk": "^5.2.0",
"fs-extra": "^11.1.1",
"fx": "*",
"globby": "^13.1.4",
"minimist": "^1.2.8",
"node-fetch": "3.3.1",
"ps-tree": "^1.2.0",
"webpod": "^0",
"which": "^3.0.0",
"yaml": "^2.2.2"
"optionalDependencies": {
"@types/fs-extra": "^11.0.4",
"@types/node": ">=20.11.19"
},
"devDependencies": {
"@stryker-mutator/core": "^6.4.2",
"@types/fs-extra": "^11.0.4",
"@types/minimist": "^1.2.5",
"@types/node": ">=20.11.19",
"@types/ps-tree": "^1.1.6",
"@types/which": "^3.0.3",
"c8": "^7.13.0",
"madge": "^6.0.0",
"chalk": "^5.3.0",
"dts-bundle-generator": "^9.3.1",
"esbuild": "^0.20.1",
"esbuild-node-externals": "^1.13.0",
"esbuild-plugin-entry-chunks": "^0.1.8",
"fs-extra": "^11.2.0",
"fx": "*",
"globby": "^14.0.1",
"madge": "^6.1.0",
"minimist": "^1.2.8",
"node-fetch-native": "^1.6.2",
"prettier": "^2.8.8",
"ps-tree": "^1.2.0",
"tsd": "^0.28.1",
"typescript": "^5.0.4"
"typescript": "^5.0.4",
"webpod": "^0",
"which": "^3.0.0",
"yaml": "^2.3.4"
},
"publishConfig": {
"registry": "https://wombat-dressing-room.appspot.com"
Expand Down
86 changes: 86 additions & 0 deletions scripts/build-dts.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/usr/bin/env node
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#!/usr/bin/env zx

😁


// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import fs from 'fs/promises'
import { generateDtsBundle } from 'dts-bundle-generator'
import glob from 'fast-glob'

const entry = {
filePath: './src/vendor.ts',
outFile: './build/vendor.d.ts',
libraries: {
allowedTypesLibraries: ['node'], // args['external-types'],
inlinedLibraries: [
'@nodelib/fs.stat',
'@nodelib/fs.scandir',
'@nodelib/fs.walk',
'fast-glob',
'@types/jsonfile',
'node-fetch-native',
'chalk',
'globby',
'webpod',
'@types/fs-extra',
'@types/minimist',
'@types/ps-tree',
'@types/which',
], // args['external-inlines'],
},
output: {
inlineDeclareExternals: true,
inlineDeclareGlobals: true,
sortNodes: false,
exportReferencedTypes: false, //args['export-referenced-types'],
},
}

const compilationOptions = {
preferredConfigPath: './tsconfig.prod.json', // args.project,
followSymlinks: true,
}

let [result] = generateDtsBundle([entry], compilationOptions)

// generateDtsBundle cannot handle the circular refs on types inlining, so we need to help it manually:
/*
build/vendor.d.ts(163,7): error TS2456: Type alias 'Options' circularly references itself.
build/vendor.d.ts(164,7): error TS2456: Type alias 'Entry' circularly references itself.
build/vendor.d.ts(165,7): error TS2456: Type alias 'Task' circularly references itself.
build/vendor.d.ts(166,7): error TS2456: Type alias 'Pattern' circularly references itself.
build/vendor.d.ts(167,7): error TS2456: Type alias 'FileSystemAdapter' circularly references itself.
build/vendor.d.ts(197,48): error TS2694: Namespace 'FastGlob' has no exported member 'FastGlobOptions
*/

result = result
.replace('type Options = Options;', 'export {Options};')
.replace('type Task = Task;', 'export {Task};')
.replace('type Pattern = Pattern;', 'export {Pattern};')
.replace('FastGlob.FastGlobOptions', 'FastGlob.Options')
.replace('type Entry =', 'export type Entry =')

await fs.writeFile(entry.outFile, result, 'utf8')

// Replaces redundant triple-slash directives
for (const dts of await glob(['build/**/*.d.ts', '!build/vendor.d.ts'])) {
const contents = (await fs.readFile(dts, 'utf8'))
.split('\n')
.filter((line) => !line.startsWith('/// <reference types'))
.join('\n')

await fs.writeFile(dts, contents, 'utf8')
}

process.exit(0)
123 changes: 123 additions & 0 deletions scripts/build-js.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#!/usr/bin/env node

// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import path from 'node:path'
import esbuild from 'esbuild'
import { nodeExternalsPlugin } from 'esbuild-node-externals'
import { entryChunksPlugin } from 'esbuild-plugin-entry-chunks'
import minimist from 'minimist'
import glob from 'fast-glob'

const argv = minimist(process.argv.slice(2), {
default: {
entry: './src/index.ts',
external: 'node:*',
bundle: 'src', // 'all' | 'none'
license: 'eof',
minify: false,
sourcemap: false,
format: 'cjs,esm',
cwd: process.cwd(),
},
boolean: ['minify', 'sourcemap', 'banner'],
string: ['entry', 'external', 'bundle', 'license', 'format', 'map', 'cwd'],
})
const {
entry,
external,
bundle,
minify,
sourcemap,
license,
format,
cwd: _cwd,
} = argv

const plugins = []
const cwd = Array.isArray(_cwd) ? _cwd[_cwd.length - 1] : _cwd
const entries = entry.split(/,\s?/)
const entryPoints = entry.includes('*')
? await glob(entries, { absolute: false, onlyFiles: true, cwd, root: cwd })
: entries.map((p) => path.relative(cwd, path.resolve(cwd, p)))

console.log('cwd=', cwd)
console.log('entryPoints=', entryPoints)

const _bundle = bundle !== 'none' && !process.argv.includes('--no-bundle')
const _external = _bundle ? external.split(',') : undefined // https://github.com/evanw/esbuild/issues/1466

if (_bundle && entryPoints.length > 1) {
plugins.push(entryChunksPlugin())
}

if (bundle === 'src') {
// https://github.com/evanw/esbuild/issues/619
// https://github.com/pradel/esbuild-node-externals/pull/52
plugins.push(nodeExternalsPlugin())
}

const formats = format.split(',')
const banner =
argv.banner && bundle === 'all'
? {
js: `
const require = (await import("node:module")).createRequire(import.meta.url);
const __filename = (await import("node:url")).fileURLToPath(import.meta.url);
const __dirname = (await import("node:path")).dirname(__filename);
`,
}
: {}

const esmConfig = {
absWorkingDir: cwd,
entryPoints,
outdir: './build',
bundle: _bundle,
external: _external,
minify,
sourcemap,
sourcesContent: false,
platform: 'node',
target: 'esnext',
format: 'esm',
outExtension: {
// '.js': '.mjs'
},
plugins,
legalComments: license,
tsconfig: './tsconfig.json',
//https://github.com/evanw/esbuild/issues/1921
banner,
}

const cjsConfig = {
...esmConfig,
outdir: './build',
target: 'es6',
format: 'cjs',
banner: {},
outExtension: {
// '.js': '.cjs'
},
}

for (const format of formats) {
const config = format === 'cjs' ? cjsConfig : esmConfig

await esbuild.build(config).catch(() => process.exit(1))
}

process.exit(0)
13 changes: 9 additions & 4 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import fs from 'fs-extra'
import minimist from 'minimist'
import { createRequire } from 'node:module'
import { basename, dirname, extname, join, resolve } from 'node:path'
import url from 'node:url'
import { updateArgv } from './goods.js'
import { $, chalk, fetch, ProcessOutput } from './index.js'
import {
$,
ProcessOutput,
updateArgv,
fetch,
chalk,
minimist,
fs,
} from './index.js'
import { startRepl } from './repl.js'
import { randomId } from './util.js'
import { installDeps, parseDeps } from './deps.js'
Expand Down
12 changes: 8 additions & 4 deletions src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ import { ChildProcess, spawn, StdioNull, StdioPipe } from 'node:child_process'
import { AsyncLocalStorage, createHook } from 'node:async_hooks'
import { Readable, Writable } from 'node:stream'
import { inspect } from 'node:util'
import { RequestInfo, RequestInit } from 'node-fetch'
import chalk, { ChalkInstance } from 'chalk'
import which from 'which'
import {
chalk,
which,
type ChalkInstance,
RequestInfo,
RequestInit,
} from './vendor.js'
import {
Duration,
errnoMessage,
Expand All @@ -39,7 +43,7 @@ export type Shell = (

const processCwd = Symbol('processCwd')

export type Options = {
export interface Options {
[processCwd]: string
cwd?: string
verbose: boolean
Expand Down
29 changes: 14 additions & 15 deletions src/goods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,21 @@
// limitations under the License.

import assert from 'node:assert'
import * as globbyModule from 'globby'
import minimist from 'minimist'
import nodeFetch, { RequestInfo, RequestInit } from 'node-fetch'
import { createInterface } from 'node:readline'
import { $, within, ProcessOutput } from './core.js'
import { Duration, isString, parseDuration } from './util.js'
import chalk from 'chalk'
import { type Duration, isString, parseDuration } from './util.js'
import {
chalk,
minimist,
globbyModule,
GlobbyOptions,
nodeFetch,
RequestInfo,
RequestInit,
} from './vendor.js'

export { default as chalk } from 'chalk'
export { default as fs } from 'fs-extra'
export { default as which } from 'which'
export { default as minimist } from 'minimist'
export { default as YAML } from 'yaml'
export { default as path } from 'node:path'
export { default as os } from 'node:os'
export { ssh } from 'webpod'
export * as os from 'node:os'

export let argv = minimist(process.argv.slice(2))
export function updateArgv(args: string[]) {
Expand All @@ -38,11 +37,11 @@ export function updateArgv(args: string[]) {

export const globby = Object.assign(function globby(
patterns: string | readonly string[],
options?: globbyModule.Options
options?: GlobbyOptions
) {
return globbyModule.globby(patterns, options)
},
globbyModule)
globbyModule) as (typeof globbyModule)['globby'] & typeof globbyModule
export const glob = globby

export function sleep(duration: Duration) {
Expand Down Expand Up @@ -198,7 +197,7 @@ export async function spinner<T>(
try {
result = await callback!()
} finally {
clearInterval(id)
clearInterval(id as NodeJS.Timeout)
process.stderr.write(' '.repeat(process.stdout.columns - 1) + '\r')
}
return result
Expand Down
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import { ProcessPromise } from './core.js'

export * from './core.js'
export * from './goods.js'
export { minimist, chalk, fs, which, YAML, ssh } from './vendor.js'

export { Duration, quote, quotePowerShell } from './util.js'
export { type Duration, quote, quotePowerShell } from './util.js'

/**
* @deprecated Use $.nothrow() instead.
Expand Down
Loading
Loading