Skip to content

Commit 307cd12

Browse files
committed
feat: reverse proxy, get host from api
1 parent de3fdfe commit 307cd12

File tree

7 files changed

+438
-42
lines changed

7 files changed

+438
-42
lines changed

bin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const NODE_VERSION_RANGE = '>=18.20.0';
1111
// has the problematic imports.
1212
if (!satisfies(process.version, NODE_VERSION_RANGE)) {
1313
red(
14-
`CodeHog requires Node.js ${NODE_VERSION_RANGE}. You are using Node.js ${process.version}. Please upgrade your Node.js version.`,
14+
`PostHog Wizard requires Node.js ${NODE_VERSION_RANGE}. You are using Node.js ${process.version}. Please upgrade your Node.js version.`,
1515
);
1616
process.exit(1);
1717
}

e2e-tests/utils/index.ts

Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
import * as fs from 'fs';
2+
import * as path from 'path';
3+
4+
import { spawn, execSync } from 'child_process';
5+
import type { ChildProcess } from 'child_process';
6+
import { dim, green, red } from '../../src/utils/logging';
7+
8+
export const KEYS = {
9+
UP: '\u001b[A',
10+
DOWN: '\u001b[B',
11+
LEFT: '\u001b[D',
12+
RIGHT: '\u001b[C',
13+
ENTER: '\r',
14+
SPACE: ' ',
15+
};
16+
17+
export const TEST_ARGS = {};
18+
19+
export const log = {
20+
success: (message: string) => {
21+
green(`[SUCCESS] ${message}`);
22+
},
23+
info: (message: string) => {
24+
dim(`[INFO] ${message}`);
25+
},
26+
error: (message: string) => {
27+
red(`[ERROR] ${message}`);
28+
},
29+
};
30+
31+
export class WizardTestEnv {
32+
taskHandle: ChildProcess;
33+
34+
constructor(
35+
cmd: string,
36+
args: string[],
37+
opts?: {
38+
cwd?: string;
39+
debug?: boolean;
40+
},
41+
) {
42+
this.taskHandle = spawn(cmd, args, { cwd: opts?.cwd, stdio: 'pipe' });
43+
44+
if (opts?.debug) {
45+
this.taskHandle.stdout?.pipe(process.stdout);
46+
this.taskHandle.stderr?.pipe(process.stderr);
47+
}
48+
}
49+
50+
sendStdin(input: string) {
51+
this.taskHandle.stdin?.write(input);
52+
}
53+
54+
/**
55+
* Sends the input and waits for the output.
56+
* @returns a promise that resolves when the output was found
57+
* @throws an error when the output was not found within the timeout
58+
*/
59+
sendStdinAndWaitForOutput(
60+
input: string | string[],
61+
output: string,
62+
options?: { timeout?: number; optional?: boolean },
63+
) {
64+
const outputPromise = this.waitForOutput(output, options);
65+
66+
if (Array.isArray(input)) {
67+
for (const i of input) {
68+
this.sendStdin(i);
69+
}
70+
} else {
71+
this.sendStdin(input);
72+
}
73+
return outputPromise;
74+
}
75+
76+
/**
77+
* Waits for the task to exit with a given `statusCode`.
78+
*
79+
* @returns a promise that resolves to `true` if the run ends with the status
80+
* code, or it rejects when the `timeout` was reached.
81+
*/
82+
waitForStatusCode(
83+
statusCode: number | null,
84+
options: {
85+
/** Timeout in ms */
86+
timeout?: number;
87+
} = {},
88+
) {
89+
const { timeout } = {
90+
timeout: 60_000,
91+
...options,
92+
};
93+
94+
return new Promise<boolean>((resolve, reject) => {
95+
const timeoutId = setTimeout(() => {
96+
reject(new Error(`Timeout waiting for status code: ${statusCode}`));
97+
}, timeout);
98+
99+
this.taskHandle.on('exit', (code: number | null) => {
100+
clearTimeout(timeoutId);
101+
resolve(code === statusCode);
102+
});
103+
});
104+
}
105+
106+
/**
107+
* Waits for the provided output with `.includes()` logic.
108+
*
109+
* @returns a promise that resolves to `true` if the output was found, `false` if the output was not found within the
110+
* timeout and `optional: true` is set, or it rejects when the timeout was reached with `optional: false`
111+
*/
112+
waitForOutput(
113+
output: string,
114+
options: {
115+
/** Timeout in ms */
116+
timeout?: number;
117+
/** Whether to always resolve after the timeout, no matter whether the input was actually found or not. */
118+
optional?: boolean;
119+
} = {},
120+
) {
121+
const { timeout, optional } = {
122+
timeout: 60_000,
123+
optional: false,
124+
...options,
125+
};
126+
127+
return new Promise<boolean>((resolve, reject) => {
128+
let outputBuffer = '';
129+
const timeoutId = setTimeout(() => {
130+
if (optional) {
131+
// The output is not found but it's optional so we can resolve the promise with false
132+
resolve(false);
133+
} else {
134+
reject(new Error(`Timeout waiting for output: ${output}`));
135+
}
136+
}, timeout);
137+
138+
this.taskHandle.stdout?.on('data', (data) => {
139+
outputBuffer += data;
140+
if (outputBuffer.includes(output)) {
141+
clearTimeout(timeoutId);
142+
// The output is found so we can resolve the promise with true
143+
resolve(true);
144+
}
145+
});
146+
});
147+
}
148+
149+
kill() {
150+
this.taskHandle.stdin?.destroy();
151+
this.taskHandle.stderr?.destroy();
152+
this.taskHandle.stdout?.destroy();
153+
this.taskHandle.kill('SIGINT');
154+
this.taskHandle.unref();
155+
}
156+
}
157+
158+
/**
159+
* Initialize a git repository in the given directory
160+
* @param projectDir
161+
*/
162+
export function initGit(projectDir: string): void {
163+
try {
164+
execSync('git init', { cwd: projectDir });
165+
// Add all files to the git repo
166+
execSync('git add -A', { cwd: projectDir });
167+
// Add author info to avoid git commit error
168+
execSync('git config user.email test@test.posthog.com', {
169+
cwd: projectDir,
170+
});
171+
execSync('git config user.name Test', { cwd: projectDir });
172+
execSync('git commit -m init', { cwd: projectDir });
173+
} catch (e) {
174+
log.error('Error initializing git');
175+
log.error(e);
176+
}
177+
}
178+
179+
/**
180+
* Cleanup the git repository in the given directory
181+
*
182+
* Caution! Make sure `projectDir` is a test project directory,
183+
* if in doubt, please commit your local non-test changes first!
184+
* @param projectDir
185+
*/
186+
export function cleanupGit(projectDir: string): void {
187+
try {
188+
// Remove the .git directory
189+
execSync(`rm -rf ${projectDir}/.git`);
190+
} catch (e) {
191+
log.error('Error cleaning up git');
192+
log.error(e);
193+
}
194+
}
195+
196+
/**
197+
* Revert local changes in the given directory
198+
*
199+
* Caution! Make sure `projectDir` is a test project directory,
200+
* if in doubt, please commit your local non-test changes first!
201+
*
202+
* @param projectDir
203+
*/
204+
export function revertLocalChanges(projectDir: string): void {
205+
try {
206+
// Revert tracked files
207+
execSync('git checkout .', { cwd: projectDir });
208+
// Revert untracked files
209+
execSync('git clean -fd .', { cwd: projectDir });
210+
} catch (e) {
211+
log.error('Error reverting local changes');
212+
log.error(e);
213+
}
214+
}
215+
216+
/**
217+
* Start the wizard instance with the given integration and project directory
218+
* @param integration
219+
* @param projectDir
220+
*
221+
* @returns WizardTestEnv
222+
*/
223+
export function startWizardInstance(
224+
projectDir: string,
225+
debug = false,
226+
): WizardTestEnv {
227+
const binPath = path.join(__dirname, '../../dist/bin.js');
228+
229+
revertLocalChanges(projectDir);
230+
cleanupGit(projectDir);
231+
initGit(projectDir);
232+
233+
return new WizardTestEnv('node', [binPath, '--debug'], {
234+
cwd: projectDir,
235+
debug,
236+
});
237+
}
238+
239+
/**
240+
* Create a file with the given content
241+
*
242+
* @param filePath
243+
* @param content
244+
*/
245+
export function createFile(filePath: string, content?: string) {
246+
return fs.writeFileSync(filePath, content || '');
247+
}
248+
249+
/**
250+
* Modify the file with the new content
251+
*
252+
* @param filePath
253+
* @param oldContent
254+
* @param newContent
255+
*/
256+
export function modifyFile(
257+
filePath: string,
258+
replaceMap: Record<string, string>,
259+
) {
260+
const fileContent = fs.readFileSync(filePath, 'utf-8');
261+
let newFileContent = fileContent;
262+
263+
for (const [oldContent, newContent] of Object.entries(replaceMap)) {
264+
newFileContent = newFileContent.replace(oldContent, newContent);
265+
}
266+
267+
fs.writeFileSync(filePath, newFileContent);
268+
}
269+
270+
/**
271+
* Read the file contents and check if it contains the given content
272+
*
273+
* @param {string} filePath
274+
* @param {(string | string[])} content
275+
*/
276+
export function checkFileContents(
277+
filePath: string,
278+
content: string | string[],
279+
) {
280+
const fileContent = fs.readFileSync(filePath, 'utf-8');
281+
const contentArray = Array.isArray(content) ? content : [content];
282+
283+
for (const c of contentArray) {
284+
expect(fileContent).toContain(c);
285+
}
286+
}
287+
288+
/**
289+
* Check if the file exists
290+
*
291+
* @param filePath
292+
*/
293+
export function checkFileExists(filePath: string) {
294+
expect(fs.existsSync(filePath)).toBe(true);
295+
}
296+
297+
/**
298+
* Check if the package.json contains the given integration
299+
*
300+
* @param projectDir
301+
* @param packageName
302+
*/
303+
export function checkPackageJson(projectDir: string, packageName: string) {
304+
checkFileContents(`${projectDir}/package.json`, packageName);
305+
}
306+
307+
/**
308+
* Check if the project builds
309+
* Check if the project builds and ends with status code 0.
310+
* @param projectDir
311+
*/
312+
export async function checkIfBuilds(projectDir: string) {
313+
const testEnv = new WizardTestEnv('npm', ['run', 'build'], {
314+
cwd: projectDir,
315+
});
316+
317+
await expect(
318+
testEnv.waitForStatusCode(0, {
319+
timeout: 120_000,
320+
}),
321+
).resolves.toBe(true);
322+
}
323+
324+
/**
325+
* Check if the project runs on dev mode
326+
* @param projectDir
327+
* @param expectedOutput
328+
*/
329+
export async function checkIfRunsOnDevMode(
330+
projectDir: string,
331+
expectedOutput: string,
332+
) {
333+
const testEnv = new WizardTestEnv('npm', ['run', 'dev'], { cwd: projectDir });
334+
335+
await expect(
336+
testEnv.waitForOutput(expectedOutput, {
337+
timeout: 120_000,
338+
}),
339+
).resolves.toBe(true);
340+
testEnv.kill();
341+
}
342+
343+
/**
344+
* Check if the project runs on prod mode
345+
* @param projectDir
346+
* @param expectedOutput
347+
*/
348+
export async function checkIfRunsOnProdMode(
349+
projectDir: string,
350+
expectedOutput: string,
351+
startCommand = 'start',
352+
) {
353+
const testEnv = new WizardTestEnv('npm', ['run', startCommand], {
354+
cwd: projectDir,
355+
});
356+
357+
await expect(
358+
testEnv.waitForOutput(expectedOutput, {
359+
timeout: 120_000,
360+
}),
361+
).resolves.toBe(true);
362+
testEnv.kill();
363+
}

lib/Constants.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,12 @@ export interface Args {
7070
disableTelemetry?: boolean;
7171
}
7272

73-
export const DEFAULT_URL = 'https://posthog.com/';
73+
export const DEFAULT_URL = 'https://app.posthog.com';
7474
export const ISSUES_URL = 'ISSUES_URL';
7575
export const INSTALL_DIR = path.join(
7676
process.cwd(),
7777
process.env.POSTHOG_WIZARD_INSTALL_DIR ?? '',
7878
);
79-
export const CLOUD_URL = 'http://localhost:8010/';
80-
export const HOST_URL = 'http://localhost:8010/';
79+
export const CLOUD_URL = 'http://localhost:8010';
80+
export const DEFAULT_HOST_URL = 'https://us.i.posthog.com';
8181
export const DUMMY_PROJECT_API_KEY = '_YOUR_POSTHOG_PROJECT_API_KEY_';

0 commit comments

Comments
 (0)