@smartdoca/plugin-contracts 0.1.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/LICENSE +661 -0
- package/dist/LICENSING.md +57 -0
- package/dist/LICENSING.zh-CN.md +48 -0
- package/dist/index.d.ts +167 -0
- package/dist/index.js +448 -0
- package/package.json +18 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
export class PluginContractError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
path;
|
|
4
|
+
name = "PluginContractError";
|
|
5
|
+
constructor(code, message, path = "$") {
|
|
6
|
+
super(`${path}: ${message}`);
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.path = path;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
const identifierPattern = /^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/;
|
|
12
|
+
const configKeyPattern = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
13
|
+
const semverPattern = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
14
|
+
const dependencyRangePattern = /^(?:\*|[\^~]?(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?)$/;
|
|
15
|
+
function fail(code, path, message) {
|
|
16
|
+
throw new PluginContractError(code, message, path);
|
|
17
|
+
}
|
|
18
|
+
function isRecord(value) {
|
|
19
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
20
|
+
return false;
|
|
21
|
+
const prototype = Object.getPrototypeOf(value);
|
|
22
|
+
return prototype === Object.prototype || prototype === null;
|
|
23
|
+
}
|
|
24
|
+
function objectAt(value, path, code) {
|
|
25
|
+
if (!isRecord(value))
|
|
26
|
+
fail(code, path, "expected an object");
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
function allowedKeys(value, allowed, path, code) {
|
|
30
|
+
const set = new Set(allowed);
|
|
31
|
+
const unexpected = Object.keys(value).find((key) => !set.has(key));
|
|
32
|
+
if (unexpected)
|
|
33
|
+
fail(code, `${path}.${unexpected}`, "unknown field");
|
|
34
|
+
}
|
|
35
|
+
function stringAt(value, path, code, options = {}) {
|
|
36
|
+
if (typeof value !== "string" ||
|
|
37
|
+
(options.nonempty && value.trim().length === 0))
|
|
38
|
+
fail(code, path, "expected a non-empty string");
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
function finiteAt(value, path, code) {
|
|
42
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
43
|
+
fail(code, path, "expected a finite number");
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
function nonNegativeIntegerAt(value, path, code) {
|
|
47
|
+
const number = finiteAt(value, path, code);
|
|
48
|
+
if (!Number.isInteger(number) || number < 0)
|
|
49
|
+
fail(code, path, "expected a non-negative integer");
|
|
50
|
+
return number;
|
|
51
|
+
}
|
|
52
|
+
function optionalDescription(value, path, code) {
|
|
53
|
+
if (value.description !== undefined)
|
|
54
|
+
stringAt(value.description, `${path}.description`, code, {
|
|
55
|
+
nonempty: true,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function assertConfigSchema(value, path) {
|
|
59
|
+
const schema = objectAt(value, path, "INVALID_MANIFEST");
|
|
60
|
+
const type = stringAt(schema.type, `${path}.type`, "INVALID_MANIFEST");
|
|
61
|
+
const common = ["type", "description", "default"];
|
|
62
|
+
optionalDescription(schema, path, "INVALID_MANIFEST");
|
|
63
|
+
if (type === "string") {
|
|
64
|
+
allowedKeys(schema, [...common, "enum", "minLength", "maxLength", "pattern"], path, "INVALID_MANIFEST");
|
|
65
|
+
if (schema.enum !== undefined) {
|
|
66
|
+
if (!Array.isArray(schema.enum) ||
|
|
67
|
+
schema.enum.some((item) => typeof item !== "string"))
|
|
68
|
+
fail("INVALID_MANIFEST", `${path}.enum`, "expected string array");
|
|
69
|
+
if (new Set(schema.enum).size !== schema.enum.length)
|
|
70
|
+
fail("INVALID_MANIFEST", `${path}.enum`, "contains duplicate values");
|
|
71
|
+
}
|
|
72
|
+
const min = schema.minLength === undefined
|
|
73
|
+
? undefined
|
|
74
|
+
: nonNegativeIntegerAt(schema.minLength, `${path}.minLength`, "INVALID_MANIFEST");
|
|
75
|
+
const max = schema.maxLength === undefined
|
|
76
|
+
? undefined
|
|
77
|
+
: nonNegativeIntegerAt(schema.maxLength, `${path}.maxLength`, "INVALID_MANIFEST");
|
|
78
|
+
if (min !== undefined && max !== undefined && min > max)
|
|
79
|
+
fail("INVALID_MANIFEST", path, "minLength cannot exceed maxLength");
|
|
80
|
+
if (schema.pattern !== undefined) {
|
|
81
|
+
const pattern = stringAt(schema.pattern, `${path}.pattern`, "INVALID_MANIFEST");
|
|
82
|
+
try {
|
|
83
|
+
new RegExp(pattern);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
fail("INVALID_MANIFEST", `${path}.pattern`, "invalid regular expression");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else if (type === "number" || type === "integer") {
|
|
91
|
+
allowedKeys(schema, [...common, "minimum", "maximum"], path, "INVALID_MANIFEST");
|
|
92
|
+
const minimum = schema.minimum === undefined
|
|
93
|
+
? undefined
|
|
94
|
+
: finiteAt(schema.minimum, `${path}.minimum`, "INVALID_MANIFEST");
|
|
95
|
+
const maximum = schema.maximum === undefined
|
|
96
|
+
? undefined
|
|
97
|
+
: finiteAt(schema.maximum, `${path}.maximum`, "INVALID_MANIFEST");
|
|
98
|
+
if (minimum !== undefined && maximum !== undefined && minimum > maximum)
|
|
99
|
+
fail("INVALID_MANIFEST", path, "minimum cannot exceed maximum");
|
|
100
|
+
}
|
|
101
|
+
else if (type === "boolean" || type === "null") {
|
|
102
|
+
allowedKeys(schema, common, path, "INVALID_MANIFEST");
|
|
103
|
+
}
|
|
104
|
+
else if (type === "array") {
|
|
105
|
+
allowedKeys(schema, [...common, "items", "minItems", "maxItems"], path, "INVALID_MANIFEST");
|
|
106
|
+
assertConfigSchema(schema.items, `${path}.items`);
|
|
107
|
+
const min = schema.minItems === undefined
|
|
108
|
+
? undefined
|
|
109
|
+
: nonNegativeIntegerAt(schema.minItems, `${path}.minItems`, "INVALID_MANIFEST");
|
|
110
|
+
const max = schema.maxItems === undefined
|
|
111
|
+
? undefined
|
|
112
|
+
: nonNegativeIntegerAt(schema.maxItems, `${path}.maxItems`, "INVALID_MANIFEST");
|
|
113
|
+
if (min !== undefined && max !== undefined && min > max)
|
|
114
|
+
fail("INVALID_MANIFEST", path, "minItems cannot exceed maxItems");
|
|
115
|
+
}
|
|
116
|
+
else if (type === "object") {
|
|
117
|
+
allowedKeys(schema, [...common, "properties", "required", "additionalProperties"], path, "INVALID_MANIFEST");
|
|
118
|
+
const properties = schema.properties === undefined
|
|
119
|
+
? {}
|
|
120
|
+
: objectAt(schema.properties, `${path}.properties`, "INVALID_MANIFEST");
|
|
121
|
+
for (const [key, child] of Object.entries(properties)) {
|
|
122
|
+
if (!configKeyPattern.test(key))
|
|
123
|
+
fail("INVALID_MANIFEST", `${path}.properties.${key}`, "property names must be stable alphanumeric identifiers");
|
|
124
|
+
assertConfigSchema(child, `${path}.properties.${key}`);
|
|
125
|
+
}
|
|
126
|
+
if (schema.required !== undefined) {
|
|
127
|
+
if (!Array.isArray(schema.required) ||
|
|
128
|
+
schema.required.some((item) => typeof item !== "string"))
|
|
129
|
+
fail("INVALID_MANIFEST", `${path}.required`, "expected string array");
|
|
130
|
+
if (new Set(schema.required).size !== schema.required.length)
|
|
131
|
+
fail("INVALID_MANIFEST", `${path}.required`, "contains duplicate values");
|
|
132
|
+
for (const key of schema.required)
|
|
133
|
+
if (!Object.hasOwn(properties, key))
|
|
134
|
+
fail("INVALID_MANIFEST", `${path}.required`, `unknown required property ${key}`);
|
|
135
|
+
}
|
|
136
|
+
if (schema.additionalProperties !== undefined &&
|
|
137
|
+
typeof schema.additionalProperties !== "boolean")
|
|
138
|
+
fail("INVALID_MANIFEST", `${path}.additionalProperties`, "expected boolean");
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
fail("INVALID_MANIFEST", `${path}.type`, `unsupported configuration type ${String(type)}`);
|
|
142
|
+
}
|
|
143
|
+
if (schema.default !== undefined)
|
|
144
|
+
readConfigValue(schema, schema.default, `${path}.default`, "INVALID_MANIFEST");
|
|
145
|
+
return schema;
|
|
146
|
+
}
|
|
147
|
+
function readJson(value, path, code) {
|
|
148
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
149
|
+
return value;
|
|
150
|
+
if (typeof value === "number") {
|
|
151
|
+
if (!Number.isFinite(value))
|
|
152
|
+
fail(code, path, "numbers must be finite");
|
|
153
|
+
return value;
|
|
154
|
+
}
|
|
155
|
+
if (Array.isArray(value))
|
|
156
|
+
return value.map((item, index) => readJson(item, `${path}[${index}]`, code));
|
|
157
|
+
const object = objectAt(value, path, code);
|
|
158
|
+
return Object.fromEntries(Object.entries(object).map(([key, item]) => [
|
|
159
|
+
key,
|
|
160
|
+
readJson(item, `${path}.${key}`, code),
|
|
161
|
+
]));
|
|
162
|
+
}
|
|
163
|
+
function readConfigValue(schema, input, path, code = "INVALID_CONFIG") {
|
|
164
|
+
if (input === undefined) {
|
|
165
|
+
if (schema.default !== undefined)
|
|
166
|
+
return readConfigValue(schema, schema.default, path, code);
|
|
167
|
+
fail(code, path, "value is required");
|
|
168
|
+
}
|
|
169
|
+
if (schema.type === "string") {
|
|
170
|
+
if (typeof input !== "string")
|
|
171
|
+
fail(code, path, "expected string");
|
|
172
|
+
if (schema.minLength !== undefined && input.length < schema.minLength)
|
|
173
|
+
fail(code, path, `must contain at least ${schema.minLength} characters`);
|
|
174
|
+
if (schema.maxLength !== undefined && input.length > schema.maxLength)
|
|
175
|
+
fail(code, path, `must contain at most ${schema.maxLength} characters`);
|
|
176
|
+
if (schema.enum && !schema.enum.includes(input))
|
|
177
|
+
fail(code, path, "is not an allowed value");
|
|
178
|
+
if (schema.pattern && !new RegExp(schema.pattern).test(input))
|
|
179
|
+
fail(code, path, "does not match the required pattern");
|
|
180
|
+
return input;
|
|
181
|
+
}
|
|
182
|
+
if (schema.type === "number" || schema.type === "integer") {
|
|
183
|
+
if (typeof input !== "number" || !Number.isFinite(input))
|
|
184
|
+
fail(code, path, "expected finite number");
|
|
185
|
+
if (schema.type === "integer" && !Number.isInteger(input))
|
|
186
|
+
fail(code, path, "expected integer");
|
|
187
|
+
if (schema.minimum !== undefined && input < schema.minimum)
|
|
188
|
+
fail(code, path, `must be at least ${schema.minimum}`);
|
|
189
|
+
if (schema.maximum !== undefined && input > schema.maximum)
|
|
190
|
+
fail(code, path, `must be at most ${schema.maximum}`);
|
|
191
|
+
return input;
|
|
192
|
+
}
|
|
193
|
+
if (schema.type === "boolean") {
|
|
194
|
+
if (typeof input !== "boolean")
|
|
195
|
+
fail(code, path, "expected boolean");
|
|
196
|
+
return input;
|
|
197
|
+
}
|
|
198
|
+
if (schema.type === "null") {
|
|
199
|
+
if (input !== null)
|
|
200
|
+
fail(code, path, "expected null");
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
if (schema.type === "array") {
|
|
204
|
+
if (!Array.isArray(input))
|
|
205
|
+
fail(code, path, "expected array");
|
|
206
|
+
if (schema.minItems !== undefined && input.length < schema.minItems)
|
|
207
|
+
fail(code, path, `must contain at least ${schema.minItems} items`);
|
|
208
|
+
if (schema.maxItems !== undefined && input.length > schema.maxItems)
|
|
209
|
+
fail(code, path, `must contain at most ${schema.maxItems} items`);
|
|
210
|
+
return input.map((item, index) => readConfigValue(schema.items, item, `${path}[${index}]`, code));
|
|
211
|
+
}
|
|
212
|
+
const objectSchema = schema;
|
|
213
|
+
const inputObject = objectAt(input, path, code);
|
|
214
|
+
const properties = objectSchema.properties ?? {};
|
|
215
|
+
const result = Object.create(null);
|
|
216
|
+
for (const [key, child] of Object.entries(properties)) {
|
|
217
|
+
if (Object.hasOwn(inputObject, key))
|
|
218
|
+
result[key] = readConfigValue(child, inputObject[key], `${path}.${key}`, code);
|
|
219
|
+
else if (child.default !== undefined)
|
|
220
|
+
result[key] = readConfigValue(child, child.default, `${path}.${key}`, code);
|
|
221
|
+
else if (objectSchema.required?.includes(key))
|
|
222
|
+
fail(code, `${path}.${key}`, "value is required");
|
|
223
|
+
}
|
|
224
|
+
for (const [key, value] of Object.entries(inputObject)) {
|
|
225
|
+
if (Object.hasOwn(properties, key))
|
|
226
|
+
continue;
|
|
227
|
+
if (!objectSchema.additionalProperties)
|
|
228
|
+
fail(code, `${path}.${key}`, "unknown configuration field");
|
|
229
|
+
result[key] = readJson(value, `${path}.${key}`, code);
|
|
230
|
+
}
|
|
231
|
+
return { ...result };
|
|
232
|
+
}
|
|
233
|
+
export function validatePluginManifest(input) {
|
|
234
|
+
const manifest = objectAt(input, "$", "INVALID_MANIFEST");
|
|
235
|
+
allowedKeys(manifest, [
|
|
236
|
+
"schemaVersion",
|
|
237
|
+
"id",
|
|
238
|
+
"version",
|
|
239
|
+
"sdkRange",
|
|
240
|
+
"displayName",
|
|
241
|
+
"description",
|
|
242
|
+
"dependencies",
|
|
243
|
+
"config",
|
|
244
|
+
"contributions",
|
|
245
|
+
], "$", "INVALID_MANIFEST");
|
|
246
|
+
if (manifest.schemaVersion !== 1)
|
|
247
|
+
fail("INVALID_MANIFEST", "$.schemaVersion", "only schema version 1 is supported");
|
|
248
|
+
const id = stringAt(manifest.id, "$.id", "INVALID_MANIFEST", {
|
|
249
|
+
nonempty: true,
|
|
250
|
+
});
|
|
251
|
+
if (!identifierPattern.test(id))
|
|
252
|
+
fail("INVALID_MANIFEST", "$.id", "expected a stable lowercase dotted or dashed identifier");
|
|
253
|
+
const version = stringAt(manifest.version, "$.version", "INVALID_MANIFEST", {
|
|
254
|
+
nonempty: true,
|
|
255
|
+
});
|
|
256
|
+
if (!semverPattern.test(version))
|
|
257
|
+
fail("INVALID_MANIFEST", "$.version", "expected a semantic version");
|
|
258
|
+
if (manifest.sdkRange !== undefined) {
|
|
259
|
+
const sdkRange = stringAt(manifest.sdkRange, "$.sdkRange", "INVALID_MANIFEST", { nonempty: true });
|
|
260
|
+
if (!dependencyRangePattern.test(sdkRange))
|
|
261
|
+
fail("INVALID_MANIFEST", "$.sdkRange", "unsupported semantic version range");
|
|
262
|
+
}
|
|
263
|
+
stringAt(manifest.displayName, "$.displayName", "INVALID_MANIFEST", {
|
|
264
|
+
nonempty: true,
|
|
265
|
+
});
|
|
266
|
+
if (manifest.description !== undefined)
|
|
267
|
+
stringAt(manifest.description, "$.description", "INVALID_MANIFEST", {
|
|
268
|
+
nonempty: true,
|
|
269
|
+
});
|
|
270
|
+
if (manifest.dependencies !== undefined &&
|
|
271
|
+
!Array.isArray(manifest.dependencies))
|
|
272
|
+
fail("INVALID_MANIFEST", "$.dependencies", "expected an array");
|
|
273
|
+
const seenDependencies = new Set();
|
|
274
|
+
const dependencies = (manifest.dependencies ?? []).map((inputDependency, index) => {
|
|
275
|
+
const path = `$.dependencies[${index}]`;
|
|
276
|
+
const dependency = objectAt(inputDependency, path, "INVALID_MANIFEST");
|
|
277
|
+
allowedKeys(dependency, ["id", "range", "optional"], path, "INVALID_MANIFEST");
|
|
278
|
+
const dependencyId = stringAt(dependency.id, `${path}.id`, "INVALID_MANIFEST", { nonempty: true });
|
|
279
|
+
if (!identifierPattern.test(dependencyId))
|
|
280
|
+
fail("INVALID_MANIFEST", `${path}.id`, "invalid plugin identifier");
|
|
281
|
+
if (dependencyId === id)
|
|
282
|
+
fail("INVALID_MANIFEST", `${path}.id`, "plugin cannot depend on itself");
|
|
283
|
+
if (seenDependencies.has(dependencyId))
|
|
284
|
+
fail("INVALID_MANIFEST", `${path}.id`, "duplicate dependency");
|
|
285
|
+
seenDependencies.add(dependencyId);
|
|
286
|
+
const range = stringAt(dependency.range, `${path}.range`, "INVALID_MANIFEST", { nonempty: true });
|
|
287
|
+
if (!dependencyRangePattern.test(range))
|
|
288
|
+
fail("INVALID_MANIFEST", `${path}.range`, "unsupported semantic version range");
|
|
289
|
+
if (dependency.optional !== undefined &&
|
|
290
|
+
typeof dependency.optional !== "boolean")
|
|
291
|
+
fail("INVALID_MANIFEST", `${path}.optional`, "expected boolean");
|
|
292
|
+
return {
|
|
293
|
+
id: dependencyId,
|
|
294
|
+
range,
|
|
295
|
+
...(dependency.optional === undefined
|
|
296
|
+
? {}
|
|
297
|
+
: { optional: dependency.optional }),
|
|
298
|
+
};
|
|
299
|
+
});
|
|
300
|
+
const config = manifest.config === undefined
|
|
301
|
+
? undefined
|
|
302
|
+
: structuredClone(assertConfigSchema(manifest.config, "$.config"));
|
|
303
|
+
if (config && config.type !== "object")
|
|
304
|
+
fail("INVALID_MANIFEST", "$.config", "plugin config root must be an object");
|
|
305
|
+
if (manifest.contributions !== undefined &&
|
|
306
|
+
!Array.isArray(manifest.contributions))
|
|
307
|
+
fail("INVALID_MANIFEST", "$.contributions", "expected an array");
|
|
308
|
+
const contributionIds = new Set();
|
|
309
|
+
const contributions = (manifest.contributions ?? []).map((inputContribution, index) => {
|
|
310
|
+
const path = `$.contributions[${index}]`;
|
|
311
|
+
const contribution = objectAt(inputContribution, path, "INVALID_MANIFEST");
|
|
312
|
+
allowedKeys(contribution, ["id", "kind", "target"], path, "INVALID_MANIFEST");
|
|
313
|
+
const contributionId = stringAt(contribution.id, `${path}.id`, "INVALID_MANIFEST", { nonempty: true });
|
|
314
|
+
if (!identifierPattern.test(contributionId))
|
|
315
|
+
fail("INVALID_MANIFEST", `${path}.id`, "invalid contribution identifier");
|
|
316
|
+
if (!contributionId.startsWith(`${id}.`))
|
|
317
|
+
fail("INVALID_MANIFEST", `${path}.id`, `contribution must use the "${id}." namespace`);
|
|
318
|
+
if (contributionIds.has(contributionId))
|
|
319
|
+
fail("INVALID_MANIFEST", `${path}.id`, "duplicate contribution identifier");
|
|
320
|
+
contributionIds.add(contributionId);
|
|
321
|
+
const kind = stringAt(contribution.kind, `${path}.kind`, "INVALID_MANIFEST", { nonempty: true });
|
|
322
|
+
if (contribution.target !== undefined &&
|
|
323
|
+
!["server", "web", "mobile"].includes(contribution.target))
|
|
324
|
+
fail("INVALID_MANIFEST", `${path}.target`, "expected server, web, or mobile");
|
|
325
|
+
return {
|
|
326
|
+
id: contributionId,
|
|
327
|
+
kind,
|
|
328
|
+
...(contribution.target === undefined
|
|
329
|
+
? {}
|
|
330
|
+
: {
|
|
331
|
+
target: contribution.target,
|
|
332
|
+
}),
|
|
333
|
+
};
|
|
334
|
+
});
|
|
335
|
+
return {
|
|
336
|
+
schemaVersion: 1,
|
|
337
|
+
id,
|
|
338
|
+
version,
|
|
339
|
+
...(manifest.sdkRange === undefined
|
|
340
|
+
? {}
|
|
341
|
+
: { sdkRange: manifest.sdkRange }),
|
|
342
|
+
displayName: manifest.displayName,
|
|
343
|
+
...(manifest.description === undefined
|
|
344
|
+
? {}
|
|
345
|
+
: { description: manifest.description }),
|
|
346
|
+
...(dependencies.length ? { dependencies } : {}),
|
|
347
|
+
...(config ? { config: config } : {}),
|
|
348
|
+
...(contributions.length ? { contributions } : {}),
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
export function validatePluginConfig(manifest, input) {
|
|
352
|
+
const config = input === undefined ? (manifest.config?.default ?? {}) : input;
|
|
353
|
+
if (!manifest.config) {
|
|
354
|
+
const object = objectAt(config, "$config", "INVALID_CONFIG");
|
|
355
|
+
if (Object.keys(object).length)
|
|
356
|
+
fail("INVALID_CONFIG", "$config", `plugin ${manifest.id} does not declare configuration`);
|
|
357
|
+
return {};
|
|
358
|
+
}
|
|
359
|
+
return readConfigValue(manifest.config, config, "$config");
|
|
360
|
+
}
|
|
361
|
+
function parseSemver(version) {
|
|
362
|
+
const match = semverPattern.exec(version);
|
|
363
|
+
if (!match)
|
|
364
|
+
return undefined;
|
|
365
|
+
return {
|
|
366
|
+
major: Number(match[1]),
|
|
367
|
+
minor: Number(match[2]),
|
|
368
|
+
patch: Number(match[3]),
|
|
369
|
+
prerelease: match[4]?.split(".") ?? [],
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
function compareIdentifiers(left, right) {
|
|
373
|
+
const leftNumber = /^\d+$/.test(left) ? Number(left) : undefined;
|
|
374
|
+
const rightNumber = /^\d+$/.test(right) ? Number(right) : undefined;
|
|
375
|
+
if (leftNumber !== undefined && rightNumber !== undefined)
|
|
376
|
+
return leftNumber - rightNumber;
|
|
377
|
+
if (leftNumber !== undefined)
|
|
378
|
+
return -1;
|
|
379
|
+
if (rightNumber !== undefined)
|
|
380
|
+
return 1;
|
|
381
|
+
return left.localeCompare(right);
|
|
382
|
+
}
|
|
383
|
+
function compareSemver(left, right) {
|
|
384
|
+
for (const key of ["major", "minor", "patch"]) {
|
|
385
|
+
const difference = left[key] - right[key];
|
|
386
|
+
if (difference)
|
|
387
|
+
return difference;
|
|
388
|
+
}
|
|
389
|
+
if (!left.prerelease.length && right.prerelease.length)
|
|
390
|
+
return 1;
|
|
391
|
+
if (left.prerelease.length && !right.prerelease.length)
|
|
392
|
+
return -1;
|
|
393
|
+
for (let index = 0; index < Math.max(left.prerelease.length, right.prerelease.length); index++) {
|
|
394
|
+
const leftPart = left.prerelease[index];
|
|
395
|
+
const rightPart = right.prerelease[index];
|
|
396
|
+
if (leftPart === undefined)
|
|
397
|
+
return -1;
|
|
398
|
+
if (rightPart === undefined)
|
|
399
|
+
return 1;
|
|
400
|
+
const difference = compareIdentifiers(leftPart, rightPart);
|
|
401
|
+
if (difference)
|
|
402
|
+
return difference;
|
|
403
|
+
}
|
|
404
|
+
return 0;
|
|
405
|
+
}
|
|
406
|
+
export function satisfiesPluginVersion(version, range) {
|
|
407
|
+
if (range === "*")
|
|
408
|
+
return !!parseSemver(version);
|
|
409
|
+
const prefix = range[0] === "^" || range[0] === "~" ? range[0] : "";
|
|
410
|
+
const minimum = parseSemver(prefix ? range.slice(1) : range);
|
|
411
|
+
const candidate = parseSemver(version);
|
|
412
|
+
if (!minimum || !candidate)
|
|
413
|
+
return false;
|
|
414
|
+
if (!prefix)
|
|
415
|
+
return compareSemver(candidate, minimum) === 0;
|
|
416
|
+
if (candidate.prerelease.length && !minimum.prerelease.length)
|
|
417
|
+
return false;
|
|
418
|
+
if (compareSemver(candidate, minimum) < 0)
|
|
419
|
+
return false;
|
|
420
|
+
const maximum = prefix === "~"
|
|
421
|
+
? {
|
|
422
|
+
major: minimum.major,
|
|
423
|
+
minor: minimum.minor + 1,
|
|
424
|
+
patch: 0,
|
|
425
|
+
prerelease: [],
|
|
426
|
+
}
|
|
427
|
+
: minimum.major > 0
|
|
428
|
+
? {
|
|
429
|
+
major: minimum.major + 1,
|
|
430
|
+
minor: 0,
|
|
431
|
+
patch: 0,
|
|
432
|
+
prerelease: [],
|
|
433
|
+
}
|
|
434
|
+
: minimum.minor > 0
|
|
435
|
+
? {
|
|
436
|
+
major: 0,
|
|
437
|
+
minor: minimum.minor + 1,
|
|
438
|
+
patch: 0,
|
|
439
|
+
prerelease: [],
|
|
440
|
+
}
|
|
441
|
+
: {
|
|
442
|
+
major: 0,
|
|
443
|
+
minor: 0,
|
|
444
|
+
patch: minimum.patch + 1,
|
|
445
|
+
prerelease: [],
|
|
446
|
+
};
|
|
447
|
+
return compareSemver(candidate, maximum) < 0;
|
|
448
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@smartdoca/plugin-contracts",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "AGPL-3.0-only",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"import": "./dist/index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
}
|
|
18
|
+
}
|