@pptb/types 1.1.3-beta.1 → 1.2.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/README.md +24 -24
- package/lib/validate.js +69 -47
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,11 +4,11 @@ TypeScript type definitions for Power Platform ToolBox APIs, plus a built-in CLI
|
|
|
4
4
|
|
|
5
5
|
- [@pptb/types](#pptbtypes)
|
|
6
6
|
- [Installation](#installation)
|
|
7
|
-
- [Overview](#overview)
|
|
8
7
|
- [Tool Validation](#tool-validation)
|
|
9
8
|
- [Quick start](#quick-start)
|
|
10
9
|
- [CLI options](#cli-options)
|
|
11
10
|
- [What is validated](#what-is-validated)
|
|
11
|
+
- [Overview](#overview)
|
|
12
12
|
- [Usage](#usage)
|
|
13
13
|
- [Include all type definitions](#include-all-type-definitions)
|
|
14
14
|
- [Include specific API types](#include-specific-api-types)
|
|
@@ -55,7 +55,7 @@ Add a script to your tool's `package.json`:
|
|
|
55
55
|
```json
|
|
56
56
|
{
|
|
57
57
|
"scripts": {
|
|
58
|
-
"
|
|
58
|
+
"validate": "pptb-validate"
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
```
|
|
@@ -63,7 +63,7 @@ Add a script to your tool's `package.json`:
|
|
|
63
63
|
Then run:
|
|
64
64
|
|
|
65
65
|
```bash
|
|
66
|
-
npm run
|
|
66
|
+
npm run validate
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
You can also run it directly (no script entry needed once `@pptb/types` is installed):
|
|
@@ -80,32 +80,32 @@ npx pptb-validate path/to/package.json
|
|
|
80
80
|
|
|
81
81
|
### CLI options
|
|
82
82
|
|
|
83
|
-
| Option
|
|
84
|
-
|
|
85
|
-
| `--skip-url-checks` | Skip URL reachability checks (faster, works offline)
|
|
86
|
-
| `--json`
|
|
87
|
-
| `--help`, `-h`
|
|
83
|
+
| Option | Description |
|
|
84
|
+
| ------------------- | ---------------------------------------------------------- |
|
|
85
|
+
| `--skip-url-checks` | Skip URL reachability checks (faster, works offline) |
|
|
86
|
+
| `--json` | Print results as a JSON object (suitable for CI pipelines) |
|
|
87
|
+
| `--help`, `-h` | Show help information |
|
|
88
88
|
|
|
89
89
|
### What is validated
|
|
90
90
|
|
|
91
91
|
The validator checks every field that the official review pipeline inspects:
|
|
92
92
|
|
|
93
|
-
| Field
|
|
94
|
-
|
|
95
|
-
| `name`
|
|
96
|
-
| `version`
|
|
97
|
-
| `displayName`
|
|
98
|
-
| `description`
|
|
99
|
-
| `license`
|
|
100
|
-
| `contributors`
|
|
101
|
-
| `configurations.repository` | ✅
|
|
102
|
-
| `configurations.readmeUrl`
|
|
103
|
-
| `configurations.website`
|
|
104
|
-
| `configurations.funding`
|
|
105
|
-
| `icon`
|
|
106
|
-
| `cspExceptions`
|
|
107
|
-
| `features.multiConnection`
|
|
108
|
-
| `features.minAPI`
|
|
93
|
+
| Field | Required | Rules |
|
|
94
|
+
| --------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
95
|
+
| `name` | ✅ | Must be a string |
|
|
96
|
+
| `version` | ✅ | Must be a string |
|
|
97
|
+
| `displayName` | ✅ | Must be a string |
|
|
98
|
+
| `description` | ✅ | Must be a string |
|
|
99
|
+
| `license` | ✅ | Must be one of the approved OSS licenses (MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, GPL-2.0, GPL-3.0, LGPL-3.0, ISC, AGPL-3.0-only) |
|
|
100
|
+
| `contributors` | ✅ | Non-empty array; each entry needs a `name` |
|
|
101
|
+
| `configurations.repository` | ✅ | Valid, reachable URL |
|
|
102
|
+
| `configurations.readmeUrl` | ✅ | Valid URL; must **not** be hosted on `github.com` (use `raw.githubusercontent.com`) |
|
|
103
|
+
| `configurations.website` | ❌ | Valid, reachable URL when provided |
|
|
104
|
+
| `configurations.funding` | ❌ | Valid, reachable URL when provided |
|
|
105
|
+
| `icon` | ❌ | Relative path to a `.svg` file bundled under `dist/`; must not be an HTTP URL or an absolute path |
|
|
106
|
+
| `cspExceptions` | ❌ | When present: must not be empty; only recognised directives; each directive must be a non-empty array |
|
|
107
|
+
| `features.multiConnection` | ❌\* | Required when `features` is present; must be `"required"`, `"optional"`, or `"none"` |
|
|
108
|
+
| `features.minAPI` | ❌ | Valid semver string when provided |
|
|
109
109
|
|
|
110
110
|
> \* Required only when the `features` object is present.
|
|
111
111
|
|
package/lib/validate.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
/** @typedef {{ name: string; url?: string }} Contributor */
|
|
10
|
-
/** @typedef {{ "connect-src"?: string[]; "script-src"?: string[]; "style-src"?: string[]; "img-src"?: string[]; "font-src"?: string[]; "frame-src"?: string[] }} CspExceptions */
|
|
10
|
+
/** @typedef {{ "connect-src"?: string[]; "script-src"?: string[]; "style-src"?: string[]; "img-src"?: string[]; "font-src"?: string[]; "frame-src"?: string[]; "media-src"?: string[] }} CspExceptions */
|
|
11
11
|
/** @typedef {{ repository?: string; website?: string; funding?: string; readmeUrl?: string }} Configurations */
|
|
12
12
|
/** @typedef {{ multiConnection?: "required" | "optional" | "none"; minAPI?: string }} Features */
|
|
13
13
|
/**
|
|
@@ -85,6 +85,16 @@ function validateIconPath(fieldName, iconPath, errors) {
|
|
|
85
85
|
errors.push(`${fieldName} must be a relative path (e.g., 'icon.svg' or 'icons/icon.svg')`);
|
|
86
86
|
return;
|
|
87
87
|
}
|
|
88
|
+
// Reject Windows absolute paths like "C:\..." or "C:/"
|
|
89
|
+
if (/^[a-zA-Z]:/.test(iconPath)) {
|
|
90
|
+
errors.push(`${fieldName} must be a relative path (e.g., 'icon.svg' or 'icons/icon.svg')`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// Reject backslashes — icon paths must use forward slashes
|
|
94
|
+
if (iconPath.includes("\\")) {
|
|
95
|
+
errors.push(`${fieldName} must use forward slashes, not backslashes (e.g., 'icons/icon.svg')`);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
88
98
|
if (iconPath.includes("..")) {
|
|
89
99
|
errors.push(`${fieldName} cannot contain '..' (path traversal not allowed)`);
|
|
90
100
|
return;
|
|
@@ -145,12 +155,12 @@ async function validatePackageJson(packageJson, options = {}) {
|
|
|
145
155
|
}
|
|
146
156
|
|
|
147
157
|
// Icon validation (optional, but must be a relative SVG path if provided)
|
|
148
|
-
if (packageJson.icon
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
158
|
+
if (packageJson.icon === undefined || packageJson.icon === null) {
|
|
159
|
+
warnings.push("icon is not set; consider adding a bundled SVG icon so your tool displays properly in the marketplace");
|
|
160
|
+
} else if (typeof packageJson.icon !== "string") {
|
|
161
|
+
errors.push("icon must be a string (relative path to bundled SVG under dist)");
|
|
162
|
+
} else {
|
|
163
|
+
validateIconPath("icon", packageJson.icon, errors);
|
|
154
164
|
}
|
|
155
165
|
|
|
156
166
|
// Contributors validation
|
|
@@ -192,19 +202,19 @@ async function validatePackageJson(packageJson, options = {}) {
|
|
|
192
202
|
}
|
|
193
203
|
}
|
|
194
204
|
|
|
195
|
-
// Website validation (optional)
|
|
196
|
-
if (configs.website) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
205
|
+
// Website validation (optional but recommended)
|
|
206
|
+
if (!configs.website) {
|
|
207
|
+
warnings.push("configurations.website is not set; consider adding a URL where users can learn more about your tool");
|
|
208
|
+
} else if (!isValidUrl(configs.website)) {
|
|
209
|
+
warnings.push("configurations.website has an invalid URL format");
|
|
210
|
+
} else if (!skipUrlChecks) {
|
|
211
|
+
const accessible = await isUrlAccessible(configs.website);
|
|
212
|
+
if (!accessible) {
|
|
213
|
+
warnings.push("configurations.website URL is not accessible");
|
|
204
214
|
}
|
|
205
215
|
}
|
|
206
216
|
|
|
207
|
-
// Funding validation (optional)
|
|
217
|
+
// Funding validation (optional but recommended)
|
|
208
218
|
if (configs.funding) {
|
|
209
219
|
if (!isValidUrl(configs.funding)) {
|
|
210
220
|
warnings.push("configurations.funding has an invalid URL format");
|
|
@@ -233,45 +243,57 @@ async function validatePackageJson(packageJson, options = {}) {
|
|
|
233
243
|
|
|
234
244
|
// CSP Exceptions validation (optional, but validated if present)
|
|
235
245
|
if (packageJson.cspExceptions) {
|
|
236
|
-
const
|
|
246
|
+
const cspExceptions = packageJson.cspExceptions;
|
|
237
247
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
248
|
+
if (typeof cspExceptions !== "object" || cspExceptions === null || Array.isArray(cspExceptions)) {
|
|
249
|
+
errors.push("cspExceptions must be an object mapping CSP directives to arrays of strings");
|
|
250
|
+
} else {
|
|
251
|
+
const validCspDirectives = ["connect-src", "script-src", "style-src", "img-src", "font-src", "frame-src", "media-src"];
|
|
242
252
|
|
|
243
|
-
|
|
244
|
-
if (!
|
|
245
|
-
|
|
246
|
-
}
|
|
247
|
-
const values = packageJson.cspExceptions?.[/** @type {keyof CspExceptions} */ (directive)];
|
|
248
|
-
if (values && !Array.isArray(values)) {
|
|
249
|
-
errors.push(`CSP directive "${directive}" must be an array of strings`);
|
|
250
|
-
} else if (values && values.length === 0) {
|
|
251
|
-
errors.push(`CSP directive "${directive}" cannot be an empty array`);
|
|
253
|
+
const hasAnyDirectives = Object.keys(cspExceptions).length > 0;
|
|
254
|
+
if (!hasAnyDirectives) {
|
|
255
|
+
errors.push("cspExceptions cannot be empty. If CSP exceptions are not needed, remove the cspExceptions field");
|
|
252
256
|
}
|
|
253
|
-
|
|
257
|
+
|
|
258
|
+
Object.keys(cspExceptions).forEach((directive) => {
|
|
259
|
+
if (!validCspDirectives.includes(directive)) {
|
|
260
|
+
warnings.push(`Unknown CSP directive: ${directive}`);
|
|
261
|
+
}
|
|
262
|
+
const values = cspExceptions[/** @type {keyof CspExceptions} */ (directive)];
|
|
263
|
+
if (values && !Array.isArray(values)) {
|
|
264
|
+
errors.push(`CSP directive "${directive}" must be an array of strings`);
|
|
265
|
+
} else if (values && values.length === 0) {
|
|
266
|
+
errors.push(`CSP directive "${directive}" cannot be an empty array`);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}
|
|
254
270
|
}
|
|
255
271
|
|
|
256
272
|
// Features validation (optional, but validated if present)
|
|
257
|
-
if (packageJson.features) {
|
|
258
|
-
const
|
|
259
|
-
const featureKeys = Object.keys(packageJson.features);
|
|
260
|
-
const invalidKeys = featureKeys.filter((key) => !VALID_FEATURE_KEYS.includes(key));
|
|
273
|
+
if (packageJson.features !== undefined) {
|
|
274
|
+
const features = packageJson.features;
|
|
261
275
|
|
|
262
|
-
if (
|
|
263
|
-
errors.push(
|
|
264
|
-
}
|
|
276
|
+
if (features === null || typeof features !== "object" || Array.isArray(features)) {
|
|
277
|
+
errors.push("features must be a non-array object with optional 'multiConnection' and 'minAPI' properties");
|
|
278
|
+
} else {
|
|
279
|
+
const VALID_FEATURE_KEYS = ["multiConnection", "minAPI"];
|
|
280
|
+
const featureKeys = Object.keys(features);
|
|
281
|
+
const invalidKeys = featureKeys.filter((key) => !VALID_FEATURE_KEYS.includes(key));
|
|
265
282
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
errors.push(`features.multiConnection must be one of: ${VALID_MULTI_CONNECTION_VALUES.join(", ")}`);
|
|
270
|
-
}
|
|
283
|
+
if (invalidKeys.length > 0) {
|
|
284
|
+
errors.push(`features can only contain ${VALID_FEATURE_KEYS.map((k) => `'${k}'`).join(", ")} properties. Invalid properties: ${invalidKeys.join(", ")}`);
|
|
285
|
+
}
|
|
271
286
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
287
|
+
if (features.multiConnection === undefined) {
|
|
288
|
+
errors.push("features.multiConnection is required when features object is provided");
|
|
289
|
+
} else if (!VALID_MULTI_CONNECTION_VALUES.includes(features.multiConnection)) {
|
|
290
|
+
errors.push(`features.multiConnection must be one of: ${VALID_MULTI_CONNECTION_VALUES.join(", ")}`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (features.minAPI !== undefined) {
|
|
294
|
+
if (typeof features.minAPI !== "string" || !SEMVER_REGEX.test(features.minAPI)) {
|
|
295
|
+
errors.push("features.minAPI must be a valid semantic version string (e.g., '1.0.0')");
|
|
296
|
+
}
|
|
275
297
|
}
|
|
276
298
|
}
|
|
277
299
|
}
|