import assert from 'node:assert';
import fs from 'node:fs';
import path from 'node:path';
import ts from 'typescript';
import { changeExtensionInImportPaths } from './change-extension-in-import-paths.ts';
import { inlineInvariant } from './inline-invariant.ts';
import {
buildESMDevModeStub,
prettify,
readPackageJSON,
readTSConfig,
showDirStats,
writeGeneratedFile,
} from './utils.ts';
console.log('\n./denoDist');
await buildPackage('./denoDist');
showDirStats('./denoDist');
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);
const emittedTSFiles = await emitTSFiles(outDir);
emitDevTSFiles(outDir, devDir, emittedTSFiles);
await writeJSRConfig(outDir, emittedTSFiles);
fs.copyFileSync('./LICENSE', path.join(outDir, 'LICENSE'));
fs.copyFileSync('./README.md', path.join(outDir, 'README.md'));
}
async function emitTSFiles(outDir: string): Promise<ReadonlyArray<string>> {
const emittedTSFiles = [];
const tsProgram = ts.createProgram(['src/index.ts'], readTSConfig());
for (const sourceFile of tsProgram.getSourceFiles()) {
if (
tsProgram.isSourceFileFromExternalLibrary(sourceFile) ||
tsProgram.isSourceFileDefaultLibrary(sourceFile)
) {
continue;
}
const transformed = ts.transform(sourceFile, [
changeExtensionInImportPaths({ extension: '.ts' }),
inlineInvariant,
]);
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
const newContent = printer.printBundle(
ts.factory.createBundle(transformed.transformed),
);
transformed.dispose();
const filepath = path.relative('./src', sourceFile.fileName);
const destPath = path.join(outDir, filepath);
const prettified = await prettify(destPath, newContent);
writeGeneratedFile(destPath, prettified);
emittedTSFiles.push(filepath);
}
return emittedTSFiles.sort((a, b) => a.localeCompare(b));
}
function emitDevTSFiles(
outDir: string,
devDir: string,
emittedTSFiles: ReadonlyArray<string>,
): void {
for (const filepath of emittedTSFiles) {
const devPath = path.join(devDir, filepath);
const relativePathToOutDir = path.relative(path.dirname(devPath), outDir);
writeGeneratedFile(
devPath,
buildESMDevModeStub(
`${relativePathToOutDir}/devMode.ts`,
`${relativePathToOutDir}/${filepath}`,
),
);
}
}
interface JSRConfig {
name: string;
version: string;
exports: { [entrypoint: string]: string };
publish: {
exclude: ReadonlyArray<string>;
};
}
async function writeJSRConfig(
outDir: string,
emittedTSFiles: ReadonlyArray<string>,
): Promise<void> {
const jsrConfigPath = path.join(outDir, 'jsr.json');
const { version } = readPackageJSON();
const jsrExports: { [entrypoint: string]: string } = {};
for (const filepath of emittedTSFiles) {
const devEntrypointPath = `./__dev__/${filepath}`;
const devEntrypointKey = `./dev/${filepath}`;
setJSRExport(jsrExports, devEntrypointKey, devEntrypointPath);
}
for (const filepath of emittedTSFiles) {
const prodEntrypointPath = `./${filepath}`;
const prodEntrypointKey = `./${filepath}`;
setJSRExport(jsrExports, prodEntrypointKey, prodEntrypointPath);
if (filepath === 'index.ts') {
setJSRExport(jsrExports, '.', prodEntrypointPath);
setJSRExport(jsrExports, './mod.ts', prodEntrypointPath);
}
}
const jsrConfig: JSRConfig = {
name: '@graphql/graphql-js',
version,
exports: jsrExports,
publish: {
exclude: ['!.'],
},
};
const prettified = await prettify(jsrConfigPath, JSON.stringify(jsrConfig));
writeGeneratedFile(jsrConfigPath, prettified);
}
function setJSRExport(
jsrExports: { [entrypoint: string]: string },
entrypoint: string,
targetPath: string,
): void {
const existingPath = jsrExports[entrypoint];
assert(
existingPath === undefined || existingPath === targetPath,
`JSR export "${entrypoint}" cannot target both "${existingPath}" and "${targetPath}".`,
);
jsrExports[entrypoint] = targetPath;
}