-
Notifications
You must be signed in to change notification settings - Fork 303
/
Copy pathapplication.ts
141 lines (135 loc) · 5.09 KB
/
application.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import * as path from 'node:path';
import { awaitableTreekill, createLogger, fs, getPort, run, waitForIdleProcess, waitForServer } from '../scripts';
import type { ApplicationConfig } from './applicationConfig.js';
import type { EnvironmentConfig } from './environment.js';
export type Application = ReturnType<typeof application>;
export const application = (
config: ApplicationConfig,
appDirPath: string,
appDirName: string,
serverUrl: string | undefined,
) => {
const { name, scripts, envWriter, copyKeylessToEnv } = config;
const logger = createLogger({ prefix: `${appDirName}` });
const state = { completedSetup: false, serverUrl: '', env: {} as EnvironmentConfig };
const cleanupFns: { (): unknown }[] = [];
const now = Date.now();
const stdoutFilePath = path.resolve(appDirPath, `e2e.${now}.log`);
const stderrFilePath = path.resolve(appDirPath, `e2e.${now}.err.log`);
let buildOutput = '';
let serveOutput = '';
const self = {
name,
scripts,
appDir: appDirPath,
get serverUrl() {
return state.serverUrl;
},
get env() {
return state.env;
},
withEnv: async (env: EnvironmentConfig) => {
state.env = env;
return envWriter(appDirPath, env);
},
keylessToEnv: async () => {
return copyKeylessToEnv(appDirPath);
},
setup: async (opts?: { strategy?: 'ci' | 'i' | 'copy'; force?: boolean }) => {
const { force } = opts || {};
const nodeModulesExist = await fs.pathExists(path.resolve(appDirPath, 'node_modules'));
if (force || !nodeModulesExist) {
const log = logger.child({ prefix: 'setup' }).info;
await run(scripts.setup, { cwd: appDirPath, log });
state.completedSetup = true;
}
},
dev: async (opts: { port?: number; manualStart?: boolean; detached?: boolean; serverUrl?: string } = {}) => {
const log = logger.child({ prefix: 'dev' }).info;
const port = opts.port || (await getPort());
const getServerUrl = () => {
if (opts.serverUrl) {
return opts.serverUrl.includes(':') ? opts.serverUrl : `${opts.serverUrl}:${port}`;
}
return serverUrl || `http://localhost:${port}`;
};
const runtimeServerUrl = getServerUrl();
log(`Will try to serve app at ${runtimeServerUrl}`);
if (opts.manualStart) {
// for debugging, you can start the dev server manually by cd'ing into the temp dir
// and running the corresponding dev command
// this allows the test to run as normally, while setup is controlled by you,
// so you can inspect the running up outside the PW lifecycle
state.serverUrl = runtimeServerUrl;
return { port, serverUrl: runtimeServerUrl };
}
const proc = run(scripts.dev, {
cwd: appDirPath,
env: { PORT: port.toString() },
detached: opts.detached,
stdout: opts.detached ? fs.openSync(stdoutFilePath, 'a') : undefined,
stderr: opts.detached ? fs.openSync(stderrFilePath, 'a') : undefined,
log: opts.detached ? undefined : log,
});
const shouldExit = () => !!proc.exitCode && proc.exitCode !== 0;
await waitForServer(runtimeServerUrl, { log, maxAttempts: Infinity, shouldExit });
log(`Server started at ${runtimeServerUrl}, pid: ${proc.pid}`);
cleanupFns.push(() => awaitableTreekill(proc.pid, 'SIGKILL'));
state.serverUrl = runtimeServerUrl;
return { port, serverUrl: runtimeServerUrl, pid: proc.pid };
},
build: async () => {
const log = logger.child({ prefix: 'build' }).info;
await run(scripts.build, {
cwd: appDirPath,
log: (msg: string) => {
buildOutput += `\n${msg}`;
log(msg);
},
});
},
get buildOutput() {
return buildOutput;
},
get serveOutput() {
return serveOutput;
},
serve: async (opts: { port?: number; manualStart?: boolean } = {}) => {
const log = logger.child({ prefix: 'serve' }).info;
const port = opts.port || (await getPort());
// TODO: get serverUrl as in dev()
const serverUrl = `http://localhost:${port}`;
// If this is ever used as a background process, we need to make sure
// it's not using the log function. See the dev() method above
const proc = run(scripts.serve, {
cwd: appDirPath,
env: { PORT: port.toString() },
log: (msg: string) => {
serveOutput += `\n${msg}`;
log(msg);
},
});
cleanupFns.push(() => awaitableTreekill(proc.pid, 'SIGKILL'));
await waitForIdleProcess(proc);
state.serverUrl = serverUrl;
return { port, serverUrl, pid: proc };
},
stop: async () => {
logger.info('Stopping...');
await Promise.all(cleanupFns.map(fn => fn()));
cleanupFns.splice(0, cleanupFns.length);
state.serverUrl = '';
return new Promise(res => setTimeout(res, 2000));
},
teardown: async () => {
logger.info('Tearing down...');
await self.stop();
try {
fs.rmSync(appDirPath, { recursive: true, force: true });
} catch (e) {
console.log(e);
}
},
};
return self;
};