@epic-web/workshop-utils 4.0.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/README.md +3 -0
- package/dist/esm/apps.server.d.ts +3861 -0
- package/dist/esm/apps.server.d.ts.map +1 -0
- package/dist/esm/apps.server.js +1011 -0
- package/dist/esm/apps.server.js.map +1 -0
- package/dist/esm/cache.server.d.ts +798 -0
- package/dist/esm/cache.server.d.ts.map +1 -0
- package/dist/esm/cache.server.js +113 -0
- package/dist/esm/cache.server.js.map +1 -0
- package/dist/esm/change-tracker.server.d.ts +8 -0
- package/dist/esm/change-tracker.server.d.ts.map +1 -0
- package/dist/esm/change-tracker.server.js +32 -0
- package/dist/esm/change-tracker.server.js.map +1 -0
- package/dist/esm/codefile-mdx.server.d.ts +16 -0
- package/dist/esm/codefile-mdx.server.d.ts.map +1 -0
- package/dist/esm/codefile-mdx.server.js +275 -0
- package/dist/esm/codefile-mdx.server.js.map +1 -0
- package/dist/esm/compile-mdx.server.d.ts +11 -0
- package/dist/esm/compile-mdx.server.d.ts.map +1 -0
- package/dist/esm/compile-mdx.server.js +330 -0
- package/dist/esm/compile-mdx.server.js.map +1 -0
- package/dist/esm/db.server.d.ts +176 -0
- package/dist/esm/db.server.d.ts.map +1 -0
- package/dist/esm/db.server.js +203 -0
- package/dist/esm/db.server.js.map +1 -0
- package/dist/esm/git.server.d.ts +27 -0
- package/dist/esm/git.server.d.ts.map +1 -0
- package/dist/esm/git.server.js +93 -0
- package/dist/esm/git.server.js.map +1 -0
- package/dist/esm/iframe-sync.d.ts +10 -0
- package/dist/esm/iframe-sync.d.ts.map +1 -0
- package/dist/esm/iframe-sync.js +101 -0
- package/dist/esm/iframe-sync.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/playwright.server.d.ts +6 -0
- package/dist/esm/playwright.server.d.ts.map +1 -0
- package/dist/esm/playwright.server.js +94 -0
- package/dist/esm/playwright.server.js.map +1 -0
- package/dist/esm/process-manager.server.d.ts +78 -0
- package/dist/esm/process-manager.server.d.ts.map +1 -0
- package/dist/esm/process-manager.server.js +267 -0
- package/dist/esm/process-manager.server.js.map +1 -0
- package/dist/esm/test.d.ts +9 -0
- package/dist/esm/test.d.ts.map +1 -0
- package/dist/esm/test.js +45 -0
- package/dist/esm/test.js.map +1 -0
- package/dist/esm/timing.server.d.ts +20 -0
- package/dist/esm/timing.server.d.ts.map +1 -0
- package/dist/esm/timing.server.js +89 -0
- package/dist/esm/timing.server.js.map +1 -0
- package/dist/esm/utils.d.ts +2 -0
- package/dist/esm/utils.d.ts.map +1 -0
- package/dist/esm/utils.js +13 -0
- package/dist/esm/utils.js.map +1 -0
- package/dist/esm/utils.server.d.ts +3 -0
- package/dist/esm/utils.server.d.ts.map +1 -0
- package/dist/esm/utils.server.js +32 -0
- package/dist/esm/utils.server.js.map +1 -0
- package/package.json +171 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
import { type ChildProcess } from 'child_process';
|
|
3
|
+
import closeWithGrace from 'close-with-grace';
|
|
4
|
+
import { type App } from './apps.server.js';
|
|
5
|
+
type DevProcessesMap = Map<string, {
|
|
6
|
+
color: (typeof colors)[number];
|
|
7
|
+
process: ChildProcess;
|
|
8
|
+
port: number;
|
|
9
|
+
}>;
|
|
10
|
+
type OutputLine = {
|
|
11
|
+
type: 'stdout' | 'stderr';
|
|
12
|
+
content: string;
|
|
13
|
+
timestamp: number;
|
|
14
|
+
};
|
|
15
|
+
type TestProcessEntry = {
|
|
16
|
+
process: ChildProcess | null;
|
|
17
|
+
output: Array<OutputLine>;
|
|
18
|
+
exitCode?: number | null;
|
|
19
|
+
};
|
|
20
|
+
type TestProcessesMap = Map<string, TestProcessEntry>;
|
|
21
|
+
declare global {
|
|
22
|
+
var __process_dev_close_with_grace_return__: ReturnType<typeof closeWithGrace>, __process_test_close_with_grace_return__: ReturnType<typeof closeWithGrace>;
|
|
23
|
+
}
|
|
24
|
+
declare const colors: readonly ["blue", "green", "yellow", "red", "magenta", "redBright", "greenBright", "yellowBright", "blueBright", "magentaBright"];
|
|
25
|
+
export declare function runAppDev(app: App): Promise<{
|
|
26
|
+
readonly status: "process-running";
|
|
27
|
+
readonly running: true;
|
|
28
|
+
readonly error?: undefined;
|
|
29
|
+
readonly portNumber?: undefined;
|
|
30
|
+
} | {
|
|
31
|
+
readonly status: "error";
|
|
32
|
+
readonly error: "no-server";
|
|
33
|
+
readonly running?: undefined;
|
|
34
|
+
readonly portNumber?: undefined;
|
|
35
|
+
} | {
|
|
36
|
+
readonly status: "port-unavailable";
|
|
37
|
+
readonly running: false;
|
|
38
|
+
readonly portNumber: number;
|
|
39
|
+
readonly error?: undefined;
|
|
40
|
+
} | {
|
|
41
|
+
readonly status: "process-started";
|
|
42
|
+
readonly running: true;
|
|
43
|
+
readonly error?: undefined;
|
|
44
|
+
readonly portNumber?: undefined;
|
|
45
|
+
}>;
|
|
46
|
+
export declare function runAppTests(app: App): Promise<import("execa").ExecaReturnValue<string> | {
|
|
47
|
+
readonly status: "error";
|
|
48
|
+
readonly error: "no-test";
|
|
49
|
+
}>;
|
|
50
|
+
export declare function waitOnApp(app: App): Promise<{
|
|
51
|
+
readonly status: "success";
|
|
52
|
+
readonly error?: undefined;
|
|
53
|
+
} | {
|
|
54
|
+
readonly status: "error";
|
|
55
|
+
readonly error: string;
|
|
56
|
+
} | null>;
|
|
57
|
+
export declare function isPortAvailable(port: number | string): Promise<boolean>;
|
|
58
|
+
export declare function isAppRunning(app: {
|
|
59
|
+
name: string;
|
|
60
|
+
}): boolean;
|
|
61
|
+
export declare function isTestRunning(app: {
|
|
62
|
+
name: string;
|
|
63
|
+
}): boolean;
|
|
64
|
+
export declare function getTestProcessEntry(app: {
|
|
65
|
+
name: string;
|
|
66
|
+
}): TestProcessEntry | undefined;
|
|
67
|
+
export declare function clearTestProcessEntry(app: {
|
|
68
|
+
name: string;
|
|
69
|
+
}): boolean;
|
|
70
|
+
export declare function getProcesses(): {
|
|
71
|
+
devProcesses: DevProcessesMap;
|
|
72
|
+
testProcesses: TestProcessesMap;
|
|
73
|
+
};
|
|
74
|
+
export declare function closeProcess(key: string): Promise<void>;
|
|
75
|
+
export declare function stopPort(port: string | number): Promise<void>;
|
|
76
|
+
export declare function waitForPortToBeAvailable(port: string | number): Promise<void>;
|
|
77
|
+
export {};
|
|
78
|
+
//# sourceMappingURL=process-manager.server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-manager.server.d.ts","sourceRoot":"","sources":["../../src/process-manager.server.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,KAAK,YAAY,EAAS,MAAM,eAAe,CAAA;AAIxD,OAAO,cAAc,MAAM,kBAAkB,CAAA;AAG7C,OAAO,EAAE,KAAK,GAAG,EAAE,MAAM,kBAAkB,CAAA;AAO3C,KAAK,eAAe,GAAG,GAAG,CACzB,MAAM,EACN;IACC,KAAK,EAAE,CAAC,OAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAA;IAC9B,OAAO,EAAE,YAAY,CAAA;IACrB,IAAI,EAAE,MAAM,CAAA;CACZ,CACD,CAAA;AAED,KAAK,UAAU,GAAG;IACjB,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAA;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,KAAK,gBAAgB,GAAG;IACvB,OAAO,EAAE,YAAY,GAAG,IAAI,CAAA;IAC5B,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC,CAAA;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB,CAAA;AAED,KAAK,gBAAgB,GAAG,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;AACrD,OAAO,CAAC,MAAM,CAAC;IACd,IAAI,uCAAuC,EAAE,UAAU,CACrD,OAAO,cAAc,CACrB,EACD,wCAAwC,EAAE,UAAU,CAAC,OAAO,cAAc,CAAC,CAAA;CAC5E;AAmCD,QAAA,MAAM,MAAM,mIAWF,CAAA;AAEV,wBAAsB,SAAS,CAAC,GAAG,EAAE,GAAG;;;;;;;;;;;;;;;;;;;;GAoEvC;AAED,wBAAsB,WAAW,CAAC,GAAG,EAAE,GAAG;;;GAmDzC;AAED,wBAAsB,SAAS,CAAC,GAAG,EAAE,GAAG;;;;;;UAuBvC;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAYvE;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,WASjD;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,WAUlD;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,gCAExD;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,WAE1D;AAED,wBAAgB,YAAY;;;EAE3B;AAED,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,iBAoB7C;AAID,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,iBAInD;AAED,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,iBAWnE"}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import net from 'node:net';
|
|
3
|
+
import { remember } from '@epic-web/remember';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import closeWithGrace from 'close-with-grace';
|
|
6
|
+
import { execaCommand } from 'execa';
|
|
7
|
+
import fkill from 'fkill';
|
|
8
|
+
import { getErrorMessage } from './utils.js';
|
|
9
|
+
const isDeployed = process.env.EPICSHOP_DEPLOYED === 'true' ||
|
|
10
|
+
process.env.EPICSHOP_DEPLOYED === '1';
|
|
11
|
+
const devProcesses = remember('dev_processes', getDevProcessesMap);
|
|
12
|
+
const testProcesses = remember('test_processes', getTestProcessesMap);
|
|
13
|
+
function getDevProcessesMap() {
|
|
14
|
+
const procs = new Map();
|
|
15
|
+
global.__process_dev_close_with_grace_return__?.uninstall();
|
|
16
|
+
global.__process_dev_close_with_grace_return__ = closeWithGrace(async () => {
|
|
17
|
+
for (const [name, proc] of procs.entries()) {
|
|
18
|
+
console.log('closing', name);
|
|
19
|
+
proc.process.kill();
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
return procs;
|
|
23
|
+
}
|
|
24
|
+
function getTestProcessesMap() {
|
|
25
|
+
const procs = new Map();
|
|
26
|
+
global.__process_test_close_with_grace_return__?.uninstall();
|
|
27
|
+
global.__process_test_close_with_grace_return__ = closeWithGrace(async () => {
|
|
28
|
+
for (const [id, proc] of procs.entries()) {
|
|
29
|
+
if (proc.process) {
|
|
30
|
+
console.log('closing', id);
|
|
31
|
+
proc.process.kill();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
return procs;
|
|
36
|
+
}
|
|
37
|
+
const colors = [
|
|
38
|
+
'blue',
|
|
39
|
+
'green',
|
|
40
|
+
'yellow',
|
|
41
|
+
'red',
|
|
42
|
+
'magenta',
|
|
43
|
+
'redBright',
|
|
44
|
+
'greenBright',
|
|
45
|
+
'yellowBright',
|
|
46
|
+
'blueBright',
|
|
47
|
+
'magentaBright',
|
|
48
|
+
];
|
|
49
|
+
export async function runAppDev(app) {
|
|
50
|
+
if (isDeployed)
|
|
51
|
+
throw new Error('cannot run apps in deployed mode');
|
|
52
|
+
const key = app.name;
|
|
53
|
+
// if the app is already running, don't start it again
|
|
54
|
+
if (devProcesses.has(key)) {
|
|
55
|
+
return { status: 'process-running', running: true };
|
|
56
|
+
}
|
|
57
|
+
if (app.dev.type !== 'script') {
|
|
58
|
+
return { status: 'error', error: 'no-server' };
|
|
59
|
+
}
|
|
60
|
+
const { portNumber } = app.dev;
|
|
61
|
+
if (!(await isPortAvailable(portNumber))) {
|
|
62
|
+
return { status: 'port-unavailable', running: false, portNumber };
|
|
63
|
+
}
|
|
64
|
+
const availableColors = colors.filter(color => Array.from(devProcesses.values()).every(p => p.color !== color));
|
|
65
|
+
const color = availableColors[devProcesses.size % availableColors.length] ?? 'blue';
|
|
66
|
+
const appProcess = spawn('npm', ['run', 'dev'], {
|
|
67
|
+
cwd: app.fullPath,
|
|
68
|
+
shell: true,
|
|
69
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
70
|
+
env: {
|
|
71
|
+
...process.env,
|
|
72
|
+
// TODO: support specifying the env
|
|
73
|
+
NODE_ENV: 'development',
|
|
74
|
+
// TODO: support specifying the port
|
|
75
|
+
PORT: String(portNumber),
|
|
76
|
+
APP_SERVER_PORT: String(portNumber),
|
|
77
|
+
// let it pick a random port...
|
|
78
|
+
REMIX_DEV_SERVER_WS_PORT: '',
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
const prefix = chalk[color](`[${app.name.replace(/^exercises\./, '')}:${portNumber}]`);
|
|
82
|
+
function handleStdOutData(data) {
|
|
83
|
+
console.log(data
|
|
84
|
+
.toString('utf-8')
|
|
85
|
+
.split('\n')
|
|
86
|
+
.map(line => `${prefix} ${line}`)
|
|
87
|
+
.join('\n'));
|
|
88
|
+
}
|
|
89
|
+
appProcess.stdout.on('data', handleStdOutData);
|
|
90
|
+
function handleStdErrData(data) {
|
|
91
|
+
console.error(data
|
|
92
|
+
.toString('utf-8')
|
|
93
|
+
.split('\n')
|
|
94
|
+
.map(line => `${prefix} ${line}`)
|
|
95
|
+
.join('\n'));
|
|
96
|
+
}
|
|
97
|
+
appProcess.stderr.on('data', handleStdErrData);
|
|
98
|
+
devProcesses.set(key, { color, process: appProcess, port: portNumber });
|
|
99
|
+
appProcess.on('exit', code => {
|
|
100
|
+
appProcess.stdout.off('data', handleStdOutData);
|
|
101
|
+
appProcess.stderr.off('data', handleStdErrData);
|
|
102
|
+
console.log(`${prefix} exited (${code})`);
|
|
103
|
+
devProcesses.delete(key);
|
|
104
|
+
});
|
|
105
|
+
return { status: 'process-started', running: true };
|
|
106
|
+
}
|
|
107
|
+
export async function runAppTests(app) {
|
|
108
|
+
if (isDeployed)
|
|
109
|
+
throw new Error('cannot run tests in deployed mode');
|
|
110
|
+
const key = app.name;
|
|
111
|
+
if (app.test.type !== 'script') {
|
|
112
|
+
return { status: 'error', error: 'no-test' };
|
|
113
|
+
}
|
|
114
|
+
const testProcess = execaCommand(app.test.script, {
|
|
115
|
+
cwd: app.fullPath,
|
|
116
|
+
shell: true,
|
|
117
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
118
|
+
env: {
|
|
119
|
+
...process.env,
|
|
120
|
+
// TODO: support specifying the env
|
|
121
|
+
NODE_ENV: 'development',
|
|
122
|
+
// TODO: support specifying the port
|
|
123
|
+
PORT: app.dev.type === 'script' ? String(app.dev.portNumber) : undefined,
|
|
124
|
+
APP_SERVER_PORT: app.dev.type === 'script' ? String(app.dev.portNumber) : undefined,
|
|
125
|
+
// let it pick a random port...
|
|
126
|
+
REMIX_DEV_SERVER_WS_PORT: '',
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
const output = [];
|
|
130
|
+
const entry = { process: testProcess, output };
|
|
131
|
+
function handleStdOutData(data) {
|
|
132
|
+
output.push({
|
|
133
|
+
type: 'stdout',
|
|
134
|
+
content: data.toString('utf-8'),
|
|
135
|
+
timestamp: Date.now(),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
testProcess.stdout?.on('data', handleStdOutData);
|
|
139
|
+
function handleStdErrData(data) {
|
|
140
|
+
output.push({
|
|
141
|
+
type: 'stderr',
|
|
142
|
+
content: data.toString('utf-8'),
|
|
143
|
+
timestamp: Date.now(),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
testProcess.stderr?.on('data', handleStdErrData);
|
|
147
|
+
void testProcess.on('exit', code => {
|
|
148
|
+
testProcess.stdout?.off('data', handleStdOutData);
|
|
149
|
+
testProcess.stderr?.off('data', handleStdErrData);
|
|
150
|
+
// don't delete the entry from the map so we can show the output at any time
|
|
151
|
+
entry.process = null;
|
|
152
|
+
entry.exitCode = code;
|
|
153
|
+
});
|
|
154
|
+
testProcesses.set(key, entry);
|
|
155
|
+
return testProcess;
|
|
156
|
+
}
|
|
157
|
+
export async function waitOnApp(app) {
|
|
158
|
+
if (app.dev.type === 'script') {
|
|
159
|
+
const startTime = Date.now();
|
|
160
|
+
const retryInterval = 100;
|
|
161
|
+
const timeout = 20_000;
|
|
162
|
+
let lastError;
|
|
163
|
+
while (Date.now() - startTime < timeout) {
|
|
164
|
+
try {
|
|
165
|
+
await fetch(`http://localhost:${app.dev.portNumber}`, {
|
|
166
|
+
method: 'HEAD',
|
|
167
|
+
headers: { Accept: '*/*' },
|
|
168
|
+
});
|
|
169
|
+
return { status: 'success' };
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
lastError = error;
|
|
173
|
+
await new Promise(resolve => setTimeout(resolve, retryInterval));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return { status: 'error', error: getErrorMessage(lastError) };
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
export function isPortAvailable(port) {
|
|
181
|
+
return new Promise(resolve => {
|
|
182
|
+
const server = net.createServer();
|
|
183
|
+
server.unref();
|
|
184
|
+
server.on('error', () => resolve(false));
|
|
185
|
+
server.listen(Number(port), () => {
|
|
186
|
+
server.close(() => {
|
|
187
|
+
resolve(true);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
export function isAppRunning(app) {
|
|
193
|
+
try {
|
|
194
|
+
const devProcess = devProcesses.get(app.name);
|
|
195
|
+
if (!devProcess)
|
|
196
|
+
return false;
|
|
197
|
+
devProcess.process.kill(0);
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
export function isTestRunning(app) {
|
|
205
|
+
try {
|
|
206
|
+
const testProcess = testProcesses.get(app.name);
|
|
207
|
+
if (!testProcess)
|
|
208
|
+
return false;
|
|
209
|
+
if (testProcess.process === null)
|
|
210
|
+
return false;
|
|
211
|
+
testProcess.process.kill(0);
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
export function getTestProcessEntry(app) {
|
|
219
|
+
return testProcesses.get(app.name);
|
|
220
|
+
}
|
|
221
|
+
export function clearTestProcessEntry(app) {
|
|
222
|
+
return testProcesses.delete(app.name);
|
|
223
|
+
}
|
|
224
|
+
export function getProcesses() {
|
|
225
|
+
return { devProcesses, testProcesses };
|
|
226
|
+
}
|
|
227
|
+
export async function closeProcess(key) {
|
|
228
|
+
if (isDeployed)
|
|
229
|
+
throw new Error('cannot close processes in deployed mode');
|
|
230
|
+
const proc = devProcesses.get(key);
|
|
231
|
+
if (proc) {
|
|
232
|
+
const exitedPromise = new Promise(resolve => proc.process.on('exit', resolve));
|
|
233
|
+
if (process.platform === 'win32') {
|
|
234
|
+
const { execa } = await import('execa');
|
|
235
|
+
await execa('taskkill', ['/pid', String(proc.process.pid), '/f', '/t']);
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
proc.process.kill();
|
|
239
|
+
}
|
|
240
|
+
await Promise.race([
|
|
241
|
+
new Promise(resolve => setTimeout(resolve, 500)),
|
|
242
|
+
exitedPromise,
|
|
243
|
+
]);
|
|
244
|
+
await stopPort(proc.port); // just in case 🤷♂️
|
|
245
|
+
devProcesses.delete(key);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const sleep = (t) => new Promise(resolve => setTimeout(resolve, t));
|
|
249
|
+
export async function stopPort(port) {
|
|
250
|
+
if (isDeployed)
|
|
251
|
+
throw new Error('cannot stop ports in deployed mode');
|
|
252
|
+
await fkill(`:${port}`, { force: true, silent: true });
|
|
253
|
+
await waitForPortToBeAvailable(port);
|
|
254
|
+
}
|
|
255
|
+
export async function waitForPortToBeAvailable(port) {
|
|
256
|
+
// wait for the port to become available again
|
|
257
|
+
const timeout = Date.now() + 10_000;
|
|
258
|
+
let portAvailable = false;
|
|
259
|
+
do {
|
|
260
|
+
portAvailable = await isPortAvailable(port);
|
|
261
|
+
await sleep(100);
|
|
262
|
+
} while (!portAvailable && Date.now() < timeout);
|
|
263
|
+
if (!portAvailable) {
|
|
264
|
+
console.error('Timed out waiting for the port to become available');
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
//# sourceMappingURL=process-manager.server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-manager.server.js","sourceRoot":"","sources":["../../src/process-manager.server.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ChildProcess, spawn } from 'child_process'\nimport net from 'node:net'\nimport { remember } from '@epic-web/remember'\nimport chalk from 'chalk'\nimport closeWithGrace from 'close-with-grace'\nimport { execaCommand } from 'execa'\nimport fkill from 'fkill'\nimport { type App } from './apps.server.js'\nimport { getErrorMessage } from './utils.js'\n\nconst isDeployed =\n\tprocess.env.EPICSHOP_DEPLOYED === 'true' ||\n\tprocess.env.EPICSHOP_DEPLOYED === '1'\n\ntype DevProcessesMap = Map<\n\tstring,\n\t{\n\t\tcolor: (typeof colors)[number]\n\t\tprocess: ChildProcess\n\t\tport: number\n\t}\n>\n\ntype OutputLine = {\n\ttype: 'stdout' | 'stderr'\n\tcontent: string\n\ttimestamp: number\n}\n\ntype TestProcessEntry = {\n\tprocess: ChildProcess | null\n\toutput: Array<OutputLine>\n\texitCode?: number | null\n}\n\ntype TestProcessesMap = Map<string, TestProcessEntry>\ndeclare global {\n\tvar __process_dev_close_with_grace_return__: ReturnType<\n\t\t\ttypeof closeWithGrace\n\t\t>,\n\t\t__process_test_close_with_grace_return__: ReturnType<typeof closeWithGrace>\n}\n\nconst devProcesses = remember('dev_processes', getDevProcessesMap)\nconst testProcesses = remember('test_processes', getTestProcessesMap)\n\nfunction getDevProcessesMap() {\n\tconst procs: DevProcessesMap = new Map()\n\n\tglobal.__process_dev_close_with_grace_return__?.uninstall()\n\n\tglobal.__process_dev_close_with_grace_return__ = closeWithGrace(async () => {\n\t\tfor (const [name, proc] of procs.entries()) {\n\t\t\tconsole.log('closing', name)\n\t\t\tproc.process.kill()\n\t\t}\n\t})\n\treturn procs\n}\n\nfunction getTestProcessesMap() {\n\tconst procs: TestProcessesMap = new Map()\n\n\tglobal.__process_test_close_with_grace_return__?.uninstall()\n\n\tglobal.__process_test_close_with_grace_return__ = closeWithGrace(async () => {\n\t\tfor (const [id, proc] of procs.entries()) {\n\t\t\tif (proc.process) {\n\t\t\t\tconsole.log('closing', id)\n\t\t\t\tproc.process.kill()\n\t\t\t}\n\t\t}\n\t})\n\treturn procs\n}\n\nconst colors = [\n\t'blue',\n\t'green',\n\t'yellow',\n\t'red',\n\t'magenta',\n\t'redBright',\n\t'greenBright',\n\t'yellowBright',\n\t'blueBright',\n\t'magentaBright',\n] as const\n\nexport async function runAppDev(app: App) {\n\tif (isDeployed) throw new Error('cannot run apps in deployed mode')\n\tconst key = app.name\n\t// if the app is already running, don't start it again\n\tif (devProcesses.has(key)) {\n\t\treturn { status: 'process-running', running: true } as const\n\t}\n\n\tif (app.dev.type !== 'script') {\n\t\treturn { status: 'error', error: 'no-server' } as const\n\t}\n\n\tconst { portNumber } = app.dev\n\tif (!(await isPortAvailable(portNumber))) {\n\t\treturn { status: 'port-unavailable', running: false, portNumber } as const\n\t}\n\tconst availableColors = colors.filter(color =>\n\t\tArray.from(devProcesses.values()).every(p => p.color !== color),\n\t)\n\tconst color =\n\t\tavailableColors[devProcesses.size % availableColors.length] ?? 'blue'\n\tconst appProcess = spawn('npm', ['run', 'dev'], {\n\t\tcwd: app.fullPath,\n\t\tshell: true,\n\t\tstdio: ['ignore', 'pipe', 'pipe'],\n\t\tenv: {\n\t\t\t...process.env,\n\t\t\t// TODO: support specifying the env\n\t\t\tNODE_ENV: 'development',\n\t\t\t// TODO: support specifying the port\n\t\t\tPORT: String(portNumber),\n\t\t\tAPP_SERVER_PORT: String(portNumber),\n\t\t\t// let it pick a random port...\n\t\t\tREMIX_DEV_SERVER_WS_PORT: '',\n\t\t},\n\t})\n\tconst prefix = chalk[color](\n\t\t`[${app.name.replace(/^exercises\\./, '')}:${portNumber}]`,\n\t)\n\tfunction handleStdOutData(data: Buffer) {\n\t\tconsole.log(\n\t\t\tdata\n\t\t\t\t.toString('utf-8')\n\t\t\t\t.split('\\n')\n\t\t\t\t.map(line => `${prefix} ${line}`)\n\t\t\t\t.join('\\n'),\n\t\t)\n\t}\n\tappProcess.stdout.on('data', handleStdOutData)\n\tfunction handleStdErrData(data: Buffer) {\n\t\tconsole.error(\n\t\t\tdata\n\t\t\t\t.toString('utf-8')\n\t\t\t\t.split('\\n')\n\t\t\t\t.map(line => `${prefix} ${line}`)\n\t\t\t\t.join('\\n'),\n\t\t)\n\t}\n\tappProcess.stderr.on('data', handleStdErrData)\n\tdevProcesses.set(key, { color, process: appProcess, port: portNumber })\n\tappProcess.on('exit', code => {\n\t\tappProcess.stdout.off('data', handleStdOutData)\n\t\tappProcess.stderr.off('data', handleStdErrData)\n\t\tconsole.log(`${prefix} exited (${code})`)\n\t\tdevProcesses.delete(key)\n\t})\n\n\treturn { status: 'process-started', running: true } as const\n}\n\nexport async function runAppTests(app: App) {\n\tif (isDeployed) throw new Error('cannot run tests in deployed mode')\n\tconst key = app.name\n\n\tif (app.test.type !== 'script') {\n\t\treturn { status: 'error', error: 'no-test' } as const\n\t}\n\n\tconst testProcess = execaCommand(app.test.script, {\n\t\tcwd: app.fullPath,\n\t\tshell: true,\n\t\tstdio: ['ignore', 'pipe', 'pipe'],\n\t\tenv: {\n\t\t\t...process.env,\n\t\t\t// TODO: support specifying the env\n\t\t\tNODE_ENV: 'development',\n\t\t\t// TODO: support specifying the port\n\t\t\tPORT: app.dev.type === 'script' ? String(app.dev.portNumber) : undefined,\n\t\t\tAPP_SERVER_PORT:\n\t\t\t\tapp.dev.type === 'script' ? String(app.dev.portNumber) : undefined,\n\t\t\t// let it pick a random port...\n\t\t\tREMIX_DEV_SERVER_WS_PORT: '',\n\t\t},\n\t})\n\tconst output: Array<OutputLine> = []\n\tconst entry: TestProcessEntry = { process: testProcess, output }\n\tfunction handleStdOutData(data: Buffer) {\n\t\toutput.push({\n\t\t\ttype: 'stdout',\n\t\t\tcontent: data.toString('utf-8'),\n\t\t\ttimestamp: Date.now(),\n\t\t})\n\t}\n\ttestProcess.stdout?.on('data', handleStdOutData)\n\tfunction handleStdErrData(data: Buffer) {\n\t\toutput.push({\n\t\t\ttype: 'stderr',\n\t\t\tcontent: data.toString('utf-8'),\n\t\t\ttimestamp: Date.now(),\n\t\t})\n\t}\n\ttestProcess.stderr?.on('data', handleStdErrData)\n\tvoid testProcess.on('exit', code => {\n\t\ttestProcess.stdout?.off('data', handleStdOutData)\n\t\ttestProcess.stderr?.off('data', handleStdErrData)\n\t\t// don't delete the entry from the map so we can show the output at any time\n\t\tentry.process = null\n\t\tentry.exitCode = code\n\t})\n\ttestProcesses.set(key, entry)\n\treturn testProcess\n}\n\nexport async function waitOnApp(app: App) {\n\tif (app.dev.type === 'script') {\n\t\tconst startTime = Date.now()\n\n\t\tconst retryInterval = 100\n\t\tconst timeout = 20_000\n\t\tlet lastError: unknown\n\t\twhile (Date.now() - startTime < timeout) {\n\t\t\ttry {\n\t\t\t\tawait fetch(`http://localhost:${app.dev.portNumber}`, {\n\t\t\t\t\tmethod: 'HEAD',\n\t\t\t\t\theaders: { Accept: '*/*' },\n\t\t\t\t})\n\t\t\t\treturn { status: 'success' } as const\n\t\t\t} catch (error) {\n\t\t\t\tlastError = error\n\t\t\t\tawait new Promise(resolve => setTimeout(resolve, retryInterval))\n\t\t\t}\n\t\t}\n\n\t\treturn { status: 'error', error: getErrorMessage(lastError) } as const\n\t}\n\treturn null\n}\n\nexport function isPortAvailable(port: number | string): Promise<boolean> {\n\treturn new Promise(resolve => {\n\t\tconst server = net.createServer()\n\t\tserver.unref()\n\t\tserver.on('error', () => resolve(false))\n\n\t\tserver.listen(Number(port), () => {\n\t\t\tserver.close(() => {\n\t\t\t\tresolve(true)\n\t\t\t})\n\t\t})\n\t})\n}\n\nexport function isAppRunning(app: { name: string }) {\n\ttry {\n\t\tconst devProcess = devProcesses.get(app.name)\n\t\tif (!devProcess) return false\n\t\tdevProcess.process.kill(0)\n\t\treturn true\n\t} catch {\n\t\treturn false\n\t}\n}\n\nexport function isTestRunning(app: { name: string }) {\n\ttry {\n\t\tconst testProcess = testProcesses.get(app.name)\n\t\tif (!testProcess) return false\n\t\tif (testProcess.process === null) return false\n\t\ttestProcess.process.kill(0)\n\t\treturn true\n\t} catch {\n\t\treturn false\n\t}\n}\n\nexport function getTestProcessEntry(app: { name: string }) {\n\treturn testProcesses.get(app.name)\n}\n\nexport function clearTestProcessEntry(app: { name: string }) {\n\treturn testProcesses.delete(app.name)\n}\n\nexport function getProcesses() {\n\treturn { devProcesses, testProcesses }\n}\n\nexport async function closeProcess(key: string) {\n\tif (isDeployed) throw new Error('cannot close processes in deployed mode')\n\tconst proc = devProcesses.get(key)\n\tif (proc) {\n\t\tconst exitedPromise = new Promise(resolve =>\n\t\t\tproc.process.on('exit', resolve),\n\t\t)\n\t\tif (process.platform === 'win32') {\n\t\t\tconst { execa } = await import('execa')\n\t\t\tawait execa('taskkill', ['/pid', String(proc.process.pid), '/f', '/t'])\n\t\t} else {\n\t\t\tproc.process.kill()\n\t\t}\n\t\tawait Promise.race([\n\t\t\tnew Promise(resolve => setTimeout(resolve, 500)),\n\t\t\texitedPromise,\n\t\t])\n\t\tawait stopPort(proc.port) // just in case 🤷♂️\n\t\tdevProcesses.delete(key)\n\t}\n}\n\nconst sleep = (t: number) => new Promise(resolve => setTimeout(resolve, t))\n\nexport async function stopPort(port: string | number) {\n\tif (isDeployed) throw new Error('cannot stop ports in deployed mode')\n\tawait fkill(`:${port}`, { force: true, silent: true })\n\tawait waitForPortToBeAvailable(port)\n}\n\nexport async function waitForPortToBeAvailable(port: string | number) {\n\t// wait for the port to become available again\n\tconst timeout = Date.now() + 10_000\n\tlet portAvailable = false\n\tdo {\n\t\tportAvailable = await isPortAvailable(port)\n\t\tawait sleep(100)\n\t} while (!portAvailable && Date.now() < timeout)\n\tif (!portAvailable) {\n\t\tconsole.error('Timed out waiting for the port to become available')\n\t}\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/// <reference types="chai" resolution-mode="require"/>
|
|
2
|
+
export declare const expect: Chai.ExpectStatic;
|
|
3
|
+
export declare function testStep<ReturnValue>(title: string | ((result: {
|
|
4
|
+
type: 'fail';
|
|
5
|
+
error: Error;
|
|
6
|
+
} | {
|
|
7
|
+
type: 'pass';
|
|
8
|
+
}) => string), get: (() => ReturnValue) | (() => Promise<ReturnValue>)): Promise<ReturnValue>;
|
|
9
|
+
//# sourceMappingURL=test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../../src/test.ts"],"names":[],"mappings":";AAWA,eAAO,MAAQ,MAAM,mBAAS,CAAA;AAY9B,wBAAsB,QAAQ,CAAC,WAAW,EACzC,KAAK,EACF,MAAM,GACN,CAAC,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,KAAK,MAAM,CAAC,EAC1E,GAAG,EAAE,CAAC,MAAM,WAAW,CAAC,GAAG,CAAC,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC,GACrD,OAAO,CAAC,WAAW,CAAC,CA+BtB"}
|
package/dist/esm/test.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { configure } from '@testing-library/dom';
|
|
2
|
+
import * as chai from 'chai';
|
|
3
|
+
import chaiDOM from 'chai-dom';
|
|
4
|
+
chai.use(chaiDOM);
|
|
5
|
+
// in the browser logging out the element is not necessary
|
|
6
|
+
configure({
|
|
7
|
+
getElementError: message => new Error(message ?? 'Unknown error'),
|
|
8
|
+
});
|
|
9
|
+
export const { expect } = chai;
|
|
10
|
+
function isError(maybeError) {
|
|
11
|
+
return (maybeError &&
|
|
12
|
+
typeof maybeError === 'object' &&
|
|
13
|
+
'message' in maybeError &&
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
15
|
+
typeof maybeError.message === 'string');
|
|
16
|
+
}
|
|
17
|
+
export async function testStep(title, get) {
|
|
18
|
+
let caughtError;
|
|
19
|
+
try {
|
|
20
|
+
const result = await get();
|
|
21
|
+
const titleString = typeof title === 'function' ? title({ type: 'pass' }) : title;
|
|
22
|
+
if (window.parent === window) {
|
|
23
|
+
console.log(`✅ ${titleString}`);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
window.parent.postMessage({
|
|
27
|
+
type: 'epicshop:test-step-update',
|
|
28
|
+
status: 'pass',
|
|
29
|
+
title: titleString,
|
|
30
|
+
timestamp: Date.now(),
|
|
31
|
+
}, '*');
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
caughtError = e;
|
|
37
|
+
}
|
|
38
|
+
const error = isError(caughtError)
|
|
39
|
+
? caughtError
|
|
40
|
+
: new Error(typeof caughtError === 'string' ? caughtError : 'Unknown error');
|
|
41
|
+
const titleString = typeof title === 'function' ? title({ type: 'fail', error }) : title;
|
|
42
|
+
error.message = `${titleString}${error.message ? `\n\n${error.message}` : ''}`;
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test.js","sourceRoot":"","sources":["../../src/test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAChD,OAAO,KAAK,IAAI,MAAM,MAAM,CAAA;AAC5B,OAAO,OAAO,MAAM,UAAU,CAAA;AAE9B,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;AAEjB,0DAA0D;AAC1D,SAAS,CAAC;IACT,eAAe,EAAE,OAAO,CAAC,EAAE,CAAC,IAAI,KAAK,CAAC,OAAO,IAAI,eAAe,CAAC;CACjE,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;AAE9B,SAAS,OAAO,CAAC,UAAe;IAC/B,OAAO,CACN,UAAU;QACV,OAAO,UAAU,KAAK,QAAQ;QAC9B,SAAS,IAAI,UAAU;QACvB,sEAAsE;QACtE,OAAO,UAAU,CAAC,OAAO,KAAK,QAAQ,CACtC,CAAA;AACF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC7B,KAE0E,EAC1E,GAAuD;IAEvD,IAAI,WAAW,CAAA;IACf,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,MAAM,GAAG,EAAE,CAAA;QAC1B,MAAM,WAAW,GAChB,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;QAC9D,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC9B,OAAO,CAAC,GAAG,CAAC,KAAK,WAAW,EAAE,CAAC,CAAA;QAChC,CAAC;aAAM,CAAC;YACP,MAAM,CAAC,MAAM,CAAC,WAAW,CACxB;gBACC,IAAI,EAAE,2BAA2B;gBACjC,MAAM,EAAE,MAAM;gBACd,KAAK,EAAE,WAAW;gBAClB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;aACrB,EACD,GAAG,CACH,CAAA;QACF,CAAC;QACD,OAAO,MAAM,CAAA;IACd,CAAC;IAAC,OAAO,CAAU,EAAE,CAAC;QACrB,WAAW,GAAG,CAAC,CAAA;IAChB,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,CAAC;QACjC,CAAC,CAAC,WAAW;QACb,CAAC,CAAC,IAAI,KAAK,CAAC,OAAO,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,eAAe,CAAC,CAAA;IAC7E,MAAM,WAAW,GAChB,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;IACrE,KAAK,CAAC,OAAO,GAAG,GAAG,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAA;IAC9E,MAAM,KAAK,CAAA;AACZ,CAAC","sourcesContent":["import { configure } from '@testing-library/dom'\nimport * as chai from 'chai'\nimport chaiDOM from 'chai-dom'\n\nchai.use(chaiDOM)\n\n// in the browser logging out the element is not necessary\nconfigure({\n\tgetElementError: message => new Error(message ?? 'Unknown error'),\n})\n\nexport const { expect } = chai\n\nfunction isError(maybeError: any): maybeError is Error {\n\treturn (\n\t\tmaybeError &&\n\t\ttypeof maybeError === 'object' &&\n\t\t'message' in maybeError &&\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n\t\ttypeof maybeError.message === 'string'\n\t)\n}\n\nexport async function testStep<ReturnValue>(\n\ttitle:\n\t\t| string\n\t\t| ((result: { type: 'fail'; error: Error } | { type: 'pass' }) => string),\n\tget: (() => ReturnValue) | (() => Promise<ReturnValue>),\n): Promise<ReturnValue> {\n\tlet caughtError\n\ttry {\n\t\tconst result = await get()\n\t\tconst titleString =\n\t\t\ttypeof title === 'function' ? title({ type: 'pass' }) : title\n\t\tif (window.parent === window) {\n\t\t\tconsole.log(`✅ ${titleString}`)\n\t\t} else {\n\t\t\twindow.parent.postMessage(\n\t\t\t\t{\n\t\t\t\t\ttype: 'epicshop:test-step-update',\n\t\t\t\t\tstatus: 'pass',\n\t\t\t\t\ttitle: titleString,\n\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t},\n\t\t\t\t'*',\n\t\t\t)\n\t\t}\n\t\treturn result\n\t} catch (e: unknown) {\n\t\tcaughtError = e\n\t}\n\n\tconst error = isError(caughtError)\n\t\t? caughtError\n\t\t: new Error(typeof caughtError === 'string' ? caughtError : 'Unknown error')\n\tconst titleString =\n\t\ttypeof title === 'function' ? title({ type: 'fail', error }) : title\n\terror.message = `${titleString}${error.message ? `\\n\\n${error.message}` : ''}`\n\tthrow error\n}\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type CreateReporter } from '@epic-web/cachified';
|
|
2
|
+
export type Timings = Record<string, Array<{
|
|
3
|
+
desc?: string;
|
|
4
|
+
} & ({
|
|
5
|
+
time: number;
|
|
6
|
+
start?: never;
|
|
7
|
+
} | {
|
|
8
|
+
time?: never;
|
|
9
|
+
start: number;
|
|
10
|
+
})>>;
|
|
11
|
+
export declare function makeTimings(type: string, desc?: string): Timings;
|
|
12
|
+
export declare function time<ReturnType>(fn: Promise<ReturnType> | (() => ReturnType | Promise<ReturnType>), { type, desc, timings, }: {
|
|
13
|
+
type: string;
|
|
14
|
+
desc?: string;
|
|
15
|
+
timings?: Timings;
|
|
16
|
+
}): Promise<ReturnType>;
|
|
17
|
+
export declare function getServerTimeHeader(timings?: Timings): string;
|
|
18
|
+
export declare function combineServerTimings(headers1: Headers, headers2: Headers): string;
|
|
19
|
+
export declare function cachifiedTimingReporter<Value>(timings?: Timings): undefined | CreateReporter<Value>;
|
|
20
|
+
//# sourceMappingURL=timing.server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timing.server.d.ts","sourceRoot":"","sources":["../../src/timing.server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAEzD,MAAM,MAAM,OAAO,GAAG,MAAM,CAC3B,MAAM,EACN,KAAK,CACJ;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,CACjB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,GAC/B;IAAE,IAAI,CAAC,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CACjC,CACD,CACD,CAAA;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,WAWtD;AAkBD,wBAAsB,IAAI,CAAC,UAAU,EACpC,EAAE,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,EAClE,EACC,IAAI,EACJ,IAAI,EACJ,OAAO,GACP,EAAE;IACF,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,OAAO,CAAA;CACjB,GACC,OAAO,CAAC,UAAU,CAAC,CASrB;AAED,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE,OAAO,UAuBpD;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,UAIxE;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAC5C,OAAO,CAAC,EAAE,OAAO,GACf,SAAS,GAAG,cAAc,CAAC,KAAK,CAAC,CA4BnC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
export function makeTimings(type, desc) {
|
|
2
|
+
const timings = {
|
|
3
|
+
[type]: [{ desc, start: performance.now() }],
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(timings, 'toString', {
|
|
6
|
+
value() {
|
|
7
|
+
return getServerTimeHeader(timings);
|
|
8
|
+
},
|
|
9
|
+
enumerable: false,
|
|
10
|
+
});
|
|
11
|
+
return timings;
|
|
12
|
+
}
|
|
13
|
+
function createTimer(type, desc) {
|
|
14
|
+
const start = performance.now();
|
|
15
|
+
return {
|
|
16
|
+
end(timings) {
|
|
17
|
+
let timingType = timings[type];
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
19
|
+
if (!timingType) {
|
|
20
|
+
// eslint-disable-next-line no-multi-assign
|
|
21
|
+
timingType = timings[type] = [];
|
|
22
|
+
}
|
|
23
|
+
timingType.push({ desc, time: performance.now() - start });
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export async function time(fn, { type, desc, timings, }) {
|
|
28
|
+
const timer = createTimer(type, desc);
|
|
29
|
+
const promise = typeof fn === 'function' ? fn() : fn;
|
|
30
|
+
if (!timings)
|
|
31
|
+
return promise;
|
|
32
|
+
const result = await promise;
|
|
33
|
+
timer.end(timings);
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
export function getServerTimeHeader(timings) {
|
|
37
|
+
if (!timings)
|
|
38
|
+
return '';
|
|
39
|
+
return Object.entries(timings)
|
|
40
|
+
.map(([key, timingInfos]) => {
|
|
41
|
+
const dur = timingInfos
|
|
42
|
+
.reduce((acc, timingInfo) => {
|
|
43
|
+
const time = timingInfo.time ?? performance.now() - timingInfo.start;
|
|
44
|
+
return acc + time;
|
|
45
|
+
}, 0)
|
|
46
|
+
.toFixed(1);
|
|
47
|
+
const desc = timingInfos
|
|
48
|
+
.map(t => t.desc)
|
|
49
|
+
.filter(Boolean)
|
|
50
|
+
.join(' & ');
|
|
51
|
+
return [
|
|
52
|
+
key.replaceAll(/(:| |@|=|;|,|\/|\\)/g, '_'),
|
|
53
|
+
desc ? `desc=${JSON.stringify(desc)}` : null,
|
|
54
|
+
`dur=${dur}`,
|
|
55
|
+
]
|
|
56
|
+
.filter(Boolean)
|
|
57
|
+
.join(';');
|
|
58
|
+
})
|
|
59
|
+
.join(',');
|
|
60
|
+
}
|
|
61
|
+
export function combineServerTimings(headers1, headers2) {
|
|
62
|
+
const newHeaders = new Headers(headers1);
|
|
63
|
+
newHeaders.append('Server-Timing', headers2.get('Server-Timing') ?? '');
|
|
64
|
+
return newHeaders.get('Server-Timing') ?? '';
|
|
65
|
+
}
|
|
66
|
+
export function cachifiedTimingReporter(timings) {
|
|
67
|
+
if (!timings)
|
|
68
|
+
return;
|
|
69
|
+
return ({ key }) => {
|
|
70
|
+
const cacheRetrievalTimer = createTimer(`cache:${key}`, `${key} cache retrieval`);
|
|
71
|
+
let getFreshValueTimer;
|
|
72
|
+
return event => {
|
|
73
|
+
switch (event.name) {
|
|
74
|
+
case 'getFreshValueStart':
|
|
75
|
+
getFreshValueTimer = createTimer(`getFreshValue:${key}`, `request forced to wait for a fresh ${key} value`);
|
|
76
|
+
break;
|
|
77
|
+
case 'getFreshValueSuccess':
|
|
78
|
+
getFreshValueTimer?.end(timings);
|
|
79
|
+
break;
|
|
80
|
+
case 'done':
|
|
81
|
+
cacheRetrievalTimer.end(timings);
|
|
82
|
+
break;
|
|
83
|
+
default:
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=timing.server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timing.server.js","sourceRoot":"","sources":["../../src/timing.server.ts"],"names":[],"mappings":"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","sourcesContent":["import { type CreateReporter } from '@epic-web/cachified'\n\nexport type Timings = Record<\n\tstring,\n\tArray<\n\t\t{ desc?: string } & (\n\t\t\t| { time: number; start?: never }\n\t\t\t| { time?: never; start: number }\n\t\t)\n\t>\n>\n\nexport function makeTimings(type: string, desc?: string) {\n\tconst timings: Timings = {\n\t\t[type]: [{ desc, start: performance.now() }],\n\t}\n\tObject.defineProperty(timings, 'toString', {\n\t\tvalue() {\n\t\t\treturn getServerTimeHeader(timings)\n\t\t},\n\t\tenumerable: false,\n\t})\n\treturn timings\n}\n\nfunction createTimer(type: string, desc?: string) {\n\tconst start = performance.now()\n\treturn {\n\t\tend(timings: Timings) {\n\t\t\tlet timingType = timings[type]\n\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n\t\t\tif (!timingType) {\n\t\t\t\t// eslint-disable-next-line no-multi-assign\n\t\t\t\ttimingType = timings[type] = []\n\t\t\t}\n\t\t\ttimingType.push({ desc, time: performance.now() - start })\n\t\t},\n\t}\n}\n\nexport async function time<ReturnType>(\n\tfn: Promise<ReturnType> | (() => ReturnType | Promise<ReturnType>),\n\t{\n\t\ttype,\n\t\tdesc,\n\t\ttimings,\n\t}: {\n\t\ttype: string\n\t\tdesc?: string\n\t\ttimings?: Timings\n\t},\n): Promise<ReturnType> {\n\tconst timer = createTimer(type, desc)\n\tconst promise = typeof fn === 'function' ? fn() : fn\n\tif (!timings) return promise\n\n\tconst result = await promise\n\n\ttimer.end(timings)\n\treturn result\n}\n\nexport function getServerTimeHeader(timings?: Timings) {\n\tif (!timings) return ''\n\treturn Object.entries(timings)\n\t\t.map(([key, timingInfos]) => {\n\t\t\tconst dur = timingInfos\n\t\t\t\t.reduce((acc, timingInfo) => {\n\t\t\t\t\tconst time = timingInfo.time ?? performance.now() - timingInfo.start\n\t\t\t\t\treturn acc + time\n\t\t\t\t}, 0)\n\t\t\t\t.toFixed(1)\n\t\t\tconst desc = timingInfos\n\t\t\t\t.map(t => t.desc)\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.join(' & ')\n\t\t\treturn [\n\t\t\t\tkey.replaceAll(/(:| |@|=|;|,|\\/|\\\\)/g, '_'),\n\t\t\t\tdesc ? `desc=${JSON.stringify(desc)}` : null,\n\t\t\t\t`dur=${dur}`,\n\t\t\t]\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.join(';')\n\t\t})\n\t\t.join(',')\n}\n\nexport function combineServerTimings(headers1: Headers, headers2: Headers) {\n\tconst newHeaders = new Headers(headers1)\n\tnewHeaders.append('Server-Timing', headers2.get('Server-Timing') ?? '')\n\treturn newHeaders.get('Server-Timing') ?? ''\n}\n\nexport function cachifiedTimingReporter<Value>(\n\ttimings?: Timings,\n): undefined | CreateReporter<Value> {\n\tif (!timings) return\n\n\treturn ({ key }) => {\n\t\tconst cacheRetrievalTimer = createTimer(\n\t\t\t`cache:${key}`,\n\t\t\t`${key} cache retrieval`,\n\t\t)\n\t\tlet getFreshValueTimer: ReturnType<typeof createTimer> | undefined\n\t\treturn event => {\n\t\t\tswitch (event.name) {\n\t\t\t\tcase 'getFreshValueStart':\n\t\t\t\t\tgetFreshValueTimer = createTimer(\n\t\t\t\t\t\t`getFreshValue:${key}`,\n\t\t\t\t\t\t`request forced to wait for a fresh ${key} value`,\n\t\t\t\t\t)\n\t\t\t\t\tbreak\n\t\t\t\tcase 'getFreshValueSuccess':\n\t\t\t\t\tgetFreshValueTimer?.end(timings)\n\t\t\t\t\tbreak\n\t\t\t\tcase 'done':\n\t\t\t\t\tcacheRetrievalTimer.end(timings)\n\t\t\t\t\tbreak\n\t\t\t\tdefault:\n\t\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAAA,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,UAY7C"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function getErrorMessage(error) {
|
|
2
|
+
if (typeof error === 'string')
|
|
3
|
+
return error;
|
|
4
|
+
if (error &&
|
|
5
|
+
typeof error === 'object' &&
|
|
6
|
+
'message' in error &&
|
|
7
|
+
typeof error.message === 'string') {
|
|
8
|
+
return error.message;
|
|
9
|
+
}
|
|
10
|
+
console.error('Unable to get error message for error', error);
|
|
11
|
+
return 'Unknown Error';
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,eAAe,CAAC,KAAc;IAC7C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAC3C,IACC,KAAK;QACL,OAAO,KAAK,KAAK,QAAQ;QACzB,SAAS,IAAI,KAAK;QAClB,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,EAChC,CAAC;QACF,OAAO,KAAK,CAAC,OAAO,CAAA;IACrB,CAAC;IACD,OAAO,CAAC,KAAK,CAAC,uCAAuC,EAAE,KAAK,CAAC,CAAA;IAC7D,OAAO,eAAe,CAAA;AACvB,CAAC","sourcesContent":["export function getErrorMessage(error: unknown) {\n\tif (typeof error === 'string') return error\n\tif (\n\t\terror &&\n\t\ttypeof error === 'object' &&\n\t\t'message' in error &&\n\t\ttypeof error.message === 'string'\n\t) {\n\t\treturn error.message\n\t}\n\tconsole.error('Unable to get error message for error', error)\n\treturn 'Unknown Error'\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.server.d.ts","sourceRoot":"","sources":["../../src/utils.server.ts"],"names":[],"mappings":"AAKA,wBAAsB,eAAe,qBAKpC;AAID,wBAAsB,UAAU,CAAC,KAAK,EACrC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,YAAY,CAAC,EAAE,KAAK,GAClB,OAAO,CAAC,KAAK,CAAC,CA0BhB"}
|