test-bdk-cli 0.1.20 → 1.0.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/commands/create.js +50 -18
- package/dist/commands/validate.js +0 -7
- package/dist/lib/package.js +114 -11
- package/package.json +1 -1
- package/schemas/manifest.schema.json +85 -8
- package/templates/default/manifest.json +24 -15
package/dist/commands/create.js
CHANGED
|
@@ -160,25 +160,28 @@ function registerCreate(program) {
|
|
|
160
160
|
if (!fs_1.default.existsSync(manifestPath)) {
|
|
161
161
|
const defaultManifest = {
|
|
162
162
|
name: appName,
|
|
163
|
-
|
|
163
|
+
developer: {
|
|
164
164
|
name: '',
|
|
165
|
-
|
|
165
|
+
contactEmail: '',
|
|
166
|
+
supportEmail: '',
|
|
167
|
+
privacyUrl: '',
|
|
168
|
+
websiteUrl: '',
|
|
169
|
+
termsOfUseUrl: ''
|
|
166
170
|
},
|
|
171
|
+
widgets: [
|
|
172
|
+
{
|
|
173
|
+
url: 'assets/iframe.html',
|
|
174
|
+
name: 'Sample Widget',
|
|
175
|
+
location: 'desk.ticket.view.rightpanel'
|
|
176
|
+
}
|
|
177
|
+
],
|
|
167
178
|
defaultLocale: 'en',
|
|
179
|
+
domainWhitelist: [],
|
|
168
180
|
private: true,
|
|
169
|
-
|
|
170
|
-
support: {
|
|
171
|
-
ticket_sidebar: {
|
|
172
|
-
url: 'assets/iframe.html',
|
|
173
|
-
flexible: true
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
},
|
|
181
|
+
product: 'BoldDesk',
|
|
177
182
|
version: '1.0.0',
|
|
178
183
|
frameworkVersion: '2.0',
|
|
179
|
-
description: ''
|
|
180
|
-
main: 'dist/index.html',
|
|
181
|
-
permissions: []
|
|
184
|
+
description: ''
|
|
182
185
|
};
|
|
183
186
|
try {
|
|
184
187
|
fs_1.default.writeFileSync(manifestPath, JSON.stringify(defaultManifest, null, 2), 'utf8');
|
|
@@ -189,15 +192,44 @@ function registerCreate(program) {
|
|
|
189
192
|
}
|
|
190
193
|
if (fs_1.default.existsSync(manifestPath)) {
|
|
191
194
|
const manifest = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
|
|
195
|
+
const dev = (manifest.developer && typeof manifest.developer === 'object') ? manifest.developer : {};
|
|
192
196
|
const dynamicImport = new Function('s', 'return import(s)');
|
|
193
197
|
const _inquirer = (await dynamicImport('inquirer')).default || (await dynamicImport('inquirer'));
|
|
198
|
+
// Placeholder sentinel values from the template — treat them as empty
|
|
199
|
+
const templatePlaceholders = new Set([
|
|
200
|
+
'Your Name', 'you@example.com', 'https://example.com/privacy',
|
|
201
|
+
'https://example.com', 'https://example.com/terms',
|
|
202
|
+
'sample-app', 'A sample BoldDesk app scaffolded by the bdk CLI'
|
|
203
|
+
]);
|
|
204
|
+
const clean = (v) => (v && typeof v === 'string' && !templatePlaceholders.has(v.trim())) ? v : '';
|
|
194
205
|
const answers = await _inquirer.prompt([
|
|
195
|
-
{ name: 'name', message: 'App name', default: appName },
|
|
196
|
-
{ name: 'version', message: 'Version', default: manifest.version || '
|
|
197
|
-
{ name: '
|
|
198
|
-
|
|
206
|
+
{ name: 'name', message: 'App name', default: clean(manifest.name) || appName },
|
|
207
|
+
{ name: 'version', message: 'Version', default: manifest.version || '1.0.0' },
|
|
208
|
+
{ name: 'description', message: 'Description', default: clean(manifest.description) },
|
|
209
|
+
// developer fields — always default to '' so pressing Enter stores empty string
|
|
210
|
+
{ name: 'devName', message: 'Developer name', default: clean(dev.name) },
|
|
211
|
+
{ name: 'devContactEmail', message: 'Developer contact email', default: clean(dev.contactEmail) },
|
|
212
|
+
{ name: 'devSupportEmail', message: 'Developer support email', default: clean(dev.supportEmail) },
|
|
213
|
+
{ name: 'devPrivacyUrl', message: 'Developer privacy URL', default: clean(dev.privacyUrl) },
|
|
214
|
+
{ name: 'devWebsiteUrl', message: 'Developer website URL', default: clean(dev.websiteUrl) },
|
|
215
|
+
{ name: 'devTermsUrl', message: 'Developer terms of use URL', default: clean(dev.termsOfUseUrl) }
|
|
199
216
|
]);
|
|
200
|
-
const newManifest = {
|
|
217
|
+
const newManifest = {
|
|
218
|
+
...manifest,
|
|
219
|
+
name: answers.name,
|
|
220
|
+
version: answers.version,
|
|
221
|
+
description: answers.description,
|
|
222
|
+
developer: {
|
|
223
|
+
name: answers.devName,
|
|
224
|
+
contactEmail: answers.devContactEmail,
|
|
225
|
+
supportEmail: answers.devSupportEmail,
|
|
226
|
+
privacyUrl: answers.devPrivacyUrl,
|
|
227
|
+
websiteUrl: answers.devWebsiteUrl,
|
|
228
|
+
termsOfUseUrl: answers.devTermsUrl
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
// Remove legacy 'author' field if present
|
|
232
|
+
delete newManifest.author;
|
|
201
233
|
fs_1.default.writeFileSync(manifestPath, JSON.stringify(newManifest, null, 2), 'utf8');
|
|
202
234
|
console.log('Updated manifest.json with provided values.');
|
|
203
235
|
}
|
|
@@ -34,7 +34,6 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.registerValidate = registerValidate;
|
|
37
|
-
const fs = __importStar(require("fs"));
|
|
38
37
|
const path = __importStar(require("path"));
|
|
39
38
|
const package_1 = require("../lib/package");
|
|
40
39
|
const appPath_1 = require("../lib/appPath");
|
|
@@ -47,14 +46,8 @@ function registerValidate(program) {
|
|
|
47
46
|
try {
|
|
48
47
|
(0, appPath_1.validateAppPath)(appDirectory);
|
|
49
48
|
const appPath = path.resolve(appDirectory);
|
|
50
|
-
// create package (not used by validatePkg here but kept for parity with legacy CLI flow)
|
|
51
|
-
const dummyName = path.basename(appPath) || 'app';
|
|
52
|
-
const pkgPath = await (0, package_1.createAppPkg)(appPath, dummyName).catch(() => null);
|
|
53
49
|
await (0, package_1.validatePkg)(appPath);
|
|
54
50
|
console.log('No validation errors');
|
|
55
|
-
// clean up
|
|
56
|
-
if (pkgPath)
|
|
57
|
-
await fs.promises.rm(pkgPath, { force: true, recursive: true });
|
|
58
51
|
}
|
|
59
52
|
catch (err) {
|
|
60
53
|
console.error(err && err.message ? err.message : err);
|
package/dist/lib/package.js
CHANGED
|
@@ -23,21 +23,124 @@ async function createAppPkg(targetDir, appName) {
|
|
|
23
23
|
archive.finalize();
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
|
-
const ajv_1 = __importDefault(require("ajv"));
|
|
27
26
|
async function validatePkg(appPath) {
|
|
28
27
|
const manifestPath = path_1.default.join(appPath, 'manifest.json');
|
|
29
28
|
if (!fs_1.default.existsSync(manifestPath))
|
|
30
29
|
throw new Error(`manifest.json not found in ${appPath}`);
|
|
31
|
-
const schemaPath = path_1.default.join(__dirname, '../../schemas/manifest.schema.json');
|
|
32
|
-
if (!fs_1.default.existsSync(schemaPath))
|
|
33
|
-
throw new Error('Manifest schema not found');
|
|
34
30
|
const manifest = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
31
|
+
const errors = [];
|
|
32
|
+
const isEmpty = (v) => v === undefined ||
|
|
33
|
+
v === null ||
|
|
34
|
+
(typeof v === 'string' && v.trim() === '') ||
|
|
35
|
+
(Array.isArray(v) && v.length === 0) ||
|
|
36
|
+
(typeof v === 'object' && !Array.isArray(v) && Object.keys(v).length === 0);
|
|
37
|
+
// ── Top-level required fields ──
|
|
38
|
+
if (isEmpty(manifest.name)) {
|
|
39
|
+
errors.push("Missing required field in manifest: 'name'.");
|
|
40
|
+
}
|
|
41
|
+
if (isEmpty(manifest.version)) {
|
|
42
|
+
errors.push("Missing required field in manifest: 'version'.");
|
|
43
|
+
}
|
|
44
|
+
else if (!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(manifest.version)) {
|
|
45
|
+
errors.push(`Invalid 'version' format: "${manifest.version}". Expected semver (e.g., 1.0.0).`);
|
|
46
|
+
}
|
|
47
|
+
if (isEmpty(manifest.frameworkVersion)) {
|
|
48
|
+
errors.push("Framework version (frameworkVersion) is missing.");
|
|
49
|
+
}
|
|
50
|
+
if (isEmpty(manifest.defaultLocale)) {
|
|
51
|
+
errors.push("Missing required field in manifest: 'defaultLocale'.");
|
|
52
|
+
}
|
|
53
|
+
// ── Rule: 'author' field is not allowed (replaced by 'developer') ──
|
|
54
|
+
if ('author' in manifest) {
|
|
55
|
+
errors.push("The 'author' field is not allowed. Use the 'developer' block instead.");
|
|
56
|
+
}
|
|
57
|
+
// ── Rule: 'location' top-level is no longer valid (location belongs inside each widget) ──
|
|
58
|
+
if ('location' in manifest && !Array.isArray(manifest.location)) {
|
|
59
|
+
errors.push("Top-level 'location' field is not allowed. Define 'location' inside each widget under 'widgets'.");
|
|
60
|
+
}
|
|
61
|
+
// ── Rule: developer block validation (only 'name' is required) ──
|
|
62
|
+
if (isEmpty(manifest.developer) || typeof manifest.developer !== 'object') {
|
|
63
|
+
errors.push("The 'developer' block is missing. Please provide developer information.");
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
if (isEmpty(manifest.developer.name)) {
|
|
67
|
+
errors.push("Developer validation failed: 'developer.name' is required.");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// ── Rule: widgets array & widget.location ──
|
|
71
|
+
if (!('widgets' in manifest)) {
|
|
72
|
+
errors.push("Missing required field in manifest: 'widgets'.");
|
|
73
|
+
}
|
|
74
|
+
else if (!Array.isArray(manifest.widgets) || manifest.widgets.length === 0) {
|
|
75
|
+
errors.push("'widgets' must be a non-empty array.");
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
manifest.widgets.forEach((widget, index) => {
|
|
79
|
+
const label = widget && widget.name ? widget.name : `index ${index}`;
|
|
80
|
+
if (isEmpty(widget === null || widget === void 0 ? void 0 : widget.location)) {
|
|
81
|
+
errors.push(`Widget "${label}": location value is missing.`);
|
|
82
|
+
}
|
|
83
|
+
if (isEmpty(widget === null || widget === void 0 ? void 0 : widget.url)) {
|
|
84
|
+
errors.push(`Widget "${label}": url value is missing.`);
|
|
85
|
+
}
|
|
86
|
+
if (isEmpty(widget === null || widget === void 0 ? void 0 : widget.name)) {
|
|
87
|
+
errors.push(`Widget at index ${index}: name value is missing.`);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
// ── Rule: OAuth validations ──
|
|
92
|
+
// If 'oauth' property is NOT present in manifest → skip entirely
|
|
93
|
+
// If 'oauth' property IS present (even with empty values) → all fields are required
|
|
94
|
+
if ('oauth' in manifest) {
|
|
95
|
+
const oauth = manifest.oauth;
|
|
96
|
+
if (!oauth || typeof oauth !== 'object') {
|
|
97
|
+
errors.push("'oauth' must be a valid object.");
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
const oauthFieldMap = {
|
|
101
|
+
clientId: 'oauth.clientId',
|
|
102
|
+
redirectUri: 'oauth.redirectUri',
|
|
103
|
+
authorizeUri: 'oauth.authorizeUri',
|
|
104
|
+
clientSecret: 'oauth.clientSecret',
|
|
105
|
+
accessTokenUri: 'oauth.accessTokenUri',
|
|
106
|
+
};
|
|
107
|
+
for (const [field, label] of Object.entries(oauthFieldMap)) {
|
|
108
|
+
if (isEmpty(oauth[field])) {
|
|
109
|
+
errors.push(`Missing required field: '${label}'.`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// ── Rule: settings.fields OAuth type check ──
|
|
115
|
+
// Only enforce when oauth block has actual values AND settings.fields is non-empty
|
|
116
|
+
if ('settings' in manifest) {
|
|
117
|
+
const settings = manifest.settings;
|
|
118
|
+
if (!settings || typeof settings !== 'object') {
|
|
119
|
+
errors.push("'settings' must be a valid object.");
|
|
120
|
+
}
|
|
121
|
+
else if (Array.isArray(settings.fields) && settings.fields.length > 0) {
|
|
122
|
+
// Each field object must have 'key' and 'type'
|
|
123
|
+
settings.fields.forEach((field, index) => {
|
|
124
|
+
const fieldLabel = field && field.key ? `"${field.key}"` : `index ${index}`;
|
|
125
|
+
if (isEmpty(field === null || field === void 0 ? void 0 : field.key)) {
|
|
126
|
+
errors.push(`settings.fields[${index}]: 'key' is required for each field object.`);
|
|
127
|
+
}
|
|
128
|
+
if (isEmpty(field === null || field === void 0 ? void 0 : field.type)) {
|
|
129
|
+
errors.push(`settings.fields[${fieldLabel}]: 'type' is required for each field object.`);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
// If 'oauth' property is present in manifest → settings.fields must have at least one type "OAuth" field
|
|
134
|
+
if ('oauth' in manifest) {
|
|
135
|
+
const fields = Array.isArray(settings.fields) ? settings.fields : [];
|
|
136
|
+
const hasOAuthField = fields.some((field) => field && typeof field.type === 'string' && field.type.toLowerCase() === 'oauth');
|
|
137
|
+
if (!hasOAuthField) {
|
|
138
|
+
errors.push("settings.fields must contain at least one field with type 'OAuth' when oauth is configured. " +
|
|
139
|
+
'Example: { "key": "token", "type": "OAuth", "secure": true, "required": true, ... }');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (errors.length > 0) {
|
|
144
|
+
throw new Error(`Validation errors in ${manifestPath}:\n - ${errors.join('\n - ')}`);
|
|
42
145
|
}
|
|
43
146
|
}
|
package/package.json
CHANGED
|
@@ -2,14 +2,91 @@
|
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"title": "BoldDesk App Manifest",
|
|
4
4
|
"type": "object",
|
|
5
|
-
"required": ["name", "version", "
|
|
5
|
+
"required": ["name", "version", "frameworkVersion", "developer", "widgets", "defaultLocale"],
|
|
6
6
|
"properties": {
|
|
7
|
-
"name": { "type": "string" },
|
|
8
|
-
"version": {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"
|
|
7
|
+
"name": { "type": "string", "minLength": 1 },
|
|
8
|
+
"version": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"pattern": "^\\d+\\.\\d+\\.\\d+(?:[-+][0-9A-Za-z.-]+)?$"
|
|
11
|
+
},
|
|
12
|
+
"frameworkVersion": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"minLength": 1,
|
|
15
|
+
"description": "Framework version is required"
|
|
16
|
+
},
|
|
17
|
+
"developer": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"required": ["name"],
|
|
20
|
+
"properties": {
|
|
21
|
+
"name": { "type": "string", "minLength": 1 },
|
|
22
|
+
"contactEmail": { "type": "string" },
|
|
23
|
+
"supportEmail": { "type": "string" },
|
|
24
|
+
"privacyUrl": { "type": "string" },
|
|
25
|
+
"websiteUrl": { "type": "string" },
|
|
26
|
+
"termsOfUseUrl":{ "type": "string" }
|
|
27
|
+
},
|
|
28
|
+
"additionalProperties": false
|
|
29
|
+
},
|
|
30
|
+
"widgets": {
|
|
31
|
+
"type": "array",
|
|
32
|
+
"minItems": 1,
|
|
33
|
+
"items": {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"required": ["url", "name", "location"],
|
|
36
|
+
"properties": {
|
|
37
|
+
"url": { "type": "string", "minLength": 1 },
|
|
38
|
+
"name": { "type": "string", "minLength": 1 },
|
|
39
|
+
"location": { "type": "string", "minLength": 1 }
|
|
40
|
+
},
|
|
41
|
+
"additionalProperties": true
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"oauth": {
|
|
45
|
+
"type": "object",
|
|
46
|
+
"required": ["clientId", "redirectUri", "authorizeUri", "clientSecret", "accessTokenUri"],
|
|
47
|
+
"properties": {
|
|
48
|
+
"clientId": { "type": "string", "minLength": 1 },
|
|
49
|
+
"redirectUri": { "type": "string", "minLength": 1 },
|
|
50
|
+
"authorizeUri": { "type": "string", "minLength": 1 },
|
|
51
|
+
"clientSecret": { "type": "string", "minLength": 1 },
|
|
52
|
+
"accessTokenUri": { "type": "string", "minLength": 1 }
|
|
53
|
+
},
|
|
54
|
+
"additionalProperties": false
|
|
55
|
+
},
|
|
56
|
+
"settings": {
|
|
57
|
+
"type": "object",
|
|
58
|
+
"properties": {
|
|
59
|
+
"fields": {
|
|
60
|
+
"type": "array",
|
|
61
|
+
"items": {
|
|
62
|
+
"type": "object",
|
|
63
|
+
"required": ["key", "type"],
|
|
64
|
+
"properties": {
|
|
65
|
+
"key": { "type": "string", "minLength": 1 },
|
|
66
|
+
"type": { "type": "string", "enum": ["text", "password", "number", "textarea", "radio", "checkbox", "dropdown", "email", "url", "hidden", "OAuth"] },
|
|
67
|
+
"label": { "type": "string" },
|
|
68
|
+
"secure": { "type": "boolean" },
|
|
69
|
+
"options": { "type": "array" },
|
|
70
|
+
"required": { "type": "boolean" },
|
|
71
|
+
"maxLength": { "type": "number" },
|
|
72
|
+
"placeholder": { "type": "string" },
|
|
73
|
+
"defaultValue": { "type": "string" },
|
|
74
|
+
"helpText": { "type": "string" }
|
|
75
|
+
},
|
|
76
|
+
"additionalProperties": false
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"enablePermission": { "type": "boolean" },
|
|
80
|
+
"enableReadPermission": { "type": "boolean" },
|
|
81
|
+
"enableWritePermission": { "type": "boolean" }
|
|
82
|
+
},
|
|
83
|
+
"additionalProperties": false
|
|
84
|
+
},
|
|
85
|
+
"defaultLocale": { "type": "string", "minLength": 1 },
|
|
86
|
+
"domainWhitelist": { "type": "array", "items": { "type": "string" } },
|
|
87
|
+
"private": { "type": "boolean" },
|
|
88
|
+
"product": { "type": "string" },
|
|
89
|
+
"description": { "type": "string" }
|
|
13
90
|
},
|
|
14
|
-
"additionalProperties":
|
|
91
|
+
"additionalProperties": false
|
|
15
92
|
}
|
|
@@ -1,22 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sample-app",
|
|
3
|
-
"
|
|
4
|
-
"name": "
|
|
5
|
-
"
|
|
3
|
+
"developer": {
|
|
4
|
+
"name": "",
|
|
5
|
+
"contactEmail": "",
|
|
6
|
+
"supportEmail": "",
|
|
7
|
+
"privacyUrl": "",
|
|
8
|
+
"websiteUrl": "",
|
|
9
|
+
"termsOfUseUrl": ""
|
|
6
10
|
},
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
"url": "assets/iframe.html",
|
|
13
|
-
"flexible": true
|
|
14
|
-
}
|
|
11
|
+
"widgets": [
|
|
12
|
+
{
|
|
13
|
+
"url": "assets/iframe.html",
|
|
14
|
+
"name": "Sample Widget",
|
|
15
|
+
"location": "desk.ticket.view.rightpanel"
|
|
15
16
|
}
|
|
17
|
+
],
|
|
18
|
+
"settings": {
|
|
19
|
+
"fields": [],
|
|
20
|
+
"enablePermission": false,
|
|
21
|
+
"enableReadPermission": false,
|
|
22
|
+
"enableWritePermission": false
|
|
16
23
|
},
|
|
24
|
+
"defaultLocale": "en",
|
|
25
|
+
"domainWhitelist": [],
|
|
26
|
+
"private": false,
|
|
27
|
+
"product": "BoldDesk",
|
|
17
28
|
"version": "1.0.0",
|
|
18
|
-
"frameworkVersion": "
|
|
19
|
-
"description": "
|
|
20
|
-
"main": "dist/index.html",
|
|
21
|
-
"permissions": []
|
|
29
|
+
"frameworkVersion": "1.0",
|
|
30
|
+
"description": ""
|
|
22
31
|
}
|