소스 검색

chore: trying to roll our own build utility, since most of the OSS ones are bad

benduran 4 주 전
부모
커밋
9e06e66859

+ 2 - 1
package.json

@@ -17,6 +17,7 @@
     "build-ts-package": "workspace:",
     "prettier": "catalog:",
     "prettier-plugin-solidity": "catalog:",
-    "turbo": "^2.5.8"
+    "turbo": "^2.5.8",
+    "typescript": "catalog:"
   }
 }

+ 22 - 0
packages/build-ts-package/README.md

@@ -0,0 +1,22 @@
+# build-ts-package
+
+A simple CLI utility for compiling TypeScript libraries and packages
+that supports dual publishing to both CommonJS and ESM formats.
+
+This tool uses TypeScript directly, to avoid any strange bundling issues,
+and it will respect your available `tsconfig.build.json` or `tsconfig.json` files,
+if present.
+
+## Usage
+
+Place the following in your `package.json#scripts.build` section:
+
+```
+build-ts-package
+```
+
+If you need more options or specialized configuration, do `build-ts-package --help` to see the available options:
+
+```bash
+
+```

+ 3 - 4
packages/build-ts-package/package.json

@@ -10,18 +10,17 @@
   "type": "module",
   "scripts": {
     "fix:format": "prettier . --write",
-    "start": "tsx ./src/build-ts-package.ts"
+    "start": "./src/build-ts-package.js"
   },
   "devDependencies": {
     "@cprussin/eslint-config": "catalog:",
     "@cprussin/tsconfig": "catalog:",
     "@types/node": "catalog:",
     "@types/yargs": "catalog:",
-    "type-fest": "^5.1.0"
+    "type-fest": "catalog:"
   },
   "dependencies": {
-    "tsdown": "^0.15.9",
-    "tsx": "catalog:",
+    "fast-glob": "^3.3.3",
     "typescript": "catalog:",
     "yargs": "catalog:"
   }

+ 54 - 113
packages/build-ts-package/src/build-ts-package.js

@@ -1,44 +1,26 @@
 #!/usr/bin/env node
 
-import fs from "fs/promises";
+import fs from "node:fs/promises";
 import path from "node:path";
-import { build } from "tsdown";
 import createCLI from "yargs";
 import { hideBin } from "yargs/helpers";
 
-/**
- * @typedef {import('tsdown').Format} Format
- */
+import { findTsconfigFile } from "./find-tsconfig-file.js";
+import { execAsync } from "./exec-async.js";
+
+const DEFAULT_EXCLUSION_PATTERNS = [
+  "!./src/**/*.stories.ts",
+  "!./src/**/*.test.ts",
+  "!./src/**/*.test.tsx",
+  "!./src/**/*.spec.ts",
+  "!./src/**/*.spec.tsx",
+  "!./src/**/*.stories.tsx",
+  "!./src/**/*.stories.mdx",
+];
 
 /**
- * returns the path of the found tsconfig file
- * or uses the provided override, instead,
- * if it's available
- *
- * @param {string} cwd
- * @param {string | undefined | null} tsconfigOverride
+ * @typedef {'cjs' | 'esm'} ModuleType
  */
-async function findTsconfigFile(cwd, tsconfigOverride) {
-  if (tsconfigOverride) {
-    const overridePath = path.isAbsolute(tsconfigOverride)
-      ? tsconfigOverride
-      : path.join(cwd, tsconfigOverride);
-    return overridePath;
-  }
-
-  const locations = [
-    path.join(cwd, "tsconfig.build.json"),
-    path.join(cwd, "tsconfig.json"),
-  ];
-
-  for (const fp of locations) {
-    try {
-      const stat = await fs.stat(fp);
-      if (stat.isFile()) return fp;
-    } catch {}
-  }
-  return null;
-}
 
 /**
  * builds a typescript package, using tsdown and its Node-friendly API
@@ -47,9 +29,7 @@ async function findTsconfigFile(cwd, tsconfigOverride) {
 export async function buildTsPackage(argv = process.argv) {
   const yargs = createCLI(hideBin(argv));
   const {
-    all,
     cwd,
-    devExports,
     exclude,
     noCjs,
     noDts,
@@ -59,12 +39,6 @@ export async function buildTsPackage(argv = process.argv) {
     watch,
   } = await yargs
     .scriptName("build-ts-package")
-    .option("all", {
-      default: false,
-      description:
-        "if true, will compile ALL files in your source folder and link them to your package.json. this is only required if you do not have an index.ts or index.tsx entrypoint that exports all of the things you want users to use.",
-      type: "boolean",
-    })
     .option("cwd", {
       default: process.cwd(),
       description: "the CWD to use when building",
@@ -73,7 +47,7 @@ export async function buildTsPackage(argv = process.argv) {
     .option("exclude", {
       default: [],
       description:
-        "one or more file exclusion glob patterns. please note, these must be EXCLUSION glob patterns, or they may end up getting picked up by the build",
+        "one or more glob patterns of files to ignore during compilation.",
       type: "array",
     })
     .option("noCjs", {
@@ -82,11 +56,6 @@ export async function buildTsPackage(argv = process.argv) {
         "if true, will not build the CommonJS variant of this package",
       type: "boolean",
     })
-    .option('devExports', {
-      default: false,
-      description: 'if set, will symlink the uncompiled typescript files during local development. if not set, compiled versions will be used, instead.',
-      type: 'boolean',
-    })
     .option("noDts", {
       default: false,
       description: "if set, will not write typescript typings",
@@ -119,7 +88,7 @@ export async function buildTsPackage(argv = process.argv) {
 
   // ESM Must come before CJS, as those typings and such take precedence
   // when dual publishing.
-  const formats = /** @type {Format[]} */ (
+  const formats = /** @type {ModuleType[]} */ (
     [noEsm ? undefined : "esm", noCjs ? undefined : "cjs"].filter(Boolean)
   );
 
@@ -137,73 +106,45 @@ export async function buildTsPackage(argv = process.argv) {
     console.info(`building ${format} variant in ${cwd}`);
     console.info(`  tsconfig: ${tsconfig}`);
 
-    /** @type {import('tsdown').Options} */
-    const buildConfig = {
-      clean: false,
-      cwd,
-      dts: !noDts,
-      entry: [
-        "./src/**/*.ts",
-        "./src/**/*.tsx",
-        // ignore all storybook entrypoints
-        "!./src/**/*.stories.ts",
-        "!./src/**/*.test.ts",
-        "!./src/**/*.test.tsx",
-        "!./src/**/*.spec.ts",
-        "!./src/**/*.spec.tsx",
-        "!./src/**/*.stories.tsx",
-        "!./src/**/*.stories.mdx",
-        ...exclude.map((ex) => String(ex)),
-      ],
-      exports:
-        format === "esm" || numFormats <= 1 ? all || devExports ? { all, devExports } : true : false,
-      // do not attempt to resolve or import CSS, SCSS or SVG files
-      external: [/\.s?css$/, /\.svg$/],
-      format,
-      // if there's only one outputted module format, we just take over the root of ./dist
-      // otherwise, we add the module type to the dist file path
-      outDir: numFormats <= 1 ? outDirPath : path.join(outDirPath, format),
-      platform: "neutral",
-      tsconfig,
-      unbundle: true,
-      watch,
-    };
-
-    console.info('using the following build config:')
-    console.info(buildConfig);
-
-    await build(buildConfig);
-  }
-  if (numFormats > 1) {
-    // we need to manually set the cjs exports, since tsdown
-    // isn't yet capable of doing this for us
-    /** @type {import('type-fest').PackageJson} */
-    const pjson = JSON.parse(await fs.readFile(pjsonPath, "utf8"));
-    if (!pjson.publishConfig?.exports) return;
-    for (const exportKey of Object.keys(pjson.publishConfig.exports)) {
-      // @ts-expect-error - we can definitely index here, so please be silenced!
-      const exportPath = String(pjson.publishConfig.exports[exportKey]);
-
-      // skip over all package.json files
-      if (exportPath.includes("package.json")) continue;
-
-      // @ts-expect-error - we can definitely index here, so please be silenced!
-      pjson.publishConfig.exports[exportKey] = {
-        import: exportPath,
-        require: exportPath
-          .replace(path.extname(exportPath), ".cjs")
-          .replace(`${path.sep}esm${path.sep}`, `${path.sep}cjs${path.sep}`),
-        types: exportPath.replace(path.extname(exportPath), ".d.ts"),
-      };
-      if (pjson.main) {
-        pjson.main = pjson.main
-          .replace(`${path.sep}esm${path.sep}`, `${path.sep}cjs${path.sep}`)
-          .replace(/\.mjs$/, ".js");
-      }
-    }
-
-    await fs.writeFile(pjsonPath, JSON.stringify(pjson, undefined, 2), "utf8");
+    const outDir = numFormats <= 1 ? outDirPath : path.join(outDirPath, format);
+
+    let cmd = `pnpm tsc --project ${tsconfig} --outDir ${outDir} --declaration ${!noDts} --module ${format === 'cjs' ? 'commonjs' : 'esnext'} --target esnext --resolveJsonModule false`;
+    if (watch) cmd += ` --watch`;
+
+    await execAsync(cmd, { cwd, stdio: 'inherit', verbose: true });
+
+    
   }
+  // if (numFormats > 1) {
+  //   // we need to manually set the cjs exports, since tsdown
+  //   // isn't yet capable of doing this for us
+  //   /** @type {import('type-fest').PackageJson} */
+  //   const pjson = JSON.parse(await fs.readFile(pjsonPath, "utf8"));
+  //   if (!pjson.publishConfig?.exports) return;
+  //   for (const exportKey of Object.keys(pjson.publishConfig.exports)) {
+  //     // @ts-expect-error - we can definitely index here, so please be silenced!
+  //     const exportPath = String(pjson.publishConfig.exports[exportKey]);
+
+  //     // skip over all package.json files
+  //     if (exportPath.includes("package.json")) continue;
+
+  //     // @ts-expect-error - we can definitely index here, so please be silenced!
+  //     pjson.publishConfig.exports[exportKey] = {
+  //       import: exportPath,
+  //       require: exportPath
+  //         .replace(path.extname(exportPath), ".cjs")
+  //         .replace(`${path.sep}esm${path.sep}`, `${path.sep}cjs${path.sep}`),
+  //       types: exportPath.replace(path.extname(exportPath), ".d.ts"),
+  //     };
+  //     if (pjson.main) {
+  //       pjson.main = pjson.main
+  //         .replace(`${path.sep}esm${path.sep}`, `${path.sep}cjs${path.sep}`)
+  //         .replace(/\.mjs$/, ".js");
+  //     }
+  //   }
+
+  //   await fs.writeFile(pjsonPath, JSON.stringify(pjson, undefined, 2), "utf8");
+  // }
 }
 
 await buildTsPackage();

+ 59 - 0
packages/build-ts-package/src/exec-async.js

@@ -0,0 +1,59 @@
+import { spawn } from 'node:child_process';
+
+/**
+ * @typedef {Object} ExecAsyncOpts
+ * @property {string} cwd
+ * @property {'inherit' | 'ignore' | 'pipe'} stdio
+ * @property {boolean} [verbose=false]
+ */
+
+/**
+ * Executes a command asynchronously via spawn.
+ * @param {string} command 
+ * @param {ExecAsyncOpts} opts
+ */
+export function execAsync(
+  command,
+  { verbose = process.env.LETS_VERSION_VERBOSE === 'true' || false, ...opts },
+) {
+  if (verbose) console.info('Executing', command, 'in', opts.cwd);
+
+  const [cmd, ...args] = command.split(/\s+/);
+
+  if (!cmd) throw new Error('unable to spawn because no command was given');
+
+  return new Promise((resolve, reject) => {
+    const child = spawn(cmd, args, opts);
+
+    let errBuffer = Buffer.alloc(0);
+    let stdoutBuffer = Buffer.alloc(0);
+
+    /** @type {Error | null} */
+    let error = null;
+    child.on('error', err => {
+      error = err;
+    });
+    child.stderr?.on('data', data => {
+      errBuffer = Buffer.concat([errBuffer, data]);
+    });
+
+    child.stdout?.on('data', data => {
+      stdoutBuffer = Buffer.concat([stdoutBuffer, data]);
+    });
+
+    child.once('exit', code => {
+      if (code) {
+        const errMsg = errBuffer.toString('utf-8');
+        console.error(errMsg);
+
+        if (error) {
+          return reject(error);
+        }
+        return reject(new Error(errMsg));
+      }
+
+      const output = stdoutBuffer.toString('utf-8');
+      return resolve(output);
+    });
+  });
+}

+ 32 - 0
packages/build-ts-package/src/find-tsconfig-file.js

@@ -0,0 +1,32 @@
+import fs from "node:fs/promises";
+import path from "node:path";
+
+/**
+ * returns the path of the found tsconfig file
+ * or uses the provided override, instead,
+ * if it's available
+ *
+ * @param {string} cwd
+ * @param {string | undefined | null} tsconfigOverride
+ */
+export async function findTsconfigFile(cwd, tsconfigOverride) {
+  if (tsconfigOverride) {
+    const overridePath = path.isAbsolute(tsconfigOverride)
+      ? tsconfigOverride
+      : path.join(cwd, tsconfigOverride);
+    return overridePath;
+  }
+
+  const locations = [
+    path.join(cwd, "tsconfig.build.json"),
+    path.join(cwd, "tsconfig.json"),
+  ];
+
+  for (const fp of locations) {
+    try {
+      const stat = await fs.stat(fp);
+      if (stat.isFile()) return fp;
+    } catch {}
+  }
+  return null;
+}

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 145 - 145
pnpm-lock.yaml


+ 2 - 1
pnpm-workspace.yaml

@@ -160,7 +160,8 @@ catalog:
   tailwindcss-react-aria-components: ^2.0.0
   tsx: ^4.20.6
   typedoc: ^0.26.8
-  typescript: ^5.8.2
+  type-fest: ^5.1.0
+  typescript: ^5.9.3
   vercel: ^41.4.1
   viem: ^2.37.13
   wagmi: ^2.14.16

+ 1 - 2
target_chains/solana/sdk/js/solana_utils/package.json

@@ -42,8 +42,7 @@
     "jest": "^29.4.0",
     "prettier": "catalog:",
     "quicktype": "^23.0.76",
-    "ts-jest": "^29.0.5",
-    "typescript": "^4.6.3"
+    "ts-jest": "^29.0.5"
   },
   "dependencies": {
     "@coral-xyz/anchor": "^0.29.0",

+ 3 - 3
target_chains/solana/sdk/js/solana_utils/src/jito.ts

@@ -78,9 +78,9 @@ export async function sendTransactionsJito(
     signedTransactions.push(tx);
   }
 
-  const firstTransactionSignature = bs58.encode(
+  const firstTransactionSignature = signedTransactions[0]?.signatures[0] ? bs58.encode(
     signedTransactions[0].signatures[0],
-  );
+  ) : '';
 
   const bundle = new Bundle(signedTransactions, 2);
 
@@ -94,7 +94,7 @@ export async function sendTransactionsJito(
       totalAttempts++;
 
       try {
-        await currentClient.sendBundle(bundle);
+        await currentClient?.sendBundle(bundle);
         logger.info(
           { clientIndex: i, totalAttempts },
           `Successfully sent bundle to Jito client after ${totalAttempts} attempts`,

+ 0 - 1
target_chains/solana/sdk/js/solana_utils/tsconfig.build.json

@@ -3,7 +3,6 @@
   "compilerOptions": {
     "noEmit": false,
     "incremental": false,
-    "declaration": true,
     "verbatimModuleSyntax": false
   }
 }

+ 1 - 1
target_chains/solana/sdk/js/solana_utils/tsconfig.json

@@ -1,6 +1,6 @@
 {
   "extends": "@cprussin/tsconfig/nextjs.json",
-  "exclude": ["node_modules", "**/__tests__/*"],
+  "exclude": ["dist/", "node_modules", "**/__tests__/*"],
   "compilerOptions": {
     "rootDir": "src/"
   }

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.