@react-native-harness/cli 1.2.0-rc.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/platform-commands.test.d.ts +2 -0
- package/dist/__tests__/platform-commands.test.d.ts.map +1 -0
- package/dist/__tests__/platform-commands.test.js +207 -0
- package/dist/index.js +113 -6
- package/dist/platform-commands.d.ts +18 -0
- package/dist/platform-commands.d.ts.map +1 -0
- package/dist/platform-commands.js +84 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/dist/wizard/bundleId.js +1 -1
- package/eslint.config.mjs +1 -1
- package/package.json +8 -8
- package/skills/core.md +92 -0
- package/skills/mocking.md +87 -0
- package/skills/ui.md +59 -0
- package/src/__tests__/platform-commands.test.ts +232 -0
- package/src/index.ts +152 -5
- package/src/platform-commands.ts +148 -0
- package/src/wizard/bundleId.ts +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform-commands.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/platform-commands.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { discoverPlatformCommands, runPlatformCommand, } from '../platform-commands.js';
|
|
3
|
+
const createCommandModuleUrl = (body) => `data:text/javascript,${encodeURIComponent(body)}`;
|
|
4
|
+
const globalState = globalThis;
|
|
5
|
+
describe('platform CLI command discovery', () => {
|
|
6
|
+
afterEach(() => {
|
|
7
|
+
delete globalState.__platformCommandCall;
|
|
8
|
+
});
|
|
9
|
+
it('runs a discovered platform command', async () => {
|
|
10
|
+
const moduleUrl = createCommandModuleUrl(`
|
|
11
|
+
export const commands = [{
|
|
12
|
+
name: 'xctest',
|
|
13
|
+
async run(args, context) {
|
|
14
|
+
globalThis.__platformCommandCall = { args, context };
|
|
15
|
+
}
|
|
16
|
+
}];
|
|
17
|
+
`);
|
|
18
|
+
const loadConfig = vi.fn(async () => ({
|
|
19
|
+
projectRoot: '/tmp/project',
|
|
20
|
+
config: {
|
|
21
|
+
entryPoint: 'index.js',
|
|
22
|
+
appRegistryComponentName: 'App',
|
|
23
|
+
runners: [
|
|
24
|
+
{
|
|
25
|
+
name: 'ios',
|
|
26
|
+
config: {},
|
|
27
|
+
runner: '/virtual/runner.js',
|
|
28
|
+
cli: moduleUrl,
|
|
29
|
+
platformId: 'ios',
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
plugins: [],
|
|
33
|
+
metroPort: 8081,
|
|
34
|
+
webSocketPort: undefined,
|
|
35
|
+
bridgeTimeout: 60000,
|
|
36
|
+
platformReadyTimeout: 300000,
|
|
37
|
+
bundleStartTimeout: 60000,
|
|
38
|
+
maxAppRestarts: 2,
|
|
39
|
+
resetEnvironmentBetweenTestFiles: true,
|
|
40
|
+
unstable__skipAlreadyIncludedModules: false,
|
|
41
|
+
unstable__enableMetroCache: false,
|
|
42
|
+
permissions: false,
|
|
43
|
+
detectNativeCrashes: true,
|
|
44
|
+
crashDetectionInterval: 500,
|
|
45
|
+
disableViewFlattening: false,
|
|
46
|
+
forwardClientLogs: false,
|
|
47
|
+
},
|
|
48
|
+
}));
|
|
49
|
+
expect(await runPlatformCommand({
|
|
50
|
+
argv: ['xctest', 'build', '--destination', 'simulator'],
|
|
51
|
+
cwd: '/tmp/project',
|
|
52
|
+
loadConfig,
|
|
53
|
+
})).toBe(true);
|
|
54
|
+
expect(globalState.__platformCommandCall).toEqual({
|
|
55
|
+
args: ['build', '--destination', 'simulator'],
|
|
56
|
+
context: {
|
|
57
|
+
cwd: '/tmp/project',
|
|
58
|
+
projectRoot: '/tmp/project',
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
it('deduplicates platform CLI modules across runners', async () => {
|
|
63
|
+
const moduleUrl = createCommandModuleUrl(`
|
|
64
|
+
export const commands = [{
|
|
65
|
+
name: 'xctest',
|
|
66
|
+
async run() {}
|
|
67
|
+
}];
|
|
68
|
+
`);
|
|
69
|
+
const loadConfig = vi.fn(async () => ({
|
|
70
|
+
projectRoot: '/tmp/project',
|
|
71
|
+
config: {
|
|
72
|
+
entryPoint: 'index.js',
|
|
73
|
+
appRegistryComponentName: 'App',
|
|
74
|
+
runners: [
|
|
75
|
+
{
|
|
76
|
+
name: 'ios-sim',
|
|
77
|
+
config: {},
|
|
78
|
+
runner: '/virtual/ios-sim-runner.js',
|
|
79
|
+
cli: moduleUrl,
|
|
80
|
+
platformId: 'ios',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: 'ios-device',
|
|
84
|
+
config: {},
|
|
85
|
+
runner: '/virtual/ios-device-runner.js',
|
|
86
|
+
cli: moduleUrl,
|
|
87
|
+
platformId: 'ios',
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
plugins: [],
|
|
91
|
+
metroPort: 8081,
|
|
92
|
+
webSocketPort: undefined,
|
|
93
|
+
bridgeTimeout: 60000,
|
|
94
|
+
platformReadyTimeout: 300000,
|
|
95
|
+
bundleStartTimeout: 60000,
|
|
96
|
+
maxAppRestarts: 2,
|
|
97
|
+
resetEnvironmentBetweenTestFiles: true,
|
|
98
|
+
unstable__skipAlreadyIncludedModules: false,
|
|
99
|
+
unstable__enableMetroCache: false,
|
|
100
|
+
permissions: false,
|
|
101
|
+
detectNativeCrashes: true,
|
|
102
|
+
crashDetectionInterval: 500,
|
|
103
|
+
disableViewFlattening: false,
|
|
104
|
+
forwardClientLogs: false,
|
|
105
|
+
},
|
|
106
|
+
}));
|
|
107
|
+
const discoveredCommands = await discoverPlatformCommands({
|
|
108
|
+
cwd: '/tmp/project',
|
|
109
|
+
loadConfig,
|
|
110
|
+
});
|
|
111
|
+
expect(discoveredCommands?.commands).toHaveLength(1);
|
|
112
|
+
});
|
|
113
|
+
it('returns false when no platform command matches', async () => {
|
|
114
|
+
const loadConfig = vi.fn(async () => ({
|
|
115
|
+
projectRoot: '/tmp/project',
|
|
116
|
+
config: {
|
|
117
|
+
entryPoint: 'index.js',
|
|
118
|
+
appRegistryComponentName: 'App',
|
|
119
|
+
runners: [
|
|
120
|
+
{
|
|
121
|
+
name: 'android',
|
|
122
|
+
config: {},
|
|
123
|
+
runner: '/virtual/android-runner.js',
|
|
124
|
+
platformId: 'android',
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
plugins: [],
|
|
128
|
+
metroPort: 8081,
|
|
129
|
+
webSocketPort: undefined,
|
|
130
|
+
bridgeTimeout: 60000,
|
|
131
|
+
platformReadyTimeout: 300000,
|
|
132
|
+
bundleStartTimeout: 60000,
|
|
133
|
+
maxAppRestarts: 2,
|
|
134
|
+
resetEnvironmentBetweenTestFiles: true,
|
|
135
|
+
unstable__skipAlreadyIncludedModules: false,
|
|
136
|
+
unstable__enableMetroCache: false,
|
|
137
|
+
permissions: false,
|
|
138
|
+
detectNativeCrashes: true,
|
|
139
|
+
crashDetectionInterval: 500,
|
|
140
|
+
disableViewFlattening: false,
|
|
141
|
+
forwardClientLogs: false,
|
|
142
|
+
},
|
|
143
|
+
}));
|
|
144
|
+
await expect(runPlatformCommand({
|
|
145
|
+
argv: ['xctest', 'build'],
|
|
146
|
+
cwd: '/tmp/project',
|
|
147
|
+
loadConfig,
|
|
148
|
+
})).resolves.toBe(false);
|
|
149
|
+
});
|
|
150
|
+
it('throws when two platform modules define the same command', async () => {
|
|
151
|
+
const firstModuleUrl = createCommandModuleUrl(`
|
|
152
|
+
export const commands = [{
|
|
153
|
+
name: 'xctest',
|
|
154
|
+
async run() {}
|
|
155
|
+
}];
|
|
156
|
+
`);
|
|
157
|
+
const secondModuleUrl = createCommandModuleUrl(`
|
|
158
|
+
// second module
|
|
159
|
+
export const commands = [{
|
|
160
|
+
name: 'xctest',
|
|
161
|
+
async run() {}
|
|
162
|
+
}];
|
|
163
|
+
`);
|
|
164
|
+
const loadConfig = vi.fn(async () => ({
|
|
165
|
+
projectRoot: '/tmp/project',
|
|
166
|
+
config: {
|
|
167
|
+
entryPoint: 'index.js',
|
|
168
|
+
appRegistryComponentName: 'App',
|
|
169
|
+
runners: [
|
|
170
|
+
{
|
|
171
|
+
name: 'ios',
|
|
172
|
+
config: {},
|
|
173
|
+
runner: '/virtual/ios-runner.js',
|
|
174
|
+
cli: firstModuleUrl,
|
|
175
|
+
platformId: 'ios',
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: 'android',
|
|
179
|
+
config: {},
|
|
180
|
+
runner: '/virtual/android-runner.js',
|
|
181
|
+
cli: secondModuleUrl,
|
|
182
|
+
platformId: 'android',
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
plugins: [],
|
|
186
|
+
metroPort: 8081,
|
|
187
|
+
webSocketPort: undefined,
|
|
188
|
+
bridgeTimeout: 60000,
|
|
189
|
+
platformReadyTimeout: 300000,
|
|
190
|
+
bundleStartTimeout: 60000,
|
|
191
|
+
maxAppRestarts: 2,
|
|
192
|
+
resetEnvironmentBetweenTestFiles: true,
|
|
193
|
+
unstable__skipAlreadyIncludedModules: false,
|
|
194
|
+
unstable__enableMetroCache: false,
|
|
195
|
+
permissions: false,
|
|
196
|
+
detectNativeCrashes: true,
|
|
197
|
+
crashDetectionInterval: 500,
|
|
198
|
+
disableViewFlattening: false,
|
|
199
|
+
forwardClientLogs: false,
|
|
200
|
+
},
|
|
201
|
+
}));
|
|
202
|
+
await expect(discoverPlatformCommands({
|
|
203
|
+
cwd: '/tmp/project',
|
|
204
|
+
loadConfig,
|
|
205
|
+
})).rejects.toThrow("Duplicate platform CLI command 'xctest'");
|
|
206
|
+
});
|
|
207
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,101 @@
|
|
|
1
1
|
import { run, yargsOptions } from 'jest-cli';
|
|
2
2
|
import { getConfig } from '@react-native-harness/config';
|
|
3
3
|
import { runInitWizard } from './wizard/index.js';
|
|
4
|
+
import { runPlatformCommand } from './platform-commands.js';
|
|
4
5
|
import fs from 'node:fs';
|
|
5
6
|
import path from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
6
8
|
const JEST_CONFIG_EXTENSIONS = ['.mjs', '.js', '.cjs'];
|
|
7
9
|
const JEST_HARNESS_CONFIG_BASE = 'jest.harness.config';
|
|
10
|
+
const SKILLS_DIRECTORY = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../skills');
|
|
11
|
+
const readSkillMetadata = (fileName) => {
|
|
12
|
+
const filePath = path.join(SKILLS_DIRECTORY, fileName);
|
|
13
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
14
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
15
|
+
const metadata = {
|
|
16
|
+
name: fileName.replace(/\.md$/, ''),
|
|
17
|
+
description: '',
|
|
18
|
+
};
|
|
19
|
+
if (frontmatterMatch) {
|
|
20
|
+
for (const line of frontmatterMatch[1].split('\n')) {
|
|
21
|
+
const separatorIndex = line.indexOf(':');
|
|
22
|
+
if (separatorIndex === -1) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const key = line.slice(0, separatorIndex).trim();
|
|
26
|
+
const value = line.slice(separatorIndex + 1).trim().replace(/^['"]|['"]$/g, '');
|
|
27
|
+
if (key === 'name') {
|
|
28
|
+
metadata.name = value;
|
|
29
|
+
}
|
|
30
|
+
if (key === 'description') {
|
|
31
|
+
metadata.description = value;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
fileName,
|
|
37
|
+
name: metadata.name,
|
|
38
|
+
description: metadata.description,
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
const listSkills = () => fs
|
|
42
|
+
.readdirSync(SKILLS_DIRECTORY)
|
|
43
|
+
.filter((file) => file.endsWith('.md'))
|
|
44
|
+
.map(readSkillMetadata)
|
|
45
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
46
|
+
const printSkillList = () => {
|
|
47
|
+
for (const skill of listSkills()) {
|
|
48
|
+
console.log(`${skill.name}: ${skill.description}`);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const printSkillUsage = () => {
|
|
52
|
+
console.log(`Usage: harness skill <command>
|
|
53
|
+
|
|
54
|
+
Commands:
|
|
55
|
+
list List bundled skills
|
|
56
|
+
get <name> Print a bundled skill file
|
|
57
|
+
|
|
58
|
+
Examples:
|
|
59
|
+
harness skill list
|
|
60
|
+
harness skill get core`);
|
|
61
|
+
};
|
|
62
|
+
const getErrorMessage = (error) => {
|
|
63
|
+
if (error instanceof Error) {
|
|
64
|
+
return error.message;
|
|
65
|
+
}
|
|
66
|
+
return String(error);
|
|
67
|
+
};
|
|
68
|
+
const runSkillCommand = () => {
|
|
69
|
+
const [, , commandName, subcommand, skillName] = process.argv;
|
|
70
|
+
if (subcommand === undefined || subcommand === 'list') {
|
|
71
|
+
printSkillList();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (subcommand === '--help' || subcommand === '-h') {
|
|
75
|
+
printSkillUsage();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (subcommand === 'get') {
|
|
79
|
+
if (!skillName) {
|
|
80
|
+
console.error('Missing skill name.');
|
|
81
|
+
printSkillUsage();
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
const skillPath = path.join(SKILLS_DIRECTORY, `${skillName}.md`);
|
|
85
|
+
if (!fs.existsSync(skillPath)) {
|
|
86
|
+
console.error(`Unknown skill '${skillName}'.`);
|
|
87
|
+
console.error(`Available skills: ${listSkills()
|
|
88
|
+
.map((skill) => skill.name)
|
|
89
|
+
.join(', ')}`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
console.log(fs.readFileSync(skillPath, 'utf8'));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
console.error(`Unknown ${commandName} subcommand '${subcommand}'.`);
|
|
96
|
+
printSkillUsage();
|
|
97
|
+
process.exit(1);
|
|
98
|
+
};
|
|
8
99
|
const checkForOldConfig = async () => {
|
|
9
100
|
try {
|
|
10
101
|
const { config } = await getConfig(process.cwd());
|
|
@@ -62,10 +153,21 @@ const patchYargsOptions = () => {
|
|
|
62
153
|
delete yargsOptions.coverageProvider;
|
|
63
154
|
delete yargsOptions.logHeapUsage;
|
|
64
155
|
};
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
156
|
+
const main = async () => {
|
|
157
|
+
if (process.argv[2] === 'skill' || process.argv[2] === 'skills') {
|
|
158
|
+
runSkillCommand();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (process.argv.includes('init')) {
|
|
162
|
+
runInitWizard();
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (await runPlatformCommand({
|
|
166
|
+
argv: process.argv.slice(2),
|
|
167
|
+
cwd: process.cwd(),
|
|
168
|
+
})) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
69
171
|
patchYargsOptions();
|
|
70
172
|
const hasConfigArg = process.argv.includes('--config') || process.argv.includes('-c');
|
|
71
173
|
if (!hasConfigArg) {
|
|
@@ -74,5 +176,10 @@ else {
|
|
|
74
176
|
process.argv.push('--config', `${JEST_HARNESS_CONFIG_BASE}${existingConfigExt}`);
|
|
75
177
|
}
|
|
76
178
|
}
|
|
77
|
-
checkForOldConfig()
|
|
78
|
-
|
|
179
|
+
await checkForOldConfig();
|
|
180
|
+
run();
|
|
181
|
+
};
|
|
182
|
+
main().catch((error) => {
|
|
183
|
+
console.error(getErrorMessage(error));
|
|
184
|
+
process.exit(1);
|
|
185
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { getConfig } from '@react-native-harness/config';
|
|
2
|
+
import type { HarnessCliCommand } from '@react-native-harness/platforms';
|
|
3
|
+
type ConfigLoader = typeof getConfig;
|
|
4
|
+
type DiscoveredPlatformCommands = {
|
|
5
|
+
commands: HarnessCliCommand[];
|
|
6
|
+
projectRoot: string;
|
|
7
|
+
};
|
|
8
|
+
export declare const discoverPlatformCommands: (options: {
|
|
9
|
+
cwd: string;
|
|
10
|
+
loadConfig?: ConfigLoader;
|
|
11
|
+
}) => Promise<DiscoveredPlatformCommands | null>;
|
|
12
|
+
export declare const runPlatformCommand: (options: {
|
|
13
|
+
argv: string[];
|
|
14
|
+
cwd: string;
|
|
15
|
+
loadConfig?: ConfigLoader;
|
|
16
|
+
}) => Promise<boolean>;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=platform-commands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform-commands.d.ts","sourceRoot":"","sources":["../src/platform-commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAC9E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAEzE,KAAK,YAAY,GAAG,OAAO,SAAS,CAAC;AAErC,KAAK,0BAA0B,GAAG;IAChC,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAsEF,eAAO,MAAM,wBAAwB,GAAU,SAAS;IACtD,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,YAAY,CAAC;CAC3B,KAAG,OAAO,CAAC,0BAA0B,GAAG,IAAI,CAgC5C,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAU,SAAS;IAChD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,YAAY,CAAC;CAC3B,KAAG,OAAO,CAAC,OAAO,CA4BlB,CAAC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { ConfigNotFoundError, getConfig } from '@react-native-harness/config';
|
|
2
|
+
const isRecord = (value) => typeof value === 'object' && value !== null;
|
|
3
|
+
const getModuleCommands = (importedModule, modulePath) => {
|
|
4
|
+
const moduleValue = isRecord(importedModule)
|
|
5
|
+
? importedModule
|
|
6
|
+
: {};
|
|
7
|
+
const defaultExport = isRecord(moduleValue.default)
|
|
8
|
+
? moduleValue.default
|
|
9
|
+
: undefined;
|
|
10
|
+
const commandsValue = moduleValue.commands ?? defaultExport?.commands;
|
|
11
|
+
if (!Array.isArray(commandsValue)) {
|
|
12
|
+
throw new Error(`Invalid platform CLI module '${modulePath}': expected a commands array.`);
|
|
13
|
+
}
|
|
14
|
+
return commandsValue.map((command, index) => {
|
|
15
|
+
if (!isRecord(command) || typeof command.name !== 'string') {
|
|
16
|
+
throw new Error(`Invalid platform CLI module '${modulePath}': command #${index + 1} is missing a valid name.`);
|
|
17
|
+
}
|
|
18
|
+
if (typeof command.run !== 'function') {
|
|
19
|
+
throw new Error(`Invalid platform CLI module '${modulePath}': command '${command.name}' is missing a run handler.`);
|
|
20
|
+
}
|
|
21
|
+
if (command.aliases !== undefined &&
|
|
22
|
+
(!Array.isArray(command.aliases) ||
|
|
23
|
+
command.aliases.some((alias) => typeof alias !== 'string'))) {
|
|
24
|
+
throw new Error(`Invalid platform CLI module '${modulePath}': command '${command.name}' has invalid aliases.`);
|
|
25
|
+
}
|
|
26
|
+
return command;
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
const registerCommandNames = (seenNames, modulePath, command) => {
|
|
30
|
+
const names = [command.name, ...(command.aliases ?? [])];
|
|
31
|
+
for (const name of names) {
|
|
32
|
+
const existingSource = seenNames.get(name);
|
|
33
|
+
if (existingSource !== undefined) {
|
|
34
|
+
throw new Error(`Duplicate platform CLI command '${name}' in '${modulePath}' and '${existingSource}'.`);
|
|
35
|
+
}
|
|
36
|
+
seenNames.set(name, modulePath);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
export const discoverPlatformCommands = async (options) => {
|
|
40
|
+
const loadConfig = options.loadConfig ?? getConfig;
|
|
41
|
+
try {
|
|
42
|
+
const { config, projectRoot } = await loadConfig(options.cwd);
|
|
43
|
+
const modulePaths = [...new Set(config.runners.map((runner) => runner.cli))].filter((modulePath) => typeof modulePath === 'string');
|
|
44
|
+
const commands = [];
|
|
45
|
+
const seenNames = new Map();
|
|
46
|
+
for (const modulePath of modulePaths) {
|
|
47
|
+
const importedModule = await import(modulePath);
|
|
48
|
+
const moduleCommands = getModuleCommands(importedModule, modulePath);
|
|
49
|
+
for (const command of moduleCommands) {
|
|
50
|
+
registerCommandNames(seenNames, modulePath, command);
|
|
51
|
+
commands.push(command);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
commands,
|
|
56
|
+
projectRoot,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
if (error instanceof ConfigNotFoundError) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
export const runPlatformCommand = async (options) => {
|
|
67
|
+
const commandName = options.argv[0];
|
|
68
|
+
if (typeof commandName !== 'string' || commandName.length === 0) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
const discoveredCommands = await discoverPlatformCommands(options);
|
|
72
|
+
if (discoveredCommands === null) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
const command = discoveredCommands.commands.find((entry) => entry.name === commandName || entry.aliases?.includes(commandName) === true);
|
|
76
|
+
if (command === undefined) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
await command.run(options.argv.slice(1), {
|
|
80
|
+
cwd: options.cwd,
|
|
81
|
+
projectRoot: discoveredCommands.projectRoot,
|
|
82
|
+
});
|
|
83
|
+
return true;
|
|
84
|
+
};
|