Skip to content

Commit

Permalink
chore: release v2.0.0
Browse files Browse the repository at this point in the history
zhengyuzi committed Dec 5, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 65523eb commit 2e5d44a
Showing 22 changed files with 3,407 additions and 2,654 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright © 2023 Yu <https://github.com/zhengyuzi>
Copyright © 2024 Yu <https://github.com/zhengyuzi>

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

76 changes: 39 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
![npm](https://img.shields.io/npm/v/stubb)
![NPM](https://img.shields.io/npm/l/stubb)

Stub ```dist``` link your project based on ```package.json``` during the development.
Stub ```dist``` link your project during the development.

## Install

@@ -13,76 +13,78 @@ npm install --save-dev stubb

## Usage

In the 'package.json' of the package in need:
In the ```package.json``` of the package in need:

```json
{
"main": "dist/index.cjs",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"scripts": {
"stub": "stubb"
}
}
```

Generate folders and files based on the ```main```, ```module```, ```types``` and ```exports``` in ```package.json```.

Default project structure:

```
|-- package
|-- dist/
|-- index.js
|-- index.cjs
|-- index.mjs
|-- index.d.ts
|-- index.d.mts
|-- index.d.cts
|-- src/
|-- index.ts
|-- package.json
```

Add ```entry``` in the ```exports``` field to modify the entry file:
## Options

### entries

Set one or more entry paths. Default: ```src/index```

```json
{
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs",
"entry": "./test/index.ts"
}
},
"main": "dist/index.cjs",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"scripts": {
"stub": "stubb"
"stub": "stubb test/index,test/plugins"
}
}
```

if exposing multiple entry points:
### outputDir

The folder name/path of the output file. Default: ```dist```

```json
{
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
},
"./plugins": {
"types": "./dist/plugins.d.ts",
"import": "./dist/plugins.mjs",
"require": "./dist/plugins.cjs"
}
},
"main": "dist/index.cjs",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"scripts": {
"stub": "stubb"
"stub": "stubb --outputDir=ouput"
}
}
```

### fill

Auto fill in exports/main/module/types in package.json. Default: ```false```

```json
{
"scripts": {
"stub": "stubb --fill"
}
}
```

### esm

Open esm. Default: ```true```

### cjs

Open cjs. Default: ```true```

### ts

Open types. Default: ```true```
4 changes: 1 addition & 3 deletions bin/stubb.js
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
import stubb from '../dist/index.mjs'

stubb()
import '../dist/cli.mjs'
1 change: 1 addition & 0 deletions build.config.ts
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ import { defineBuildConfig } from 'unbuild'
export default defineBuildConfig({
entries: [
'src/index',
'src/cli',
],
clean: true,
declaration: true,
8 changes: 3 additions & 5 deletions examples/package-a/package.json
Original file line number Diff line number Diff line change
@@ -4,20 +4,18 @@
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs",
"entry": "./test/index.ts"
"require": "./dist/index.cjs"
},
"./plugins": {
"types": "./dist/plugins.d.ts",
"import": "./dist/plugins.mjs",
"require": "./dist/plugins.cjs",
"entry": "./test/plugins.ts"
"require": "./dist/plugins.cjs"
}
},
"main": "dist/index.cjs",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"scripts": {
"stubb": "esno ../../bin/stubb.js"
"stubb": "esno ../../bin/stubb.js test/index,test/plugins --fill"
}
}
9 changes: 6 additions & 3 deletions examples/package-b/package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
{
"name": "package-b",
"exports": {
".": {
"default": "./dist/index.cjs"
}
},
"main": "dist/index.cjs",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"scripts": {
"stubb": "esno ../../bin/stubb.js"
"stubb": "esno ../../bin/stubb.js --esm=false --ts=false --fill"
}
}
1 change: 1 addition & 0 deletions examples/package-c/ouput/index.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('F:/stubb/examples/package-c/src/index.ts')
2 changes: 2 additions & 0 deletions examples/package-c/ouput/index.d.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from 'F:/stubb/examples/package-c/src/index.ts'
export { default } from 'F:/stubb/examples/package-c/src/index.ts'
2 changes: 2 additions & 0 deletions examples/package-c/ouput/index.d.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from 'F:/stubb/examples/package-c/src/index.ts'
export { default } from 'F:/stubb/examples/package-c/src/index.ts'
2 changes: 2 additions & 0 deletions examples/package-c/ouput/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from 'F:/stubb/examples/package-c/src/index.ts'
export { default } from 'F:/stubb/examples/package-c/src/index.ts'
2 changes: 2 additions & 0 deletions examples/package-c/ouput/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from 'F:/stubb/examples/package-c/src/index.ts'
export { default } from 'F:/stubb/examples/package-c/src/index.ts'
2 changes: 2 additions & 0 deletions examples/package-c/ouput/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from 'F:/stubb/examples/package-c/src/index.ts'
export { default } from 'F:/stubb/examples/package-c/src/index.ts'
12 changes: 11 additions & 1 deletion examples/package-c/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
{
"name": "package-c",
"exports": {
".": {
"types": "./ouput/index.d.ts",
"import": "./ouput/index.mjs",
"require": "./ouput/index.cjs"
}
},
"main": "ouput/index.cjs",
"module": "ouput/index.mjs",
"types": "ouput/index.d.ts",
"scripts": {
"stubb": "esno ../../bin/stubb.js"
"stubb": "esno ../../bin/stubb.js --outputDir=ouput --fill"
}
}
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "stubb",
"type": "module",
"version": "1.3.0",
"version": "2.0.0",
"packageManager": "[email protected]",
"description": "Stub 'dist' link your project based on package.json during the development",
"description": "Stub dist link your project during the development",
"author": "yu <[email protected]> (https://github.com/zhengyuzi)",
"license": "MIT",
"homepage": "https://github.com/zhengyuzi/stubb",
@@ -48,14 +48,18 @@
"prepare": "simple-git-hooks"
},
"dependencies": {
"citty": "^0.1.6",
"consola": "^3.2.3",
"fast-glob": "^3.3.2",
"fs-extra": "^11.2.0",
"mlly": "^1.7.1"
"mlly": "^1.7.1",
"path-parse": "^1.0.7"
},
"devDependencies": {
"@antfu/eslint-config": "^2.21.3",
"@types/fs-extra": "^11.0.4",
"@types/node": "^20.10.3",
"@types/path-parse": "^1.0.22",
"eslint": "^9.6.0",
"esno": "^4.0.0",
"lint-staged": "^15.2.0",
5,578 changes: 3,071 additions & 2,507 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

64 changes: 64 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { defineCommand, runMain } from 'citty'
import { description, name, version } from '../package.json'
import {
DEFAULT_ENTRY,
DEFAULT_OUTPUT_DIR,
OUTPUT_SUFFIXES,
} from './constants'
import { stub } from './stub'
import { fill } from './fill'

const main = defineCommand({
meta: {
name,
version,
description,
},
args: {
entries: {
type: 'positional',
description: `Entry file paths. Default: ${DEFAULT_ENTRY}`,
required: false,
},
outputDir: {
type: 'string',
description: `The folder name/path of the output file. Default: ${DEFAULT_OUTPUT_DIR}`,
},
fill: {
type: 'boolean',
description: 'Auto fill in exports/main/module/types in package.json. Default: false',
},
esm: {
type: 'boolean',
description: 'Open esm. Default: true',
},
cjs: {
type: 'boolean',
description: 'Open cjs. Default: true',
},
ts: {
type: 'boolean',
description: 'Open Types. Default: true',
},
},
async run({ args }) {
const { esm = true, cjs = true, ts = true, fill: isFill = false } = args

const entries = (args.entries || DEFAULT_ENTRY).split(',')

const outputDir = args.outputDir || DEFAULT_OUTPUT_DIR

const outputSuffixes = {
...(cjs && { cjs: OUTPUT_SUFFIXES.cjs }),
...(esm && { esm: OUTPUT_SUFFIXES.esm }),
...(ts && { ts: OUTPUT_SUFFIXES.ts }),
}

await stub(entries, outputDir, outputSuffixes)

if (isFill)
await fill(entries, outputDir, outputSuffixes)
},
})

runMain(main)
29 changes: 23 additions & 6 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
export const StubbDefaultConfig = {
entry: 'src/index',
export const DEFAULT_ENTRY = 'src/index'

export const DEFAULT_OUTPUT_DIR = 'dist'

export const SUFFIX_JS = '.js'
export const SUFFIX_CJS = '.cjs'
export const SUFFIX_MJS = '.mjs'
export const SUFFIX_TS = '.d.ts'

export const OUTPUT_SUFFIXES = {
cjs: [SUFFIX_JS, SUFFIX_CJS],
esm: [SUFFIX_JS, SUFFIX_MJS],
ts: [SUFFIX_TS],
}

export const EXPORT_CONTENT = {
defaultExport: `export { default } from 'path'`,
namedExport: `export * from 'path'`,
moduleExport: `module.exports = require('path')`,
}

export const ExitCode = {
Success: 0,
InvalidArgument: 9,
FatalException: 1,
export const EXIT_CODE = {
success: 0,
invalidArgument: 9,
fatalException: 1,
}
100 changes: 100 additions & 0 deletions src/fill.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { resolve } from 'node:path'
import pathParse from 'path-parse'
import fs from 'fs-extra'
import consola from 'consola'
import { OUTPUT_SUFFIXES, SUFFIX_CJS, SUFFIX_MJS, SUFFIX_TS } from './constants'

export interface PackageExportFields {
[key: string]: {
types?: string
default?: string
import?: string
require?: string
}
}

export interface Fields {
exports: PackageExportFields
main?: string
module?: string
types?: string
}

/**
* Auto fill in exports/main/module/types in package.json.
*/
export async function fill(
entries: string[],
outputDir: string,
outputSuffixes: Partial<typeof OUTPUT_SUFFIXES> = OUTPUT_SUFFIXES,
) {
const jsonPath = './package.json'

const fields: Fields = {
exports: {},
}

const dir = outputDir.replace(/\/$/, '')

const dirPath = !dir.startsWith('.') && !dir.startsWith('/') ? `./${dir}` : dir

for (const entry of entries) {
const { name } = pathParse(entry)

const exportName = name === 'index' ? '.' : `./${name}`

if (outputSuffixes.cjs && outputSuffixes.esm) {
fields.exports[exportName] = {
...fields.exports[exportName],
import: `${dirPath}/${name}${SUFFIX_MJS}`,
require: `${dirPath}/${name}${SUFFIX_CJS}`,
}

if (!fields.main)
fields.main = `${dir}/${name}${SUFFIX_CJS}`

if (!fields.module)
fields.module = `${dir}/${name}${SUFFIX_MJS}`
}
else if (outputSuffixes.cjs) {
fields.exports[exportName] = {
...fields.exports[exportName],
default: `${dirPath}/${name}${SUFFIX_CJS}`,
}

if (!fields.main)
fields.main = `${dir}/${name}${SUFFIX_CJS}`
}
else if (outputSuffixes.esm) {
fields.exports[exportName] = {
...fields.exports[exportName],
default: `${dirPath}/${name}${SUFFIX_MJS}`,
}

if (!fields.main)
fields.main = `${dir}/${name}${SUFFIX_MJS}`

if (!fields.module)
fields.module = `${dir}/${name}${SUFFIX_MJS}`
}

if (outputSuffixes.ts) {
fields.exports[exportName] = {
...fields.exports[exportName],
types: `${dirPath}/${name}${SUFFIX_TS}`,
}

if (!fields.types)
fields.types = `${dir}/${name}${SUFFIX_TS}`
}
}

try {
const json = await fs.readJson(jsonPath)
await fs.outputJson(jsonPath, { ...json, ...fields }, { spaces: 2 })
consola.success(`Autofill success! ${resolve(jsonPath)}`)
}
catch (err) {
consola.error(`Autofill Fail! ${resolve(jsonPath)} ${err}`)
}
}
4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export { stub as default } from './stub'
export * from './constants'
export * from './stub'
export * from './fill'
145 changes: 65 additions & 80 deletions src/stub.ts
Original file line number Diff line number Diff line change
@@ -1,117 +1,107 @@
import process from 'node:process'
import { resolve } from 'node:path'
import fs from 'fs-extra'
import process from 'node:process'
import fg from 'fast-glob'
import fs from 'fs-extra'
import pathParse from 'path-parse'
import { resolveModuleExportNames } from 'mlly'
import { ExitCode, StubbDefaultConfig } from './constants'
import type { PackageJsonExport } from './types'
import consola from 'consola'
import { EXIT_CODE, EXPORT_CONTENT, OUTPUT_SUFFIXES } from './constants'

export async function stub() {
process.on('uncaughtException', error => errorHandler(`${error}`))
process.on('unhandledRejection', reason => errorHandler(`${reason}`))
/**
* Stub
* @param entries Entry file paths.
* @param outputDir The folder name/path of the output file.
* @param outputSuffixes ESM/CJS/TS file suffixes.
*/
export async function stub(
entries: string[],
outputDir: string,
outputSuffixes: Partial<typeof OUTPUT_SUFFIXES> = OUTPUT_SUFFIXES,
) {
// Path to the output file folder
const outputDirPath = resolve(outputDir)

try {
const exports = await readPackageJson()

for (const _export of exports) {
const path = await getEntryPath(_export.entry || StubbDefaultConfig.entry)
for (const entry of entries) {
// Path to the entry file
const entryPath = await findEntryPath(entry)
// Does the path exist
const psthExists = await fs.pathExists(entryPath)

if (!psthExists) {
throw new Error(`The entry file ${entryPath} does not exist.`)
}

const hasDefaultExport = await resolveDefaultExport(path)
const { name } = pathParse(entryPath)

const _path = path.replace(/\\/g, '/')
const data = await getStubData(entryPath)

const data = {
import: [`export * from '${_path}'`, hasDefaultExport ? `export { default } from '${_path}'` : ''].join('\n'),
require: `module.exports = require('${_path}')`,
types: [`export * from '${_path}'`, hasDefaultExport ? `export { default } from '${_path}'` : ''].join('\n'),
}
for (const [key, suffixes] of Object.entries(outputSuffixes)) {
const fileData = data[key as keyof typeof data]

for (const [key, value] of Object.entries(_export)) {
if (key === 'entry' || !value)
continue
for (const suffix of suffixes) {
// Output file path
const ouputPath = resolve(`${outputDirPath}/${name}${suffix}`)

fs.outputFile(resolve(value), String(data[key as keyof typeof data]))
// Write and output file
await fs.outputFile(ouputPath, fileData).catch((error) => {
throw new Error(`Failed to output file to ${ouputPath}: ${error}`)
})
}
}
}
}
catch (error) {
errorHandler((error as Error).message)
}
}

/**
* Read package.json
*/
async function readPackageJson() {
const { exports, main, module, types } = await fs.readJson('./package.json')

/**
* 'exports' have higher priority than 'main', 'module', and 'types'
*/
if (exports) {
return Object.values(exports) as PackageJsonExport[]
consola.success(`Stub success! ${outputDirPath}`)
}
else if (main || module || types) {
return [
{
import: module,
require: main,
types,
},
] as PackageJsonExport[]
}
else {
// eslint-disable-next-line no-console
console.log(`WARNING: Not set exports/main/module/types\n at ${resolve('./package.json')}`)
return []
catch (error: any) {
consola.error(`Stub Fail! Error: ${error.message}\nat ${outputDirPath}`)
process.exit(EXIT_CODE.fatalException)
}
}

/**
* Retrieve the export name of the entry file
* @param entry
* Get stub export content
*/
async function getEntryPath(entry: string) {
const entryPath = await findEntryFilePath(entry)
async function getStubData(entryPath: string): Promise<{ [key in keyof typeof OUTPUT_SUFFIXES]: string }> {
const isDefaultExport = await resolveDefaultExport(entryPath)

const path = resolve(entryPath)
const path = entryPath.replace(/\\/g, '/')

const exists = await fs.pathExists(entryPath)
const esm = `${EXPORT_CONTENT.namedExport}\n${isDefaultExport ? EXPORT_CONTENT.defaultExport : ''}`.replaceAll('path', path)

if (!exists) {
throw new Error(`ERROR: The entry file 【${entryPath}】 does not exist. To set the correct entry path!`)
}
const cjs = EXPORT_CONTENT.moduleExport.replaceAll('path', path)

return path
return {
cjs,
esm,
ts: esm,
}
}

/**
* Determine whether it is a default export, otherwise it is a named export
* @param filePath
* @returns boolean
* Is the default export or named export
* @param path
* @returns Promise<boolean>
*/
async function resolveDefaultExport(filePath: string) {
async function resolveDefaultExport(path: string) {
const namedExports: string[] = await resolveModuleExportNames(
filePath,
path,
{
extensions: ['.ts', '.js'],
extensions: ['.ts', '.mts', '.cts', '.js', '.mjs', '.cjs'],
},
).catch((error) => {
errorHandler(`Cannot analyze ${filePath} for exports:${error}`)
return []
})
)

return namedExports.includes('default') || namedExports.length === 0
}

/**
* Handling paths without ext
* Find the entry file path
* @param entry
* @returns Promise<string>
*/
async function findEntryFilePath(entry: string) {
async function findEntryPath(entry: string) {
const files = await fg.glob(
`${entry}.+(ts|js)`,
`${entry}.+(ts|mts|cts|js|mjs|cjs)`,
{
onlyFiles: true,
deep: 1,
@@ -120,10 +110,5 @@ async function findEntryFilePath(entry: string) {
},
)

return files[0] || entry
}

function errorHandler(message: string) {
console.error(message)
process.exit(ExitCode.FatalException)
return resolve(files[0] || entry)
}
6 changes: 0 additions & 6 deletions src/types.ts

This file was deleted.

2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "ES6",
"target": "ESNext",
"baseUrl": ".",
"module": "esnext",
"moduleResolution": "Bundler",

0 comments on commit 2e5d44a

Please sign in to comment.