// The published package as a project's tsc sees it: `import type { ShipConfig } from 'storebound'` in a scaffolded // storebound.config.ts must type-check with plain TypeScript (next build, vue-tsc, ts-loader, tsc --noEmit), not only // under Bun. 0.8.5 shipped "exports": "./cli/lib/config.ts" and no declarations: a Next app stopped building. import { describe, expect, test } from 'bun:test'; import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { buildEntry } from '../tools/build-node'; const ROOT = join(import.meta.dir, '..'); const TSC = join(ROOT, 'node_modules', 'typescript', 'bin', 'tsc'); describe('package types', () => { test('the scaffolded config type-checks against the package under bundler, node16 and nodenext', () => { const dir = mkdtempSync(join(tmpdir(), 'sb-pkg-types-')); try { const dist = join(dir, 'dist'); buildEntry(dist); const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8')); // what npm ships: package.json, the entry files it names, nothing from the checkout's node_modules const sb = join(dir, 'app', 'node_modules', 'storebound'); mkdirSync(sb, { recursive: true }); writeFileSync(join(sb, 'package.json'), JSON.stringify(pkg)); cpSync(dist, join(sb, 'dist'), { recursive: true }); expect(pkg.files).toEqual(expect.arrayContaining(['dist/config.js', 'dist/types', 'dist/types-cjs'])); symlinkSync(join(ROOT, 'node_modules', 'typescript'), join(dir, 'app', 'node_modules', 'typescript')); cpSync(join(ROOT, 'templates', 'storebound.config.ts'), join(dir, 'app', 'storebound.config.ts')); // every project a config lands in: ES module and CommonJS (node16 / nodenext read the nearest package.json), // Next / Vite / Expo (bundler), old tsconfigs (node10 reads "types"); no @types/node, skipLibCheck off const results: string[] = []; for (const type of ['module', 'commonjs']) { writeFileSync(join(dir, 'app', 'package.json'), JSON.stringify({ name: 'app', private: true, type })); for (const [module, moduleResolution] of [['esnext', 'bundler'], ['preserve', 'bundler'], ['node16', 'node16'], ['nodenext', 'nodenext'], ['commonjs', 'node10']]) { writeFileSync(join(dir, 'app', 'tsconfig.json'), JSON.stringify({ compilerOptions: { strict: true, noEmit: true, target: 'es2022', module, moduleResolution, skipLibCheck: false, types: [] }, include: ['*.ts'] })); const r = Bun.spawnSync(['node', TSC, '-p', '.'], { cwd: join(dir, 'app'), stdout: 'pipe', stderr: 'pipe' }); results.push(`${type} ${module}/${moduleResolution}: ${`${r.stdout.toString()}${r.stderr.toString()}`.trim() || 'ok'}`); } } expect(results.filter(r => !r.endsWith(': ok'))).toEqual([]); // the runtime entry under Node: import() and require() of 'storebound' load (no TypeScript source, no Bun) for (const code of [`import('storebound').then(m => console.log(typeof m.validateConfig))`, `console.log(typeof require('storebound').validateConfig)`]) { const r = Bun.spawnSync(['node', '-e', code], { cwd: join(dir, 'app'), stdout: 'pipe', stderr: 'pipe' }); expect(`${r.stdout.toString().trim()}${r.stderr.toString().split('\n')[0]}`).toBe('function'); } } finally { rmSync(dir, { recursive: true, force: true }); } }, 120_000); });