Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Manifest builder improvements #401

Merged
merged 11 commits into from
Jul 14, 2020
5 changes: 5 additions & 0 deletions .changeset/bundler-shutdown.md
Original file line number Diff line number Diff line change
@@ -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.
5 changes: 5 additions & 0 deletions .changeset/manifest-builder-warnings.md
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions .changeset/manifest-watcher.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bigtest/bundler": patch
---

Properly ignore `node_modules` from `Bundler` file watcher.
25 changes: 15 additions & 10 deletions packages/bundler/src/bundler.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -28,6 +28,9 @@ export type BundlerMessage =
| { type: 'error'; error: BundlerError };

function prepareRollupOptions(bundles: Array<BundleOptions>, { mainFields }: BundlerOptions = { mainFields: ["browser", "main"] }): Array<RollupWatchOptions> {
// 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,
Expand All @@ -38,7 +41,7 @@ function prepareRollupOptions(bundles: Array<BundleOptions>, { mainFields }: Bun
format: 'umd',
},
watch: {
exclude: ['node_modules/**']
exclude: [/node_modules/]
},
plugins: [
resolve({
Expand All @@ -64,16 +67,18 @@ export class Bundler implements Subscribable<BundlerMessage, undefined> {

static *create(bundles: Array<BundleOptions>): Operation<Bundler> {
let bundler = new Bundler();
let rollup = watch(prepareRollupOptions(bundles));
let events = Subscribable
.from(on<Array<RollupWatcherEvent>>(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<Array<RollupWatcherEvent>, undefined> = yield subscribe(on<Array<RollupWatcherEvent>>(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 {
Expand Down
39 changes: 33 additions & 6 deletions packages/server/src/manifest-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand All @@ -24,14 +24,39 @@ interface ManifestBuilderOptions {
distDir: string;
};

function* ftruncate(fd: number, len: number): Operation<void> {
let { resolve, reject, promise } = Deferred<void>();

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.`);
};
Expand Down Expand Up @@ -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<BundlerMessage, undefined>, delegate: Mailbox): Operation {
Expand Down Expand Up @@ -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 });
}
});
Expand Down