UNPKG

convex

Version:

Client for the Convex Cloud

171 lines (151 loc) 5.87 kB
// Remove internal types from the tarball produced by npm pack before publishing. import url from "url"; import path from "path"; import { spawnSync } from "child_process"; import fs from "fs"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const convexDir = path.join(__dirname, ".."); const tarball = getOnlyTarball(convexDir); const tmpDir = path.join( convexDir, "tmpPackage" + ("" + Math.random()).slice(2, 8), ); console.log("creating temp folder", tmpDir); fs.rmSync(tmpDir, { force: true, recursive: true }); fs.mkdirSync(tmpDir); run("tar", "xzf", tarball, "-C", tmpDir); const tmpPackage = path.join(tmpDir, "package"); console.log("modifying package.json"); let packageJson = JSON.parse( fs.readFileSync(path.join(tmpPackage, "package.json")), ); pointToPublic(packageJson.exports); pointToPublic(packageJson.typesVersions); console.log("removing dev-only ts-node CLI script"); packageJson.bin["convex"] = packageJson.bin["convex-bundled"]; delete packageJson.bin["convex-bundled"]; delete packageJson.bin["//"]; fs.writeFileSync( path.join(tmpPackage, "package.json"), JSON.stringify(packageJson, null, 2) + "\n", ); console.log("modifying stub directories"); const stubs = getStubDirectories(convexDir); for (const [dirname, contents] of Object.entries(stubs)) { pointToPublic(contents); fs.writeFileSync( path.join(tmpPackage, dirname, "package.json"), JSON.stringify(contents, null, 2) + "\n", ); } // Remove internal types fs.rmSync(path.join(tmpPackage, "dist", "internal-cjs-types"), { recursive: true, }); fs.rmSync(path.join(tmpPackage, "dist", "internal-esm-types"), { recursive: true, }); // Remove a few more @internal types console.log("modifying types to remove remaining @internal types"); rewriteDtsToRemoveInternal(tmpPackage); run("tar", "czvf", tarball, "-C", tmpDir, "package"); fs.rmSync(tmpDir, { recursive: true }); function getOnlyTarball(dirname) { const files = fs.readdirSync(dirname); const tarballs = files.filter((f) => f.endsWith(".tgz")); if (tarballs.length < 1) throw new Error("No tarball found."); if (tarballs.length > 1) { throw new Error( "Multiple tarballs found, please `rm *.tgz` and run again. `--pack-destination` is not allowed.", ); } return path.join(dirname, tarballs[0]); } function pointToPublic(obj) { for (const key of Object.keys(obj)) { let value = obj[key]; if (typeof value === "string") { value = value.replace("-internal", "").replace("internal-", ""); obj[key] = value; } if (typeof value === "object") { pointToPublic(value); } } } function getStubDirectories(dirname) { return Object.fromEntries( fs .readdirSync(dirname) .filter((d) => fs.existsSync(path.join(dirname, d, "package.json"))) .map((d) => [ d, JSON.parse( fs.readFileSync(path.join(dirname, d, "package.json"), { encoding: "utf-8", }), ), ]), ); } function run(...args) { console.log(args.join(" ")); spawnSync(args[0], args.slice(1), { stdio: "inherit" }); } // `tsc --removeInternal` works on methods of classes but not properties of // objects or function parameters so until we move back to api-extractor we // manually remove a list of @internal properties. // // To maintain declaration maps it's helpful to avoid changing line numbers. function rewriteDtsToRemoveInternal(dirname) { // Properties aren't removed by tsc --removeInternal replaceType( dirname, "values/validator.d.ts", `/** @internal */ record<Key extends Validator<any, "required", any>, Value extends Validator<any, "required", any>>(keys: Key, values: Value): VRecord<Value["isOptional"] extends true ? { [key in Infer<Key>]?: Value["type"] | undefined; } : Record<Infer<Key>, Value["type"]>, Key, Value, "required", string>;`, `/* @internal record<Key extends Validator<any, "required", any>, Value extends Validator<any, "required", any>>(keys: Key, values: Value): VRecord<Value["isOptional"] extends true ? { [key in Infer<Key>]?: Value["type"] | undefined; } : Record<Infer<Key>, Value["type"]>, Key, Value, "required", string>; */`, ); auditForInternal(path.join(dirname, "dist", "cjs-types")); auditForInternal(path.join(dirname, "dist", "esm-types")); } function replaceType(dirname, relPath, needle, replacement) { for (const types of [ path.join(dirname, "dist", "cjs-types"), path.join(dirname, "dist", "esm-types"), ]) { const file = path.join(types, relPath); let contents = fs.readFileSync(file, { encoding: "utf-8" }); if (!contents.includes(needle)) { throw new Error(`Can't find string ${needle} in ${file}`); } const modified = contents.replace(needle, replacement); fs.writeFileSync(file, modified, { encoding: "utf-8" }); console.log("replaced\n", needle, "\nwith\n", replacement); } } // Assert that no `@internal` docstrings exist in types. function auditForInternal(dir) { for (const entry of fs.readdirSync(dir)) { const file = path.join(dir, entry); if (fs.statSync(file)?.isDirectory()) { auditForInternal(file); } else if (file.endsWith(".d.ts")) { const contents = fs.readFileSync(file, { encoding: "utf-8" }); const pattern = /\/\*\*(?<docstringContents>(\*(?!\/)|[^*])*@internal(\*(?!\/)|[^*])*)\*\/.*\n(?<nextLine>.*)\n/gm; const matches = [...contents.matchAll(pattern)]; for (const [match] of matches) { console.log( "found @internal type in", file, `\n\`\`\`\n${match}\`\`\``, ); throw new Error( "Found @internal type in published types! Until we switch to api-extractor you need to add a pattern for this in scripts/postpack.mjs in rewriteDtsToRemoveInternal().", ); } } } }