Skip to content

Commit

Permalink
Version 0.1.1 (#41)
Browse files Browse the repository at this point in the history
- Add custom responses for cache misses while network access is disabled
- Add custom responses for unmocked requests while network access is
disabled
- Add `--reset`/`-r` reset flag to restart jambox

---------

Co-authored-by: Arthur Buldauskas <[email protected]>
  • Loading branch information
ballercat and Arthur Buldauskas authored Nov 8, 2023
1 parent 4c73440 commit ffdcc82
Show file tree
Hide file tree
Showing 15 changed files with 287 additions and 65 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 0.1.1 - `--reset` cli arg, better messages

- Add custom responses for cache misses while network access is disabled
- Add custom responses for unmocked requests while network access is disabled
- Add `--reset`/`-r` reset flag to restart jambox

## 0.1.0 - Refactor

- Rework internal logic, bump to `0.1.0`
Expand Down
8 changes: 6 additions & 2 deletions jam.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env node
import record from './src/record.mjs';
import entrypoint from './src/entrypoint.mjs';
import * as constants from './src/constants.mjs';
import { enable as enableDiagnostics } from './src/diagnostics.js';

Expand All @@ -14,7 +14,7 @@ const run = async (argv, cwd) => {
process.exit(0);
}

record({
entrypoint({
script,
cwd,
log: console.log,
Expand All @@ -24,6 +24,10 @@ const run = async (argv, cwd) => {
.then((result) => {
if (result.browser) {
console.log('Browser launched');
// TODO: Figure out why spawning the browser process leaves
// our original node process running.
// Maybe we should always exit even for process launches
process.exit(0);
}

if (result.process) {
Expand Down
2 changes: 1 addition & 1 deletion recipes/nextjs-graphql-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"react-dom": "^18.2.0"
},
"scripts": {
"dev": "../../jam.mjs next",
"dev": "../../jam.mjs --reset next",
"build": "next build",
"start": "next start",
"visit": "../../jam.mjs http://jambox-demo-graphql.vercel.app",
Expand Down
6 changes: 5 additions & 1 deletion src/Config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ export default class Config extends Emitter {
noProxy = ['<-loopback->'];
trust = new Set();
forward = null;
cache = null;
/**
* @member {object|null}
*/
cache;
stub = null;
blockNetworkRequests = false;
paused = false;
Expand Down Expand Up @@ -70,6 +73,7 @@ export default class Config extends Emitter {
this.serverURL = new URL('http://localhost');
this.serverURL.port = port || '9000';
this.proxy = proxy;
this.cache = null;
this.update(rest);
}

Expand Down
54 changes: 35 additions & 19 deletions src/Jambox.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -45,54 +45,64 @@ export default class Jambox extends Emitter {
this.cache = new Cache();
this.proxy = proxy;

this.config.subscribe(this.reset.bind(this));
this.onAbort = this.onAbort.bind(this);
this.onRequest = this.onRequest.bind(this);
this.onResponse = this.onResponse.bind(this);
this.reset = this.reset.bind(this);

this.config.subscribe(this.reset);
}

async reset() {
debug('Reset');
await this.proxy.reset();
await this.cache.reset({ ...this.config.cache });

this.proxy.on('abort', this.#onAbort.bind(this));
this.proxy.on('request', this.#onRequest.bind(this));
this.proxy.on('response', this.#onResponse.bind(this));
this.proxy.on('abort', this.onAbort);
this.proxy.on('request', this.onRequest);
this.proxy.on('response', this.onResponse);

if (!this.config.blockNetworkRequests) {
await this.proxy
.forAnyRequest()
.asPriority(98)
.thenPassThrough({
// Trust any hosts specified.
ignoreHostHttpsErrors: [...this.config.trust],
ignoreHostHttpsErrors: Array.from(this.config.trust),
});
} else {
await this.proxy
.forAnyRequest()
.asPriority(98)
.thenReply(418, 'Network access disabled', '');
}

if (this.config.paused) {
return;
}

if (this.config.cache) {
await this.#record(this.config.cache);
await this.record(this.config.cache);
}

if (this.config.forward) {
await this.#forward(this.config.forward);
await this.forward(this.config.forward);
}

if (this.config.stub) {
await this.#stub(this.config.stub);
await this.stub(this.config.stub);
}
}

#record(setting) {
record(setting) {
return this.proxy.addRequestRule({
priority: 100,
matchers: [new CacheMatcher(this, setting)],
handler: new CacheHandler(this),
});
}

#forward(setting) {
forward(setting) {
return Promise.all(
Object.entries(setting).map(async ([original, ...rest]) => {
const options =
Expand Down Expand Up @@ -150,15 +160,15 @@ export default class Jambox extends Emitter {
);
}

#stub(setting) {
stub(setting) {
return Promise.all(
Object.entries(setting).map(([path, value]) => {
const options = typeof value === 'object' ? value : { status: value };
if (options.preferNetwork && !this.config.blockNetworkRequests) {
return;
}

let response = null;
let response = Buffer.from('');
if (options.file) {
response = fs.readFileSync(options.file);
} else if (options.body && typeof options.body === 'object') {
Expand All @@ -178,7 +188,7 @@ export default class Jambox extends Emitter {
);
}

#shouldStage(url) {
shouldStage(url) {
if (this.cache.bypass() || this.config.blockNetworkRequests) {
return false;
}
Expand All @@ -187,19 +197,25 @@ export default class Jambox extends Emitter {
const stageList = this.config.cache?.stage || [];

const matchValue = url.hostname + url.pathname;
if (ignoreList.some((glob) => minimatch(matchValue, glob))) {
if (
ignoreList.some((/** @type {string} */ glob) =>
minimatch(matchValue, glob)
)
) {
return false;
}

return stageList.some((glob) => minimatch(matchValue, glob));
return stageList.some((/** @type {string} */ glob) =>
minimatch(matchValue, glob)
);
}

async #onRequest(request) {
async onRequest(request) {
try {
const url = new URL(request.url);
const hash = await Cache.hash(request);
const cached = this.cache.has(hash);
const staged = cached ? false : this.#shouldStage(url);
const staged = cached ? false : this.shouldStage(url);

if (staged) {
this.cache.add(request);
Expand All @@ -218,7 +234,7 @@ export default class Jambox extends Emitter {
}
}

async #onResponse(response) {
async onResponse(response) {
try {
if (!this.cache.bypass() && this.cache.hasStaged(response)) {
await this.cache.commit(response);
Expand All @@ -231,7 +247,7 @@ export default class Jambox extends Emitter {
}
}

async #onAbort(abortedRequest) {
async onAbort(abortedRequest) {
if (this.cache.hasStaged(abortedRequest)) {
this.cache.abort(abortedRequest);
}
Expand Down
43 changes: 43 additions & 0 deletions src/__tests__/__snapshots__/parse-args.test.mjs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Snapshot report for `src/__tests__/parse-args.test.mjs`

The actual snapshot is saved in `parse-args.test.mjs.snap`.

Generated by [AVA](https://avajs.dev).

## arg parsing, jambox flags: no

> Snapshot 1
{
target: [
'yarn',
'dev',
],
}

## arg parsing, jambox flags: yes

> Snapshot 1
{
reset: true,
target: [
'yarn',
'dev',
],
}

## arg parsing, custom flags

> Snapshot 1
{
port: 99,
reset: true,
target: [
'yarn',
'dev',
'--port',
'9000',
],
}
Binary file added src/__tests__/__snapshots__/parse-args.test.mjs.snap
Binary file not shown.
37 changes: 37 additions & 0 deletions src/__tests__/parse-args.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import test from 'ava';
import { parseArgs, JAMBOX_FLAGS } from '../parse-args.mjs';

test('arg parsing, jambox flags: no', (t) => {
const flags = parseArgs(['yarn', 'dev'], JAMBOX_FLAGS);
t.snapshot(flags);
});

test('arg parsing, jambox flags: yes', (t) => {
const flags = parseArgs(['-r', 'yarn', 'dev'], JAMBOX_FLAGS);
t.snapshot(flags);
});

test('arg parsing, custom flags', (t) => {
const flags = parseArgs(
[
'--port',
'0',
'-p',
'99',
'-r',
'--reset',
'yarn',
'dev',
'--port',
'9000',
],
{
...JAMBOX_FLAGS,
'--port': ['port', 1, String],
// shouldn't do it this way but it's allowed to change the types here
'-p': ['port', 1, Number],
}
);

t.snapshot(flags);
});
4 changes: 2 additions & 2 deletions src/__tests__/server.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ test.before(async (t) => {
try {
t.context.server = await server({
port: SERVER_PORT,
nodeProcess: { on() {}, exit() {} },
nodeProcess: { on() {}, exit() {}, pid: '0' },
});

// Setup a tiny server
Expand Down Expand Up @@ -199,7 +199,7 @@ test.serial('pause', async (t) => {
});

// NOTE: This does work but needs a better cache mock
test('server - reset', async (t) => {
test.serial('server - reset', async (t) => {
t.assert(t.context.server, `Server init error: ${t.context.error?.stack}`);

const cacheDir = path.join(PROJECT_ROOT, 'src', '__mocks__', 'cache-dir');
Expand Down
12 changes: 7 additions & 5 deletions src/record.mjs → src/entrypoint.mjs
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
// @ts-check
import * as path from 'node:path';
import { spawn } from 'node:child_process';
import fetch from 'node-fetch';
import path from 'path';
import { spawn } from 'child_process';
import persistRuntimeConfig from './persist-runtime-config.mjs';
import launchProxiedChrome from './browser.mjs';
import isURI from './is-uri.mjs';
import launchServer from './server-launcher.mjs';
import Config from './Config.mjs';
import { parseArgs, JAMBOX_FLAGS } from './parse-args.mjs';
import { createDebug } from './diagnostics.js';

const debug = createDebug();

export default async function record(options) {
export default async function cli(options) {
const { script, cwd = process.cwd(), log, env, constants } = options;
const [entrypoint, ...args] = script;
const flags = parseArgs(script, JAMBOX_FLAGS);
const [entrypoint, ...args] = flags.target;

debug('Checking if a server instance is running.');

const config = new Config();
config.load(cwd);

try {
await launchServer({ log, constants, config });
await launchServer({ log, constants, config, flags });
} catch (error) {
log(`Failed to launch a server, terminating. ${error}`);
throw error;
Expand Down
Loading

0 comments on commit ffdcc82

Please sign in to comment.