Skip to content

Commit 644f897

Browse files
thymikeeOlimpiaZurekadhorodyski
authored
feat: allow running dev server from run commands with --dev-server flag (#301)
* wip * make it run * add ios part and fix ts * fix tests * add nx cache to gitignore * fix: cr comments * move dev server startup and add repack support * add dev server flag * add changeset * prettier and small cleanup * fix tests * reuse startDevServer * docs * update changeset * fix: keypress events not available with --dev-server --------- Co-authored-by: Test <[email protected]> Co-authored-by: Adam Horodyski <[email protected]>
1 parent 0a593f4 commit 644f897

File tree

15 files changed

+287
-75
lines changed

15 files changed

+287
-75
lines changed

.changeset/little-rocks-mix.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@rock-js/platform-apple-helpers': patch
3+
'@rock-js/platform-android': patch
4+
'@rock-js/plugin-repack': patch
5+
'@rock-js/platform-ios': patch
6+
'@rock-js/plugin-metro': patch
7+
'@rock-js/config': patch
8+
---
9+
10+
feat: allow running dev server from run commands with `--dev-server` flag

packages/config/src/lib/config.ts

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,34 @@ export type PluginOutput = {
1313
description: string;
1414
};
1515

16+
export type DevServerArgs = {
17+
interactive: boolean;
18+
clientLogs: boolean;
19+
port?: string;
20+
host?: string;
21+
https?: boolean;
22+
resetCache?: boolean;
23+
devServer?: boolean;
24+
platforms?: string[];
25+
[key: string]: unknown;
26+
};
27+
28+
export type StartDevServerArgs = {
29+
root: string;
30+
args: DevServerArgs;
31+
reactNativeVersion: string;
32+
reactNativePath: string;
33+
platforms: Record<string, object>;
34+
};
35+
36+
type StartDevServerFunction = (options: StartDevServerArgs) => Promise<void>;
37+
38+
export type BundlerPluginOutput = {
39+
name: string;
40+
description: string;
41+
start: StartDevServerFunction;
42+
};
43+
1644
export type PlatformOutput = PluginOutput & {
1745
autolinkingConfig: { project: Record<string, unknown> | undefined };
1846
};
@@ -27,10 +55,11 @@ export type PluginApi = {
2755
null | undefined | (() => RemoteBuildCache)
2856
>;
2957
getFingerprintOptions: () => FingerprintSources;
58+
getBundlerStart: () => ({ args }: { args: DevServerArgs }) => void;
3059
};
3160

3261
type PluginType = (args: PluginApi) => PluginOutput;
33-
62+
type BundlerPluginType = (args: PluginApi) => BundlerPluginOutput;
3463
type PlatformType = (args: PluginApi) => PlatformOutput;
3564

3665
type ArgValue = string | string[] | boolean;
@@ -63,7 +92,7 @@ export type ConfigType = {
6392
root?: string;
6493
reactNativeVersion?: string;
6594
reactNativePath?: string;
66-
bundler?: PluginType;
95+
bundler?: BundlerPluginType;
6796
plugins?: PluginType[];
6897
platforms?: Record<string, PlatformType>;
6998
commands?: Array<CommandType>;
@@ -79,6 +108,7 @@ export type ConfigOutput = {
79108
root: string;
80109
commands?: Array<CommandType>;
81110
platforms?: Record<string, PlatformOutput>;
111+
bundler?: BundlerPluginOutput;
82112
} & PluginApi;
83113

84114
const extensions = ['.js', '.ts', '.mjs'];
@@ -160,6 +190,8 @@ export async function getConfig(
160190
process.exit(1);
161191
}
162192

193+
let bundler: BundlerPluginOutput | undefined;
194+
163195
const api = {
164196
registerCommand: (command: CommandType) => {
165197
validatedConfig.commands = [...(validatedConfig.commands || []), command];
@@ -186,6 +218,17 @@ Read more: ${colorLink('https://rockjs.dev/docs/configuration#github-actions-pro
186218
},
187219
getFingerprintOptions: () =>
188220
validatedConfig.fingerprint as FingerprintSources,
221+
getBundlerStart:
222+
() =>
223+
({ args }: { args: DevServerArgs }) => {
224+
return bundler?.start({
225+
root: api.getProjectRoot(),
226+
args,
227+
reactNativeVersion: api.getReactNativeVersion(),
228+
reactNativePath: api.getReactNativePath(),
229+
platforms: api.getPlatforms(),
230+
});
231+
},
189232
};
190233

191234
const platforms: Record<string, PlatformOutput> = {};
@@ -205,7 +248,11 @@ Read more: ${colorLink('https://rockjs.dev/docs/configuration#github-actions-pro
205248
}
206249

207250
if (validatedConfig.bundler) {
208-
assignOriginToCommand(validatedConfig.bundler, api, validatedConfig);
251+
bundler = assignOriginToCommand(
252+
validatedConfig.bundler,
253+
api,
254+
validatedConfig,
255+
) as BundlerPluginOutput;
209256
}
210257

211258
for (const internalPlugin of internalPlugins) {
@@ -220,6 +267,7 @@ Read more: ${colorLink('https://rockjs.dev/docs/configuration#github-actions-pro
220267
root: projectRoot,
221268
commands: validatedConfig.commands ?? [],
222269
platforms: platforms ?? {},
270+
bundler,
223271
...api,
224272
};
225273

@@ -236,16 +284,17 @@ function resolveReactNativePath(root: string) {
236284
* Assigns __origin property to each command in the config for later use in error handling.
237285
*/
238286
function assignOriginToCommand(
239-
plugin: PluginType,
287+
plugin: PluginType | BundlerPluginType,
240288
api: PluginApi,
241289
config: ConfigType,
242290
) {
243291
const len = config.commands?.length ?? 0;
244-
const { name } = plugin(api);
292+
const { name, ...rest } = plugin(api);
245293
const newlen = config.commands?.length ?? 0;
246294
for (let i = len; i < newlen; i++) {
247295
if (config.commands?.[i]) {
248296
config.commands[i].__origin = name;
249297
}
250298
}
299+
return { name, ...rest };
251300
}

packages/platform-android/src/lib/commands/runAndroid/__tests__/runAndroid.test.ts

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ const androidProject: AndroidProjectConfig = {
3232
const OLD_ENV = process.env;
3333
let adbDevicesCallsCount = 0;
3434

35+
const mockPlatforms = { ios: {}, android: {} };
36+
3537
beforeEach(() => {
3638
adbDevicesCallsCount = 0;
3739
vi.clearAllMocks();
@@ -312,9 +314,12 @@ test.each([['release'], ['debug'], ['staging']])(
312314
'/',
313315
undefined,
314316
{ extraSources: [], ignorePaths: [], env: [] },
317+
vi.fn(), // startDevServer mock
318+
'/path/to/react-native', // reactNativePath
319+
'0.79.0', // reactNativeVersion
320+
mockPlatforms,
315321
);
316322

317-
expect(tools.outro).toBeCalledWith('Success 🎉.');
318323
expect(tools.logger.error).not.toBeCalled();
319324

320325
// Runs installDebug with only active architecture arm64-v8a
@@ -361,9 +366,12 @@ test('runAndroid runs gradle build with custom --appId, --appIdSuffix and --main
361366
'/',
362367
undefined,
363368
{ extraSources: [], ignorePaths: [], env: [] },
369+
vi.fn(), // startDevServer mock
370+
'/path/to/react-native', // reactNativePath
371+
'0.79.0', // reactNativeVersion
372+
mockPlatforms,
364373
);
365374

366-
expect(tools.outro).toBeCalledWith('Success 🎉.');
367375
expect(logErrorSpy).not.toBeCalled();
368376

369377
// launches com.custom.suffix app with OtherActivity on emulator-5552
@@ -388,6 +396,10 @@ test('runAndroid fails to launch an app on not-connected device when specified w
388396
'/',
389397
undefined,
390398
{ extraSources: [], ignorePaths: [], env: [] },
399+
vi.fn(), // startDevServer mock
400+
'/path/to/react-native', // reactNativePath
401+
'0.79.0', // reactNativeVersion
402+
mockPlatforms,
391403
);
392404
expect(logWarnSpy).toBeCalledWith(
393405
'No devices or emulators found matching "emulator-5554". Using available one instead.',
@@ -457,6 +469,10 @@ test.each([['release'], ['debug']])(
457469
'/',
458470
undefined,
459471
{ extraSources: [], ignorePaths: [], env: [] },
472+
vi.fn(), // startDevServer mock
473+
'/path/to/react-native', // reactNativePath
474+
'0.79.0', // reactNativeVersion
475+
mockPlatforms,
460476
);
461477

462478
// we don't want to run installDebug when a device is selected, because gradle will install the app on all connected devices
@@ -513,11 +529,21 @@ test('runAndroid launches an app on all connected devices', async () => {
513529
});
514530
});
515531

516-
await runAndroid({ ...androidProject }, { ...args }, '/', undefined, {
517-
extraSources: [],
518-
ignorePaths: [],
519-
env: [],
520-
});
532+
await runAndroid(
533+
{ ...androidProject },
534+
{ ...args },
535+
'/',
536+
undefined,
537+
{
538+
extraSources: [],
539+
ignorePaths: [],
540+
env: [],
541+
},
542+
vi.fn(),
543+
'/path/to/react-native',
544+
'0.79.0',
545+
mockPlatforms,
546+
);
521547

522548
// Runs assemble debug task with active architectures arm64-v8a, armeabi-v7a
523549
expect(spawn).toBeCalledWith(
@@ -584,6 +610,10 @@ test('runAndroid skips building when --binary-path is passed', async () => {
584610
'/root',
585611
undefined,
586612
{ extraSources: [], ignorePaths: [], env: [] },
613+
vi.fn(), // startDevServer mock
614+
'/path/to/react-native', // reactNativePath
615+
'0.79.0', // reactNativeVersion
616+
mockPlatforms,
587617
);
588618

589619
// Skips gradle

packages/platform-android/src/lib/commands/runAndroid/command.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { AndroidProjectConfig } from '@react-native-community/cli-types';
22
import type { PluginApi } from '@rock-js/config';
3+
import { intro, outro } from '@rock-js/tools';
34
import { getValidProjectConfig } from '../getValidProjectConfig.js';
45
import type { Flags } from './runAndroid.js';
56
import { runAndroid, runOptions } from './runAndroid.js';
@@ -13,6 +14,7 @@ export function registerRunCommand(
1314
description:
1415
'Builds your app and starts it on a connected Android emulator or a device.',
1516
action: async (args) => {
17+
intro('Running Android app');
1618
const projectRoot = api.getProjectRoot();
1719
const androidConfig = getValidProjectConfig(projectRoot, pluginConfig);
1820
await runAndroid(
@@ -21,7 +23,12 @@ export function registerRunCommand(
2123
projectRoot,
2224
await api.getRemoteCacheProvider(),
2325
api.getFingerprintOptions(),
26+
api.getBundlerStart(),
27+
api.getReactNativeVersion(),
28+
api.getReactNativePath(),
29+
api.getPlatforms(),
2430
);
31+
outro('Success 🎉.');
2532
},
2633
options: runOptions,
2734
});

packages/platform-android/src/lib/commands/runAndroid/runAndroid.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@ import type {
44
AndroidProjectConfig,
55
Config,
66
} from '@react-native-community/cli-types';
7+
import type { StartDevServerArgs } from '@rock-js/config';
78
import type { FingerprintSources, RemoteBuildCache } from '@rock-js/tools';
89
import {
910
color,
1011
formatArtifactName,
11-
intro,
1212
isInteractive,
1313
logger,
14-
outro,
1514
promptSelect,
1615
RockError,
1716
spinner,
@@ -37,6 +36,8 @@ export interface Flags extends BuildFlags {
3736
binaryPath?: string;
3837
user?: string;
3938
local?: boolean;
39+
devServer?: boolean;
40+
clientLogs?: boolean;
4041
}
4142

4243
export type AndroidProject = NonNullable<Config['project']['android']>;
@@ -50,8 +51,26 @@ export async function runAndroid(
5051
projectRoot: string,
5152
remoteCacheProvider: null | (() => RemoteBuildCache) | undefined,
5253
fingerprintOptions: FingerprintSources,
54+
startDevServer: (options: StartDevServerArgs) => void,
55+
reactNativeVersion: string,
56+
reactNativePath: string,
57+
platforms: { [platform: string]: object },
5358
) {
54-
intro('Running Android app');
59+
const startDevServerHelper = () => {
60+
if (args.devServer) {
61+
logger.info('Starting dev server...');
62+
startDevServer({
63+
root: projectRoot,
64+
reactNativePath,
65+
reactNativeVersion,
66+
platforms,
67+
args: {
68+
interactive: isInteractive(),
69+
clientLogs: args.clientLogs ?? true,
70+
},
71+
});
72+
}
73+
};
5574

5675
normalizeArgs(args, projectRoot);
5776

@@ -111,7 +130,7 @@ export async function runAndroid(
111130
}
112131
}
113132

114-
outro('Success 🎉.');
133+
startDevServerHelper();
115134
}
116135

117136
async function selectAndLaunchDevice() {
@@ -279,4 +298,13 @@ export const runOptions = [
279298
name: '--user <number>',
280299
description: 'Id of the User Profile you want to install the app on.',
281300
},
301+
{
302+
name: '--client-logs',
303+
description: 'Enable client logs in dev server.',
304+
},
305+
{
306+
name: '--dev-server',
307+
description:
308+
'Automatically start a dev server (bundler) after building the app.',
309+
},
282310
];

0 commit comments

Comments
 (0)