diff --git a/workspaces/fetch-leetcode-problem-list/package.json b/workspaces/fetch-leetcode-problem-list/package.json index e327ea33..9a67c3ee 100644 --- a/workspaces/fetch-leetcode-problem-list/package.json +++ b/workspaces/fetch-leetcode-problem-list/package.json @@ -15,7 +15,7 @@ "type": "module", "exports": "./src/main.ts", "scripts": { - "build": "cross-env NODE_OPTIONS=\"--import tsx\" webpack && chmod +x dist/fetch-leetcode-problem-list.cjs", + "build": "cross-env NODE_OPTIONS=\"--import tsx\" webpack", "format": "prettier --color --write .", "lint": "eslint --color --max-warnings=0 .", "start": "tsx src/main.ts", diff --git a/workspaces/fetch-leetcode-problem-list/webpack.config.ts b/workspaces/fetch-leetcode-problem-list/webpack.config.ts index 06963adb..24ca8303 100644 --- a/workspaces/fetch-leetcode-problem-list/webpack.config.ts +++ b/workspaces/fetch-leetcode-problem-list/webpack.config.ts @@ -1,7 +1,9 @@ +import { chmod } from "node:fs/promises"; import { builtinModules } from "node:module"; import path from "node:path"; import webpack, { + type Compiler, type Configuration, type ExternalItemFunctionData, } from "webpack"; @@ -11,6 +13,35 @@ import { stripPrefixOrThrow } from "@code-chronicles/util/stripPrefixOrThrow"; import packageJson from "./package.json" with { type: "json" }; +class WebpackMakeOutputExecutablePlugin { + // eslint-disable-next-line class-methods-use-this -- This is the interface expected by webpack. + apply(compiler: Compiler): void { + compiler.hooks.afterEmit.tapAsync( + "WebpackMakeOutputExecutablePlugin", + async (compilation) => { + const promises: Promise[] = []; + + for (const chunk of compilation.chunks) { + if (!chunk.canBeInitial()) { + continue; + } + + for (const file of chunk.files) { + promises.push( + chmod( + path.join(compilation.outputOptions.path ?? ".", file), + 0o755, + ), + ); + } + } + + await Promise.all(promises); + }, + ); + } +} + const config: Configuration = { target: "node", entry: path.resolve(__dirname, packageJson.exports), @@ -58,6 +89,8 @@ const config: Configuration = { raw: true, entryOnly: true, }), + + new WebpackMakeOutputExecutablePlugin(), ], }; diff --git a/workspaces/util/src/only.ts b/workspaces/util/src/only.ts index 16fb2177..b2e2ab20 100644 --- a/workspaces/util/src/only.ts +++ b/workspaces/util/src/only.ts @@ -1,6 +1,22 @@ import invariant from "invariant"; +import nullthrows from "nullthrows"; -export function only(array: readonly T[]): T { - invariant(array.length === 1, "Expected a single element array!"); - return array[0]; +export function only(iterable: Iterable): T { + if (Array.isArray(iterable)) { + const { length } = iterable; + invariant( + length === 1, + "Given array has length %d, not 1 as expected!", + length, + ); + return iterable[0]; + } + + let elementBox: { element: T } | null = null; + for (const element of iterable) { + invariant(elementBox == null, "Iterable had multiple elements!"); + elementBox = { element }; + } + + return nullthrows(elementBox, "Empty iterable!").element; }