Skip to content

Commit f42468f

Browse files
committed
feat: validate configuration with arktype
1 parent c9af25b commit f42468f

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,66 +68,67 @@ export async function build(argv: Argv) {
9468
source,
9569
output,
9670
exclude,
97-
options,
71+
config: parsed,
9872
variants,
9973
});
10074
} else {
101-
for (const target of options.targets!) {
75+
for (const target of targets) {
10276
buildTarget({
10377
root,
104-
target,
78+
target: Array.isArray(target) ? target[0] : target,
10579
source,
10680
output,
10781
exclude,
108-
options,
82+
config: parsed,
10983
variants,
11084
});
11185
}
11286
}
11387
}
11488

115-
async function buildTarget({
89+
async function buildTarget<T extends Target>({
11690
root,
11791
target,
11892
source,
11993
output,
12094
exclude,
121-
options,
95+
config,
12296
variants,
12397
}: {
12498
root: string;
125-
target: Exclude<Options['targets'], undefined>[number];
99+
target: T;
126100
source: string;
127101
output: string;
128102
exclude: string;
129-
options: Options;
103+
config: Config;
130104
variants: {
131105
commonjs?: boolean;
132106
module?: boolean;
133107
};
134108
}) {
135-
const targetName = Array.isArray(target) ? target[0] : target;
136-
const targetOptions = Array.isArray(target) ? target[1] : undefined;
109+
const options = config.targets
110+
.map((t) => (Array.isArray(t) ? t : ([t, undefined] as const)))
111+
.find((t) => t[0] === target)?.[1];
137112

138-
const report = logger.grouped(targetName);
113+
const report = logger.grouped(target);
139114

140-
switch (targetName) {
115+
switch (target) {
141116
case 'commonjs':
142117
case 'module':
143-
await run(targetName, {
118+
await run(target, {
144119
root,
145120
source: path.resolve(root, source),
146-
output: path.resolve(root, output, targetName),
121+
output: path.resolve(root, output, target),
147122
exclude,
148-
options: targetOptions,
123+
options: options as TargetOptions<'commonjs' | 'module'>,
149124
variants,
150125
report,
151126
});
152127
break;
153128
case 'typescript':
154129
{
155130
const esm =
156-
options.targets?.some((t) => {
131+
config.targets?.some((t) => {
157132
if (Array.isArray(t)) {
158133
const [name, options] = t;
159134

@@ -169,7 +144,7 @@ async function buildTarget({
169144
root,
170145
source: path.resolve(root, source),
171146
output: path.resolve(root, output, 'typescript'),
172-
options: targetOptions,
147+
options: options as TargetOptions<'typescript'>,
173148
esm,
174149
variants,
175150
report,
@@ -180,19 +155,18 @@ async function buildTarget({
180155
await run('codegen', {
181156
root,
182157
source: path.resolve(root, source),
183-
output: path.resolve(root, output, 'typescript'),
184158
report,
185159
});
186160
break;
187161
case 'custom':
188162
await run('custom', {
189-
options: targetOptions,
163+
root,
190164
source: path.resolve(root, source),
165+
options: options as TargetOptions<'custom'>,
191166
report,
192-
root,
193167
});
194168
break;
195169
default:
196-
throw new Error(`Invalid target ${kleur.blue(targetName)}.`);
170+
throw new Error(`Invalid target ${kleur.blue(target)}.`);
197171
}
198172
}

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: 'boolean?',
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
@@ -10,7 +10,8 @@ import commonjs from '../targets/commonjs';
1010
import custom from '../targets/custom';
1111
import module from '../targets/module';
1212
import typescript from '../targets/typescript';
13-
import type { Report, Target } from '../types';
13+
import type { Report } from '../types';
14+
import type { Target } from '../schema';
1415

1516
type WorkerData<T extends Target> = {
1617
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)