Skip to content

Commit f3ff47f

Browse files
satya164atlj
authored andcommitted
feat: validate configuration with arktype
1 parent 23f5ac7 commit f3ff47f

File tree

9 files changed

+152
-73
lines changed

9 files changed

+152
-73
lines changed

packages/react-native-builder-bob/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"@babel/preset-flow": "^7.24.7",
5252
"@babel/preset-react": "^7.24.7",
5353
"@babel/preset-typescript": "^7.24.7",
54+
"arktype": "^2.1.15",
5455
"babel-plugin-module-resolver": "^5.0.2",
5556
"browserslist": "^4.20.4",
5657
"cross-spawn": "^7.0.3",

packages/react-native-builder-bob/src/build.ts

Lines changed: 29 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import fs from 'fs-extra';
22
import kleur from 'kleur';
33
import path from 'path';
44
import yargs from 'yargs';
5-
import { type Options, type Target } from './types';
65
import { loadConfig } from './utils/loadConfig';
76
import * as logger from './utils/logger';
87
import { run } from './utils/workerize';
8+
import { config, type Config, type Target, type TargetOptions } from './schema';
9+
import { type } from 'arktype';
910

1011
export const args = {
1112
target: {
@@ -39,46 +40,19 @@ export async function build(argv: Argv) {
3940
);
4041
}
4142

42-
const options: Options = result!.config;
43+
const parsed = config(result.config);
4344

44-
if (!options.targets?.length) {
45-
throw new Error(
46-
`No 'targets' found in the configuration in '${path.relative(
47-
root,
48-
result!.filepath
49-
)}'.`
50-
);
51-
}
52-
53-
const source = options.source;
54-
55-
if (!source) {
56-
throw new Error(
57-
`No 'source' option found in the configuration in '${path.relative(
58-
root,
59-
result!.filepath
60-
)}'.`
61-
);
45+
if (parsed instanceof type.errors) {
46+
throw new Error(`Invalid configuration: ${parsed.summary}`);
6247
}
6348

64-
const output = options.output;
49+
const { source, output, targets, exclude } = parsed;
6550

66-
if (!output) {
67-
throw new Error(
68-
`No 'output' option found in the configuration in '${path.relative(
69-
root,
70-
result!.filepath
71-
)}'.`
72-
);
73-
}
74-
75-
const exclude = options.exclude ?? '**/{__tests__,__fixtures__,__mocks__}/**';
76-
77-
const commonjs = options.targets?.some((t) =>
51+
const commonjs = targets.some((t) =>
7852
Array.isArray(t) ? t[0] === 'commonjs' : t === 'commonjs'
7953
);
8054

81-
const module = options.targets?.some((t) =>
55+
const module = targets.some((t) =>
8256
Array.isArray(t) ? t[0] === 'module' : t === 'module'
8357
);
8458

@@ -94,68 +68,69 @@ export async function build(argv: Argv) {
9468
source,
9569
output,
9670
exclude,
97-
options,
71+
config: parsed,
9872
variants,
9973
});
10074
} else {
10175
await Promise.all(
102-
options.targets?.map((target) =>
76+
targets.map((target) =>
10377
buildTarget({
10478
root,
105-
target,
79+
target: Array.isArray(target) ? target[0] : target,
10680
source,
10781
output,
10882
exclude,
109-
options,
83+
config: parsed,
11084
variants,
11185
})
11286
)
11387
);
11488
}
11589
}
11690

117-
async function buildTarget({
91+
async function buildTarget<T extends Target>({
11892
root,
11993
target,
12094
source,
12195
output,
12296
exclude,
123-
options,
97+
config,
12498
variants,
12599
}: {
126100
root: string;
127-
target: Exclude<Options['targets'], undefined>[number];
101+
target: T;
128102
source: string;
129103
output: string;
130104
exclude: string;
131-
options: Options;
105+
config: Config;
132106
variants: {
133107
commonjs?: boolean;
134108
module?: boolean;
135109
};
136110
}) {
137-
const targetName = Array.isArray(target) ? target[0] : target;
138-
const targetOptions = Array.isArray(target) ? target[1] : undefined;
111+
const options = config.targets
112+
.map((t) => (Array.isArray(t) ? t : ([t, undefined] as const)))
113+
.find((t) => t[0] === target)?.[1];
139114

140-
const report = logger.grouped(targetName);
115+
const report = logger.grouped(target);
141116

142-
switch (targetName) {
117+
switch (target) {
143118
case 'commonjs':
144119
case 'module':
145-
await run(targetName, {
120+
await run(target, {
146121
root,
147122
source: path.resolve(root, source),
148-
output: path.resolve(root, output, targetName),
123+
output: path.resolve(root, output, target),
149124
exclude,
150-
options: targetOptions,
125+
options: options as TargetOptions<'commonjs' | 'module'>,
151126
variants,
152127
report,
153128
});
154129
break;
155130
case 'typescript':
156131
{
157132
const esm =
158-
options.targets?.some((t) => {
133+
config.targets?.some((t) => {
159134
if (Array.isArray(t)) {
160135
const [name, options] = t;
161136

@@ -171,7 +146,7 @@ async function buildTarget({
171146
root,
172147
source: path.resolve(root, source),
173148
output: path.resolve(root, output, 'typescript'),
174-
options: targetOptions,
149+
options: options as TargetOptions<'typescript'>,
175150
esm,
176151
variants,
177152
report,
@@ -182,19 +157,18 @@ async function buildTarget({
182157
await run('codegen', {
183158
root,
184159
source: path.resolve(root, source),
185-
output: path.resolve(root, output, 'typescript'),
186160
report,
187161
});
188162
break;
189163
case 'custom':
190164
await run('custom', {
191-
options: targetOptions,
165+
root,
192166
source: path.resolve(root, source),
167+
options: options as TargetOptions<'custom'>,
193168
report,
194-
root,
195169
});
196170
break;
197171
default:
198-
throw new Error(`Invalid target ${kleur.blue(targetName)}.`);
172+
throw new Error(`Invalid target ${kleur.blue(target)}.`);
199173
}
200174
}

packages/react-native-builder-bob/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import yargs from 'yargs';
22
import { build } from './build';
33
import { init } from './init';
4-
import type { Target } from './types';
4+
import type { Target } from './schema';
55

66
type ArgName = 'target';
77

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { type } from 'arktype';
2+
3+
const module = {
4+
name: '"module"',
5+
options: type({
6+
esm: 'boolean?',
7+
babelrc: 'boolean?',
8+
configFile: 'boolean?',
9+
sourceMaps: 'boolean?',
10+
copyFlow: 'boolean?',
11+
jsxRuntime: '"automatic" | "classic"?',
12+
}),
13+
} as const;
14+
15+
const commonjs = {
16+
name: '"commonjs"',
17+
options: module.options,
18+
} as const;
19+
20+
const typescript = {
21+
name: '"typescript"',
22+
options: type({
23+
project: 'string?',
24+
tsc: 'string?',
25+
}),
26+
} as const;
27+
28+
const codegen = {
29+
name: '"codegen"',
30+
} as const;
31+
32+
const custom = {
33+
name: '"custom"',
34+
options: type({
35+
script: 'string',
36+
clean: 'string?',
37+
}),
38+
} as const;
39+
40+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
41+
const target = type.or(
42+
commonjs.name,
43+
module.name,
44+
typescript.name,
45+
codegen.name,
46+
custom.name
47+
);
48+
49+
export const config = type({
50+
source: 'string',
51+
output: 'string',
52+
targets: type
53+
.or(
54+
type.or(module.name, [module.name], [module.name, module.options]),
55+
type.or(
56+
commonjs.name,
57+
[commonjs.name],
58+
[commonjs.name, commonjs.options]
59+
),
60+
type.or(
61+
typescript.name,
62+
[typescript.name],
63+
[typescript.name, typescript.options]
64+
),
65+
type.or(codegen.name, [codegen.name]),
66+
[custom.name, custom.options]
67+
)
68+
.array()
69+
.moreThanLength(0),
70+
exclude: type.string.default('**/{__tests__,__fixtures__,__mocks__}/**'),
71+
});
72+
73+
export type Config = typeof config.infer;
74+
75+
export type Target = typeof target.infer;
76+
77+
export type TargetOptions<T extends Target> = T extends typeof commonjs.name
78+
? typeof commonjs.options.infer
79+
: T extends typeof module.name
80+
? typeof module.options.infer
81+
: T extends typeof typescript.name
82+
? typeof typescript.options.infer
83+
: T extends typeof custom.name
84+
? typeof custom.options.infer
85+
: T extends typeof codegen.name
86+
? undefined
87+
: never;

packages/react-native-builder-bob/src/targets/codegen/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import del from 'del';
77
import { runRNCCli } from '../../utils/runRNCCli';
88
import { removeCodegenAppLevelCode } from './patches/removeCodegenAppLevelCode';
99

10-
type Options = Input;
10+
type Options = Omit<Input, 'output'>;
1111

1212
export default async function build({ root, report }: Options) {
1313
const packageJsonPath = path.resolve(root, 'package.json');

packages/react-native-builder-bob/src/types.ts

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export type Log = (message: string) => void;
2+
23
export type Report = {
34
info: Log;
45
warn: Log;
@@ -13,20 +14,6 @@ export type Input = {
1314
report: Report;
1415
};
1516

16-
export type Target =
17-
| 'commonjs'
18-
| 'module'
19-
| 'typescript'
20-
| 'codegen'
21-
| 'custom';
22-
23-
export type Options = {
24-
source?: string;
25-
output?: string;
26-
targets?: (Target | [target: Target, options: object])[];
27-
exclude?: string;
28-
};
29-
3017
export type Variants = {
3118
commonjs?: boolean;
3219
module?: boolean;

packages/react-native-builder-bob/src/utils/loadConfig.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ const searchPlaces = [
88
'package.json',
99
];
1010

11-
export const loadConfig = (root: string) => {
11+
export const loadConfig = (
12+
root: string
13+
): { filepath: string; config: unknown } | undefined => {
1214
for (const filename of searchPlaces) {
1315
const result = requireConfig(root, filename);
1416

packages/react-native-builder-bob/src/utils/workerize.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import commonjs from '../targets/commonjs';
99
import custom from '../targets/custom';
1010
import module from '../targets/module';
1111
import typescript from '../targets/typescript';
12-
import type { Report, Target } from '../types';
12+
import type { Report } from '../types';
13+
import type { Target } from '../schema';
1314

1415
type WorkerData<T extends Target> = {
1516
target: T;

yarn.lock

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,22 @@ __metadata:
2222
languageName: node
2323
linkType: hard
2424

25+
"@ark/schema@npm:0.45.5":
26+
version: 0.45.5
27+
resolution: "@ark/schema@npm:0.45.5"
28+
dependencies:
29+
"@ark/util": 0.45.5
30+
checksum: d4cd8f3e67f785f1a499e966bc9d10d43597a2dee26ae3226c681d9dfa5e88c8229c989b30703b785fa009eeef7158c54e52ca4bdebe67940e9bb0295d03a185
31+
languageName: node
32+
linkType: hard
33+
34+
"@ark/util@npm:0.45.5":
35+
version: 0.45.5
36+
resolution: "@ark/util@npm:0.45.5"
37+
checksum: 2074b5c0055b3ac857e33c6ffd26463cdd2813aad46b280de8238edf2b7e8a7264e03e73c2e2890cfe2af589742fb982ebaee3f60605238a14b0eb0a0262eb14
38+
languageName: node
39+
linkType: hard
40+
2541
"@babel/cli@npm:^7.24.8":
2642
version: 7.24.8
2743
resolution: "@babel/cli@npm:7.24.8"
@@ -4448,6 +4464,16 @@ __metadata:
44484464
languageName: node
44494465
linkType: hard
44504466

4467+
"arktype@npm:^2.1.15":
4468+
version: 2.1.15
4469+
resolution: "arktype@npm:2.1.15"
4470+
dependencies:
4471+
"@ark/schema": 0.45.5
4472+
"@ark/util": 0.45.5
4473+
checksum: 2f407713f42a7976362c5394435c841b9913e32722c8e0cbf2313a5931a5e7dad64b1efc8fbcac79fd4e7a58be22ab68b6b9c07b7f19abb65862ed548429006a
4474+
languageName: node
4475+
linkType: hard
4476+
44514477
"array-differ@npm:^3.0.0":
44524478
version: 3.0.0
44534479
resolution: "array-differ@npm:3.0.0"
@@ -12723,6 +12749,7 @@ __metadata:
1272312749
"@types/prompts": ^2.0.14
1272412750
"@types/which": ^2.0.1
1272512751
"@types/yargs": ^17.0.10
12752+
arktype: ^2.1.15
1272612753
babel-plugin-module-resolver: ^5.0.2
1272712754
browserslist: ^4.20.4
1272812755
concurrently: ^7.2.2

0 commit comments

Comments
 (0)