local-bdk-cli 1.0.7

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.
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createAppPkg = createAppPkg;
7
+ exports.validatePkg = validatePkg;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const os_1 = __importDefault(require("os"));
11
+ const archiver_1 = __importDefault(require("archiver"));
12
+ async function createAppPkg(targetDir, appName) {
13
+ const outName = `${appName.replace(/[^a-z0-9-_]/gi, '-')}-${Date.now()}.zip`;
14
+ const outPath = path_1.default.join(os_1.default.tmpdir(), outName);
15
+ return new Promise((resolve, reject) => {
16
+ const output = fs_1.default.createWriteStream(outPath);
17
+ const archive = (0, archiver_1.default)('zip', { zlib: { level: 9 } });
18
+ output.on('close', () => resolve(outPath));
19
+ output.on('error', (err) => reject(err));
20
+ archive.on('error', (err) => reject(err));
21
+ archive.pipe(output);
22
+ archive.directory(targetDir, false);
23
+ archive.finalize();
24
+ });
25
+ }
26
+ async function validatePkg(appPath) {
27
+ const manifestPath = path_1.default.join(appPath, 'manifest.json');
28
+ if (!fs_1.default.existsSync(manifestPath))
29
+ throw new Error(`manifest.json not found in ${appPath}`);
30
+ const manifest = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
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
+ // ── Rule: settings.fields key uniqueness ──
133
+ const seenKeys = new Map();
134
+ settings.fields.forEach((field, index) => {
135
+ if (field && typeof field.key === 'string' && field.key.trim() !== '') {
136
+ const key = field.key.trim();
137
+ if (seenKeys.has(key)) {
138
+ errors.push(`settings.fields: duplicate key "${key}" found at index ${index} (first used at index ${seenKeys.get(key)}). Each field key must be unique.`);
139
+ }
140
+ else {
141
+ seenKeys.set(key, index);
142
+ }
143
+ }
144
+ });
145
+ }
146
+ // ── Rule: enablePermission / enableReadPermission / enableWritePermission ──
147
+ // If enablePermission is true → at least one of enableReadPermission or enableWritePermission must be true.
148
+ // If enablePermission is false → both enableReadPermission and enableWritePermission must be false.
149
+ if ('enablePermission' in settings) {
150
+ const enablePermission = settings.enablePermission;
151
+ const enableReadPermission = settings.enableReadPermission;
152
+ const enableWritePermission = settings.enableWritePermission;
153
+ if (enablePermission === true) {
154
+ if (enableReadPermission !== true && enableWritePermission !== true) {
155
+ errors.push("settings: 'enablePermission' is true, so at least one of " +
156
+ "'enableReadPermission' or 'enableWritePermission' must also be true.");
157
+ }
158
+ }
159
+ else if (enablePermission === false) {
160
+ if (enableReadPermission === true) {
161
+ errors.push("settings: 'enableReadPermission' must be false when 'enablePermission' is false.");
162
+ }
163
+ if (enableWritePermission === true) {
164
+ errors.push("settings: 'enableWritePermission' must be false when 'enablePermission' is false.");
165
+ }
166
+ }
167
+ }
168
+ // If 'oauth' property is present in manifest → settings.fields must have at least one type "OAuth" field
169
+ if ('oauth' in manifest) {
170
+ const fields = Array.isArray(settings.fields) ? settings.fields : [];
171
+ const hasOAuthField = fields.some((field) => field && typeof field.type === 'string' && field.type.toLowerCase() === 'oauth');
172
+ if (!hasOAuthField) {
173
+ errors.push("settings.fields must contain at least one field with type 'OAuth' when oauth is configured. " +
174
+ 'Example: { "key": "token", "type": "OAuth", "secure": true, "required": true, ... }');
175
+ }
176
+ }
177
+ }
178
+ if (errors.length > 0) {
179
+ throw new Error(`Validation errors in ${manifestPath}:\n - ${errors.join('\n - ')}`);
180
+ }
181
+ }
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getAllConfigs = getAllConfigs;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ function getAllConfigs(appPath) {
10
+ // Look for a config file (config.json or app.config.json). Return parsed object or empty.
11
+ const candidates = ['config.json', 'app.config.json'];
12
+ for (const c of candidates) {
13
+ const p = path_1.default.join(appPath, c);
14
+ if (fs_1.default.existsSync(p)) {
15
+ try {
16
+ return JSON.parse(fs_1.default.readFileSync(p, 'utf8'));
17
+ }
18
+ catch (e) {
19
+ return {};
20
+ }
21
+ }
22
+ }
23
+ return {};
24
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.uploadAppPkg = uploadAppPkg;
7
+ exports.deployApp = deployApp;
8
+ exports.createProductInstallation = createProductInstallation;
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const node_fetch_1 = __importDefault(require("node-fetch"));
11
+ const form_data_1 = __importDefault(require("form-data"));
12
+ async function uploadAppPkg(apiUrl, apiToken, pkgPath) {
13
+ const form = new form_data_1.default();
14
+ form.append('file', fs_1.default.createReadStream(pkgPath));
15
+ const headers = form.getHeaders();
16
+ headers['Authorization'] = `Bearer ${apiToken}`;
17
+ const res = await (0, node_fetch_1.default)(`${apiUrl.replace(/\/+$/, '')}/apps/upload`, {
18
+ method: 'POST',
19
+ headers,
20
+ body: form
21
+ });
22
+ if (!res.ok)
23
+ throw new Error(`Upload failed: ${res.status} ${res.statusText}`);
24
+ return res.json();
25
+ }
26
+ async function deployApp(apiUrl, apiToken, uploadId, name) {
27
+ const res = await (0, node_fetch_1.default)(`${apiUrl.replace(/\/+$/, '')}/apps/deploy`, {
28
+ method: 'POST',
29
+ headers: {
30
+ 'Content-Type': 'application/json',
31
+ 'Authorization': `Bearer ${apiToken}`
32
+ },
33
+ body: JSON.stringify({ upload_id: uploadId, name })
34
+ });
35
+ if (!res.ok)
36
+ throw new Error(`Deploy request failed: ${res.status} ${res.statusText}`);
37
+ return res.json();
38
+ }
39
+ async function createProductInstallation(apiUrl, apiToken, settings, manifest, appId, product) {
40
+ const url = `${apiUrl.replace(/\/+$/, '')}/products/${encodeURIComponent(product)}/installations`;
41
+ const res = await (0, node_fetch_1.default)(url, {
42
+ method: 'POST',
43
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiToken}` },
44
+ body: JSON.stringify({ app_id: appId, settings, manifest })
45
+ });
46
+ return res.ok;
47
+ }
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getAppSettings = getAppSettings;
4
+ async function getAppSettings(manifest, configParams = {}) {
5
+ const params = manifest.parameters || {};
6
+ const settings = {};
7
+ for (const [k, v] of Object.entries(params)) {
8
+ // prefer configParams value, otherwise manifest default, otherwise empty string
9
+ // @ts-ignore
10
+ settings[k] = (configParams && configParams[k] !== undefined) ? configParams[k] : ((v && v.default) ? v.default : '');
11
+ }
12
+ return settings;
13
+ }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getManifestFile = getManifestFile;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ function getManifestFile(appPath) {
10
+ const manifestPath = path_1.default.resolve(appPath, 'manifest.json');
11
+ if (!fs_1.default.existsSync(manifestPath))
12
+ throw new Error(`manifest.json not found in ${appPath}`);
13
+ return JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
14
+ }
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getUploadJobStatus = getUploadJobStatus;
4
+ async function getUploadJobStatus(apiUrl, apiToken, jobId, timeoutMs = 2 * 60 * 1000) {
5
+ const start = Date.now();
6
+ while (true) {
7
+ const res = await fetch(`${apiUrl.replace(/\/+$/, '')}/jobs/${jobId}`, {
8
+ method: 'GET',
9
+ headers: { 'Authorization': `Bearer ${apiToken}` }
10
+ });
11
+ if (!res.ok)
12
+ throw new Error(`Job status request failed: ${res.status} ${res.statusText}`);
13
+ const body = await res.json();
14
+ const status = body.status || body.state || '';
15
+ if (status === 'success' || status === 'completed' || status === 'ok') {
16
+ return body;
17
+ }
18
+ if (status === 'failed' || status === 'error') {
19
+ throw new Error(`Job failed: ${JSON.stringify(body)}`);
20
+ }
21
+ if (Date.now() - start > timeoutMs)
22
+ throw new Error('Timed out waiting for job to complete');
23
+ await new Promise((r) => setTimeout(r, 2000));
24
+ }
25
+ }
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "local-bdk-cli",
3
+ "version": "1.0.7",
4
+ "description": "Local BoldDesk CLI for app development and testing",
5
+ "author": "@BoldDesk",
6
+ "bin": {
7
+ "bdk": "dist/index.js"
8
+ },
9
+ "npmRegistry": "https://registry.npmjs.org",
10
+ "license": "MIT",
11
+ "scripts": {
12
+ "build": "npx tsc -p tsconfig.json",
13
+ "dev": "ts-node src/index.ts",
14
+ "start": "node dist/index.js",
15
+ "test": "jest"
16
+ },
17
+ "dependencies": {
18
+ "ajv": "^8.12.0",
19
+ "archiver": "^5.3.1",
20
+ "chokidar": "^3.5.3",
21
+ "commander": "^10.0.0",
22
+ "form-data": "^4.0.0",
23
+ "inquirer": "^9.0.0",
24
+ "node-fetch": "^2.6.7"
25
+ },
26
+ "devDependencies": {
27
+ "@types/archiver": "^7.0.0",
28
+ "@types/commander": "^2.12.2",
29
+ "@types/inquirer": "^9.0.9",
30
+ "@types/jest": "^30.0.0",
31
+ "@types/node": "^18.0.0",
32
+ "@types/node-fetch": "^2.6.13",
33
+ "jest": "^29.0.0",
34
+ "ts-jest": "^29.0.0",
35
+ "ts-node": "^10.9.1",
36
+ "typescript": "^5.0.0"
37
+ },
38
+ "main": "dist/index.js",
39
+ "files": [
40
+ "dist",
41
+ "schemas",
42
+ "templates",
43
+ "README.md"
44
+ ],
45
+ "publishConfig": {
46
+ "access": "public"
47
+ },
48
+ "jest": {
49
+ "preset": "ts-jest",
50
+ "testEnvironment": "node",
51
+ "roots": [
52
+ "<rootDir>/__tests__"
53
+ ],
54
+ "testMatch": [
55
+ "**/__tests__/**/*.test.ts"
56
+ ],
57
+ "testPathIgnorePatterns": [
58
+ "/node_modules/",
59
+ "/dist/",
60
+ "/src/commands/",
61
+ "/src/"
62
+ ],
63
+ "transform": {
64
+ "^.+\\.tsx?$": ["ts-jest", {}]
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,92 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "BoldDesk App Manifest",
4
+ "type": "object",
5
+ "required": ["name", "version", "frameworkVersion", "developer", "widgets", "defaultLocale"],
6
+ "properties": {
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" }
90
+ },
91
+ "additionalProperties": false
92
+ }
@@ -0,0 +1,18 @@
1
+ # App name
2
+
3
+ Short description: What this BoldDesk app does.
4
+
5
+ ## Setup
6
+
7
+ 1. Install dependencies: `npm install`
8
+ 2. Build the app: `npm run build`
9
+ 3. Run locally: `bdk run . --open`
10
+
11
+ ## Publishing
12
+
13
+ Use `bdk pack` to create an archive and `bdk publish` to upload to the BoldDesk marketplace.
14
+
15
+ Please submit bug reports to the project repository. Pull requests are welcome.
16
+
17
+ ### Screenshot(s)
18
+ Place screenshots of your app here.
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
6
+ <title>Sample Iframe</title>
7
+ <style>body{font-family:Arial,Helvetica,sans-serif;padding:20px}</style>
8
+ </head>
9
+ <body>
10
+ <h1>Hello from the app iframe</h1>
11
+ <p>This placeholder iframe is created by the bdk CLI — customize it for your BoldDesk app.</p>
12
+ </body>
13
+ </html>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
2
+ <rect width="64" height="64" rx="8" fill="#0b6c6c" />
3
+ <rect x="12" y="12" width="40" height="40" rx="6" fill="#fff" />
4
+ </svg>
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="240" height="240" viewBox="0 0 240 240">
2
+ <rect width="240" height="240" rx="24" fill="#0b6c6c" />
3
+ <g fill="#fff" transform="translate(40,40)">
4
+ <rect x="0" y="0" width="160" height="160" rx="12" />
5
+ </g>
6
+ </svg>
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "sample-app",
3
+ "developer": {
4
+ "name": "",
5
+ "contactEmail": "",
6
+ "supportEmail": "",
7
+ "privacyUrl": "",
8
+ "websiteUrl": "",
9
+ "termsOfUseUrl": ""
10
+ },
11
+ "widgets": [
12
+ {
13
+ "url": "assets/iframe.html",
14
+ "name": "Sample Widget",
15
+ "location": "desk.ticket.view.rightpanel"
16
+ }
17
+ ],
18
+ "settings": {
19
+ "fields": [],
20
+ "enablePermission": false,
21
+ "enableReadPermission": false,
22
+ "enableWritePermission": false
23
+ },
24
+ "defaultLocale": "en",
25
+ "domainWhitelist": [],
26
+ "private": false,
27
+ "product": "BoldDesk",
28
+ "version": "1.0.0",
29
+ "frameworkVersion": "1.0",
30
+ "description": ""
31
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "sample-app",
3
+ "version": "0.1.0",
4
+ "description": "Sample app scaffolded by bdk",
5
+ "scripts": {
6
+ "start": "echo Starting app...",
7
+ "build": "echo Building app...",
8
+ "test": "echo Running tests..."
9
+ },
10
+ "author": "",
11
+ "license": "MIT"
12
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "app": {
3
+ "name": "Bold Tunes",
4
+ "short_description": "Play the famous Bold tunes in your help desk.",
5
+ "long_description": "Play the famous Bold tunes in your help desk and \n listen to the beats it has to offer.",
6
+ "installation_instructions": "Simply click install."
7
+ }
8
+ }