Skip to content

Commit

Permalink
feat: add support for package bin field
Browse files Browse the repository at this point in the history
  • Loading branch information
jacob-alford committed Sep 9, 2024
1 parent 1d24b18 commit 396e7c9
Show file tree
Hide file tree
Showing 12 changed files with 215 additions and 63 deletions.
37 changes: 36 additions & 1 deletion src/BuildService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,35 @@ export const BuildServiceLive: RTE.ReaderTaskEither<
),
),
),
// Validate Bin Field
RTE.tapEither(({ config, entrypoints }) =>
pipe(
config.bin,
E.fromPredicate(
binField => {
if (binField === null) {
return true
} else if (typeof binField === 'string') {
return entrypoints.includes(normalizePath(binField))
} else {
return pipe(
binField,
RR.every(file => entrypoints.includes(normalizePath(file))),
)
}
},
binField =>
new BuildServiceError(
`The \`bin\` field (${JSON.stringify(
binField,
)}) files must be listed as entrypoints in \`buildMode\`. Received entrypoints: [${entrypoints.join(
',',
)}]`,
null,
),
),
),
),
RTE.let('resolvedIndex', ({ config, entrypoints }) =>
config.buildMode.type === 'Single'
? config.buildMode.entrypoint
Expand Down Expand Up @@ -160,6 +189,7 @@ export const BuildServiceLive: RTE.ReaderTaskEither<
module: __,
exports: ___,
types: ____,
bin: preBin,
...rest
},
}) =>
Expand All @@ -170,7 +200,7 @@ export const BuildServiceLive: RTE.ReaderTaskEither<
),
RTE.flatMapTaskEither(Exports.pkgExports),
RTE.map(
([exports, main, module, types]): RR.ReadonlyRecord<string, unknown> => ({
([exports, main, module, types, bin]): RR.ReadonlyRecord<string, unknown> => ({
name,
version,
description,
Expand All @@ -181,6 +211,7 @@ export const BuildServiceLive: RTE.ReaderTaskEither<
module,
types,
exports,
bin: bin ?? preBin,
...pipe(
rest,
RR.filterWithIndex(key => !config.omittedPackageKeys.includes(key)),
Expand Down Expand Up @@ -394,6 +425,10 @@ export const BuildServiceLive: RTE.ReaderTaskEither<

const rootDirRegex = /^\.\/(.*).(m|c)?ts/

function normalizePath(p: string): string {
return path.join(path.dirname(p), path.basename(p))
}

export const configuration: R.Reader<
BuildService,
BuildServiceMethods['configuration']
Expand Down
12 changes: 12 additions & 0 deletions src/ConfigService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,16 @@ export type ConfigParameters = {
* @default { }
*/
readonly dtsCompilerOverrides?: Partial<CompilerOptions>

/**
* Allows you to specify the package-command entrypoints as TypeScript files that will
* be re-pointed to their emitted javascript files.
*
* @remarks
* **Any binary file specified in this key or record must be an entrypoint, an error
* will be raised if that file is excluded from the entrypoint globs**
*/
readonly bin?: string | Record<string, string> | null
}

export class ConfigService {
Expand All @@ -136,6 +146,7 @@ export class ConfigService {
emitTypes = true,
dtsConfig = 'tsconfig.json',
dtsCompilerOverrides = {},
bin = null,
}: ConfigParameters) {
this[ConfigServiceSymbol] = {
buildType,
Expand All @@ -149,6 +160,7 @@ export class ConfigService {
buildMode,
emitTypes,
dtsCompilerOverrides,
bin,
}
}
}
Expand Down
159 changes: 99 additions & 60 deletions src/ExportsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,26 @@ export class ExportsService {
main: string | undefined,
module: string | undefined,
types: string | undefined,
bin: string | Record<string, string> | undefined,
]
constructor(
pkgExports: Exports,
main?: string | undefined,
module?: string | undefined,
types?: string | undefined,
bin?: string | Record<string, string> | undefined,
) {
this[ExportsServiceSymbol] = [pkgExports, main, module, types]
this[ExportsServiceSymbol] = [pkgExports, main, module, types, bin]
}
static of: (
main?: string | undefined,
module?: string | undefined,
types?: string | undefined,
bin?: string | Record<string, string> | undefined,
) => (pkgExports?: Exports) => ExportsService =
(main, module, types) =>
(main, module, types, bin) =>
(pkgExports = { './package.json': './package.json' }) =>
new ExportsService(pkgExports, main, module, types)
new ExportsService(pkgExports, main, module, types, bin)
}

export const pkgExports: RTE.ReaderTaskEither<
Expand Down Expand Up @@ -152,10 +155,15 @@ type ToExports = {
readonly default: Endomorphism<string>
}

type ExportsConfig = {
readonly import?: ToExports
readonly require?: ToExports
readonly default?: ToExports
type ToBin = Endomorphism<string>

interface ExportsConfig {
readonly exports: {
readonly import?: ToExports
readonly require?: ToExports
readonly default?: ToExports
}
readonly bin: ToBin
}

const addGlobalExportSingle = (
Expand All @@ -164,7 +172,7 @@ const addGlobalExportSingle = (
Required<Config.ConfigParameters>['buildMode'],
{ type: 'Multi' }
>,
{ default: d }: ExportsConfig,
{ exports: { default: d } }: ExportsConfig,
): DefaultExports =>
config.iife
? {
Expand All @@ -178,7 +186,7 @@ const addGlobalExportSingle = (
const addGlobalExportMulti = (
config: Required<Config.ConfigParameters>,
file: string,
{ default: d }: ExportsConfig,
{ exports: { default: d } }: ExportsConfig,
): DefaultExports =>
config.iife
? {
Expand All @@ -199,7 +207,10 @@ const toExportsService = (
pipe(
Common,
RTE.map(({ config, deps }) => {
const { import: i, require: r } = exportsConfig
const {
exports: { import: i, require: r },
bin,
} = exportsConfig
const buildType = config.buildMode.type
if (buildType === 'Single') {
return pipe(
Expand Down Expand Up @@ -229,6 +240,11 @@ const toExportsService = (
r?.default(config.buildMode.entrypoint),
i?.default(config.buildMode.entrypoint),
(r ?? i)?.types(config, config.buildMode.entrypoint)['types'],
config.bin === null
? undefined
: typeof config.bin === 'string'
? bin(config.bin)
: pipe(config.bin, RR.map(bin)),
),
)
}
Expand Down Expand Up @@ -265,83 +281,106 @@ const toExportsService = (
r?.default(deps.resolvedIndex),
i?.default(deps.resolvedIndex),
(r ?? i)?.types(config, deps.resolvedIndex)['types'],
config.bin === null
? undefined
: typeof config.bin === 'string'
? bin(config.bin)
: pipe(config.bin, RR.map(bin)),
),
)
}),
)

const DualTypeModuleExports = toExportsService({
import: {
types: addDtsExports,
default: tsToJs,
},
require: {
types: addDctsExports,
default: tsToCjs,
},
default: {
types: addDctsExports,
default: tsToGlobalCjs,
exports: {
import: {
types: addDtsExports,
default: tsToJs,
},
require: {
types: addDctsExports,
default: tsToCjs,
},
default: {
types: addDctsExports,
default: tsToGlobalCjs,
},
},
bin: tsToJs,
})

const DualTypeCommonExports = toExportsService({
import: {
types: addDmtsExports,
default: tsToMjs,
},
require: {
types: addDtsExports,
default: tsToJs,
},
default: {
types: addDtsExports,
default: tsToGlobal,
exports: {
import: {
types: addDmtsExports,
default: tsToMjs,
},
require: {
types: addDtsExports,
default: tsToJs,
},
default: {
types: addDtsExports,
default: tsToGlobal,
},
},
bin: tsToJs,
})

const CjsTypeModuleExports = toExportsService({
require: {
types: addDctsExports,
default: tsToCjs,
},
default: {
types: addDctsExports,
default: tsToGlobalCjs,
exports: {
require: {
types: addDctsExports,
default: tsToCjs,
},
default: {
types: addDctsExports,
default: tsToGlobalCjs,
},
},
bin: tsToCjs,
})

const CjsTypeCommonExports = toExportsService({
require: {
types: addDtsExports,
default: tsToJs,
},
default: {
types: addDtsExports,
default: tsToGlobal,
exports: {
require: {
types: addDtsExports,
default: tsToJs,
},
default: {
types: addDtsExports,
default: tsToGlobal,
},
},
bin: tsToJs,
})

const EsmTypeModuleExports = toExportsService({
import: {
types: addDtsExports,
default: tsToJs,
},
default: {
types: addDctsExports,
default: tsToGlobalCjs,
exports: {
import: {
types: addDtsExports,
default: tsToJs,
},
default: {
types: addDctsExports,
default: tsToGlobalCjs,
},
},
bin: tsToJs,
})

const EsmTypeCommonExports = toExportsService({
import: {
types: addDmtsExports,
default: tsToMjs,
},
default: {
types: addDtsExports,
default: tsToGlobal,
exports: {
import: {
types: addDmtsExports,
default: tsToMjs,
},
default: {
types: addDtsExports,
default: tsToGlobal,
},
},
bin: tsToMjs,
})

export const ExportsServiceLive: (
Expand Down
1 change: 1 addition & 0 deletions src/PackageJson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const PackageJsonSchema = S.ParseJsonString(
module: S.Unknown,
exports: S.Unknown,
type: S.Optional(S.Literal('module', 'commonjs'), 'commonjs'),
bin: S.Unknown,
},
S.Unknown,
),
Expand Down
6 changes: 4 additions & 2 deletions tests/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import { promisify } from 'node:util'
import { pipe } from 'fp-ts/lib/function'
import * as RA from 'fp-ts/lib/ReadonlyArray'

// import { makeConfig } from '../src'

describe('test projects', () => {
test.each(
pipe(
Expand Down Expand Up @@ -42,6 +40,10 @@ describe('test projects', () => {
// Used to test single-entrypoint root based dual libs
// ------------------------------------------------
'single-root-dual',
// ------------------------------------------------
// Used to test usage of the bin field
// ------------------------------------------------
'bin-cjs-dual',
]),
),
)(
Expand Down
1 change: 1 addition & 0 deletions tests/test-projects/bin-cjs-dual/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
7 changes: 7 additions & 0 deletions tests/test-projects/bin-cjs-dual/baz.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env node

export function main2() {
console.log('success!')
}

main2()
Loading

0 comments on commit 396e7c9

Please sign in to comment.