@pptb/types 1.0.20 → 1.1.3-beta.1
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 +71 -1
- package/bin/pptb-validate.js +181 -0
- package/lib/validate.js +302 -0
- package/package.json +13 -2
package/README.md
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
# @pptb/types
|
|
2
2
|
|
|
3
|
-
TypeScript type definitions for Power Platform ToolBox APIs.
|
|
3
|
+
TypeScript type definitions for Power Platform ToolBox APIs, plus a built-in CLI validator that checks your tool's `package.json` against the official review criteria before you publish to npm.
|
|
4
4
|
|
|
5
5
|
- [@pptb/types](#pptbtypes)
|
|
6
6
|
- [Installation](#installation)
|
|
7
7
|
- [Overview](#overview)
|
|
8
|
+
- [Tool Validation](#tool-validation)
|
|
9
|
+
- [Quick start](#quick-start)
|
|
10
|
+
- [CLI options](#cli-options)
|
|
11
|
+
- [What is validated](#what-is-validated)
|
|
8
12
|
- [Usage](#usage)
|
|
9
13
|
- [Include all type definitions](#include-all-type-definitions)
|
|
10
14
|
- [Include specific API types](#include-specific-api-types)
|
|
@@ -18,6 +22,7 @@ TypeScript type definitions for Power Platform ToolBox APIs.
|
|
|
18
22
|
- [FetchXML Queries](#fetchxml-queries)
|
|
19
23
|
- [Metadata Operations](#metadata-operations)
|
|
20
24
|
- [Execute Actions/Functions](#execute-actionsfunctions)
|
|
25
|
+
- [Deploy Solutions](#deploy-solutions)
|
|
21
26
|
- [API Reference](#api-reference)
|
|
22
27
|
- [ToolBox API (`window.toolboxAPI`)](#toolbox-api-windowtoolboxapi)
|
|
23
28
|
- [Connections](#connections-1)
|
|
@@ -39,6 +44,71 @@ TypeScript type definitions for Power Platform ToolBox APIs.
|
|
|
39
44
|
npm install --save-dev @pptb/types
|
|
40
45
|
```
|
|
41
46
|
|
|
47
|
+
## Tool Validation
|
|
48
|
+
|
|
49
|
+
The `@pptb/types` package ships with a `pptb-validate` binary that validates your tool's `package.json` against the **same rules** used by the official Power Platform ToolBox review process. Running it before publishing helps you catch configuration problems early, reduces failed reviews, and avoids publishing unnecessary npm versions.
|
|
50
|
+
|
|
51
|
+
### Quick start
|
|
52
|
+
|
|
53
|
+
Add a script to your tool's `package.json`:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"scripts": {
|
|
58
|
+
"pptb-validate": "pptb-validate"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Then run:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm run pptb-validate
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
You can also run it directly (no script entry needed once `@pptb/types` is installed):
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npx pptb-validate
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Or point it at a specific file:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npx pptb-validate path/to/package.json
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### CLI options
|
|
82
|
+
|
|
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
|
+
|
|
89
|
+
### What is validated
|
|
90
|
+
|
|
91
|
+
The validator checks every field that the official review pipeline inspects:
|
|
92
|
+
|
|
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
|
+
|
|
110
|
+
> \* Required only when the `features` object is present.
|
|
111
|
+
|
|
42
112
|
## Overview
|
|
43
113
|
|
|
44
114
|
The `@pptb/types` package provides TypeScript definitions for two main APIs:
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-check
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* pptb-validate - CLI tool for validating Power Platform ToolBox tool packages
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* pptb-validate [options] [path/to/package.json]
|
|
10
|
+
*
|
|
11
|
+
* Options:
|
|
12
|
+
* --skip-url-checks Skip URL accessibility checks (faster, offline-friendly)
|
|
13
|
+
* --json Output results as JSON
|
|
14
|
+
* --help, -h Show help information
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require("fs");
|
|
18
|
+
const path = require("path");
|
|
19
|
+
const { validatePackageJson } = require("../lib/validate");
|
|
20
|
+
|
|
21
|
+
// ANSI colour helpers – gracefully degrade when colours are unsupported
|
|
22
|
+
const NO_COLOR = !process.stdout.isTTY || process.env.NO_COLOR;
|
|
23
|
+
const c = {
|
|
24
|
+
red: (s) => (NO_COLOR ? s : `\x1b[31m${s}\x1b[0m`),
|
|
25
|
+
yellow: (s) => (NO_COLOR ? s : `\x1b[33m${s}\x1b[0m`),
|
|
26
|
+
green: (s) => (NO_COLOR ? s : `\x1b[32m${s}\x1b[0m`),
|
|
27
|
+
cyan: (s) => (NO_COLOR ? s : `\x1b[36m${s}\x1b[0m`),
|
|
28
|
+
bold: (s) => (NO_COLOR ? s : `\x1b[1m${s}\x1b[0m`),
|
|
29
|
+
dim: (s) => (NO_COLOR ? s : `\x1b[2m${s}\x1b[0m`),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function printHelp() {
|
|
33
|
+
console.log(`
|
|
34
|
+
${c.bold("pptb-validate")} – Power Platform ToolBox tool validator
|
|
35
|
+
|
|
36
|
+
${c.bold("USAGE")}
|
|
37
|
+
pptb-validate [options] [path/to/package.json]
|
|
38
|
+
|
|
39
|
+
When no path is given the tool looks for ${c.cyan("package.json")} in the current
|
|
40
|
+
working directory.
|
|
41
|
+
|
|
42
|
+
${c.bold("OPTIONS")}
|
|
43
|
+
${c.cyan("--skip-url-checks")} Skip URL reachability checks (faster, works offline)
|
|
44
|
+
${c.cyan("--json")} Print results as a JSON object instead of human-readable text
|
|
45
|
+
${c.cyan("--help")}, ${c.cyan("-h")} Show this help message
|
|
46
|
+
|
|
47
|
+
${c.bold("ADD TO YOUR TOOL'S package.json")}
|
|
48
|
+
${c.dim(`"scripts": {
|
|
49
|
+
"validate": "pptb-validate"
|
|
50
|
+
}`)}
|
|
51
|
+
|
|
52
|
+
Then run: ${c.cyan("npm run validate")}
|
|
53
|
+
|
|
54
|
+
${c.bold("EXAMPLES")}
|
|
55
|
+
npm run validate
|
|
56
|
+
npm run validate ./my-tool/package.json
|
|
57
|
+
npm run validate --skip-url-checks
|
|
58
|
+
npm run validate --json
|
|
59
|
+
`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function main() {
|
|
63
|
+
const args = process.argv.slice(2);
|
|
64
|
+
|
|
65
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
66
|
+
printHelp();
|
|
67
|
+
process.exit(0);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const skipUrlChecks = args.includes("--skip-url-checks");
|
|
71
|
+
const jsonOutput = args.includes("--json");
|
|
72
|
+
|
|
73
|
+
// Find the package.json path from positional args (skip flags)
|
|
74
|
+
const positional = args.filter((a) => !a.startsWith("-"));
|
|
75
|
+
let packageJsonPath = positional[0] || path.join(process.cwd(), "package.json");
|
|
76
|
+
|
|
77
|
+
// Resolve to absolute path
|
|
78
|
+
if (!path.isAbsolute(packageJsonPath)) {
|
|
79
|
+
packageJsonPath = path.resolve(process.cwd(), packageJsonPath);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// --- Load package.json ---
|
|
83
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
84
|
+
if (jsonOutput) {
|
|
85
|
+
console.log(JSON.stringify({ valid: false, errors: [`package.json not found at: ${packageJsonPath}`], warnings: [] }, null, 2));
|
|
86
|
+
} else {
|
|
87
|
+
console.error(c.red(`✖ package.json not found at: ${packageJsonPath}`));
|
|
88
|
+
}
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let packageJson;
|
|
93
|
+
try {
|
|
94
|
+
packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
95
|
+
} catch (err) {
|
|
96
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
97
|
+
if (jsonOutput) {
|
|
98
|
+
console.log(JSON.stringify({ valid: false, errors: [`Failed to parse package.json: ${message}`], warnings: [] }, null, 2));
|
|
99
|
+
} else {
|
|
100
|
+
console.error(c.red(`✖ Failed to parse package.json: ${message}`));
|
|
101
|
+
}
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// --- Run validation ---
|
|
106
|
+
if (!jsonOutput) {
|
|
107
|
+
console.log();
|
|
108
|
+
console.log(c.bold("Power Platform ToolBox – Tool Validator"));
|
|
109
|
+
console.log(c.dim("─".repeat(45)));
|
|
110
|
+
console.log(c.dim(`File: ${packageJsonPath}`));
|
|
111
|
+
if (skipUrlChecks) {
|
|
112
|
+
console.log(c.yellow("⚠ URL reachability checks are skipped"));
|
|
113
|
+
}
|
|
114
|
+
console.log();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let result;
|
|
118
|
+
try {
|
|
119
|
+
result = await validatePackageJson(packageJson, { skipUrlChecks });
|
|
120
|
+
} catch (err) {
|
|
121
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
122
|
+
if (jsonOutput) {
|
|
123
|
+
console.log(JSON.stringify({ valid: false, errors: [`Unexpected validation error: ${message}`], warnings: [] }, null, 2));
|
|
124
|
+
} else {
|
|
125
|
+
console.error(c.red(`✖ Unexpected validation error: ${message}`));
|
|
126
|
+
}
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// --- Output results ---
|
|
131
|
+
if (jsonOutput) {
|
|
132
|
+
console.log(JSON.stringify(result, null, 2));
|
|
133
|
+
process.exit(result.valid ? 0 : 1);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Human-readable output
|
|
137
|
+
if (result.errors.length > 0) {
|
|
138
|
+
console.log(c.bold(c.red(`Errors (${result.errors.length})`)));
|
|
139
|
+
result.errors.forEach((e) => console.log(` ${c.red("✖")} ${e}`));
|
|
140
|
+
console.log();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (result.warnings.length > 0) {
|
|
144
|
+
console.log(c.bold(c.yellow(`Warnings (${result.warnings.length})`)));
|
|
145
|
+
result.warnings.forEach((w) => console.log(` ${c.yellow("⚠")} ${w}`));
|
|
146
|
+
console.log();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (result.valid) {
|
|
150
|
+
const info = result.packageInfo;
|
|
151
|
+
console.log(c.green(c.bold("✔ Validation passed")));
|
|
152
|
+
console.log();
|
|
153
|
+
console.log(c.bold("Package summary"));
|
|
154
|
+
console.log(c.dim("─".repeat(45)));
|
|
155
|
+
console.log(` Name : ${info.name}`);
|
|
156
|
+
console.log(` Version : ${info.version}`);
|
|
157
|
+
console.log(` Display name: ${info.displayName}`);
|
|
158
|
+
console.log(` Description : ${info.description}`);
|
|
159
|
+
console.log(` License : ${info.license}`);
|
|
160
|
+
console.log(` Contributors: ${info.contributors.map((c) => c.name).join(", ")}`);
|
|
161
|
+
if (info.icon) {
|
|
162
|
+
console.log(` Icon : ${info.icon}`);
|
|
163
|
+
}
|
|
164
|
+
if (info.features) {
|
|
165
|
+
console.log(` Features : multiConnection=${info.features.multiConnection}${info.features.minAPI ? `, minAPI=${info.features.minAPI}` : ""}`);
|
|
166
|
+
}
|
|
167
|
+
console.log();
|
|
168
|
+
} else {
|
|
169
|
+
console.log(c.red(c.bold("✖ Validation failed")));
|
|
170
|
+
console.log();
|
|
171
|
+
console.log(c.dim("Fix the errors listed above and re-run pptb-validate before publishing."));
|
|
172
|
+
console.log();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
process.exit(result.valid ? 0 : 1);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
main().catch((err) => {
|
|
179
|
+
console.error(c.red(`✖ Fatal error: ${err instanceof Error ? err.message : String(err)}`));
|
|
180
|
+
process.exit(1);
|
|
181
|
+
});
|
package/lib/validate.js
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Tool validation logic for Power Platform ToolBox tools.
|
|
6
|
+
* Mirrors the validation rules used during the official review process.
|
|
7
|
+
*/
|
|
8
|
+
|
|
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 */
|
|
11
|
+
/** @typedef {{ repository?: string; website?: string; funding?: string; readmeUrl?: string }} Configurations */
|
|
12
|
+
/** @typedef {{ multiConnection?: "required" | "optional" | "none"; minAPI?: string }} Features */
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {{
|
|
15
|
+
* name: string;
|
|
16
|
+
* version: string;
|
|
17
|
+
* displayName?: string;
|
|
18
|
+
* description?: string;
|
|
19
|
+
* contributors?: Contributor[];
|
|
20
|
+
* cspExceptions?: CspExceptions;
|
|
21
|
+
* license?: string;
|
|
22
|
+
* icon?: string;
|
|
23
|
+
* configurations?: Configurations;
|
|
24
|
+
* features?: Features;
|
|
25
|
+
* }} ToolPackageJson
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {{
|
|
29
|
+
* valid: boolean;
|
|
30
|
+
* errors: string[];
|
|
31
|
+
* warnings: string[];
|
|
32
|
+
* packageInfo?: object;
|
|
33
|
+
* }} ValidationResult
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
// List of approved open source licenses
|
|
37
|
+
const APPROVED_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"];
|
|
38
|
+
|
|
39
|
+
// Valid multiConnection values
|
|
40
|
+
const VALID_MULTI_CONNECTION_VALUES = ["required", "optional", "none"];
|
|
41
|
+
|
|
42
|
+
// Semver regex for minAPI validation
|
|
43
|
+
const SEMVER_REGEX = /^\d+\.\d+\.\d+(-[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$/;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Checks if a string is a valid URL.
|
|
47
|
+
* @param {string} url
|
|
48
|
+
* @returns {boolean}
|
|
49
|
+
*/
|
|
50
|
+
function isValidUrl(url) {
|
|
51
|
+
try {
|
|
52
|
+
new URL(url);
|
|
53
|
+
return true;
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Checks if a URL hostname is a GitHub domain.
|
|
61
|
+
* @param {string} url
|
|
62
|
+
* @returns {boolean}
|
|
63
|
+
*/
|
|
64
|
+
function isGithubDomain(url) {
|
|
65
|
+
try {
|
|
66
|
+
const hostname = new URL(url).hostname.toLowerCase();
|
|
67
|
+
return hostname === "github.com" || hostname.endsWith(".github.com");
|
|
68
|
+
} catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Validates an icon path string.
|
|
75
|
+
* @param {string} fieldName
|
|
76
|
+
* @param {string} iconPath
|
|
77
|
+
* @param {string[]} errors
|
|
78
|
+
*/
|
|
79
|
+
function validateIconPath(fieldName, iconPath, errors) {
|
|
80
|
+
if (iconPath.startsWith("http://") || iconPath.startsWith("https://")) {
|
|
81
|
+
errors.push(`${fieldName} cannot be an HTTP(S) URL - icons must be bundled under dist`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (iconPath.startsWith("/")) {
|
|
85
|
+
errors.push(`${fieldName} must be a relative path (e.g., 'icon.svg' or 'icons/icon.svg')`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (iconPath.includes("..")) {
|
|
89
|
+
errors.push(`${fieldName} cannot contain '..' (path traversal not allowed)`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (!iconPath.toLowerCase().endsWith(".svg")) {
|
|
93
|
+
errors.push(`${fieldName} must be an SVG file with .svg extension`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Checks if a URL is accessible by making a HEAD request.
|
|
99
|
+
* @param {string} url
|
|
100
|
+
* @returns {Promise<boolean>}
|
|
101
|
+
*/
|
|
102
|
+
async function isUrlAccessible(url) {
|
|
103
|
+
try {
|
|
104
|
+
const response = await fetch(url, { method: "HEAD", redirect: "follow" });
|
|
105
|
+
return response.ok;
|
|
106
|
+
} catch {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Validates a tool's package.json against the official review criteria.
|
|
113
|
+
*
|
|
114
|
+
* @param {ToolPackageJson} packageJson - The parsed package.json object.
|
|
115
|
+
* @param {{ skipUrlChecks?: boolean }} [options] - Validation options.
|
|
116
|
+
* @returns {Promise<ValidationResult>}
|
|
117
|
+
*/
|
|
118
|
+
async function validatePackageJson(packageJson, options = {}) {
|
|
119
|
+
const { skipUrlChecks = false } = options;
|
|
120
|
+
const errors = /** @type {string[]} */ ([]);
|
|
121
|
+
const warnings = /** @type {string[]} */ ([]);
|
|
122
|
+
|
|
123
|
+
// Required fields
|
|
124
|
+
if (!packageJson.name || typeof packageJson.name !== "string") {
|
|
125
|
+
errors.push("Package name is required and must be a string");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!packageJson.version || typeof packageJson.version !== "string") {
|
|
129
|
+
errors.push("Package version is required and must be a string");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!packageJson.displayName || typeof packageJson.displayName !== "string") {
|
|
133
|
+
errors.push("displayName is required and must be a string");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!packageJson.description || typeof packageJson.description !== "string") {
|
|
137
|
+
errors.push("description is required and must be a string");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// License validation
|
|
141
|
+
if (!packageJson.license) {
|
|
142
|
+
errors.push("license is required");
|
|
143
|
+
} else if (!APPROVED_LICENSES.includes(packageJson.license)) {
|
|
144
|
+
errors.push(`License "${packageJson.license}" is not in the approved list. Approved licenses: ${APPROVED_LICENSES.join(", ")}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Icon validation (optional, but must be a relative SVG path if provided)
|
|
148
|
+
if (packageJson.icon !== undefined && packageJson.icon !== null) {
|
|
149
|
+
if (typeof packageJson.icon !== "string") {
|
|
150
|
+
errors.push("icon must be a string (relative path to bundled SVG under dist)");
|
|
151
|
+
} else {
|
|
152
|
+
validateIconPath("icon", packageJson.icon, errors);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Contributors validation
|
|
157
|
+
if (!packageJson.contributors || !Array.isArray(packageJson.contributors)) {
|
|
158
|
+
errors.push("contributors is required and must be an array");
|
|
159
|
+
} else if (packageJson.contributors.length === 0) {
|
|
160
|
+
errors.push("At least one contributor is required");
|
|
161
|
+
} else {
|
|
162
|
+
packageJson.contributors.forEach((contributor, index) => {
|
|
163
|
+
if (!contributor.name || typeof contributor.name !== "string") {
|
|
164
|
+
errors.push(`Contributor at index ${index} must have a name`);
|
|
165
|
+
}
|
|
166
|
+
if (contributor.url && !isValidUrl(contributor.url)) {
|
|
167
|
+
warnings.push(`Contributor "${contributor.name}" has an invalid URL`);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Configurations validation
|
|
173
|
+
if (!packageJson.configurations || typeof packageJson.configurations !== "object") {
|
|
174
|
+
errors.push("configurations is required and must include repository and readmeUrl");
|
|
175
|
+
} else {
|
|
176
|
+
const configs = packageJson.configurations;
|
|
177
|
+
|
|
178
|
+
// configurations.iconUrl is no longer supported
|
|
179
|
+
if (/** @type {Record<string, unknown>} */ (configs).iconUrl !== undefined) {
|
|
180
|
+
errors.push("configurations.iconUrl is no longer supported; use top-level 'icon' for bundled SVG path");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Repository validation
|
|
184
|
+
if (!configs.repository || typeof configs.repository !== "string") {
|
|
185
|
+
errors.push("configurations.repository is required and must be a URL");
|
|
186
|
+
} else if (!isValidUrl(configs.repository)) {
|
|
187
|
+
errors.push("configurations.repository has an invalid URL format");
|
|
188
|
+
} else if (!skipUrlChecks) {
|
|
189
|
+
const accessible = await isUrlAccessible(configs.repository);
|
|
190
|
+
if (!accessible) {
|
|
191
|
+
errors.push("configurations.repository URL is not accessible");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Website validation (optional)
|
|
196
|
+
if (configs.website) {
|
|
197
|
+
if (!isValidUrl(configs.website)) {
|
|
198
|
+
warnings.push("configurations.website has an invalid URL format");
|
|
199
|
+
} else if (!skipUrlChecks) {
|
|
200
|
+
const accessible = await isUrlAccessible(configs.website);
|
|
201
|
+
if (!accessible) {
|
|
202
|
+
warnings.push("configurations.website URL is not accessible");
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Funding validation (optional)
|
|
208
|
+
if (configs.funding) {
|
|
209
|
+
if (!isValidUrl(configs.funding)) {
|
|
210
|
+
warnings.push("configurations.funding has an invalid URL format");
|
|
211
|
+
} else if (!skipUrlChecks) {
|
|
212
|
+
const accessible = await isUrlAccessible(configs.funding);
|
|
213
|
+
if (!accessible) {
|
|
214
|
+
warnings.push("configurations.funding URL is not accessible");
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ReadmeUrl validation
|
|
220
|
+
if (!configs.readmeUrl || typeof configs.readmeUrl !== "string") {
|
|
221
|
+
errors.push("configurations.readmeUrl is required and must be a URL");
|
|
222
|
+
} else if (!isValidUrl(configs.readmeUrl)) {
|
|
223
|
+
errors.push("configurations.readmeUrl has an invalid URL format");
|
|
224
|
+
} else if (isGithubDomain(configs.readmeUrl)) {
|
|
225
|
+
errors.push("configurations.readmeUrl cannot be hosted on github.com; use raw.githubusercontent.com or another domain");
|
|
226
|
+
} else if (!skipUrlChecks) {
|
|
227
|
+
const accessible = await isUrlAccessible(configs.readmeUrl);
|
|
228
|
+
if (!accessible) {
|
|
229
|
+
errors.push("configurations.readmeUrl is not accessible");
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// CSP Exceptions validation (optional, but validated if present)
|
|
235
|
+
if (packageJson.cspExceptions) {
|
|
236
|
+
const validCspDirectives = ["connect-src", "script-src", "style-src", "img-src", "font-src", "frame-src"];
|
|
237
|
+
|
|
238
|
+
const hasAnyDirectives = Object.keys(packageJson.cspExceptions).length > 0;
|
|
239
|
+
if (!hasAnyDirectives) {
|
|
240
|
+
errors.push("cspExceptions cannot be empty. If CSP exceptions are not needed, remove the cspExceptions field");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
Object.keys(packageJson.cspExceptions).forEach((directive) => {
|
|
244
|
+
if (!validCspDirectives.includes(directive)) {
|
|
245
|
+
warnings.push(`Unknown CSP directive: ${directive}`);
|
|
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`);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Features validation (optional, but validated if present)
|
|
257
|
+
if (packageJson.features) {
|
|
258
|
+
const VALID_FEATURE_KEYS = ["multiConnection", "minAPI"];
|
|
259
|
+
const featureKeys = Object.keys(packageJson.features);
|
|
260
|
+
const invalidKeys = featureKeys.filter((key) => !VALID_FEATURE_KEYS.includes(key));
|
|
261
|
+
|
|
262
|
+
if (invalidKeys.length > 0) {
|
|
263
|
+
errors.push(`features can only contain ${VALID_FEATURE_KEYS.map((k) => `'${k}'`).join(", ")} properties. Invalid properties: ${invalidKeys.join(", ")}`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (packageJson.features.multiConnection === undefined) {
|
|
267
|
+
errors.push("features.multiConnection is required when features object is provided");
|
|
268
|
+
} else if (!VALID_MULTI_CONNECTION_VALUES.includes(packageJson.features.multiConnection)) {
|
|
269
|
+
errors.push(`features.multiConnection must be one of: ${VALID_MULTI_CONNECTION_VALUES.join(", ")}`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (packageJson.features.minAPI !== undefined) {
|
|
273
|
+
if (typeof packageJson.features.minAPI !== "string" || !SEMVER_REGEX.test(packageJson.features.minAPI)) {
|
|
274
|
+
errors.push("features.minAPI must be a valid semantic version string (e.g., '1.0.0')");
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const valid = errors.length === 0;
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
valid,
|
|
283
|
+
errors,
|
|
284
|
+
warnings,
|
|
285
|
+
packageInfo: valid
|
|
286
|
+
? {
|
|
287
|
+
name: packageJson.name,
|
|
288
|
+
version: packageJson.version,
|
|
289
|
+
displayName: packageJson.displayName,
|
|
290
|
+
description: packageJson.description,
|
|
291
|
+
license: packageJson.license,
|
|
292
|
+
contributors: packageJson.contributors,
|
|
293
|
+
cspExceptions: packageJson.cspExceptions,
|
|
294
|
+
icon: packageJson.icon,
|
|
295
|
+
configurations: packageJson.configurations,
|
|
296
|
+
features: packageJson.features,
|
|
297
|
+
}
|
|
298
|
+
: undefined,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
module.exports = { validatePackageJson, isValidUrl, APPROVED_LICENSES };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pptb/types",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.1.3-beta.1",
|
|
4
|
+
"description": "Type definitions for Power Platform ToolBox APIs and validity checks for tool packages",
|
|
5
5
|
"main": "index.d.ts",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"keywords": [
|
|
@@ -19,6 +19,17 @@
|
|
|
19
19
|
"url": "https://github.com/PowerPlatformToolBox/desktop-app.git",
|
|
20
20
|
"directory": "packages"
|
|
21
21
|
},
|
|
22
|
+
"bin": {
|
|
23
|
+
"pptb-validate": "./bin/pptb-validate.js"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"index.d.ts",
|
|
27
|
+
"toolboxAPI.d.ts",
|
|
28
|
+
"dataverseAPI.d.ts",
|
|
29
|
+
"bin/",
|
|
30
|
+
"lib/",
|
|
31
|
+
"README.md"
|
|
32
|
+
],
|
|
22
33
|
"scripts": {
|
|
23
34
|
"version:beta": "pnpm version prerelease --preid beta --no-git-tag-version",
|
|
24
35
|
"version:stable": "pnpm version patch --no-git-tag-version",
|