Skip to content

Commit 3904f4f

Browse files
authored
feat: make --default the default, and add an --eu flag to make things simpler (#81)
1 parent a864b3d commit 3904f4f

3 files changed

Lines changed: 256 additions & 14 deletions

File tree

README.md

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,34 @@ Currently the wizard can be used for **React, NextJS, Svelte, Astro and React Na
2121
projects. If you have other integrations you would like the wizard to support,
2222
please open a [GitHub issue](https://github.com/posthog/wizard/issues)!
2323

24+
## MCP Commands
25+
26+
The wizard also includes commands for managing PostHog MCP (Model Context Protocol) servers:
27+
28+
```bash
29+
# Install PostHog MCP server to supported clients
30+
npx @posthog/wizard mcp add
31+
32+
# Remove PostHog MCP server from supported clients
33+
npx @posthog/wizard mcp remove
34+
```
35+
2436
# Options
2537

2638
The following CLI arguments are available:
2739

28-
| Option | Description | Type | Default | Choices | Environment Variable |
29-
| ----------------- | -------------------------------------------------------------------------- | ------- | ------------------------------- | ------------------------------------------- | ---------------------------- |
30-
| `--help` | Show help | boolean | | | |
31-
| `--version` | Show version number | boolean | | | |
32-
| `--debug` | Enable verbose logging | boolean | `false` | | `POSTHOG_WIZARD_DEBUG` |
33-
| `--integration` | Choose the integration to setup | choices | Select integration during setup | "nextjs", "astro", "react", "svelte", "react-native" | `POSTHOG_WIZARD_INTEGRATION` |
34-
| `--force-install` | Force install the SDK NPM package (use with caution!) | boolean | `false` | | |
35-
| `--install-dir` | Relative path to install in | string | `.` | | `POSTHOG_WIZARD_INSTALL_DIR` |
36-
| `--region` | PostHog region to use | choices | | "us", "eu" | `POSTHOG_WIZARD_REGION` |
37-
| `--default` | Select the default option for all questions automatically (where possible) | boolean | `false` | | `POSTHOG_WIZARD_DEFAULT` |
38-
| `--signup` | Create a new PostHog account during setup | boolean | `false` | | `POSTHOG_WIZARD_SIGNUP` |
40+
| Option | Description | Type | Default | Choices | Environment Variable |
41+
| ----------------- | -------------------------------------------------------------------------- | ------- | ------------------------------- | ---------------------------------------------------- | --------------------------------- |
42+
| `--help` | Show help | boolean | | | |
43+
| `--version` | Show version number | boolean | | | |
44+
| `--debug` | Enable verbose logging | boolean | `false` | | `POSTHOG_WIZARD_DEBUG` |
45+
| `--region` | PostHog cloud region | string | `us` | "us", "eu" | `POSTHOG_WIZARD_REGION` |
46+
| `--eu` | Use EU region (shorthand for --region eu) | boolean | `false` | | |
47+
| `--default` | Use default options for all prompts | boolean | `true` | | `POSTHOG_WIZARD_DEFAULT` |
48+
| `--signup` | Create a new PostHog account during setup | boolean | `false` | | `POSTHOG_WIZARD_SIGNUP` |
49+
| `--integration` | Integration to set up | string | | "nextjs", "astro", "react", "svelte", "react-native" | |
50+
| `--force-install` | Force install packages even if peer dependency checks fail | boolean | `false` | | `POSTHOG_WIZARD_FORCE_INSTALL` |
51+
| `--install-dir` | Directory to install PostHog in | string | | | `POSTHOG_WIZARD_INSTALL_DIR` |
3952

4053
> Note: A large amount of the scaffolding for this came from the amazing Sentry
4154
> wizard, which you can find [here](https://github.com/getsentry/sentry-wizard)

bin.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import type { CloudRegion, WizardOptions } from './src/utils/types';
2121
import { runWizard } from './src/run';
2222

2323
yargs(hideBin(process.argv))
24+
.env('POSTHOG_WIZARD')
2425
// global options
2526
.options({
2627
debug: {
@@ -32,9 +33,15 @@ yargs(hideBin(process.argv))
3233
describe: 'PostHog cloud region\nenv: POSTHOG_WIZARD_REGION',
3334
choices: ['us', 'eu'],
3435
type: 'string',
36+
default: 'us',
3537
},
36-
default: {
38+
eu: {
39+
describe: 'Use EU region (shorthand for --region eu)',
40+
type: 'boolean',
3741
default: false,
42+
},
43+
default: {
44+
default: true,
3845
describe:
3946
'Use default options for all prompts\nenv: POSTHOG_WIZARD_DEFAULT',
4047
type: 'boolean',
@@ -70,7 +77,11 @@ yargs(hideBin(process.argv))
7077
});
7178
},
7279
(argv) => {
73-
void runWizard(argv as unknown as WizardOptions);
80+
const options = { ...argv };
81+
if (argv.eu) {
82+
options.region = 'eu';
83+
}
84+
void runWizard(options as unknown as WizardOptions);
7485
},
7586
)
7687
.command('mcp <command>', 'MCP server management commands', (yargs) => {
@@ -82,8 +93,12 @@ yargs(hideBin(process.argv))
8293
return yargs.options({});
8394
},
8495
(argv) => {
96+
const options = { ...argv };
97+
if (argv.eu) {
98+
options.region = 'eu';
99+
}
85100
void runMCPInstall(
86-
argv as unknown as { signup: boolean; region?: CloudRegion },
101+
options as unknown as { signup: boolean; region?: CloudRegion },
87102
);
88103
},
89104
)

src/__tests__/cli.test.ts

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
// Mock functions must be defined before imports
2+
const mockRunWizard = jest.fn();
3+
const mockRunMCPInstall = jest.fn();
4+
const mockRunMCPRemove = jest.fn();
5+
6+
jest.mock('../run', () => ({ runWizard: mockRunWizard }));
7+
jest.mock('../mcp', () => ({
8+
runMCPInstall: mockRunMCPInstall,
9+
runMCPRemove: mockRunMCPRemove,
10+
}));
11+
jest.mock('semver', () => ({ satisfies: () => true }));
12+
13+
describe('CLI argument parsing', () => {
14+
const originalArgv = process.argv;
15+
// eslint-disable-next-line @typescript-eslint/unbound-method
16+
const originalExit = process.exit;
17+
const originalEnv = { ...process.env };
18+
19+
beforeEach(() => {
20+
jest.clearAllMocks();
21+
22+
// Reset environment
23+
process.env = { ...originalEnv };
24+
delete process.env.POSTHOG_WIZARD_REGION;
25+
delete process.env.POSTHOG_WIZARD_DEFAULT;
26+
27+
// Mock process.exit to prevent test runner from exiting
28+
process.exit = jest.fn() as any;
29+
});
30+
31+
afterEach(() => {
32+
process.argv = originalArgv;
33+
process.exit = originalExit;
34+
process.env = originalEnv;
35+
jest.resetModules();
36+
});
37+
38+
/**
39+
* Helper to run the CLI with given arguments
40+
*/
41+
async function runCLI(args: string[]) {
42+
process.argv = ['node', 'bin.ts', ...args];
43+
44+
jest.isolateModules(() => {
45+
require('../../bin.ts');
46+
});
47+
48+
// Allow yargs to process
49+
await new Promise((resolve) => setImmediate(resolve));
50+
}
51+
52+
/**
53+
* Helper to get the arguments passed to a mock function
54+
*/
55+
function getLastCallArgs(mockFn: jest.Mock) {
56+
expect(mockFn).toHaveBeenCalled();
57+
return mockFn.mock.calls[mockFn.mock.calls.length - 1][0];
58+
}
59+
60+
describe('--default flag', () => {
61+
test('defaults to true when not specified', async () => {
62+
await runCLI([]);
63+
64+
const args = getLastCallArgs(mockRunWizard);
65+
expect(args.default).toBe(true);
66+
});
67+
68+
test('can be explicitly set to false with --no-default', async () => {
69+
await runCLI(['--no-default']);
70+
71+
const args = getLastCallArgs(mockRunWizard);
72+
expect(args.default).toBe(false);
73+
});
74+
75+
test('can be explicitly set to true', async () => {
76+
await runCLI(['--default']);
77+
78+
const args = getLastCallArgs(mockRunWizard);
79+
expect(args.default).toBe(true);
80+
});
81+
});
82+
83+
describe('--region flag', () => {
84+
test('defaults to "us" when not specified', async () => {
85+
await runCLI([]);
86+
87+
const args = getLastCallArgs(mockRunWizard);
88+
expect(args.region).toBe('us');
89+
});
90+
91+
test.each(['us', 'eu'])(
92+
'accepts "%s" as a valid region',
93+
async (region) => {
94+
await runCLI(['--region', region]);
95+
96+
const args = getLastCallArgs(mockRunWizard);
97+
expect(args.region).toBe(region);
98+
},
99+
);
100+
});
101+
102+
describe('--eu flag (shorthand for --region eu)', () => {
103+
test('sets region to "eu"', async () => {
104+
await runCLI(['--eu']);
105+
106+
const args = getLastCallArgs(mockRunWizard);
107+
expect(args.region).toBe('eu');
108+
expect(args.eu).toBe(true);
109+
});
110+
111+
test('overrides --region flag when both are specified', async () => {
112+
await runCLI(['--region', 'us', '--eu']);
113+
114+
const args = getLastCallArgs(mockRunWizard);
115+
expect(args.region).toBe('eu');
116+
});
117+
118+
test('overrides --region flag regardless of order', async () => {
119+
await runCLI(['--eu', '--region', 'us']);
120+
121+
const args = getLastCallArgs(mockRunWizard);
122+
expect(args.region).toBe('eu');
123+
});
124+
});
125+
126+
describe('environment variables', () => {
127+
test('respects POSTHOG_WIZARD_REGION', async () => {
128+
process.env.POSTHOG_WIZARD_REGION = 'eu';
129+
130+
await runCLI([]);
131+
132+
const args = getLastCallArgs(mockRunWizard);
133+
expect(args.region).toBe('eu');
134+
});
135+
136+
test('respects POSTHOG_WIZARD_DEFAULT', async () => {
137+
process.env.POSTHOG_WIZARD_DEFAULT = 'false';
138+
139+
await runCLI([]);
140+
141+
const args = getLastCallArgs(mockRunWizard);
142+
expect(args.default).toBe(false);
143+
});
144+
145+
test('CLI args override environment variables', async () => {
146+
process.env.POSTHOG_WIZARD_REGION = 'us';
147+
process.env.POSTHOG_WIZARD_DEFAULT = 'false';
148+
149+
await runCLI(['--region', 'eu', '--default']);
150+
151+
const args = getLastCallArgs(mockRunWizard);
152+
expect(args.region).toBe('eu');
153+
expect(args.default).toBe(true);
154+
});
155+
});
156+
157+
describe('backward compatibility', () => {
158+
test('all existing flags continue to work', async () => {
159+
await runCLI([
160+
'--debug',
161+
'--signup',
162+
'--force-install',
163+
'--install-dir',
164+
'/custom/path',
165+
'--integration',
166+
'nextjs',
167+
]);
168+
169+
const args = getLastCallArgs(mockRunWizard);
170+
171+
// Existing flags
172+
expect(args.debug).toBe(true);
173+
expect(args.signup).toBe(true);
174+
expect(args['force-install']).toBe(true);
175+
expect(args['install-dir']).toBe('/custom/path');
176+
expect(args.integration).toBe('nextjs');
177+
178+
// New defaults
179+
expect(args.default).toBe(true);
180+
expect(args.region).toBe('us');
181+
});
182+
});
183+
184+
describe('mcp commands', () => {
185+
test('mcp add respects --eu flag', async () => {
186+
await runCLI(['mcp', 'add', '--eu']);
187+
188+
const args = getLastCallArgs(mockRunMCPInstall);
189+
expect(args.region).toBe('eu');
190+
});
191+
192+
test('mcp add uses default region when not specified', async () => {
193+
await runCLI(['mcp', 'add']);
194+
195+
const args = getLastCallArgs(mockRunMCPInstall);
196+
expect(args.region).toBe('us');
197+
});
198+
199+
test('mcp add respects --region flag', async () => {
200+
await runCLI(['mcp', 'add', '--region', 'eu']);
201+
202+
const args = getLastCallArgs(mockRunMCPInstall);
203+
expect(args.region).toBe('eu');
204+
});
205+
206+
test('mcp commands inherit global flags', async () => {
207+
await runCLI(['mcp', 'add', '--no-default', '--debug']);
208+
209+
const args = getLastCallArgs(mockRunMCPInstall);
210+
expect(args.default).toBe(false);
211+
expect(args.debug).toBe(true);
212+
});
213+
});
214+
});

0 commit comments

Comments
 (0)