Skip to content

Commit 81c823b

Browse files
committed
fix(deno): Add prepack for deno build
1 parent c548c3c commit 81c823b

File tree

2 files changed

+162
-2
lines changed

2 files changed

+162
-2
lines changed

packages/deno/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
"build:types": "run-s deno-types build:types:tsc build:types:bundle",
4242
"build:types:tsc": "tsc -p tsconfig.types.json",
4343
"build:types:bundle": "rollup -c rollup.types.config.mjs",
44-
"build:tarball": "npm pack",
44+
"build:tarball": "ts-node ./scripts/prepack.ts && npm pack",
4545
"circularDepCheck": "madge --circular src/index.ts",
4646
"clean": "rimraf build build-types build-test coverage",
4747
"prefix": "yarn deno-types",
@@ -55,7 +55,7 @@
5555
"test:types": "deno check ./build/index.mjs",
5656
"test:unit": "deno test --allow-read --allow-run",
5757
"test:unit:update": "deno test --allow-read --allow-write --allow-run -- --update",
58-
"yalc:publish": "yalc publish --push --sig"
58+
"yalc:publish": "ts-node ./scripts/prepack.ts && yalc publish --push --sig"
5959
},
6060
"volta": {
6161
"extends": "../../package.json"

packages/deno/scripts/prepack.ts

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/* eslint-disable no-console */
2+
3+
/**
4+
* This script prepares the central `build` directory for NPM package creation.
5+
* It first copies all non-code files into the `build` directory, including `package.json`, which
6+
* is edited to adjust entry point paths. These corrections are performed so that the paths align with
7+
* the directory structure inside `build`.
8+
*
9+
* TODO(v9): Remove this script and change the Deno SDK to import from build/X.
10+
*/
11+
12+
import * as fs from 'fs';
13+
import * as path from 'path';
14+
15+
const NPM_BUILD_DIR = 'build/npm';
16+
const BUILD_DIR = 'build';
17+
18+
const ASSETS = ['README.md', 'LICENSE', 'package.json', '.npmignore'];
19+
20+
const ENTRY_POINTS = ['main', 'module', 'types', 'browser'] as const;
21+
const CONDITIONAL_EXPORT_ENTRY_POINTS = ['import', 'require', ...ENTRY_POINTS] as const;
22+
const EXPORT_MAP_ENTRY_POINT = 'exports';
23+
const TYPES_VERSIONS_ENTRY_POINT = 'typesVersions';
24+
25+
const packageWithBundles = process.argv.includes('--bundles');
26+
const buildDir = packageWithBundles ? NPM_BUILD_DIR : BUILD_DIR;
27+
28+
type PackageJsonEntryPoints = Record<(typeof ENTRY_POINTS)[number], string>;
29+
type ConditionalExportEntryPoints = Record<(typeof CONDITIONAL_EXPORT_ENTRY_POINTS)[number], string>;
30+
31+
interface TypeVersions {
32+
[key: string]: {
33+
[key: string]: string[];
34+
};
35+
}
36+
37+
type PackageJsonExports = Partial<ConditionalExportEntryPoints> & {
38+
[key: string]: Partial<ConditionalExportEntryPoints>;
39+
};
40+
41+
interface PackageJson extends Record<string, unknown>, PackageJsonEntryPoints {
42+
[EXPORT_MAP_ENTRY_POINT]: PackageJsonExports;
43+
[TYPES_VERSIONS_ENTRY_POINT]: TypeVersions;
44+
}
45+
46+
// eslint-disable-next-line @typescript-eslint/no-var-requires
47+
const pkgJson: PackageJson = require(path.resolve('package.json'));
48+
49+
// check if build dir exists
50+
if (!fs.existsSync(path.resolve(buildDir))) {
51+
console.error(`\nERROR: Directory '${buildDir}' does not exist in ${pkgJson.name}.`);
52+
console.error("This script should only be executed after you've run `yarn build`.");
53+
process.exit(1);
54+
}
55+
56+
// copy non-code assets to build dir
57+
ASSETS.forEach(asset => {
58+
const assetPath = path.resolve(asset);
59+
if (fs.existsSync(assetPath)) {
60+
const destinationPath = path.resolve(buildDir, path.basename(asset));
61+
console.log(`Copying ${path.basename(asset)} to ${path.relative('../..', destinationPath)}.`);
62+
fs.copyFileSync(assetPath, destinationPath);
63+
}
64+
});
65+
66+
// package.json modifications
67+
const newPackageJsonPath = path.resolve(buildDir, 'package.json');
68+
// eslint-disable-next-line @typescript-eslint/no-var-requires
69+
const newPkgJson: PackageJson = require(newPackageJsonPath);
70+
71+
// modify entry points to point to correct paths (i.e. strip out the build directory)
72+
ENTRY_POINTS.filter(entryPoint => newPkgJson[entryPoint]).forEach(entryPoint => {
73+
newPkgJson[entryPoint] = newPkgJson[entryPoint].replace(`${buildDir}/`, '');
74+
});
75+
76+
/**
77+
* Recursively traverses the exports object and rewrites all string values to remove the build directory.
78+
*/
79+
function rewriteConditionalExportEntryPoint(
80+
exportsObject: Record<string, string | Record<string, string>>,
81+
key: string,
82+
): void {
83+
const exportsField = exportsObject[key];
84+
if (!exportsField) {
85+
return;
86+
}
87+
88+
if (typeof exportsField === 'string') {
89+
exportsObject[key] = exportsField.replace(`${buildDir}/`, '');
90+
return;
91+
}
92+
Object.keys(exportsField).forEach(subfieldKey => {
93+
rewriteConditionalExportEntryPoint(exportsField, subfieldKey);
94+
});
95+
}
96+
97+
if (newPkgJson[EXPORT_MAP_ENTRY_POINT]) {
98+
Object.keys(newPkgJson[EXPORT_MAP_ENTRY_POINT]).forEach(key => {
99+
rewriteConditionalExportEntryPoint(newPkgJson[EXPORT_MAP_ENTRY_POINT], key);
100+
});
101+
}
102+
103+
if (newPkgJson[TYPES_VERSIONS_ENTRY_POINT]) {
104+
Object.entries(newPkgJson[TYPES_VERSIONS_ENTRY_POINT]).forEach(([key, val]) => {
105+
newPkgJson[TYPES_VERSIONS_ENTRY_POINT][key] = Object.entries(val).reduce(
106+
(acc, [key, val]) => {
107+
const newKey = key.replace(`${buildDir}/`, '');
108+
acc[newKey] = val.map(v => v.replace(`${buildDir}/`, ''));
109+
return acc;
110+
},
111+
{} as Record<string, string[]>,
112+
);
113+
});
114+
}
115+
116+
delete newPkgJson.scripts;
117+
delete newPkgJson.volta;
118+
delete newPkgJson.jest;
119+
120+
// write modified package.json to file (pretty-printed with 2 spaces)
121+
try {
122+
fs.writeFileSync(newPackageJsonPath, JSON.stringify(newPkgJson, null, 2));
123+
} catch (error) {
124+
console.error(`\nERROR: Error while writing modified 'package.json' to disk in ${pkgJson.name}:\n`, error);
125+
process.exit(1);
126+
}
127+
128+
async function runPackagePrepack(packagePrepackPath: string): Promise<void> {
129+
const { prepack } = await import(packagePrepackPath);
130+
if (prepack && typeof prepack === 'function') {
131+
const isSuccess = prepack(buildDir);
132+
if (!isSuccess) {
133+
process.exit(1);
134+
}
135+
} else {
136+
console.error(`\nERROR: Could not find a \`prepack\` function in './scripts/prepack.ts' in ${pkgJson.name}.`);
137+
console.error(
138+
'Make sure your package-specific prepack script exports `function prepack(buildDir: string): boolean`.',
139+
);
140+
process.exit(1);
141+
}
142+
}
143+
144+
// execute package specific settings
145+
// 1. check if a script called `<package-root>/scripts/prepack.ts` exists
146+
// if yes, 2.) execute that script for things that are package-specific
147+
async function runPackageSpecificScripts(): Promise<void> {
148+
const packagePrepackPath = path.resolve('scripts', 'prepack.ts');
149+
try {
150+
if (fs.existsSync(packagePrepackPath)) {
151+
await runPackagePrepack(packagePrepackPath);
152+
}
153+
} catch (error) {
154+
console.error(`\nERROR: Error while trying to load and run ./scripts/prepack.ts in ${pkgJson.name}:\n`, error);
155+
process.exit(1);
156+
}
157+
console.log(`\nSuccessfully finished prepack commands for ${pkgJson.name}\n`);
158+
}
159+
160+
void runPackageSpecificScripts();

0 commit comments

Comments
 (0)