UNPKG

3.31 kBJavaScriptView Raw
1// @flow strict-local
2import type {PackageName} from '@parcel/types';
3import type {SchemaEntity} from '@parcel/utils';
4import assert from 'assert';
5
6// Reasoning behind this validation:
7// https://github.com/parcel-bundler/parcel/issues/3397#issuecomment-521353931
8export function validatePackageName(
9 pkg: ?PackageName,
10 pluginType: string,
11 key: string,
12) {
13 // $FlowFixMe
14 if (!pkg) {
15 return;
16 }
17
18 assert(typeof pkg === 'string', `"${key}" must be a string`);
19
20 if (pkg.startsWith('@parcel')) {
21 assert(
22 pkg.replace(/^@parcel\//, '').startsWith(`${pluginType}-`),
23 `Official parcel ${pluginType} packages must be named according to "@parcel/${pluginType}-{name}"`,
24 );
25 } else if (pkg.startsWith('@')) {
26 let [scope, name] = pkg.split('/');
27 assert(
28 name.startsWith(`parcel-${pluginType}-`),
29 `Scoped parcel ${pluginType} packages must be named according to "${scope}/parcel-${pluginType}-{name}"`,
30 );
31 } else {
32 assert(
33 pkg.startsWith(`parcel-${pluginType}-`),
34 `Parcel ${pluginType} packages must be named according to "parcel-${pluginType}-{name}"`,
35 );
36 }
37}
38
39const validatePluginName = (pluginType: string, key: string) => {
40 return (val: string) => {
41 // allow plugin spread...
42 if (val === '...') return;
43
44 try {
45 validatePackageName(val, pluginType, key);
46 } catch (e) {
47 return e.message;
48 }
49 };
50};
51
52const validateExtends = (val: string) => {
53 // allow relative paths...
54 if (val.startsWith('.')) return;
55
56 try {
57 validatePackageName(val, 'config', 'extends');
58 } catch (e) {
59 return e.message;
60 }
61};
62
63const pipelineSchema = (pluginType: string, key: string): SchemaEntity => {
64 return {
65 type: 'array',
66 items: {
67 type: 'string',
68 __validate: validatePluginName(pluginType, key),
69 },
70 };
71};
72
73const mapPipelineSchema = (pluginType: string, key: string): SchemaEntity => {
74 return {
75 type: 'object',
76 properties: {},
77 additionalProperties: pipelineSchema(pluginType, key),
78 };
79};
80
81const mapStringSchema = (pluginType: string, key: string): SchemaEntity => {
82 return {
83 type: 'object',
84 properties: {},
85 additionalProperties: {
86 type: 'string',
87 __validate: validatePluginName(pluginType, key),
88 },
89 };
90};
91
92export default {
93 type: 'object',
94 properties: {
95 extends: {
96 oneOf: [
97 {
98 type: 'string',
99 __validate: validateExtends,
100 },
101 {
102 type: 'array',
103 items: {
104 type: 'string',
105 __validate: validateExtends,
106 },
107 },
108 ],
109 },
110 bundler: {
111 type: 'string',
112 __validate: validatePluginName('bundler', 'bundler'),
113 },
114 resolvers: pipelineSchema('resolver', 'resolvers'),
115 transforms: mapPipelineSchema('transformer', 'transforms'),
116 validators: mapPipelineSchema('validator', 'validators'),
117 namers: pipelineSchema('namer', 'namers'),
118 packagers: mapStringSchema('packager', 'packagers'),
119 optimizers: mapPipelineSchema('optimizer', 'optimizers'),
120 reporters: pipelineSchema('reporter', 'reporters'),
121 runtimes: mapPipelineSchema('runtime', 'runtimes'),
122 filePath: {
123 type: 'string',
124 },
125 resolveFrom: {
126 type: 'string',
127 },
128 },
129 additionalProperties: false,
130};