test-bdk-cli 1.0.2 → 1.0.4
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 +314 -0
- package/dist/commands/help.js +83 -0
- package/dist/commands/pack.js +116 -0
- package/dist/commands/publish.js +97 -0
- package/dist/commands/run.js +155 -0
- package/dist/commands/test.js +61 -0
- package/dist/commands/validate.js +57 -0
- package/dist/commands/version.js +62 -0
- package/dist/index.js +24 -0
- package/dist/lib/appPath.js +17 -0
- package/dist/lib/package.js +168 -0
- package/dist/utils/appConfig.js +24 -0
- package/dist/utils/createApp.js +47 -0
- package/dist/utils/getAppSettings.js +13 -0
- package/dist/utils/manifest.js +14 -0
- package/dist/utils/uploadApp.js +25 -0
- package/package.json +3 -2
|
@@ -0,0 +1,314 @@
|
|
|
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.registerCreate = registerCreate;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
// inquirer is ESM-only; import dynamically at runtime to avoid require() errors
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
11
|
+
const package_1 = require("../lib/package");
|
|
12
|
+
const createApp_1 = require("../utils/createApp");
|
|
13
|
+
const uploadApp_1 = require("../utils/uploadApp");
|
|
14
|
+
const appConfig_1 = require("../utils/appConfig");
|
|
15
|
+
const getAppSettings_1 = require("../utils/getAppSettings");
|
|
16
|
+
function registerCreate(program) {
|
|
17
|
+
program
|
|
18
|
+
.command('apps:create [appDirectories...]')
|
|
19
|
+
.description('Scaffold one or more new app projects (usage: apps:create ./appName)')
|
|
20
|
+
.option('--template <template>', 'template name', 'default')
|
|
21
|
+
.option('--path <dir>', 'base path for created directories', '.')
|
|
22
|
+
.option('--force', 'overwrite if exists', false)
|
|
23
|
+
.option('--git', 'initialize a git repository', false)
|
|
24
|
+
.option('--npm', 'run npm init -y in the new project', false)
|
|
25
|
+
.option('--deploy', 'upload and deploy the app to BoldDesk', false)
|
|
26
|
+
.option('--api-url <url>', 'BoldDesk API base URL (env BOLD_API_URL)')
|
|
27
|
+
.option('--token <token>', 'BoldDesk API token (env BOLD_API_TOKEN)')
|
|
28
|
+
.action(async (...args) => {
|
|
29
|
+
// normalize action arguments: support both (appDirectories..., opts) and (opts) shapes
|
|
30
|
+
let opts = {};
|
|
31
|
+
let appDirectories = ['.'];
|
|
32
|
+
if (args.length === 0) {
|
|
33
|
+
opts = {};
|
|
34
|
+
appDirectories = ['.'];
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
// pick the last plain object (non-array) as options if present
|
|
38
|
+
for (let i = args.length - 1; i >= 0; i--) {
|
|
39
|
+
const a = args[i];
|
|
40
|
+
if (typeof a === 'object' && !Array.isArray(a)) {
|
|
41
|
+
opts = a;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// collect directory arguments: strings or arrays of strings
|
|
46
|
+
const collected = [];
|
|
47
|
+
for (const a of args) {
|
|
48
|
+
if (typeof a === 'string')
|
|
49
|
+
collected.push(a);
|
|
50
|
+
else if (Array.isArray(a)) {
|
|
51
|
+
for (const s of a)
|
|
52
|
+
if (typeof s === 'string')
|
|
53
|
+
collected.push(s);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
appDirectories = collected.length ? collected : ['.'];
|
|
57
|
+
}
|
|
58
|
+
const template = opts.template || 'default';
|
|
59
|
+
const basePath = path_1.default.resolve(opts.path || '.');
|
|
60
|
+
// flatten nested array if commander wrapped variadic into single array
|
|
61
|
+
if (Array.isArray(appDirectories) && appDirectories.length === 1 && Array.isArray(appDirectories[0])) {
|
|
62
|
+
appDirectories = appDirectories[0];
|
|
63
|
+
}
|
|
64
|
+
const templatesDir = path_1.default.resolve(__dirname, '../../templates', template);
|
|
65
|
+
if (!fs_1.default.existsSync(templatesDir)) {
|
|
66
|
+
console.error(`Template not found: ${templatesDir}`);
|
|
67
|
+
process.exit(2);
|
|
68
|
+
}
|
|
69
|
+
for (const appDir of appDirectories) {
|
|
70
|
+
const targetDir = path_1.default.resolve(basePath, appDir);
|
|
71
|
+
const appName = path_1.default.basename(targetDir);
|
|
72
|
+
if (fs_1.default.existsSync(targetDir) && !opts.force) {
|
|
73
|
+
const dynamicImport = new Function('s', 'return import(s)');
|
|
74
|
+
const _inquirer = (await dynamicImport('inquirer')).default || (await dynamicImport('inquirer'));
|
|
75
|
+
const { overwrite } = await _inquirer.prompt([
|
|
76
|
+
{
|
|
77
|
+
type: 'confirm',
|
|
78
|
+
name: 'overwrite',
|
|
79
|
+
message: `Target directory ${targetDir} exists. Overwrite?`,
|
|
80
|
+
default: false
|
|
81
|
+
}
|
|
82
|
+
]);
|
|
83
|
+
if (!overwrite) {
|
|
84
|
+
console.error('Aborted by user.');
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
fs_1.default.mkdirSync(targetDir, { recursive: true });
|
|
89
|
+
// Copy template directory. Use built-in cp if available, otherwise fallback to recursive copy.
|
|
90
|
+
if (fs_1.default.promises.cp) {
|
|
91
|
+
await fs_1.default.promises.cp(templatesDir, targetDir, { recursive: true });
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
// recursive copy fallback
|
|
95
|
+
async function copyRecursive(src, dest) {
|
|
96
|
+
const entries = await fs_1.default.promises.readdir(src, { withFileTypes: true });
|
|
97
|
+
await fs_1.default.promises.mkdir(dest, { recursive: true });
|
|
98
|
+
for (const entry of entries) {
|
|
99
|
+
const srcPath = path_1.default.join(src, entry.name);
|
|
100
|
+
const destPath = path_1.default.join(dest, entry.name);
|
|
101
|
+
if (entry.isDirectory()) {
|
|
102
|
+
await copyRecursive(srcPath, destPath);
|
|
103
|
+
}
|
|
104
|
+
else if (entry.isSymbolicLink()) {
|
|
105
|
+
try {
|
|
106
|
+
const link = await fs_1.default.promises.readlink(srcPath);
|
|
107
|
+
await fs_1.default.promises.symlink(link, destPath);
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
// fallback to copyFile if symlink fails
|
|
111
|
+
await fs_1.default.promises.copyFile(srcPath, destPath);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
await fs_1.default.promises.copyFile(srcPath, destPath);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
await copyRecursive(templatesDir, targetDir);
|
|
120
|
+
}
|
|
121
|
+
// Ensure template contains basic assets and translations. If the template
|
|
122
|
+
// did not include them, create defaults (iframe, logos, translations).
|
|
123
|
+
try {
|
|
124
|
+
const assetsDir = path_1.default.join(targetDir, 'assets');
|
|
125
|
+
if (!fs_1.default.existsSync(assetsDir))
|
|
126
|
+
fs_1.default.mkdirSync(assetsDir, { recursive: true });
|
|
127
|
+
const iframePath = path_1.default.join(assetsDir, 'iframe.html');
|
|
128
|
+
if (!fs_1.default.existsSync(iframePath)) {
|
|
129
|
+
fs_1.default.writeFileSync(iframePath, `<!doctype html>\n<html lang="en">\n <head>\n <meta charset="utf-8" />\n <meta name="viewport" content="width=device-width,initial-scale=1" />\n <title>Sample Iframe</title>\n <style>body{font-family:Arial,Helvetica,sans-serif;padding:20px}</style>\n </head>\n <body>\n <h1>Hello from the app iframe</h1>\n <p>This is a simple placeholder iframe page generated by the CLI.</p>\n </body>\n</html>\n`, 'utf8');
|
|
130
|
+
}
|
|
131
|
+
const logoPath = path_1.default.join(assetsDir, 'logo.svg');
|
|
132
|
+
if (!fs_1.default.existsSync(logoPath)) {
|
|
133
|
+
fs_1.default.writeFileSync(logoPath, `<svg xmlns="http://www.w3.org/2000/svg" width="240" height="240" viewBox="0 0 240 240">\n <rect width="240" height="240" rx="24" fill="#0b6c6c" />\n <g fill="#fff" transform="translate(40,40)">\n <rect x="0" y="0" width="160" height="160" rx="12" />\n </g>\n</svg>\n`, 'utf8');
|
|
134
|
+
}
|
|
135
|
+
const logoSmall = path_1.default.join(assetsDir, 'logo-small.svg');
|
|
136
|
+
if (!fs_1.default.existsSync(logoSmall)) {
|
|
137
|
+
fs_1.default.writeFileSync(logoSmall, `<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">\n <rect width="64" height="64" rx="8" fill="#0b6c6c" />\n <rect x="12" y="12" width="40" height="40" rx="6" fill="#fff" />\n</svg>\n`, 'utf8');
|
|
138
|
+
}
|
|
139
|
+
const translationsDir = path_1.default.join(targetDir, 'translations');
|
|
140
|
+
if (!fs_1.default.existsSync(translationsDir))
|
|
141
|
+
fs_1.default.mkdirSync(translationsDir, { recursive: true });
|
|
142
|
+
const enPath = path_1.default.join(translationsDir, 'en.json');
|
|
143
|
+
if (!fs_1.default.existsSync(enPath)) {
|
|
144
|
+
fs_1.default.writeFileSync(enPath, JSON.stringify({
|
|
145
|
+
app: {
|
|
146
|
+
name: 'Sample BoldDesk App',
|
|
147
|
+
short_description: 'A short description of your BoldDesk app.',
|
|
148
|
+
long_description: 'A longer description of your BoldDesk app and its features. Replace this text with real product details.',
|
|
149
|
+
installation_instructions: 'Follow the installation steps in the BoldDesk admin to install this app.'
|
|
150
|
+
}
|
|
151
|
+
}, null, 2), 'utf8');
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch (e) {
|
|
155
|
+
// ignore any template-creation errors
|
|
156
|
+
}
|
|
157
|
+
const manifestPath = path_1.default.resolve(targetDir, 'manifest.json');
|
|
158
|
+
// If template didn't include a manifest, create a sensible default so
|
|
159
|
+
// the CLI can prompt and the app can be packaged.
|
|
160
|
+
if (!fs_1.default.existsSync(manifestPath)) {
|
|
161
|
+
const defaultManifest = {
|
|
162
|
+
name: appName,
|
|
163
|
+
developer: {
|
|
164
|
+
name: '',
|
|
165
|
+
contactEmail: '',
|
|
166
|
+
supportEmail: '',
|
|
167
|
+
privacyUrl: '',
|
|
168
|
+
websiteUrl: '',
|
|
169
|
+
termsOfUseUrl: ''
|
|
170
|
+
},
|
|
171
|
+
widgets: [
|
|
172
|
+
{
|
|
173
|
+
url: 'assets/iframe.html',
|
|
174
|
+
name: 'Sample Widget',
|
|
175
|
+
location: 'desk.ticket.view.rightpanel'
|
|
176
|
+
}
|
|
177
|
+
],
|
|
178
|
+
defaultLocale: 'en',
|
|
179
|
+
domainWhitelist: [],
|
|
180
|
+
private: true,
|
|
181
|
+
product: 'BoldDesk',
|
|
182
|
+
version: '1.0.0',
|
|
183
|
+
frameworkVersion: '2.0',
|
|
184
|
+
description: ''
|
|
185
|
+
};
|
|
186
|
+
try {
|
|
187
|
+
fs_1.default.writeFileSync(manifestPath, JSON.stringify(defaultManifest, null, 2), 'utf8');
|
|
188
|
+
}
|
|
189
|
+
catch (e) {
|
|
190
|
+
// ignore write errors
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (fs_1.default.existsSync(manifestPath)) {
|
|
194
|
+
const manifest = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
|
|
195
|
+
const dev = (manifest.developer && typeof manifest.developer === 'object') ? manifest.developer : {};
|
|
196
|
+
const dynamicImport = new Function('s', 'return import(s)');
|
|
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 : '';
|
|
205
|
+
const answers = await _inquirer.prompt([
|
|
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) }
|
|
216
|
+
]);
|
|
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;
|
|
233
|
+
fs_1.default.writeFileSync(manifestPath, JSON.stringify(newManifest, null, 2), 'utf8');
|
|
234
|
+
console.log('Updated manifest.json with provided values.');
|
|
235
|
+
}
|
|
236
|
+
if (opts.npm) {
|
|
237
|
+
try {
|
|
238
|
+
console.log('Running `npm init -y`...');
|
|
239
|
+
(0, child_process_1.execSync)('npm init -y', { cwd: targetDir, stdio: 'inherit' });
|
|
240
|
+
}
|
|
241
|
+
catch (e) {
|
|
242
|
+
console.warn('npm init failed:', e.message);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (opts.git) {
|
|
246
|
+
try {
|
|
247
|
+
console.log('Initializing git repository...');
|
|
248
|
+
(0, child_process_1.execSync)('git init', { cwd: targetDir, stdio: 'inherit' });
|
|
249
|
+
(0, child_process_1.execSync)('git add .', { cwd: targetDir, stdio: 'inherit' });
|
|
250
|
+
(0, child_process_1.execSync)('git commit -m "chore: scaffold initial project"', { cwd: targetDir, stdio: 'ignore' });
|
|
251
|
+
}
|
|
252
|
+
catch (e) {
|
|
253
|
+
console.warn('git init/commit failed (git may not be installed or user identity not set).');
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
console.log(`Created ${appName} at ${targetDir} from template ${template}`);
|
|
257
|
+
// If deploy flag is set, package and upload to BoldDesk
|
|
258
|
+
if (opts.deploy) {
|
|
259
|
+
const API_URL = opts.apiUrl || process.env.BOLD_API_URL;
|
|
260
|
+
const API_TOKEN = opts.token || process.env.BOLD_API_TOKEN;
|
|
261
|
+
if (!API_URL || !API_TOKEN) {
|
|
262
|
+
console.error('Missing BoldDesk API configuration. Set --api-url and --token or BOLD_API_URL and BOLD_API_TOKEN environment variables.');
|
|
263
|
+
process.exit(4);
|
|
264
|
+
}
|
|
265
|
+
try {
|
|
266
|
+
console.log('Creating app package...');
|
|
267
|
+
const pkgPath = await (0, package_1.createAppPkg)(targetDir, appName);
|
|
268
|
+
console.log('Uploading package to BoldDesk...');
|
|
269
|
+
const uploadRes = await (0, createApp_1.uploadAppPkg)(API_URL, API_TOKEN, pkgPath);
|
|
270
|
+
if (!uploadRes || !uploadRes.upload_id) {
|
|
271
|
+
console.error('Upload failed: no upload_id returned');
|
|
272
|
+
process.exit(5);
|
|
273
|
+
}
|
|
274
|
+
console.log('Deploying uploaded package...');
|
|
275
|
+
const deployRes = await (0, createApp_1.deployApp)(API_URL, API_TOKEN, uploadRes.upload_id, appName);
|
|
276
|
+
if (!deployRes || !deployRes.job_id) {
|
|
277
|
+
console.error('Deploy failed: no job_id returned');
|
|
278
|
+
process.exit(6);
|
|
279
|
+
}
|
|
280
|
+
console.log('Polling job status...');
|
|
281
|
+
const jobResult = await (0, uploadApp_1.getUploadJobStatus)(API_URL, API_TOKEN, deployRes.job_id);
|
|
282
|
+
if (!jobResult || !jobResult.app_id) {
|
|
283
|
+
console.error('Job did not produce an app_id');
|
|
284
|
+
process.exit(7);
|
|
285
|
+
}
|
|
286
|
+
console.log(`Deployed successfully with app_id: ${jobResult.app_id}`);
|
|
287
|
+
// Optionally create product installations if manifest specifies locations
|
|
288
|
+
const manifestPath2 = path_1.default.resolve(targetDir, 'manifest.json');
|
|
289
|
+
if (fs_1.default.existsSync(manifestPath2)) {
|
|
290
|
+
const manifest2 = JSON.parse(fs_1.default.readFileSync(manifestPath2, 'utf8'));
|
|
291
|
+
const allConfigs = (0, appConfig_1.getAllConfigs)(targetDir);
|
|
292
|
+
const configParams = (allConfigs === null || allConfigs === void 0 ? void 0 : allConfigs.parameters) || {};
|
|
293
|
+
const settings = manifest2.parameters ? await (0, getAppSettings_1.getAppSettings)(manifest2, configParams) : {};
|
|
294
|
+
if (!manifest2.requirementsOnly && manifest2.location) {
|
|
295
|
+
for (const product of Object.keys(manifest2.location)) {
|
|
296
|
+
const installed = await (0, createApp_1.createProductInstallation)(API_URL, API_TOKEN, settings, manifest2, jobResult.app_id, product);
|
|
297
|
+
if (!installed) {
|
|
298
|
+
console.error(`Failed to create installation for product ${product}`);
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
console.log(`Installed on product ${product}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
catch (e) {
|
|
308
|
+
console.error('Deploy flow failed:', e && e.message ? e.message : e);
|
|
309
|
+
process.exit(8);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerHelp = registerHelp;
|
|
4
|
+
const COMMANDS = [
|
|
5
|
+
{
|
|
6
|
+
command: 'apps:create',
|
|
7
|
+
alias: 'apps:create [appDirectories...]',
|
|
8
|
+
description: 'Scaffold one or more new app projects from a template.',
|
|
9
|
+
usage: 'bdk apps:create ./my-app [--template default] [--git] [--npm] [--deploy]',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
command: 'apps:run',
|
|
13
|
+
alias: 'apps:run [source]',
|
|
14
|
+
description: 'Start a local dev server with live file serving for the app.',
|
|
15
|
+
usage: 'bdk apps:run [source] [--port 3000] [--env dev] [--open]',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
command: 'apps:validate',
|
|
19
|
+
alias: 'apps:validate [appDirectory]',
|
|
20
|
+
description: 'Validate the app structure and manifest.json against the schema.',
|
|
21
|
+
usage: 'bdk apps:validate [appDirectory]',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
command: 'apps:pack',
|
|
25
|
+
alias: 'apps:pack [source]',
|
|
26
|
+
description: 'Package the app into a zip/tar archive ready for publishing.',
|
|
27
|
+
usage: 'bdk apps:pack [source] [--output ./dist] [--format zip|tar]',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
command: 'apps:test',
|
|
31
|
+
alias: 'apps:test [source]',
|
|
32
|
+
description: 'Run the app\'s unit and integration tests via npm test.',
|
|
33
|
+
usage: 'bdk apps:test [source] [--watch] [--coverage]',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
command: 'apps:version',
|
|
37
|
+
alias: 'apps:version [source]',
|
|
38
|
+
description: 'Show or bump the app version in manifest.json.',
|
|
39
|
+
usage: 'bdk apps:version [source] [--bump patch|minor|major] [--set <version>]',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
command: 'apps:help',
|
|
43
|
+
alias: 'apps:help [command]',
|
|
44
|
+
description: 'Show the list of available commands with descriptions and usage.',
|
|
45
|
+
usage: 'bdk apps:help [command]',
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
function registerHelp(program) {
|
|
49
|
+
program
|
|
50
|
+
.command('help [commandName]')
|
|
51
|
+
.alias('apps:help')
|
|
52
|
+
.description('Show available commands and their descriptions')
|
|
53
|
+
.action((commandName) => {
|
|
54
|
+
if (commandName) {
|
|
55
|
+
// Show detailed info for a specific command
|
|
56
|
+
const found = COMMANDS.find((c) => c.command.toLowerCase() === commandName.toLowerCase() ||
|
|
57
|
+
c.command.toLowerCase() === `apps:${commandName.toLowerCase()}`);
|
|
58
|
+
if (!found) {
|
|
59
|
+
console.error(`\n Unknown command: "${commandName}"`);
|
|
60
|
+
console.error(` Run "bdk apps:help" to see all available commands.\n`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
console.log('');
|
|
65
|
+
console.log(` Command : ${found.command}`);
|
|
66
|
+
console.log(` Synopsis : ${found.alias}`);
|
|
67
|
+
console.log(` Info : ${found.description}`);
|
|
68
|
+
console.log(` Usage : ${found.usage}`);
|
|
69
|
+
console.log('');
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// Show summary of all commands
|
|
73
|
+
console.log('');
|
|
74
|
+
console.log(' BoldDesk Developer Kit (bdk) — Available Commands');
|
|
75
|
+
console.log(' ' + '─'.repeat(55));
|
|
76
|
+
const maxCmd = Math.max(...COMMANDS.map((c) => c.command.length));
|
|
77
|
+
for (const cmd of COMMANDS) {
|
|
78
|
+
const padding = ' '.repeat(maxCmd - cmd.command.length + 2);
|
|
79
|
+
console.log(` ${cmd.command}${padding}${cmd.description}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
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.registerPack = registerPack;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const archiver_1 = __importDefault(require("archiver"));
|
|
10
|
+
const ajv_1 = __importDefault(require("ajv"));
|
|
11
|
+
const child_process_1 = require("child_process");
|
|
12
|
+
function registerPack(program) {
|
|
13
|
+
program
|
|
14
|
+
.command('pack [source]')
|
|
15
|
+
.alias('apps:pack')
|
|
16
|
+
.description('Package the app for publishing')
|
|
17
|
+
.option('--output <path>', 'output directory or filename', './dist')
|
|
18
|
+
.option('--format <fmt>', 'archive format (zip|tar)', 'zip')
|
|
19
|
+
.option('--no-validate', 'skip manifest validation')
|
|
20
|
+
.option('--source <dir>', 'source directory to package', '.')
|
|
21
|
+
.action(async (sourceArg, opts) => {
|
|
22
|
+
const sourceDir = path_1.default.resolve(process.cwd(), sourceArg || opts.source || '.');
|
|
23
|
+
const manifestPath = path_1.default.resolve(sourceDir, 'manifest.json');
|
|
24
|
+
if (!fs_1.default.existsSync(manifestPath)) {
|
|
25
|
+
console.error('manifest.json not found in source directory:', sourceDir);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
if (opts.validate !== false) {
|
|
29
|
+
const schemaPath = path_1.default.resolve(__dirname, '../../schemas/manifest.schema.json');
|
|
30
|
+
if (fs_1.default.existsSync(schemaPath)) {
|
|
31
|
+
const manifest = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
|
|
32
|
+
const schema = JSON.parse(fs_1.default.readFileSync(schemaPath, 'utf8'));
|
|
33
|
+
const ajv = new ajv_1.default({ allowUnionTypes: true, allErrors: true });
|
|
34
|
+
const validate = ajv.compile(schema);
|
|
35
|
+
const valid = validate(manifest);
|
|
36
|
+
if (!valid) {
|
|
37
|
+
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');
|
|
38
|
+
console.error(`Manifest validation failed for ${manifestPath}:\n${errs}`);
|
|
39
|
+
console.error(`Suggestions: ensure 'version' is a valid semver (e.g. 0.1.0), or run 'bdk apps:version <source> --set 0.1.0'. To skip validation: use '--no-validate'.`);
|
|
40
|
+
process.exit(2);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
console.warn('Schema not found; skipping schema validation.');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const pkgPath = path_1.default.resolve(sourceDir, 'package.json');
|
|
48
|
+
if (fs_1.default.existsSync(pkgPath)) {
|
|
49
|
+
try {
|
|
50
|
+
const pkg = JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf8'));
|
|
51
|
+
if (pkg.scripts && pkg.scripts.build) {
|
|
52
|
+
try {
|
|
53
|
+
console.log('Running build script (npm run build)...');
|
|
54
|
+
(0, child_process_1.execSync)('npm run build', { cwd: sourceDir, stdio: 'inherit' });
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
console.warn('Build failed or returned non-zero exit code; continuing to pack.');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
// ignore
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const manifest = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
|
|
66
|
+
const name = manifest.name || path_1.default.basename(sourceDir);
|
|
67
|
+
const version = manifest.version || '0.0.0';
|
|
68
|
+
const fmt = (opts.format || 'zip').toLowerCase();
|
|
69
|
+
const outTarget = opts.output || './dist';
|
|
70
|
+
// Resolve output path relative to source directory, not current working directory
|
|
71
|
+
let outputPath = path_1.default.resolve(sourceDir, outTarget);
|
|
72
|
+
try {
|
|
73
|
+
const stat = fs_1.default.existsSync(outputPath) && fs_1.default.statSync(outputPath);
|
|
74
|
+
if (!stat || stat.isDirectory()) {
|
|
75
|
+
fs_1.default.mkdirSync(outputPath, { recursive: true });
|
|
76
|
+
const filename = `${name}-${version}.${fmt === 'zip' ? 'zip' : 'tar'}`;
|
|
77
|
+
outputPath = path_1.default.join(outputPath, filename);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
fs_1.default.mkdirSync(path_1.default.dirname(outputPath), { recursive: true });
|
|
82
|
+
}
|
|
83
|
+
const output = fs_1.default.createWriteStream(outputPath);
|
|
84
|
+
const archive = (0, archiver_1.default)(fmt === 'zip' ? 'zip' : 'tar', fmt === 'zip' ? {} : { gzip: true });
|
|
85
|
+
output.on('close', () => {
|
|
86
|
+
console.log(`Created archive: ${outputPath} (${archive.pointer()} bytes)`);
|
|
87
|
+
});
|
|
88
|
+
archive.on('warning', (err) => {
|
|
89
|
+
if (err.code === 'ENOENT')
|
|
90
|
+
console.warn(err.message);
|
|
91
|
+
else
|
|
92
|
+
throw err;
|
|
93
|
+
});
|
|
94
|
+
archive.on('error', (err) => {
|
|
95
|
+
throw err;
|
|
96
|
+
});
|
|
97
|
+
archive.pipe(output);
|
|
98
|
+
const preferDirs = ['dist', 'build', 'public'];
|
|
99
|
+
let addedAny = false;
|
|
100
|
+
for (const d of preferDirs) {
|
|
101
|
+
const p = path_1.default.join(sourceDir, d);
|
|
102
|
+
if (fs_1.default.existsSync(p) && fs_1.default.statSync(p).isDirectory()) {
|
|
103
|
+
archive.directory(p, d);
|
|
104
|
+
addedAny = true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (!addedAny) {
|
|
108
|
+
archive.glob('**/*', {
|
|
109
|
+
cwd: sourceDir,
|
|
110
|
+
ignore: ['node_modules/**', '**/node_modules/**']
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
archive.file(manifestPath, { name: 'manifest.json' });
|
|
114
|
+
await archive.finalize();
|
|
115
|
+
});
|
|
116
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
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.registerPublish = registerPublish;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const child_process_1 = require("child_process");
|
|
10
|
+
function registerPublish(program) {
|
|
11
|
+
program
|
|
12
|
+
.command('publish [source]')
|
|
13
|
+
.alias('apps:publish')
|
|
14
|
+
.description('Publish app archive to BoldDesk marketplace')
|
|
15
|
+
.option('--file <path>', 'package file to upload (zip or tar)')
|
|
16
|
+
.option('--token <token>', 'API token for BoldDesk')
|
|
17
|
+
.option('--url <url>', 'publish endpoint URL', 'https://api.bolddesk.com/apps/upload')
|
|
18
|
+
.option('--dry-run', 'do not actually upload, show what would be sent', false)
|
|
19
|
+
.option('--private', 'mark uploaded app as private', false)
|
|
20
|
+
.option('--source <dir>', 'source directory to pack before publishing', '.')
|
|
21
|
+
.action(async (_sourceArg, _opts) => {
|
|
22
|
+
// Command is currently disabled — exit silently without any output
|
|
23
|
+
return;
|
|
24
|
+
/* ── disabled block ─────────────────────────────────────────────── */
|
|
25
|
+
// eslint-disable-next-line no-unreachable
|
|
26
|
+
try {
|
|
27
|
+
let filePath = _opts.file;
|
|
28
|
+
const sourceDir = path_1.default.resolve(process.cwd(), _sourceArg || _opts.source || '.');
|
|
29
|
+
if (!filePath) {
|
|
30
|
+
console.log('No package file given — running `bdk pack` to create archive...');
|
|
31
|
+
try {
|
|
32
|
+
const cliEntry = path_1.default.resolve(__dirname, '../index.js');
|
|
33
|
+
const sourceArg_str = _sourceArg ? `"${_sourceArg}"` : '.';
|
|
34
|
+
(0, child_process_1.execSync)(`node "${cliEntry}" pack ${sourceArg_str} --output ./dist --format zip`, { cwd: process.cwd(), stdio: 'inherit' });
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
console.error('Automatic pack failed; please run `bdk pack` manually or provide --file');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
const manifestPath = path_1.default.join(sourceDir, 'manifest.json');
|
|
41
|
+
if (!fs_1.default.existsSync(manifestPath)) {
|
|
42
|
+
console.error('manifest.json missing; cannot determine archive filename.');
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
const manifest = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
|
|
46
|
+
const name = manifest.name || path_1.default.basename(sourceDir);
|
|
47
|
+
const version = manifest.version || '0.0.0';
|
|
48
|
+
const candidate = path_1.default.resolve(sourceDir, 'dist', `${name}-${version}.zip`);
|
|
49
|
+
if (!fs_1.default.existsSync(candidate)) {
|
|
50
|
+
console.error('Expected archive not found:', candidate);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
filePath = candidate;
|
|
54
|
+
}
|
|
55
|
+
filePath = path_1.default.resolve(process.cwd(), filePath);
|
|
56
|
+
if (!fs_1.default.existsSync(filePath)) {
|
|
57
|
+
console.error('Package file not found:', filePath);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
if (_opts.dryRun) {
|
|
61
|
+
console.log('Dry-run: would upload', filePath, 'to', _opts.url);
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}
|
|
64
|
+
const token = _opts.token || process.env.BOLD_API_TOKEN || process.env.BDK_TOKEN;
|
|
65
|
+
if (!token) {
|
|
66
|
+
console.error('No API token provided. Use --token or set BOLD_API_TOKEN/BDK_TOKEN env var.');
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
const FormData = require('form-data');
|
|
70
|
+
const fetch = require('node-fetch');
|
|
71
|
+
const form = new FormData();
|
|
72
|
+
form.append('file', fs_1.default.createReadStream(filePath));
|
|
73
|
+
form.append('private', _opts.private ? 'true' : 'false');
|
|
74
|
+
console.log(`Uploading ${path_1.default.basename(filePath)} to ${_opts.url} ...`);
|
|
75
|
+
const res = await fetch(_opts.url, {
|
|
76
|
+
method: 'POST',
|
|
77
|
+
headers: {
|
|
78
|
+
Authorization: `Bearer ${token}`,
|
|
79
|
+
...form.getHeaders()
|
|
80
|
+
},
|
|
81
|
+
body: form
|
|
82
|
+
});
|
|
83
|
+
if (!res.ok) {
|
|
84
|
+
const text = await res.text();
|
|
85
|
+
console.error('Upload failed:', res.status, res.statusText, text);
|
|
86
|
+
process.exit(2);
|
|
87
|
+
}
|
|
88
|
+
const json = await res.json();
|
|
89
|
+
console.log('Publish succeeded:', JSON.stringify(json, null, 2));
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
console.error('Publish failed:', err && err.message ? err.message : err);
|
|
93
|
+
process.exit(3);
|
|
94
|
+
}
|
|
95
|
+
/* ── end disabled block ─────────────────────────────────────────── */
|
|
96
|
+
});
|
|
97
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.registerRun = registerRun;
|
|
37
|
+
const http = __importStar(require("http"));
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const child_process_1 = require("child_process");
|
|
41
|
+
function registerRun(program) {
|
|
42
|
+
program
|
|
43
|
+
.command('run [source]')
|
|
44
|
+
.alias('apps:run')
|
|
45
|
+
.description('Run the app locally with live reload')
|
|
46
|
+
.option('--port <number>', 'port to run', '3000')
|
|
47
|
+
.option('--env <env>', 'environment', 'dev')
|
|
48
|
+
.option('--open', 'open browser after start', false)
|
|
49
|
+
.action((sourceArg, opts) => {
|
|
50
|
+
const sourceDir = path.resolve(process.cwd(), sourceArg || '.');
|
|
51
|
+
const port = parseInt(opts.port || '3000', 10);
|
|
52
|
+
const env = opts.env || 'dev';
|
|
53
|
+
// Check if manifest.json exists
|
|
54
|
+
const manifestPath = path.join(sourceDir, 'manifest.json');
|
|
55
|
+
if (!fs.existsSync(manifestPath)) {
|
|
56
|
+
console.error(`manifest.json not found in ${sourceDir}`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
// Create a simple HTTP server
|
|
60
|
+
const server = http.createServer((req, res) => {
|
|
61
|
+
let filePath = req.url || '/';
|
|
62
|
+
if (filePath === '/') {
|
|
63
|
+
filePath = '/assets/iframe.html';
|
|
64
|
+
}
|
|
65
|
+
// Resolve the file path
|
|
66
|
+
const fullPath = path.join(sourceDir, filePath);
|
|
67
|
+
// Security: prevent directory traversal attacks
|
|
68
|
+
const resolvedPath = path.resolve(fullPath);
|
|
69
|
+
const basePath = path.resolve(sourceDir);
|
|
70
|
+
if (!resolvedPath.startsWith(basePath)) {
|
|
71
|
+
res.writeHead(403, { 'Content-Type': 'text/plain' });
|
|
72
|
+
res.end('Forbidden');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
// Try to serve the file
|
|
76
|
+
fs.readFile(resolvedPath, (err, data) => {
|
|
77
|
+
if (err) {
|
|
78
|
+
// If file not found, try to serve index.html or 404
|
|
79
|
+
if (err.code === 'ENOENT') {
|
|
80
|
+
res.writeHead(404, { 'Content-Type': 'text/html' });
|
|
81
|
+
res.end(`
|
|
82
|
+
<!DOCTYPE html>
|
|
83
|
+
<html>
|
|
84
|
+
<head><title>404 Not Found</title></head>
|
|
85
|
+
<body>
|
|
86
|
+
<h1>404 - File Not Found</h1>
|
|
87
|
+
<p>Could not find: ${filePath}</p>
|
|
88
|
+
<p><a href="/">Go back</a></p>
|
|
89
|
+
</body>
|
|
90
|
+
</html>
|
|
91
|
+
`);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
95
|
+
res.end('Internal Server Error');
|
|
96
|
+
}
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// Determine content type
|
|
100
|
+
const ext = path.extname(resolvedPath);
|
|
101
|
+
let contentType = 'application/octet-stream';
|
|
102
|
+
if (ext === '.html')
|
|
103
|
+
contentType = 'text/html; charset=utf-8';
|
|
104
|
+
else if (ext === '.js')
|
|
105
|
+
contentType = 'application/javascript; charset=utf-8';
|
|
106
|
+
else if (ext === '.json')
|
|
107
|
+
contentType = 'application/json; charset=utf-8';
|
|
108
|
+
else if (ext === '.css')
|
|
109
|
+
contentType = 'text/css; charset=utf-8';
|
|
110
|
+
else if (ext === '.svg')
|
|
111
|
+
contentType = 'image/svg+xml';
|
|
112
|
+
else if (ext === '.png')
|
|
113
|
+
contentType = 'image/png';
|
|
114
|
+
else if (ext === '.jpg' || ext === '.jpeg')
|
|
115
|
+
contentType = 'image/jpeg';
|
|
116
|
+
else if (ext === '.gif')
|
|
117
|
+
contentType = 'image/gif';
|
|
118
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
119
|
+
res.end(data);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
server.listen(port, () => {
|
|
123
|
+
console.log(`✅ Dev server started on http://localhost:${port} (env=${env})`);
|
|
124
|
+
console.log(`📁 Serving from: ${sourceDir}`);
|
|
125
|
+
console.log(`📄 Iframe: http://localhost:${port}/assets/iframe.html`);
|
|
126
|
+
console.log(`\nPress Ctrl+C to stop the server\n`);
|
|
127
|
+
// Open browser if --open flag is set
|
|
128
|
+
if (opts.open) {
|
|
129
|
+
const url = `http://localhost:${port}`;
|
|
130
|
+
try {
|
|
131
|
+
const isWindows = process.platform === 'win32';
|
|
132
|
+
const command = isWindows
|
|
133
|
+
? `start ${url}`
|
|
134
|
+
: process.platform === 'darwin'
|
|
135
|
+
? `open ${url}`
|
|
136
|
+
: `xdg-open ${url}`;
|
|
137
|
+
(0, child_process_1.execSync)(command, { stdio: 'ignore' });
|
|
138
|
+
}
|
|
139
|
+
catch (e) {
|
|
140
|
+
// Silently ignore if browser open fails
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
server.on('error', (err) => {
|
|
145
|
+
if (err.code === 'EADDRINUSE') {
|
|
146
|
+
console.error(`❌ Port ${port} is already in use. Try a different port:`);
|
|
147
|
+
console.error(` npx bdk apps:run [source] --port 3001`);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
console.error(`❌ Server error: ${err.message}`);
|
|
151
|
+
}
|
|
152
|
+
process.exit(1);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerTest = registerTest;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
function registerTest(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('test [source]')
|
|
8
|
+
.alias('apps:test')
|
|
9
|
+
.description('Run unit and integration tests')
|
|
10
|
+
.option('--watch', 're-run tests on file changes', false)
|
|
11
|
+
.option('--coverage', 'generate coverage report', false)
|
|
12
|
+
.action((sourceArg, opts) => {
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const cwd = path.resolve(process.cwd(), sourceArg || '.');
|
|
16
|
+
// Check if package.json exists and has test script
|
|
17
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
18
|
+
const hasPkg = (() => {
|
|
19
|
+
try {
|
|
20
|
+
if (!fs.existsSync(pkgPath)) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
const p = require(pkgPath);
|
|
24
|
+
return !!p.scripts && (p.scripts.test || p.scripts['test:watch']);
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
})();
|
|
30
|
+
if (!hasPkg) {
|
|
31
|
+
console.error('No test script found in package.json. Please add a `test` script.');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
if (opts.watch) {
|
|
35
|
+
// prefer npm run test -- --watch if available
|
|
36
|
+
try {
|
|
37
|
+
console.log('Running tests in watch mode...');
|
|
38
|
+
const child = (0, child_process_1.spawn)('npm', ['run', 'test', '--', '--watch'], { stdio: 'inherit', cwd: cwd, shell: true });
|
|
39
|
+
child.on('exit', (code) => process.exit(code || 0));
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
console.error('Failed to start watch tests:', e);
|
|
43
|
+
process.exit(2);
|
|
44
|
+
}
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const args = ['run', 'test'];
|
|
49
|
+
if (opts.coverage) {
|
|
50
|
+
// many projects use `npm run test -- --coverage` to enable coverage
|
|
51
|
+
args.push('--', '--coverage');
|
|
52
|
+
}
|
|
53
|
+
console.log('Running tests...');
|
|
54
|
+
(0, child_process_1.execSync)('npm ' + args.join(' '), { stdio: 'inherit', cwd: cwd });
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
console.error('Tests failed.');
|
|
58
|
+
process.exit(e.status || 1);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.registerValidate = registerValidate;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const package_1 = require("../lib/package");
|
|
39
|
+
const appPath_1 = require("../lib/appPath");
|
|
40
|
+
function registerValidate(program) {
|
|
41
|
+
program
|
|
42
|
+
.command('validate [appDirectory]')
|
|
43
|
+
.alias('apps:validate')
|
|
44
|
+
.description('Validate app structure and manifest')
|
|
45
|
+
.action(async (appDirectory = '.') => {
|
|
46
|
+
try {
|
|
47
|
+
(0, appPath_1.validateAppPath)(appDirectory);
|
|
48
|
+
const appPath = path.resolve(appDirectory);
|
|
49
|
+
await (0, package_1.validatePkg)(appPath);
|
|
50
|
+
console.log('No validation errors');
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
console.error(err && err.message ? err.message : err);
|
|
54
|
+
process.exit(2);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
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.registerVersion = registerVersion;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
function bumpVersion(v, level) {
|
|
10
|
+
const parts = v.split('.').map((s) => parseInt(s, 10));
|
|
11
|
+
while (parts.length < 3)
|
|
12
|
+
parts.push(0);
|
|
13
|
+
if (level === 'patch')
|
|
14
|
+
parts[2] = (parts[2] || 0) + 1;
|
|
15
|
+
if (level === 'minor') {
|
|
16
|
+
parts[1] = (parts[1] || 0) + 1;
|
|
17
|
+
parts[2] = 0;
|
|
18
|
+
}
|
|
19
|
+
if (level === 'major') {
|
|
20
|
+
parts[0] = (parts[0] || 0) + 1;
|
|
21
|
+
parts[1] = 0;
|
|
22
|
+
parts[2] = 0;
|
|
23
|
+
}
|
|
24
|
+
return parts.join('.');
|
|
25
|
+
}
|
|
26
|
+
function registerVersion(program) {
|
|
27
|
+
program
|
|
28
|
+
.command('version [source]')
|
|
29
|
+
.alias('apps:version')
|
|
30
|
+
.description('Show or bump app version')
|
|
31
|
+
.option('--bump <level>', 'patch|minor|major')
|
|
32
|
+
.option('--set <version>', 'set exact version')
|
|
33
|
+
.action((sourceArg, opts) => {
|
|
34
|
+
const manifestPath = path_1.default.resolve(process.cwd(), sourceArg || '.', 'manifest.json');
|
|
35
|
+
if (!fs_1.default.existsSync(manifestPath)) {
|
|
36
|
+
console.error('manifest.json not found at', manifestPath);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
const manifest = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
|
|
40
|
+
let version = manifest.version || '0.0.0';
|
|
41
|
+
if (opts.set) {
|
|
42
|
+
version = opts.set;
|
|
43
|
+
manifest.version = version;
|
|
44
|
+
fs_1.default.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
|
|
45
|
+
console.log(`Set version to ${version}`);
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
if (opts.bump) {
|
|
49
|
+
const level = opts.bump;
|
|
50
|
+
if (!['patch', 'minor', 'major'].includes(level)) {
|
|
51
|
+
console.error('Invalid bump level. Use patch, minor or major.');
|
|
52
|
+
process.exit(2);
|
|
53
|
+
}
|
|
54
|
+
version = bumpVersion(version, level);
|
|
55
|
+
manifest.version = version;
|
|
56
|
+
fs_1.default.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
|
|
57
|
+
console.log(`Bumped version to ${version}`);
|
|
58
|
+
process.exit(0);
|
|
59
|
+
}
|
|
60
|
+
console.log(version);
|
|
61
|
+
});
|
|
62
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const create_1 = require("./commands/create");
|
|
6
|
+
const run_1 = require("./commands/run");
|
|
7
|
+
const validate_1 = require("./commands/validate");
|
|
8
|
+
const pack_1 = require("./commands/pack");
|
|
9
|
+
const test_1 = require("./commands/test");
|
|
10
|
+
const version_1 = require("./commands/version");
|
|
11
|
+
const publish_1 = require("./commands/publish");
|
|
12
|
+
const help_1 = require("./commands/help");
|
|
13
|
+
const pkg = require('../package.json');
|
|
14
|
+
const program = new commander_1.Command();
|
|
15
|
+
program.name('bdk').version(pkg.version).description('BoldDesk Developer Kit CLI');
|
|
16
|
+
(0, create_1.registerCreate)(program);
|
|
17
|
+
(0, run_1.registerRun)(program);
|
|
18
|
+
(0, validate_1.registerValidate)(program);
|
|
19
|
+
(0, pack_1.registerPack)(program);
|
|
20
|
+
(0, test_1.registerTest)(program);
|
|
21
|
+
(0, version_1.registerVersion)(program);
|
|
22
|
+
(0, publish_1.registerPublish)(program);
|
|
23
|
+
(0, help_1.registerHelp)(program);
|
|
24
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,17 @@
|
|
|
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.validateAppPath = validateAppPath;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
function validateAppPath(appPath) {
|
|
10
|
+
const resolved = path_1.default.resolve(appPath);
|
|
11
|
+
if (!fs_1.default.existsSync(resolved))
|
|
12
|
+
throw new Error(`App path does not exist: ${resolved}`);
|
|
13
|
+
const manifest = path_1.default.join(resolved, 'manifest.json');
|
|
14
|
+
if (!fs_1.default.existsSync(manifest))
|
|
15
|
+
throw new Error(`manifest.json not found in ${resolved}`);
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
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
|
+
}
|
|
133
|
+
// ── Rule: enablePermission / enableReadPermission / enableWritePermission ──
|
|
134
|
+
// If enablePermission is true → at least one of enableReadPermission or enableWritePermission must be true.
|
|
135
|
+
// If enablePermission is false → both enableReadPermission and enableWritePermission must be false.
|
|
136
|
+
if ('enablePermission' in settings) {
|
|
137
|
+
const enablePermission = settings.enablePermission;
|
|
138
|
+
const enableReadPermission = settings.enableReadPermission;
|
|
139
|
+
const enableWritePermission = settings.enableWritePermission;
|
|
140
|
+
if (enablePermission === true) {
|
|
141
|
+
if (enableReadPermission !== true && enableWritePermission !== true) {
|
|
142
|
+
errors.push("settings: 'enablePermission' is true, so at least one of " +
|
|
143
|
+
"'enableReadPermission' or 'enableWritePermission' must also be true.");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
else if (enablePermission === false) {
|
|
147
|
+
if (enableReadPermission === true) {
|
|
148
|
+
errors.push("settings: 'enableReadPermission' must be false when 'enablePermission' is false.");
|
|
149
|
+
}
|
|
150
|
+
if (enableWritePermission === true) {
|
|
151
|
+
errors.push("settings: 'enableWritePermission' must be false when 'enablePermission' is false.");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// If 'oauth' property is present in manifest → settings.fields must have at least one type "OAuth" field
|
|
156
|
+
if ('oauth' in manifest) {
|
|
157
|
+
const fields = Array.isArray(settings.fields) ? settings.fields : [];
|
|
158
|
+
const hasOAuthField = fields.some((field) => field && typeof field.type === 'string' && field.type.toLowerCase() === 'oauth');
|
|
159
|
+
if (!hasOAuthField) {
|
|
160
|
+
errors.push("settings.fields must contain at least one field with type 'OAuth' when oauth is configured. " +
|
|
161
|
+
'Example: { "key": "token", "type": "OAuth", "secure": true, "required": true, ... }');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (errors.length > 0) {
|
|
166
|
+
throw new Error(`Validation errors in ${manifestPath}:\n - ${errors.join('\n - ')}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "test-bdk-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "test CLI",
|
|
5
5
|
"author": "@BoldDesk",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"npmRegistry": "https://registry.npmjs.org",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"scripts": {
|
|
12
|
-
"build": "
|
|
12
|
+
"build": "tsc -p tsconfig.json",
|
|
13
|
+
"prepare": "tsc -p tsconfig.json",
|
|
13
14
|
"dev": "ts-node src/index.ts",
|
|
14
15
|
"start": "node dist/index.js",
|
|
15
16
|
"test": "jest"
|