diff --git a/dts.snapshot.json b/dts.snapshot.json index 528a28e61..347f78b3c 100644 --- a/dts.snapshot.json +++ b/dts.snapshot.json @@ -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" @@ -83,7 +83,7 @@ "run.d.mts": { "#exports": [] }, - "types-!~{00g}~.d.mts": { + "types-!~{00k}~.d.mts": { "Arrayable": "type Arrayable = 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 | Promise", @@ -126,7 +126,7 @@ "PackageJsonScriptWithPreAndPost": "type PackageJsonScriptWithPreAndPost = 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 {}", "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, 'globalName' | 'inputOptions' | 'outputOptions' | 'minify' | 'define' | 'alias' | 'external' | 'onSuccess' | 'outExtensions' | 'hooks' | 'copy' | 'loader' | 'name' | 'banner' | 'footer' | 'checks'>, { entry: Record; nameLabel: string | undefined; format: NormalizedFormat; target?: string[]; clean: string[]; pkg?: PackageJsonWithPath; nodeProtocol: 'strip' | boolean; logger: Logger; ignoreWatch: Array; noExternal?: NoExternalFn; inlineOnly?: Array | false; css: Required; dts: false | DtsOptions; report: false | ReportOptions; tsconfig: false | string; exports: false | ExportsOptions; devtools: false | DevtoolsOptions; publint: false | PublintOptions; attw: false | AttwOptions; unused: false | UnusedOptions }>", diff --git a/package.json b/package.json index 4c650299c..b60b721de 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4473ff3b9..a08746299 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,6 +13,9 @@ catalogs: specifier: ^3.1.1 version: 3.1.1 dev: + '@publint/pack': + specifier: ^0.1.3 + version: 0.1.3 '@sxzz/eslint-config': specifier: ^7.5.1 version: 7.5.1 @@ -262,6 +265,9 @@ importers: '@arethetypeswrong/core': specifier: catalog:peer version: 0.18.2 + '@publint/pack': + specifier: catalog:dev + version: 0.1.3 '@sxzz/eslint-config': specifier: catalog:dev version: 7.5.1(@typescript-eslint/eslint-plugin@8.53.1(@typescript-eslint/parser@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/parser@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@unocss/eslint-plugin@66.6.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(prettier@3.8.1)(typescript@5.9.3) @@ -316,6 +322,9 @@ importers: memfs: specifier: catalog:dev version: 4.56.10(tslib@2.8.1) + package-manager-detector: + specifier: catalog:prod + version: 1.6.0 pkg-types: specifier: catalog:dev version: 2.3.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 173ad62e5..2ed964077 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -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 diff --git a/src/features/pkg/attw.ts b/src/features/pkg/attw.ts index 514a4e2a7..080b666c0 100644 --- a/src/features/pkg/attw.ts +++ b/src/features/pkg/attw.ts @@ -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]` @@ -96,7 +92,10 @@ const profiles: Record['profile'], string[]> = { 'esm-only': ['node10', 'node16-cjs'], } -export async function attw(options: ResolvedConfig): Promise { +export async function attw( + options: ResolvedConfig, + tarball: Buffer, +): Promise { if (!options.attw) return if (!options.pkg) { options.logger.warn('attw is enabled but package.json is not found') @@ -121,34 +120,12 @@ export async function attw(options: ResolvedConfig): Promise { 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) { diff --git a/src/features/pkg/index.ts b/src/features/pkg/index.ts index 0df3c4d7f..30d05714e 100644 --- a/src/features/pkg/index.ts +++ b/src/features/pkg/index.ts @@ -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 @@ -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> { + 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( configs: Array, key: K, diff --git a/src/features/pkg/publint.ts b/src/features/pkg/publint.ts index 497731f21..c90f3d850 100644 --- a/src/features/pkg/publint.ts +++ b/src/features/pkg/publint.ts @@ -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 { /** @internal */ resolvePaths?: string[] } -export async function publint(options: ResolvedConfig): Promise { +export async function publint( + options: ResolvedConfig, + tarball: Buffer, +): Promise { if (!options.publint) return if (!options.pkg) { options.logger.warn( @@ -36,7 +39,7 @@ export async function publint(options: ResolvedConfig): Promise { const { messages } = await publint({ ...options.publint, - pkgDir: path.dirname(options.pkg.packageJsonPath), + pack: { tarball: tarball.buffer }, }) debug('Found %d issues', messages.length) diff --git a/tsdown.config.ts b/tsdown.config.ts index 9cdaed96a..2005c1f9e 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -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',