Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions dts.snapshot.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"config-!~{00i}~.d.mts": {
"config-!~{00m}~.d.mts": {
"defineConfig": "declare function defineConfig(_: UserConfigExport): UserConfigExport",
"mergeConfig": "declare function mergeConfig(_: InlineConfig, _: InlineConfig): InlineConfig",
"resolveUserConfig": "declare function resolveUserConfig(_: UserConfig, _: InlineConfig): Promise<ResolvedConfig[]>"
Expand Down Expand Up @@ -83,7 +83,7 @@
"run.d.mts": {
"#exports": []
},
"types-!~{00g}~.d.mts": {
"types-!~{00k}~.d.mts": {
"Arrayable": "type Arrayable<T> = T | T[]",
"AttwOptions": "interface AttwOptions extends CheckPackageOptions {\n profile?: 'strict' | 'node16' | 'esm-only'\n level?: 'error' | 'warn'\n ignoreRules?: string[]\n}",
"Awaitable": "type Awaitable<T> = T | Promise<T>",
Expand Down Expand Up @@ -126,7 +126,7 @@
"PackageJsonScriptWithPreAndPost": "type PackageJsonScriptWithPreAndPost<S extends string> = S | `${'pre' | 'post'}${S}`",
"PackageJsonWithPath": "interface PackageJsonWithPath extends PackageJson {\n packageJsonPath: string\n}",
"PackageType": "type PackageType = 'module' | 'commonjs' | undefined",
"PublintOptions": "interface PublintOptions extends Options {}",
"PublintOptions": "interface PublintOptions extends Omit<Options, 'pack' | 'pkgDir'> {}",
"ReportOptions": "interface ReportOptions {\n gzip?: boolean\n brotli?: boolean\n maxCompressSize?: number\n}",
"ReportPlugin": "declare function ReportPlugin(_: ReportOptions, _: Logger, _: string, _: boolean, _: string, _: boolean): Plugin",
"ResolvedConfig": "type ResolvedConfig = Overwrite<MarkPartial<Omit<UserConfig, 'workspace' | 'fromVite' | 'publicDir' | 'bundle' | 'removeNodeProtocol' | 'logLevel' | 'failOnWarn' | 'customLogger' | 'envFile' | 'envPrefix'>, 'globalName' | 'inputOptions' | 'outputOptions' | 'minify' | 'define' | 'alias' | 'external' | 'onSuccess' | 'outExtensions' | 'hooks' | 'copy' | 'loader' | 'name' | 'banner' | 'footer' | 'checks'>, { entry: Record<string, string>; nameLabel: string | undefined; format: NormalizedFormat; target?: string[]; clean: string[]; pkg?: PackageJsonWithPath; nodeProtocol: 'strip' | boolean; logger: Logger; ignoreWatch: Array<string | RegExp>; noExternal?: NoExternalFn; inlineOnly?: Array<string | RegExp> | false; css: Required<CssOptions>; dts: false | DtsOptions; report: false | ReportOptions; tsconfig: false | string; exports: false | ExportsOptions; devtools: false | DevtoolsOptions; publint: false | PublintOptions; attw: false | AttwOptions; unused: false | UnusedOptions }>",
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
},
"devDependencies": {
"@arethetypeswrong/core": "catalog:peer",
"@publint/pack": "catalog:dev",
"@sxzz/eslint-config": "catalog:dev",
"@sxzz/prettier-config": "catalog:dev",
"@sxzz/test-utils": "catalog:dev",
Expand All @@ -126,6 +127,7 @@
"is-in-ci": "catalog:prod",
"lightningcss": "catalog:dev",
"memfs": "catalog:dev",
"package-manager-detector": "catalog:prod",
"pkg-types": "catalog:dev",
"prettier": "catalog:dev",
"publint": "catalog:peer",
Expand Down
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ catalogs:
'@clack/prompts': ^0.11.0
giget: ^3.1.1
dev:
'@publint/pack': ^0.1.3
'@sxzz/eslint-config': ^7.5.1
'@sxzz/prettier-config': ^2.2.6
'@sxzz/test-utils': ^0.5.15
Expand Down
37 changes: 7 additions & 30 deletions src/features/pkg/attw.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import { mkdtemp, readFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import path from 'node:path'
import { dim } from 'ansis'
import { createDebug } from 'obug'
import { exec } from 'tinyexec'
import { fsRemove } from '../../utils/fs.ts'
import { importWithError, slash } from '../../utils/general.ts'
import type { ResolvedConfig } from '../../config/index.ts'
import type {
CheckPackageOptions,
CheckResult,
Problem,
ProblemKind,
} from '@arethetypeswrong/core'
import type { Buffer } from 'node:buffer'

const debug = createDebug('tsdown:attw')
const label = dim`[attw]`
Expand Down Expand Up @@ -96,7 +92,10 @@ const profiles: Record<Required<AttwOptions>['profile'], string[]> = {
'esm-only': ['node10', 'node16-cjs'],
}

export async function attw(options: ResolvedConfig): Promise<void> {
export async function attw(
options: ResolvedConfig,
tarball: Buffer<ArrayBuffer>,
): Promise<void> {
if (!options.attw) return
if (!options.pkg) {
options.logger.warn('attw is enabled but package.json is not found')
Expand All @@ -121,34 +120,12 @@ export async function attw(options: ResolvedConfig): Promise<void> {
const t = performance.now()
debug('Running attw check')

const tempDir = await mkdtemp(path.join(tmpdir(), 'tsdown-attw-'))

const attwCore = await importWithError<
typeof import('@arethetypeswrong/core')
>('@arethetypeswrong/core', options.attw.resolvePaths)
let checkResult: CheckResult

try {
const { stdout: tarballInfo } = await exec(
'npm',
['pack', '--json', '--ignore-scripts', '--pack-destination', tempDir],
{ nodeOptions: { cwd: options.cwd } },
)
const parsed = JSON.parse(tarballInfo)
if (!Array.isArray(parsed) || !parsed[0]?.filename) {
throw new Error('Invalid npm pack output format')
}
const tarballPath = path.join(tempDir, parsed[0].filename as string)
const tarball = await readFile(tarballPath)

const pkg = attwCore.createPackageFromTarballData(tarball)
checkResult = await attwCore.checkPackage(pkg, attwOptions)
} catch (error) {
options.logger.error('ATTW check failed:', error)
return
} finally {
await fsRemove(tempDir)
}
const pkg = attwCore.createPackageFromTarballData(tarball)
const checkResult = await attwCore.checkPackage(pkg, attwOptions)

let errorMessage: string | undefined
if (checkResult.types) {
Expand Down
44 changes: 40 additions & 4 deletions src/features/pkg/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { mkdtemp, readFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import path from 'node:path'
import { formatWithOptions } from 'node:util'
import { fsRemove } from '../../utils/fs.ts'
import { promiseWithResolvers } from '../../utils/general.ts'
import { attw } from './attw.ts'
import { writeExports } from './exports.ts'
import { publint } from './publint.ts'
import type { ResolvedConfig } from '../../config/types.ts'
import type { ChunksByFormat, TsdownBundle } from '../../utils/chunks.ts'
import type { Buffer } from 'node:buffer'

export type BundleByPkg = Record<
string, // pkgPath
Expand Down Expand Up @@ -91,14 +96,45 @@ export async function bundleDone(
)
}

await Promise.all([
...publintConfigs.map((config) => publint(config)),
...attwConfigs.map((config) => attw(config)),
])
try {
if (publintConfigs.length || attwConfigs.length) {
const tarball = await packTarball(pkg.packageJsonPath)
await Promise.all([
...publintConfigs.map((config) => publint(config, tarball)),
...attwConfigs.map((config) => attw(config, tarball)),
])
}
} catch (error) {
configs[0].logger.error('Pack failed:', error)
}

ctx.resolve()
}

async function packTarball(
packageJsonPath: string,
): Promise<Buffer<ArrayBuffer>> {
const pkgDir = path.dirname(packageJsonPath)
const destination = await mkdtemp(path.join(tmpdir(), 'tsdown-pack-'))
const [{ detect }, { pack }] = await Promise.all([
import('package-manager-detector/detect'),
import('@publint/pack'),
])
try {
const detected = await detect({ cwd: pkgDir })
if (detected?.name === 'deno') {
throw new Error(`Cannot pack tarball for Deno projects at ${pkgDir}`)
}
const tarballPath = await pack(pkgDir, {
destination,
packageManager: detected?.name,
})
return readFile(tarballPath)
} finally {
await fsRemove(destination)
}
}

function dedupeConfigs<K extends 'publint' | 'attw' | 'exports'>(
configs: Array<ResolvedConfig>,
key: K,
Expand Down
11 changes: 7 additions & 4 deletions src/features/pkg/publint.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import path from 'node:path'
import { dim } from 'ansis'
import { createDebug } from 'obug'
import { importWithError } from '../../utils/general.ts'
import type { ResolvedConfig } from '../../config/index.ts'
import type { Buffer } from 'node:buffer'
import type { Options } from 'publint'

const debug = createDebug('tsdown:publint')
const label = dim`[publint]`

export interface PublintOptions extends Options {
export interface PublintOptions extends Omit<Options, 'pack' | 'pkgDir'> {
/** @internal */
resolvePaths?: string[]
}

export async function publint(options: ResolvedConfig): Promise<void> {
export async function publint(
options: ResolvedConfig,
tarball: Buffer<ArrayBuffer>,
): Promise<void> {
if (!options.publint) return
if (!options.pkg) {
options.logger.warn(
Expand All @@ -36,7 +39,7 @@ export async function publint(options: ResolvedConfig): Promise<void> {

const { messages } = await publint({
...options.publint,
pkgDir: path.dirname(options.pkg.packageJsonPath),
pack: { tarball: tarball.buffer },
})
debug('Found %d issues', messages.length)

Expand Down
2 changes: 2 additions & 0 deletions tsdown.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ export default defineConfig([
entry: ['./src/{index,run,plugins,config}.ts'],
name: 'tsdown',
inlineOnly: [
'@publint/pack',
'is-in-ci',
'package-manager-detector',
'pkg-types', // type-only
],
platform: 'node',
Expand Down
Loading