import assert from 'node:assert';
import fs from 'node:fs';
import path from 'node:path';
import ts from 'typescript';
import {
changeExtensionInImportPaths,
changeExtensionInImportPathsInBundle,
} from './change-extension-in-import-paths.ts';
import { inlineInvariant } from './inline-invariant.ts';
import { stripPrivateDeclarations } from './strip-private-declarations.ts';
import type { PlatformConditionalExports } from './utils.ts';
import {
buildCJSDevModeStub,
buildESMDevModeStub,
prettify,
readPackageJSON,
readTSConfig,
showDirStats,
writeGeneratedFile,
} from './utils.ts';
const devTypeFiles = [
['.ts', '.js'],
['.mts', '.mjs'],
] as const;
console.log('\n./npmDist');
await buildPackage('./npmDist');
showDirStats('./npmDist');
async function buildPackage(outDir: string): Promise<void> {
const devDir = path.join(outDir, '__dev__');
fs.rmSync(outDir, { recursive: true, force: true });
fs.mkdirSync(outDir);
fs.mkdirSync(devDir);
fs.copyFileSync('./LICENSE', `./${outDir}/LICENSE`);
fs.copyFileSync('./README.md', `./${outDir}/README.md`);
const packageJSON = readPackageJSON();
delete packageJSON.private;
delete packageJSON.scripts;
delete packageJSON.devDependencies;
assert(packageJSON.types === undefined, 'Unexpected "types" in package.json');
const supportedTSVersions = Object.keys(packageJSON.typesVersions);
assert(
supportedTSVersions.length === 1,
'Property "typesVersions" should have exactly one key.',
);
const notSupportedTSVersionFile = 'NotSupportedTSVersion.d.ts';
fs.writeFileSync(
path.join(outDir, notSupportedTSVersionFile),
`"Package 'graphql' support only TS versions that are ${supportedTSVersions[0]}".`,
);
packageJSON.typesVersions = {
...packageJSON.typesVersions,
'*': { '*': [notSupportedTSVersionFile] },
};
delete packageJSON.type;
packageJSON.main = 'index.js';
packageJSON.module = 'index.mjs';
packageJSON.types = 'index.d.ts';
const { emittedTSFiles } = emitTSFiles({
outDir,
module: 'commonjs',
moduleResolution: 'node10',
extension: '.js',
});
emitTSFiles({ outDir, extension: '.mjs' });
packageJSON.exports = {};
for (const prodFile of emittedTSFiles) {
const { dir, base, name, ext } = path.parse(prodFile);
if (ext === '.map') {
continue;
} else if (path.basename(dir) === 'dev') {
packageJSON.exports['./dev'] = buildPlatformConditionalExports(
'./dev',
'index',
);
continue;
}
const relativePathToProd = path.relative(prodFile, outDir);
const { name: innerName, ext: innerExt } = path.parse(name);
if (innerExt === '.d') {
const relativePathAndName = path.relative(outDir, `${dir}/${innerName}`);
for (const [typeExt, targetExt] of devTypeFiles) {
const line = `export * from '${relativePathToProd}/${relativePathAndName}${targetExt}';`;
writeGeneratedFile(
path.join(devDir, path.relative(outDir, `${dir}/${name}${typeExt}`)),
line,
);
}
continue;
}
const relativePathAndName = path.relative(outDir, `${dir}/${name}`);
writeGeneratedFile(
path.join(devDir, path.relative(outDir, `${dir}/${name}.js`)),
buildCJSDevModeStub(
`${relativePathToProd}/devMode.js`,
`${relativePathToProd}/${relativePathAndName}.js`,
),
);
writeGeneratedFile(
path.join(devDir, path.relative(outDir, `${dir}/${name}.mjs`)),
buildESMDevModeStub(
`${relativePathToProd}/devMode.mjs`,
`${relativePathToProd}/${relativePathAndName}.mjs`,
),
);
if (base === 'index.js') {
const dirname = path.dirname(relativePathAndName);
packageJSON.exports[dirname === '.' ? dirname : `./${dirname}`] = {
development: buildPlatformConditionalExports(
'./__dev__',
relativePathAndName,
),
default: buildPlatformConditionalExports('.', relativePathAndName),
};
}
}
const globEntryPoints = {
development: buildPlatformConditionalExports('./__dev__', '*'),
default: buildPlatformConditionalExports('.', '*'),
};
packageJSON.exports['./*.js'] = globEntryPoints;
packageJSON.exports['./*'] = globEntryPoints;
packageJSON.sideEffects = ['__dev__/*'];
const packageJsonPath = `./${outDir}/package.json`;
const prettified = await prettify(
packageJsonPath,
JSON.stringify(packageJSON),
);
writeGeneratedFile(packageJsonPath, prettified);
}
function emitTSFiles(options: {
outDir: string;
module?: string;
moduleResolution?: string;
extension: string;
}): {
emittedTSFiles: ReadonlyArray<string>;
} {
const { extension, ...rest } = options;
const rootFiles = ['src/index.ts', 'src/dev/index.ts'];
const jsOptions = readTSConfig({
...rest,
noEmit: false,
declaration: false,
listEmittedFiles: true,
removeComments: true,
});
const dtsOptions = readTSConfig({
...rest,
noEmit: false,
declaration: true,
declarationDir: rest.outDir,
emitDeclarationOnly: true,
listEmittedFiles: true,
});
const writeFile = (filepath: string, body: string) => {
if (extension === '.mjs') {
if (filepath.match(/.js$/)) {
let bodyToWrite = body;
bodyToWrite = bodyToWrite.replace(
'//# sourceMappingURL=graphql.js.map',
'//# sourceMappingURL=graphql.mjs.map',
);
writeGeneratedFile(filepath.replace(/.js$/, extension), bodyToWrite);
return;
}
if (filepath.match(/.js.map$/)) {
writeGeneratedFile(
filepath.replace(/.js.map$/, extension + '.map'),
body,
);
return;
}
if (filepath.match(/.d.ts$/)) {
writeGeneratedFile(filepath.replace(/.d.ts$/, '.d.mts'), body);
return;
}
}
writeGeneratedFile(filepath, body);
};
const jsHost = ts.createCompilerHost(jsOptions);
jsHost.writeFile = writeFile;
const jsProgram = ts.createProgram(rootFiles, jsOptions, jsHost);
const jsResult = jsProgram.emit(undefined, undefined, undefined, undefined, {
before: [changeExtensionInImportPaths({ extension })],
after: [inlineInvariant],
});
const dtsHost = ts.createCompilerHost(dtsOptions);
dtsHost.writeFile = writeFile;
const dtsProgram = ts.createProgram(rootFiles, dtsOptions, dtsHost);
const dtsResult = dtsProgram.emit(
undefined,
undefined,
undefined,
undefined,
{
afterDeclarations: [
stripPrivateDeclarations(),
changeExtensionInImportPathsInBundle({ extension }),
],
},
);
assert(
!jsResult.emitSkipped && !dtsResult.emitSkipped,
'Fail to generate `*.d.ts` files, please run `npm run check`',
);
assert(jsResult.emittedFiles != null);
assert(dtsResult.emittedFiles != null);
return {
emittedTSFiles: [...jsResult.emittedFiles, ...dtsResult.emittedFiles].sort(
(a, b) => a.localeCompare(b),
),
};
}
function buildPlatformConditionalExports(
dir: string,
name: string,
): PlatformConditionalExports {
const base = `./${path.join(dir, name)}`;
return {
module: `${base}.mjs`,
bun: `${base}.mjs`,
'module-sync': `${base}.mjs`,
node: `${base}.js`,
require: `${base}.js`,
default: `${base}.mjs`,
};
}