diff --git a/.changeset/bundler-shutdown.md b/.changeset/bundler-shutdown.md new file mode 100644 index 000000000..853a1f8b2 --- /dev/null +++ b/.changeset/bundler-shutdown.md @@ -0,0 +1,5 @@ +--- +"@bigtest/bundler": patch +--- + +The `try`/`finally` in `Bundler` was not wrapping the code that it should have; fix this by wrapping more. \ No newline at end of file diff --git a/.changeset/manifest-builder-warnings.md b/.changeset/manifest-builder-warnings.md new file mode 100644 index 000000000..5e0c17150 --- /dev/null +++ b/.changeset/manifest-builder-warnings.md @@ -0,0 +1,5 @@ +--- +"@bigtest/server": patch +--- + +Work around bug in node.js that throws warnings when using `fs.promises.truncate()`: https://github.com/nodejs/node/issues/34189 diff --git a/.changeset/manifest-watcher.md b/.changeset/manifest-watcher.md new file mode 100644 index 000000000..93bf432ef --- /dev/null +++ b/.changeset/manifest-watcher.md @@ -0,0 +1,5 @@ +--- +"@bigtest/bundler": patch +--- + +Properly ignore `node_modules` from `Bundler` file watcher. diff --git a/packages/bundler/src/bundler.ts b/packages/bundler/src/bundler.ts index 7e4fb7f52..2efcb4c6c 100644 --- a/packages/bundler/src/bundler.ts +++ b/packages/bundler/src/bundler.ts @@ -1,8 +1,8 @@ import { Operation, resource } from 'effection'; import { on } from '@effection/events'; -import { Subscribable, SymbolSubscribable } from '@effection/subscription'; +import { subscribe, Subscribable, SymbolSubscribable, ChainableSubscription } from '@effection/subscription'; import { Channel } from '@effection/channel'; -import { watch, RollupWatchOptions, RollupWatcherEvent } from 'rollup'; +import { watch, RollupWatchOptions, RollupWatcherEvent, RollupWatcher } from 'rollup'; import resolve from '@rollup/plugin-node-resolve'; import * as commonjs from '@rollup/plugin-commonjs'; // eslint-disable-next-line @typescript-eslint/ban-ts-ignore @@ -28,6 +28,9 @@ export type BundlerMessage = | { type: 'error'; error: BundlerError }; function prepareRollupOptions(bundles: Array, { mainFields }: BundlerOptions = { mainFields: ["browser", "main"] }): Array { + // Rollup types are wrong; `watch.exclude` allows RegExp[] + // eslint-disable-next-line @typescript-eslint/ban-ts-ignore + // @ts-ignore return bundles.map(bundle => { return { input: bundle.entry, @@ -38,7 +41,7 @@ function prepareRollupOptions(bundles: Array, { mainFields }: Bun format: 'umd', }, watch: { - exclude: ['node_modules/**'] + exclude: [/node_modules/] }, plugins: [ resolve({ @@ -64,16 +67,18 @@ export class Bundler implements Subscribable { static *create(bundles: Array): Operation { let bundler = new Bundler(); - let rollup = watch(prepareRollupOptions(bundles)); - let events = Subscribable - .from(on>(rollup, 'event')) - .map(([event]) => event) - .filter(event => event.code === 'END' || event.code === 'ERROR') - .map(event => event.code === 'ERROR' ? { type: 'error', error: event.error } : { type: 'update' }); return yield resource(bundler, function*() { + let rollup: RollupWatcher = watch(prepareRollupOptions(bundles));; + try { - yield events.forEach(function*(message) { + let events: ChainableSubscription, undefined> = yield subscribe(on>(rollup, 'event')); + let messages = events + .map(([event]) => event) + .filter(event => event.code === 'END' || event.code === 'ERROR') + .map(event => event.code === 'ERROR' ? { type: 'error', error: event.error } : { type: 'update' }); + + yield messages.forEach(function*(message) { bundler.channel.send(message as BundlerMessage); }); } finally { diff --git a/packages/server/src/manifest-builder.ts b/packages/server/src/manifest-builder.ts index d48f11878..14ef006af 100644 --- a/packages/server/src/manifest-builder.ts +++ b/packages/server/src/manifest-builder.ts @@ -2,7 +2,7 @@ import { bigtestGlobals } from '@bigtest/globals'; import { Operation } from 'effection'; import { once } from '@effection/events'; import { subscribe, ChainableSubscription } from '@effection/subscription'; -import { Mailbox } from '@bigtest/effection'; +import { Mailbox, Deferred } from '@bigtest/effection'; import { Bundler, BundlerMessage, BundlerError } from '@bigtest/bundler'; import { Atom } from '@bigtest/atom'; import { createFingerprint } from 'fprint'; @@ -14,7 +14,7 @@ import { Test } from '@bigtest/suite'; import { OrchestratorState } from './orchestrator/state'; -const { copyFile, mkdir, truncate } = fs.promises; +const { copyFile, mkdir, stat, appendFile, open } = fs.promises; interface ManifestBuilderOptions { delegate: Mailbox; @@ -24,14 +24,39 @@ interface ManifestBuilderOptions { distDir: string; }; +function* ftruncate(fd: number, len: number): Operation { + let { resolve, reject, promise } = Deferred(); + + fs.ftruncate(fd, len, err => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + + yield promise; +} + +// https://github.com/nodejs/node/issues/34189#issuecomment-654878715 +function* truncate(path: string, len: number): Operation { + let file: fs.promises.FileHandle = yield open(path, 'r+'); + + try { + yield ftruncate(file.fd, len); + } finally { + file.close(); + } +} + export function* updateSourceMapURL(filePath: string, sourcemapName: string): Operation{ - let { size } = fs.statSync(filePath); + let { size } = yield stat(filePath); let readStream = fs.createReadStream(filePath, {start: size - 16}); let [currentURL]: [Buffer] = yield once(readStream, 'data'); if (currentURL.toString().trim() === 'manifest.js.map') { yield truncate(filePath, size - 16); - fs.appendFileSync(filePath, sourcemapName); + yield appendFile(filePath, sourcemapName); } else { throw new Error(`Expected a sourcemapping near the end of the generated test bundle, but found "${currentURL}" instead.`); }; @@ -66,7 +91,9 @@ function* processManifest(options: ManifestBuilderOptions): Operation { function logBuildError(error: BundlerError) { console.error("[manifest builder] build error:", error.message); - console.error("[manifest builder] build error frame:\n", error.frame); + if (error.frame) { + console.error("[manifest builder] build error frame:\n", error.frame); + } } function* waitForSuccessfulBuild(bundlerEvents: ChainableSubscription, delegate: Mailbox): Operation { @@ -103,7 +130,7 @@ export function* createManifestBuilder(options: ManifestBuilderOptions): Operati options.delegate.send({ event: "error" }); } else { let distPath = yield processManifest(options); - console.debug("[manifest builder] manifest updated"); + console.info("[manifest builder] manifest updated"); options.delegate.send({ event: "update", path: distPath }); } });