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.
@@ -160,25 +160,28 @@ function registerCreate(program) {
160
160
  if (!fs_1.default.existsSync(manifestPath)) {
161
161
  const defaultManifest = {
162
162
  name: appName,
163
- author: {
163
+ developer: {
164
164
  name: '',
165
- email: ''
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
- location: {
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 || '0.1.0' },
197
- { name: 'author', message: 'Author', default: manifest.author || '' },
198
- { name: 'description', message: 'Description', default: manifest.description || '' }
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 = { ...manifest, ...answers };
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);
@@ -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 schema = JSON.parse(fs_1.default.readFileSync(schemaPath, 'utf8'));
36
- const ajv = new ajv_1.default({ allErrors: true, allowUnionTypes: true });
37
- const validate = ajv.compile(schema);
38
- const valid = validate(manifest);
39
- if (!valid) {
40
- const errs = (validate.errors || []).map((e) => `${(e === null || e === void 0 ? void 0 : e.instancePath) || '/'}: ${e === null || e === void 0 ? void 0 : e.message}`).join('\n');
41
- throw new Error(`Validation errors in ${manifestPath}:\n${errs}`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "test-bdk-cli",
3
- "version": "0.1.20",
3
+ "version": "1.0.0",
4
4
  "description": "test CLI",
5
5
  "author": "@raisulaslam",
6
6
  "bin": {
@@ -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", "author"],
5
+ "required": ["name", "version", "frameworkVersion", "developer", "widgets", "defaultLocale"],
6
6
  "properties": {
7
- "name": { "type": "string" },
8
- "version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+(?:[-+][0-9A-Za-z.-]+)?$" },
9
- "author": { "type": ["string", "object"] },
10
- "description": { "type": "string" },
11
- "main": { "type": "string" },
12
- "permissions": { "type": "array", "items": { "type": "string" } }
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": true
91
+ "additionalProperties": false
15
92
  }
@@ -1,22 +1,31 @@
1
1
  {
2
2
  "name": "sample-app",
3
- "author": {
4
- "name": "Your Name",
5
- "email": ""
3
+ "developer": {
4
+ "name": "",
5
+ "contactEmail": "",
6
+ "supportEmail": "",
7
+ "privacyUrl": "",
8
+ "websiteUrl": "",
9
+ "termsOfUseUrl": ""
6
10
  },
7
- "defaultLocale": "en",
8
- "private": true,
9
- "location": {
10
- "support": {
11
- "ticket_sidebar": {
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": "2.0",
19
- "description": "A sample BoldDesk app scaffolded by the bdk CLI",
20
- "main": "dist/index.html",
21
- "permissions": []
29
+ "frameworkVersion": "1.0",
30
+ "description": ""
22
31
  }