create-pmos 0.247.0 → 0.249.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/KIT-VERSION +1 -1
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/scripts/delivery-index.js +34 -3
- package/payload/state/discoveries.md +1 -1
- package/workspace/lib/engine/delivery-index.js +34 -3
- package/workspace/lib/find-browser.js +19 -4
- package/workspace/lib/vendor/scripts/delivery-index.js +34 -3
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/server.js +51 -13
- package/workspace/test-files.json +1 -0
- package/workspace/test-lists/workspace-event-loop-lag.json +5 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-pmos",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.249.0",
|
|
4
4
|
"description": "Scaffold the PMOS portable kit — run the PMOS product-management workflow (initiative → lane-fit spec → contract → build → three gates, with work-type lanes for features, research, maintenance, incidents, and content) entirely inside your own repo, with all state as git-versioned files. Includes `workflow-sweep`, which audits the state every board derives from and separates real defects from work you simply have not done. `create-pmos workspace` also installs + launches the local Terminal Workspace GUI. No database, no network, no telemetry.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
package/payload/KIT-VERSION
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Reference
|
|
3
3
|
title: "Eval calibration log"
|
|
4
4
|
description: "Append-only record of every PM override of an evaluator verdict."
|
|
5
|
-
generated: {by: process:create-pmos, at: "2026-10-
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-15T09:38:27Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Reference
|
|
3
3
|
title: "Health budgets"
|
|
4
4
|
description: "Closed registry of health-budget anchors (D59), edited only by the PM."
|
|
5
|
-
generated: {by: process:create-pmos, at: "2026-10-
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-15T09:38:27Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -78,12 +78,43 @@ function gitCap(root, args) {
|
|
|
78
78
|
} catch (e) { return { ok: false, out: '', err: String((e && e.message) || e) }; }
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
// `trunkRef` runs on every call, and each probe is a `git` subprocess started with spawnSync, which holds the whole
|
|
82
|
+
// event loop of a long-lived server (the workspace calls this on every Initiatives and Library request). The answer
|
|
83
|
+
// only changes when a ref the probe reads changes, so it is kept per root against those refs' own content: the loose
|
|
84
|
+
// ref files and the stat of packed-refs. A key that cannot be read falls back to asking git, exactly as before.
|
|
85
|
+
const TRUNK_CANDIDATES = ['origin/main', 'main', 'origin/master', 'master'];
|
|
86
|
+
const trunkMemo = new Map(); // root -> { key, value }
|
|
87
|
+
function trunkKey(root) {
|
|
88
|
+
const fs = require('fs');
|
|
89
|
+
const path = require('path');
|
|
90
|
+
const dotgit = path.join(root, '.git');
|
|
91
|
+
let common = dotgit;
|
|
92
|
+
if (!fs.statSync(dotgit).isDirectory()) {
|
|
93
|
+
const m = /^gitdir:\s*(.+)\s*$/m.exec(fs.readFileSync(dotgit, 'utf8'));
|
|
94
|
+
if (!m) throw new Error('no gitdir');
|
|
95
|
+
const dir = path.resolve(root, m[1].trim());
|
|
96
|
+
common = dir;
|
|
97
|
+
try { common = path.resolve(dir, fs.readFileSync(path.join(dir, 'commondir'), 'utf8').trim()); } catch { /* not a linked worktree */ }
|
|
98
|
+
}
|
|
99
|
+
const loose = (rel) => { try { return fs.readFileSync(path.join(common, rel), 'utf8').trim(); } catch { return '-'; } };
|
|
100
|
+
let packed = '-';
|
|
101
|
+
try { const st = fs.statSync(path.join(common, 'packed-refs')); packed = `${st.size}:${st.mtimeMs}:${st.ino}`; } catch { /* no packed refs */ }
|
|
102
|
+
const parts = TRUNK_CANDIDATES.map((c) => loose(c.startsWith('origin/') ? `refs/remotes/${c}` : `refs/heads/${c}`));
|
|
103
|
+
return [packed, ...parts].join('|');
|
|
104
|
+
}
|
|
105
|
+
|
|
81
106
|
function trunkRef(root) {
|
|
82
|
-
|
|
107
|
+
let key = null;
|
|
108
|
+
try { key = trunkKey(root); } catch { key = null; }
|
|
109
|
+
const hit = trunkMemo.get(root);
|
|
110
|
+
if (key && hit && hit.key === key) return hit.value;
|
|
111
|
+
let value = null;
|
|
112
|
+
for (const c of TRUNK_CANDIDATES) {
|
|
83
113
|
const r = gitCap(root, ['rev-parse', '--verify', '--quiet', c]);
|
|
84
|
-
if (r.ok && r.out.trim())
|
|
114
|
+
if (r.ok && r.out.trim()) { value = { ref: c, head: r.out.trim() }; break; }
|
|
85
115
|
}
|
|
86
|
-
|
|
116
|
+
if (key) trunkMemo.set(root, { key, value });
|
|
117
|
+
return value;
|
|
87
118
|
}
|
|
88
119
|
|
|
89
120
|
// ONE `git log` per trunk head, memoised on that sha — ~0.3 s and ~800 KB on this repository's
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Reference
|
|
3
3
|
title: "Discoveries and friction log"
|
|
4
4
|
description: "Append-only record of what runs discovered and where the process rubbed."
|
|
5
|
-
generated: {by: process:create-pmos, at: "2026-10-
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-15T09:38:27Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -78,12 +78,43 @@ function gitCap(root, args) {
|
|
|
78
78
|
} catch (e) { return { ok: false, out: '', err: String((e && e.message) || e) }; }
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
// `trunkRef` runs on every call, and each probe is a `git` subprocess started with spawnSync, which holds the whole
|
|
82
|
+
// event loop of a long-lived server (the workspace calls this on every Initiatives and Library request). The answer
|
|
83
|
+
// only changes when a ref the probe reads changes, so it is kept per root against those refs' own content: the loose
|
|
84
|
+
// ref files and the stat of packed-refs. A key that cannot be read falls back to asking git, exactly as before.
|
|
85
|
+
const TRUNK_CANDIDATES = ['origin/main', 'main', 'origin/master', 'master'];
|
|
86
|
+
const trunkMemo = new Map(); // root -> { key, value }
|
|
87
|
+
function trunkKey(root) {
|
|
88
|
+
const fs = require('fs');
|
|
89
|
+
const path = require('path');
|
|
90
|
+
const dotgit = path.join(root, '.git');
|
|
91
|
+
let common = dotgit;
|
|
92
|
+
if (!fs.statSync(dotgit).isDirectory()) {
|
|
93
|
+
const m = /^gitdir:\s*(.+)\s*$/m.exec(fs.readFileSync(dotgit, 'utf8'));
|
|
94
|
+
if (!m) throw new Error('no gitdir');
|
|
95
|
+
const dir = path.resolve(root, m[1].trim());
|
|
96
|
+
common = dir;
|
|
97
|
+
try { common = path.resolve(dir, fs.readFileSync(path.join(dir, 'commondir'), 'utf8').trim()); } catch { /* not a linked worktree */ }
|
|
98
|
+
}
|
|
99
|
+
const loose = (rel) => { try { return fs.readFileSync(path.join(common, rel), 'utf8').trim(); } catch { return '-'; } };
|
|
100
|
+
let packed = '-';
|
|
101
|
+
try { const st = fs.statSync(path.join(common, 'packed-refs')); packed = `${st.size}:${st.mtimeMs}:${st.ino}`; } catch { /* no packed refs */ }
|
|
102
|
+
const parts = TRUNK_CANDIDATES.map((c) => loose(c.startsWith('origin/') ? `refs/remotes/${c}` : `refs/heads/${c}`));
|
|
103
|
+
return [packed, ...parts].join('|');
|
|
104
|
+
}
|
|
105
|
+
|
|
81
106
|
function trunkRef(root) {
|
|
82
|
-
|
|
107
|
+
let key = null;
|
|
108
|
+
try { key = trunkKey(root); } catch { key = null; }
|
|
109
|
+
const hit = trunkMemo.get(root);
|
|
110
|
+
if (key && hit && hit.key === key) return hit.value;
|
|
111
|
+
let value = null;
|
|
112
|
+
for (const c of TRUNK_CANDIDATES) {
|
|
83
113
|
const r = gitCap(root, ['rev-parse', '--verify', '--quiet', c]);
|
|
84
|
-
if (r.ok && r.out.trim())
|
|
114
|
+
if (r.ok && r.out.trim()) { value = { ref: c, head: r.out.trim() }; break; }
|
|
85
115
|
}
|
|
86
|
-
|
|
116
|
+
if (key) trunkMemo.set(root, { key, value });
|
|
117
|
+
return value;
|
|
87
118
|
}
|
|
88
119
|
|
|
89
120
|
// ONE `git log` per trunk head, memoised on that sha — ~0.3 s and ~800 KB on this repository's
|
|
@@ -26,13 +26,21 @@ const path = require('path');
|
|
|
26
26
|
// matching (which is the same skip-looks-like-pass failure, arriving later).
|
|
27
27
|
function playwrightCandidates() {
|
|
28
28
|
const roots = [process.env.PLAYWRIGHT_BROWSERS_PATH, '/opt/pw-browsers',
|
|
29
|
-
path.join(process.env.HOME || '', '.cache', 'ms-playwright')
|
|
29
|
+
path.join(process.env.HOME || '', '.cache', 'ms-playwright'),
|
|
30
|
+
path.join(process.env.HOME || '', 'Library', 'Caches', 'ms-playwright')].filter(Boolean);
|
|
30
31
|
const out = [];
|
|
31
32
|
for (const root of roots) {
|
|
32
33
|
let entries = [];
|
|
33
34
|
try { entries = fs.readdirSync(root); } catch { continue; }
|
|
34
35
|
for (const dir of entries.sort().reverse()) { // newest version first
|
|
35
36
|
if (!/^chromium/.test(dir)) continue;
|
|
37
|
+
// On a Mac or Windows PC a full Chromium shows in the Dock or taskbar, so a headless shell goes first there. Linux
|
|
38
|
+
// keeps its order: CI runs the browser suites on the browser it always picked.
|
|
39
|
+
if (process.platform !== 'linux') out.push(
|
|
40
|
+
path.join(root, dir, 'chrome-headless-shell-mac-arm64', 'chrome-headless-shell'),
|
|
41
|
+
path.join(root, dir, 'chrome-headless-shell-mac-x64', 'chrome-headless-shell'),
|
|
42
|
+
path.join(root, dir, 'chrome-headless-shell-win64', 'chrome-headless-shell.exe'),
|
|
43
|
+
);
|
|
36
44
|
out.push(
|
|
37
45
|
path.join(root, dir, 'chrome-linux', 'chrome'),
|
|
38
46
|
path.join(root, dir, 'chrome-linux', 'headless_shell'),
|
|
@@ -44,15 +52,22 @@ function playwrightCandidates() {
|
|
|
44
52
|
return out;
|
|
45
53
|
}
|
|
46
54
|
|
|
55
|
+
/* THE PM'S OWN CHROME IS NOT A TEST BROWSER. A suite that launches /Applications/Google Chrome.app opens a second Chrome
|
|
56
|
+
* that shows in the Dock and the taskbar for a few seconds, so a parallel run makes Chrome icons flicker on the PM's screen
|
|
57
|
+
* (and they cannot be opened). Only a headless browser is picked unless PMOS_CHROME / PMOS_CHROMIUM names one, or
|
|
58
|
+
* PMOS_ALLOW_SYSTEM_CHROME=1 says the visible app is fine (initiative chrome-dock-flicker). */
|
|
47
59
|
function findBrowser() {
|
|
60
|
+
const visible = process.env.PMOS_ALLOW_SYSTEM_CHROME === '1' ? [
|
|
61
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
62
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
63
|
+
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
64
|
+
] : [];
|
|
48
65
|
const cands = [
|
|
49
66
|
process.env.PMOS_CHROME,
|
|
50
67
|
process.env.PMOS_CHROMIUM,
|
|
51
68
|
...playwrightCandidates(),
|
|
52
|
-
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
53
|
-
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
54
69
|
'/usr/bin/google-chrome', '/usr/bin/chromium', '/usr/bin/chromium-browser',
|
|
55
|
-
|
|
70
|
+
...visible,
|
|
56
71
|
].filter(Boolean);
|
|
57
72
|
return cands.find((c) => { try { fs.accessSync(c, fs.constants.X_OK); return true; } catch { return false; } });
|
|
58
73
|
}
|
|
@@ -78,12 +78,43 @@ function gitCap(root, args) {
|
|
|
78
78
|
} catch (e) { return { ok: false, out: '', err: String((e && e.message) || e) }; }
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
// `trunkRef` runs on every call, and each probe is a `git` subprocess started with spawnSync, which holds the whole
|
|
82
|
+
// event loop of a long-lived server (the workspace calls this on every Initiatives and Library request). The answer
|
|
83
|
+
// only changes when a ref the probe reads changes, so it is kept per root against those refs' own content: the loose
|
|
84
|
+
// ref files and the stat of packed-refs. A key that cannot be read falls back to asking git, exactly as before.
|
|
85
|
+
const TRUNK_CANDIDATES = ['origin/main', 'main', 'origin/master', 'master'];
|
|
86
|
+
const trunkMemo = new Map(); // root -> { key, value }
|
|
87
|
+
function trunkKey(root) {
|
|
88
|
+
const fs = require('fs');
|
|
89
|
+
const path = require('path');
|
|
90
|
+
const dotgit = path.join(root, '.git');
|
|
91
|
+
let common = dotgit;
|
|
92
|
+
if (!fs.statSync(dotgit).isDirectory()) {
|
|
93
|
+
const m = /^gitdir:\s*(.+)\s*$/m.exec(fs.readFileSync(dotgit, 'utf8'));
|
|
94
|
+
if (!m) throw new Error('no gitdir');
|
|
95
|
+
const dir = path.resolve(root, m[1].trim());
|
|
96
|
+
common = dir;
|
|
97
|
+
try { common = path.resolve(dir, fs.readFileSync(path.join(dir, 'commondir'), 'utf8').trim()); } catch { /* not a linked worktree */ }
|
|
98
|
+
}
|
|
99
|
+
const loose = (rel) => { try { return fs.readFileSync(path.join(common, rel), 'utf8').trim(); } catch { return '-'; } };
|
|
100
|
+
let packed = '-';
|
|
101
|
+
try { const st = fs.statSync(path.join(common, 'packed-refs')); packed = `${st.size}:${st.mtimeMs}:${st.ino}`; } catch { /* no packed refs */ }
|
|
102
|
+
const parts = TRUNK_CANDIDATES.map((c) => loose(c.startsWith('origin/') ? `refs/remotes/${c}` : `refs/heads/${c}`));
|
|
103
|
+
return [packed, ...parts].join('|');
|
|
104
|
+
}
|
|
105
|
+
|
|
81
106
|
function trunkRef(root) {
|
|
82
|
-
|
|
107
|
+
let key = null;
|
|
108
|
+
try { key = trunkKey(root); } catch { key = null; }
|
|
109
|
+
const hit = trunkMemo.get(root);
|
|
110
|
+
if (key && hit && hit.key === key) return hit.value;
|
|
111
|
+
let value = null;
|
|
112
|
+
for (const c of TRUNK_CANDIDATES) {
|
|
83
113
|
const r = gitCap(root, ['rev-parse', '--verify', '--quiet', c]);
|
|
84
|
-
if (r.ok && r.out.trim())
|
|
114
|
+
if (r.ok && r.out.trim()) { value = { ref: c, head: r.out.trim() }; break; }
|
|
85
115
|
}
|
|
86
|
-
|
|
116
|
+
if (key) trunkMemo.set(root, { key, value });
|
|
117
|
+
return value;
|
|
87
118
|
}
|
|
88
119
|
|
|
89
120
|
// ONE `git log` per trunk head, memoised on that sha — ~0.3 s and ~800 KB on this repository's
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmos-workspace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.249.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pmos-workspace",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.249.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@xterm/addon-fit": "^0.11.0",
|
package/workspace/package.json
CHANGED
package/workspace/server.js
CHANGED
|
@@ -5864,19 +5864,48 @@ function fsList(dir) {
|
|
|
5864
5864
|
const TYPES = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8',
|
|
5865
5865
|
'.css': 'text/css; charset=utf-8', '.json': 'application/json', '.map': 'application/json',
|
|
5866
5866
|
'.svg': 'image/svg+xml', '.woff2': 'font/woff2' };
|
|
5867
|
+
// Compression and validators (workspace-event-loop-lag). The app shell is 5.7 MB of script and style and a library answer
|
|
5868
|
+
// is about 1 MB of JSON, and none of it was compressed, so a phone or an Online-address visit paid for every byte on every
|
|
5869
|
+
// load. A response is gzipped only when the client asks for it and the body is text over the threshold. Gzip runs on the
|
|
5870
|
+
// thread pool and never blocks the loop; a served file's gzip is made once per mtime and kept.
|
|
5871
|
+
const zlib = require('zlib');
|
|
5872
|
+
const GZIP_MIN_BYTES = 1024;
|
|
5873
|
+
const GZIP_TYPES = /^(?:text\/|application\/(?:json|javascript|manifest\+json)|image\/svg\+xml)/;
|
|
5874
|
+
const wantsGzip = (res) => { const q = res && res.req; return !!(q && q.headers && /\bgzip\b/i.test(String(q.headers['accept-encoding'] || ''))); };
|
|
5875
|
+
const fileMemo = new Map(); // file -> { sig, buf, etag, gz }
|
|
5876
|
+
const FILE_MEMO_MAX = 400;
|
|
5867
5877
|
function serveFile(res, file, type, noCache) {
|
|
5868
|
-
fs.
|
|
5869
|
-
if (
|
|
5870
|
-
const
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5878
|
+
fs.stat(file, (serr, st) => {
|
|
5879
|
+
if (serr || !st.isFile()) { res.writeHead(404); res.end('not found'); return; }
|
|
5880
|
+
const sig = `${st.size}:${st.mtimeMs}`;
|
|
5881
|
+
const send = (m) => {
|
|
5882
|
+
const ctype = type || TYPES[path.extname(file)] || 'application/octet-stream';
|
|
5883
|
+
const headers = { 'Content-Type': ctype, 'ETag': m.etag };
|
|
5884
|
+
// The app SHELL (index.html + the /pm/ module graph) must revalidate every load: without this a tab left open
|
|
5885
|
+
// across a `git merge` + server restart keeps running the pre-merge JS, so a fix that IS being
|
|
5886
|
+
// served never reaches the screen. `no-cache` = "always revalidate", not "never store".
|
|
5887
|
+
// Every script and stylesheet revalidates too, not only /pm/: a /lib/ module served with no header was kept by the
|
|
5888
|
+
// browser's heuristic cache, and at the Online address by Cloudflare's edge, so an updated workspace still drew
|
|
5889
|
+
// the old labels (workspace-kit-update). Images and fonts keep the default.
|
|
5890
|
+
// The ETag keeps that rule cheap: the revalidation of an unchanged file is a 304 with no body.
|
|
5891
|
+
if (noCache || /\.(js|mjs|css)$/.test(file)) headers['Cache-Control'] = 'no-cache';
|
|
5892
|
+
if (GZIP_TYPES.test(ctype)) headers['Vary'] = 'Accept-Encoding';
|
|
5893
|
+
const inm = res.req && res.req.headers && res.req.headers['if-none-match'];
|
|
5894
|
+
if (inm && String(inm).split(',').some((t) => t.trim() === m.etag)) { res.writeHead(304, headers); res.end(); return; }
|
|
5895
|
+
if (m.gz && wantsGzip(res)) { headers['Content-Encoding'] = 'gzip'; headers['Content-Length'] = m.gz.length; res.writeHead(200, headers); res.end(m.gz); return; }
|
|
5896
|
+
res.writeHead(200, headers);
|
|
5897
|
+
res.end(m.buf);
|
|
5898
|
+
};
|
|
5899
|
+
const hit = fileMemo.get(file);
|
|
5900
|
+
if (hit && hit.sig === sig) return send(hit);
|
|
5901
|
+
fs.readFile(file, (err, buf) => {
|
|
5902
|
+
if (err) { res.writeHead(404); res.end('not found'); return; }
|
|
5903
|
+
const m = { sig, buf, etag: `W/"${st.size.toString(16)}-${Math.floor(st.mtimeMs).toString(16)}"`, gz: null };
|
|
5904
|
+
const ctype = type || TYPES[path.extname(file)] || '';
|
|
5905
|
+
const finish = () => { if (fileMemo.size >= FILE_MEMO_MAX) fileMemo.clear(); fileMemo.set(file, m); send(m); };
|
|
5906
|
+
if (buf.length >= GZIP_MIN_BYTES && GZIP_TYPES.test(ctype)) zlib.gzip(buf, (e, gz) => { if (!e) m.gz = gz; finish(); });
|
|
5907
|
+
else finish();
|
|
5908
|
+
});
|
|
5880
5909
|
});
|
|
5881
5910
|
}
|
|
5882
5911
|
// claims-ledger — the claims ledger and its local checks. Local only: no key, no network. The ledger is file-mode
|
|
@@ -5986,8 +6015,17 @@ function readBody(req) {
|
|
|
5986
6015
|
return new Promise((resolve) => { let b = ''; req.on('data', (c) => (b += c)); req.on('end', () => resolve(b)); });
|
|
5987
6016
|
}
|
|
5988
6017
|
function json(res, obj, code = 200) {
|
|
6018
|
+
const body = JSON.stringify(obj);
|
|
6019
|
+
if (body.length >= GZIP_MIN_BYTES && wantsGzip(res)) {
|
|
6020
|
+
// Async, so a 1 MB library answer costs the loop nothing beyond the stringify (workspace-event-loop-lag).
|
|
6021
|
+
return zlib.gzip(body, (e, gz) => {
|
|
6022
|
+
if (e) { res.writeHead(code, { 'Content-Type': 'application/json' }); res.end(body); return; }
|
|
6023
|
+
res.writeHead(code, { 'Content-Type': 'application/json', 'Content-Encoding': 'gzip', 'Vary': 'Accept-Encoding', 'Content-Length': gz.length });
|
|
6024
|
+
res.end(gz);
|
|
6025
|
+
});
|
|
6026
|
+
}
|
|
5989
6027
|
res.writeHead(code, { 'Content-Type': 'application/json' });
|
|
5990
|
-
res.end(
|
|
6028
|
+
res.end(body);
|
|
5991
6029
|
}
|
|
5992
6030
|
|
|
5993
6031
|
async function handleRequest(req, res) {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "workspace-event-loop-lag: the server gzips text responses on request, sends an ETag with every served file, and reads the delivery trunk once per ref state (http-compression).",
|
|
3
|
+
"modules": ["http-compression.test.js"],
|
|
4
|
+
"seconds": { "http-compression.test.js": 8 }
|
|
5
|
+
}
|