forked from clerk/javascript
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlongRunningApplication.ts
More file actions
133 lines (127 loc) · 4.17 KB
/
Copy pathlongRunningApplication.ts
File metadata and controls
133 lines (127 loc) · 4.17 KB
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
import { awaitableTreekill, fs } from '../scripts';
import type { Application } from './application';
import type { ApplicationConfig } from './applicationConfig';
import type { EnvironmentConfig } from './environment';
import { environmentConfig } from './environment';
import { stateFile } from './stateFile';
const getPort = (_url: string) => {
if (!_url) {
return undefined;
}
const url = new URL(_url);
return Number.parseInt(url.port || (url.protocol === 'https:' ? '443' : '80'));
};
export type LongRunningApplication = ReturnType<typeof longRunningApplication>;
export type LongRunningApplicationParams = {
id: string;
config: ApplicationConfig;
env: EnvironmentConfig;
serverUrl?: string;
};
/**
* A long-running app is an app that is started once and then used for all tests.
* Its interface is the same as the Application and the ApplicationConfig interface,
* making it interchangeable with the Application and ApplicationConfig.
*
* After init() is called, all mutating methods on the config are ignored.
*/
export const longRunningApplication = (params: LongRunningApplicationParams) => {
const { id } = params;
const name = `long-running--${params.id}`;
const config = params.config.clone().setName(name);
let app: Application;
let pid: number;
let port = getPort(params.serverUrl);
let serverUrl: string = params.serverUrl;
let appDir: string;
let env: EnvironmentConfig = params.env;
const readFromStateFile = () => {
if (!stateFile.getLongRunningApps() || [port, serverUrl, pid, appDir, env].filter(Boolean).length === 0) {
return;
}
const data = stateFile.getLongRunningApps()[id] || {};
port ||= data.port;
serverUrl ||= data.serverUrl;
pid ||= data.pid;
appDir ||= data.appDir;
env ||= environmentConfig().fromJson(data.env);
};
const self = new Proxy(
{
// will be called by global.setup.ts and by the test runner
// the first time this is called, the app starts and the state is persisted in the state file
init: async () => {
try {
app = await config.commit();
} catch (error) {
console.error('Error committing config:', error);
throw error;
}
try {
await app.withEnv(params.env);
} catch (error) {
console.error('Error setting up environment:', error);
throw error;
}
try {
await app.setup();
} catch (error) {
console.error('Error during app setup:', error);
throw error;
}
try {
const { port, serverUrl, pid } = await app.dev({ detached: true });
stateFile.addLongRunningApp({ port, serverUrl, pid, id, appDir: app.appDir, env: params.env.toJson() });
} catch (error) {
console.error('Error during app dev:', error);
throw error;
}
},
// will be called by global.teardown.ts
destroy: async () => {
readFromStateFile();
console.log(`Destroying ${serverUrl}`);
await awaitableTreekill(pid, 'SIGKILL');
// TODO: Test whether this is necessary now that we have awaitableTreekill
await new Promise(res => setTimeout(res, 2000));
await fs.rm(appDir, { recursive: true, force: true });
},
// read the persisted state and behave like an app
commit: () => {
if (!serverUrl) {
readFromStateFile();
}
},
dev: () => ({ port, serverUrl, pid }),
setup: () => Promise.resolve(),
withEnv: () => Promise.resolve(),
teardown: () => Promise.resolve(),
build: () => {
throw new Error('build for long running apps is not supported yet');
},
get name() {
return name;
},
get id() {
return id;
},
get env() {
readFromStateFile();
return env;
},
get serverUrl() {
readFromStateFile();
return serverUrl;
},
},
{
get(target, prop: string) {
if (!(prop in target) && prop in config) {
return () => self;
}
return target[prop];
},
},
);
return self as any as Application & ApplicationConfig & typeof self;
};