@scout9/app 1.0.0-alpha.0.1.9 → 1.0.0-alpha.0.1.91
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -0
- package/dist/{index-92deaa5f.cjs → exports-212ef6be.cjs} +46636 -4591
- package/dist/index.cjs +58 -15
- package/dist/{multipart-parser-090f08a9.cjs → multipart-parser-54a3ab5f.cjs} +13 -7
- package/dist/spirits-3b603262.cjs +1218 -0
- package/dist/spirits.cjs +9 -0
- package/dist/testing-tools.cjs +48 -0
- package/package.json +37 -8
- package/src/cli.js +162 -69
- package/src/core/config/agents.js +300 -7
- package/src/core/config/entities.js +58 -28
- package/src/core/config/index.js +37 -15
- package/src/core/config/project.js +160 -6
- package/src/core/config/workflow.js +13 -12
- package/src/core/data.js +27 -0
- package/src/core/index.js +386 -137
- package/src/core/sync.js +71 -0
- package/src/core/templates/Dockerfile +22 -0
- package/src/core/templates/app.js +453 -0
- package/src/core/templates/project-files.js +36 -0
- package/src/core/templates/template-package.json +13 -0
- package/src/exports.js +21 -17
- package/src/platform.js +189 -33
- package/src/public.d.ts.text +330 -0
- package/src/report.js +117 -0
- package/src/runtime/client/api.js +56 -159
- package/src/runtime/client/config.js +60 -11
- package/src/runtime/client/entity.js +19 -6
- package/src/runtime/client/index.js +5 -3
- package/src/runtime/client/message.js +13 -3
- package/src/runtime/client/platform.js +86 -0
- package/src/runtime/client/{agent.js → users.js} +35 -3
- package/src/runtime/client/utils.js +10 -9
- package/src/runtime/client/workflow.js +132 -9
- package/src/runtime/entry.js +2 -2
- package/src/testing-tools/dev.js +373 -0
- package/src/testing-tools/index.js +1 -0
- package/src/testing-tools/mocks.js +37 -5
- package/src/testing-tools/spirits.js +530 -0
- package/src/utils/audio-buffer.js +16 -0
- package/src/utils/audio-type.js +27 -0
- package/src/utils/configs/agents.js +68 -0
- package/src/utils/configs/entities.js +145 -0
- package/src/utils/configs/project.js +23 -0
- package/src/utils/configs/workflow.js +47 -0
- package/src/utils/file-type.js +569 -0
- package/src/utils/file.js +164 -0
- package/src/utils/glob.js +30 -0
- package/src/utils/image-buffer.js +23 -0
- package/src/utils/image-type.js +39 -0
- package/src/utils/index.js +1 -0
- package/src/utils/is-svg.js +37 -0
- package/src/utils/logger.js +111 -0
- package/src/utils/module.js +14 -25
- package/src/utils/project-templates.js +191 -0
- package/src/utils/project.js +387 -0
- package/src/utils/video-type.js +29 -0
- package/types/index.d.ts +7588 -206
- package/types/index.d.ts.map +97 -22
- package/dist/index-1b8d7dd2.cjs +0 -49555
- package/dist/index-2ccb115e.cjs +0 -49514
- package/dist/index-66b06a30.cjs +0 -49549
- package/dist/index-bc029a1d.cjs +0 -49528
- package/dist/index-d9a93523.cjs +0 -49527
- package/dist/multipart-parser-1508046a.cjs +0 -413
- package/dist/multipart-parser-7007403a.cjs +0 -413
- package/dist/multipart-parser-70c32c1d.cjs +0 -413
- package/dist/multipart-parser-71dec101.cjs +0 -413
- package/dist/multipart-parser-f15bf2e0.cjs +0 -414
- package/src/public.d.ts +0 -209
package/src/core/index.js
CHANGED
|
@@ -1,33 +1,39 @@
|
|
|
1
1
|
import archiver from 'archiver';
|
|
2
2
|
import { globSync } from 'glob';
|
|
3
|
-
import { exec } from 'node:child_process';
|
|
4
3
|
import fss from 'node:fs';
|
|
5
4
|
import fs from 'node:fs/promises';
|
|
6
5
|
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import fetch, { FormData } from 'node-fetch';
|
|
8
|
-
import {
|
|
9
|
-
import { checkVariableType, requireProjectFile } from '../utils/index.js';
|
|
8
|
+
import { Configuration, Scout9Api } from '@scout9/admin';
|
|
9
|
+
import { checkVariableType, ProgressLogger, requireProjectFile } from '../utils/index.js';
|
|
10
|
+
import decompress from 'decompress';
|
|
11
|
+
import { loadUserPackageJson } from './config/project.js';
|
|
12
|
+
import { platformApi } from './data.js';
|
|
13
|
+
import { syncData } from './sync.js';
|
|
14
|
+
import ProjectFiles from '../utils/project.js';
|
|
15
|
+
import { projectTemplates } from '../utils/project-templates.js';
|
|
16
|
+
import { WorkflowEventSchema } from '../runtime/index.js';
|
|
17
|
+
import { logUserValidationError } from '../report.js';
|
|
10
18
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return new Promise((resolve, reject) => {
|
|
14
|
-
exec('npm run build', {cwd}, (error, stdout, stderr) => {
|
|
15
|
-
if (error) {
|
|
16
|
-
console.error(`Build failed: ${error.message}`);
|
|
17
|
-
return reject(error);
|
|
18
|
-
}
|
|
19
|
-
console.log('Build successful');
|
|
20
|
-
return resolve(undefined);
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
}
|
|
19
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
20
|
+
const __dirname = path.dirname(__filename);
|
|
24
21
|
|
|
25
22
|
|
|
26
23
|
function zipDirectory(source, out) {
|
|
27
|
-
const archive = archiver('
|
|
24
|
+
const archive = archiver('tar', {
|
|
25
|
+
gzip: true,
|
|
26
|
+
gzipOptions: {level: 9}
|
|
27
|
+
// zlib: {level: 9}
|
|
28
|
+
});
|
|
28
29
|
const stream = fss.createWriteStream(out);
|
|
29
30
|
|
|
31
|
+
|
|
30
32
|
return new Promise((resolve, reject) => {
|
|
33
|
+
setTimeout(() => {
|
|
34
|
+
reject(new Error('Zip timed out'));
|
|
35
|
+
}, 10 * 1000);
|
|
36
|
+
|
|
31
37
|
archive
|
|
32
38
|
.directory(source, false)
|
|
33
39
|
.on('error', err => reject(err))
|
|
@@ -40,65 +46,196 @@ function zipDirectory(source, out) {
|
|
|
40
46
|
|
|
41
47
|
async function deployZipDirectory(zipFilePath, config) {
|
|
42
48
|
const form = new FormData();
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
if (!fss.existsSync(zipFilePath)) {
|
|
50
|
+
throw new Error(`Missing required zip file ${zipFilePath}`);
|
|
51
|
+
}
|
|
52
|
+
const blob = new Blob([await fs.readFile(zipFilePath)], {type: 'application/gzip'});
|
|
53
|
+
form.set('file', blob, path.basename(zipFilePath), {contentType: 'application/gzip'});
|
|
54
|
+
// const blob = new Blob([await fs.readFile(zipFilePath)], {type: 'application/zip'});
|
|
55
|
+
// form.set('file', blob, path.basename(zipFilePath), {contentType: 'application/zip'});
|
|
45
56
|
form.set('config', JSON.stringify(config));
|
|
46
57
|
|
|
47
58
|
// @TODO append signature secret header
|
|
48
|
-
const
|
|
59
|
+
// const url = 'http://localhost:3000/api/b/platform/upload';
|
|
60
|
+
const url = 'https://us-central1-jumpstart.cloudfunctions.net/v1-utils-platform-upload';
|
|
61
|
+
const response = await platformApi(url, {
|
|
49
62
|
method: 'POST',
|
|
50
63
|
body: form,
|
|
51
64
|
headers: {
|
|
52
|
-
'Authorization': process.env.SCOUT9_API_KEY || ''
|
|
65
|
+
'Authorization': 'Bearer ' + process.env.SCOUT9_API_KEY || ''
|
|
53
66
|
}
|
|
54
67
|
});
|
|
55
68
|
if (!response.ok) {
|
|
56
|
-
throw new Error(
|
|
69
|
+
throw new Error(`${url} responded with ${response.status}: ${response.statusText}`);
|
|
57
70
|
}
|
|
58
71
|
|
|
59
|
-
|
|
60
|
-
return true;
|
|
72
|
+
return response;
|
|
61
73
|
}
|
|
62
74
|
|
|
63
75
|
async function downloadAndUnpackZip(outputDir) {
|
|
64
|
-
const downloadLocalResponse = await
|
|
65
|
-
`https://pocket-guide.vercel.app/api/b/platform/download`,
|
|
66
|
-
{
|
|
67
|
-
headers: {
|
|
68
|
-
'Authorization': process.env.SCOUT9_API_KEY || ''
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
);
|
|
76
|
+
const downloadLocalResponse = await platformApi(`https://scout9.com/api/b/platform/download`);
|
|
72
77
|
if (!downloadLocalResponse.ok) {
|
|
73
78
|
throw new Error(`Error downloading project file ${downloadLocalResponse.statusText}`);
|
|
74
79
|
}
|
|
75
80
|
|
|
76
81
|
try {
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
|
|
82
|
+
const arrayBuffer = await downloadLocalResponse.arrayBuffer();
|
|
83
|
+
const outputPath = path.resolve(outputDir, 'build');
|
|
84
|
+
// Convert ArrayBuffer to Buffer
|
|
85
|
+
await decompress(Buffer.from(arrayBuffer), outputPath);
|
|
86
|
+
|
|
87
|
+
console.log('Files unpacked successfully at ' + outputPath);
|
|
88
|
+
|
|
89
|
+
return outputPath;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error('Error unpacking file:', error);
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Builds the app to a specified location
|
|
98
|
+
* @param {string} cwd
|
|
99
|
+
* @param {string} src
|
|
100
|
+
* @param {string} dest
|
|
101
|
+
* @param {import('../runtime/client/config.js').IScout9ProjectBuildConfig} config
|
|
102
|
+
* @returns {Promise<void>}
|
|
103
|
+
*/
|
|
104
|
+
async function buildApp(cwd, src, dest, config) {
|
|
105
|
+
// Remove existing directory
|
|
106
|
+
await fs.rm(dest, {recursive: true, force: true});
|
|
107
|
+
// Ensures directory exists
|
|
108
|
+
await fs.mkdir(dest, {recursive: true});
|
|
109
|
+
|
|
110
|
+
const root = src.replace(process.cwd(), '');
|
|
111
|
+
|
|
112
|
+
const copyDirectory = async (source, destination, permittedExtensions) => {
|
|
113
|
+
await fs.mkdir(destination, {recursive: true});
|
|
80
114
|
|
|
81
|
-
|
|
115
|
+
const dir = await fs.readdir(source, {withFileTypes: true});
|
|
116
|
+
for (const dirent of dir) {
|
|
117
|
+
if (dirent.name.includes('.spec') || dirent.name.includes('.test')) {
|
|
118
|
+
continue; // Skip this file or directory
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const sourcePath = path.resolve(source, dirent.name);
|
|
122
|
+
const destinationPath = path.resolve(destination, dirent.name);
|
|
123
|
+
|
|
124
|
+
if (dirent.isDirectory()) {
|
|
125
|
+
await copyDirectory(sourcePath, destinationPath, permittedExtensions);
|
|
126
|
+
} else {
|
|
127
|
+
if (permittedExtensions && permittedExtensions.length > 0) {
|
|
128
|
+
const fileExtension = path.extname(dirent.name).replace('.', '');
|
|
129
|
+
if (!permittedExtensions.includes(fileExtension)) {
|
|
130
|
+
// console.log(`Skipping ${dirent.name} because it's not in the permitted extensions list`);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (sourcePath.includes('entities/agents/index') || sourcePath.includes('entities/agents/config')) {
|
|
135
|
+
// Special case where we have to paste the agent raw data to avoid uploading large audio/txt files
|
|
136
|
+
await fs.writeFile(destinationPath, projectTemplates.entities.agents(config.agents, path.extname(destinationPath)));
|
|
137
|
+
} else if (sourcePath.includes(`${root}/index`)) {
|
|
138
|
+
await fs.writeFile(destinationPath, projectTemplates.root(config, path.extname(destinationPath)));
|
|
139
|
+
} else {
|
|
140
|
+
await fs.copyFile(sourcePath, destinationPath);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const srcDir = path.resolve(cwd, src);
|
|
149
|
+
const appTemplateJsPath = path.resolve(__dirname, './templates/app.js');
|
|
150
|
+
const templatePackagePath = path.resolve(__dirname, './templates/template-package.json');
|
|
151
|
+
|
|
152
|
+
// Copy src directory
|
|
153
|
+
await copyDirectory(srcDir, path.resolve(dest, 'src'), ['js', 'ts', 'cjs', 'mjs', 'json', 'env']);
|
|
154
|
+
|
|
155
|
+
// Copy user target package.json, first load app.js dependencies/scripts and append to target package.json
|
|
156
|
+
const packageTemplate = JSON.parse(await fs.readFile(new URL(templatePackagePath, import.meta.url), 'utf-8'));
|
|
157
|
+
const {pkg} = await loadUserPackageJson({cwd});
|
|
158
|
+
pkg.dependencies = {...pkg.dependencies, ...packageTemplate.dependencies};
|
|
159
|
+
pkg.scripts.start = 'node app.js';
|
|
160
|
+
await fs.writeFile(path.resolve(dest, 'package.json'), JSON.stringify(pkg, null, 2));
|
|
161
|
+
|
|
162
|
+
// Copy app.js
|
|
163
|
+
await fs.copyFile(appTemplateJsPath, path.resolve(dest, 'app.js'));
|
|
164
|
+
|
|
165
|
+
// Copy .env file
|
|
166
|
+
await fs.copyFile(path.resolve(cwd, '.env'), path.resolve(dest, '.env'));
|
|
167
|
+
|
|
168
|
+
// Copy config.js - redact any sensitive information // @TODO use security encoder
|
|
169
|
+
const redactedConfig = {
|
|
170
|
+
...config
|
|
171
|
+
};
|
|
172
|
+
for (const agent of redactedConfig.agents) {
|
|
173
|
+
// agent.forwardEmail = 'REDACTED';
|
|
174
|
+
// agent.forwardPhone = 'REDACTED';
|
|
175
|
+
// agent.programmableEmail = 'REDACTED';
|
|
176
|
+
// agent.programmablePhoneNumber = 'REDACTED';
|
|
177
|
+
}
|
|
178
|
+
await fs.writeFile(path.resolve(dest, 'config.js'), `export default ${JSON.stringify(redactedConfig, null, 2)}`);
|
|
179
|
+
|
|
180
|
+
// Copy Dockerfile (if it exists)
|
|
181
|
+
const dockerfile = path.resolve(cwd, './Dockerfile');
|
|
182
|
+
if (fss.existsSync(dockerfile)) {
|
|
183
|
+
await fs.copyFile(dockerfile, path.resolve(dest, 'Dockerfile'));
|
|
184
|
+
} else {
|
|
185
|
+
await fs.copyFile(path.resolve(__dirname, './templates/Dockerfile'), path.resolve(dest, 'Dockerfile'));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (process.env.DEV_MODE === 'true') {
|
|
189
|
+
// Copy dev app folder
|
|
190
|
+
// const clientFolder = path.resolve(__dirname, './templates/public');
|
|
191
|
+
// await copyDirectory(clientFolder, path.resolve(dest, 'public'));
|
|
192
|
+
|
|
193
|
+
// @TODO migrate this into a package
|
|
194
|
+
const devAppFolder = path.resolve(dest, 'public');
|
|
195
|
+
const exists = fss.existsSync(devAppFolder);
|
|
196
|
+
if (!exists) {
|
|
197
|
+
await downloadDevApp(devAppFolder, process.env.DEV_APP_VERSION || 'default');
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
82
201
|
|
|
83
|
-
|
|
202
|
+
// For dev server, downloads the dev app if it doesn't exist
|
|
203
|
+
async function downloadDevApp(destination, version) {
|
|
204
|
+
const url = `https://scout9.com/api/b/platform/dev?v=${version}`;
|
|
205
|
+
const downloadLocalResponse = await platformApi(url);
|
|
206
|
+
if (!downloadLocalResponse.ok) {
|
|
207
|
+
throw new Error(`Error downloading scout9 dev app project file ${downloadLocalResponse.statusText}`);
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
const arrayBuffer = await downloadLocalResponse.arrayBuffer();
|
|
211
|
+
await decompress(Buffer.from(arrayBuffer), destination);
|
|
212
|
+
console.log('Dev server unpacked successfully at ' + destination);
|
|
84
213
|
} catch (error) {
|
|
85
214
|
console.error('Error unpacking file:', error);
|
|
86
215
|
throw error;
|
|
87
216
|
}
|
|
217
|
+
|
|
88
218
|
}
|
|
89
219
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
220
|
+
/**
|
|
221
|
+
* @param {Object} [options]
|
|
222
|
+
* @param {string} [options.cwd]
|
|
223
|
+
* @param {string} [options.src]
|
|
224
|
+
* @param {boolean} [options.ignoreAppRequire]
|
|
225
|
+
* @returns {Promise<{app: *, fileName: string, exe: string, filePath: string}>}
|
|
226
|
+
*/
|
|
227
|
+
export async function getApp({cwd = process.cwd(), src = 'src', ignoreAppRequire = false} = {}) {
|
|
228
|
+
const indexTsPath = path.resolve(cwd, src, 'index.ts');
|
|
229
|
+
const indexJsPath = path.resolve(cwd, src, 'index.js');
|
|
93
230
|
let exe = '';
|
|
94
231
|
if (fss.existsSync(indexTsPath)) {
|
|
95
232
|
exe = path.extname(indexTsPath);
|
|
96
233
|
} else if (fss.existsSync(indexJsPath)) {
|
|
97
234
|
exe = path.extname(indexJsPath);
|
|
98
235
|
} else {
|
|
99
|
-
throw new Error(`Missing main project entry file ${
|
|
236
|
+
throw new Error(`Missing main project entry file ${src}/index.{js|ts}`);
|
|
100
237
|
}
|
|
101
|
-
const filePath = path.resolve(cwd,
|
|
238
|
+
const filePath = path.resolve(cwd, src, `app${exe}`);
|
|
102
239
|
let app;
|
|
103
240
|
if (!ignoreAppRequire) {
|
|
104
241
|
app = await requireProjectFile(filePath).then(mod => mod.default);
|
|
@@ -110,146 +247,258 @@ export async function getApp({cwd = process.cwd(), folder = 'src', ignoreAppRequ
|
|
|
110
247
|
return {app, exe, filePath, fileName: `app${exe}`};
|
|
111
248
|
}
|
|
112
249
|
|
|
250
|
+
/**
|
|
251
|
+
* For each agent, lists out each method of contact
|
|
252
|
+
* @returns {Promise<T>}
|
|
253
|
+
*/
|
|
254
|
+
export async function getAgentContacts() {
|
|
255
|
+
const configuration = new Configuration({
|
|
256
|
+
apiKey: process.env.SCOUT9_API_KEY
|
|
257
|
+
});
|
|
258
|
+
const scout9 = new Scout9Api(configuration);
|
|
259
|
+
return Promise.all([
|
|
260
|
+
scout9.agents(),
|
|
261
|
+
platformApi('https://us-central1-jumpstart.cloudfunctions.net/v1-utils-auth', {
|
|
262
|
+
method: 'GET',
|
|
263
|
+
headers: {
|
|
264
|
+
'Authorization': 'Bearer ' + process.env.SCOUT9_API_KEY || ''
|
|
265
|
+
}
|
|
266
|
+
}).then((res) => {
|
|
267
|
+
console.log(res);
|
|
268
|
+
return res.json();
|
|
269
|
+
})
|
|
270
|
+
])
|
|
271
|
+
.then((res) => {
|
|
272
|
+
console.log(res[1]);
|
|
273
|
+
return res[0].data.map((agent) => {
|
|
274
|
+
let output = `\n\t${agent.firstName || 'Agent'}${agent.lastName ? ' ' + agent.lastName : ''}:\n`;
|
|
275
|
+
if (agent.programmablePhoneNumber) {
|
|
276
|
+
output += `\t\t- ${agent.programmablePhoneNumber}\n`;
|
|
277
|
+
}
|
|
278
|
+
if (agent.programmableEmail) {
|
|
279
|
+
output += `\t\t- ${agent.programmableEmail}\n`;
|
|
280
|
+
}
|
|
281
|
+
output += `\t\t- https://scout9.com/${res[1].id}/${agent.id || agent.$id}\n`;
|
|
282
|
+
return output;
|
|
283
|
+
}).join('\n');
|
|
284
|
+
})
|
|
285
|
+
.catch((err) => {
|
|
286
|
+
err.message = `Error fetching agents: ${err.message}`;
|
|
287
|
+
throw err;
|
|
288
|
+
});
|
|
289
|
+
}
|
|
113
290
|
|
|
114
291
|
/**
|
|
115
292
|
* Runs a given project container from scout9 to given environment
|
|
293
|
+
* Runs the project in a container
|
|
294
|
+
*
|
|
295
|
+
* @param {import('../runtime/client/workflow.js').IWorkflowEvent} event - every workflow receives an event object
|
|
296
|
+
* @param {Object} options
|
|
297
|
+
* @param {string} options.eventSource - the source path of the event
|
|
298
|
+
* @returns {Promise<import('../runtime/client/workflow.js').IWorkflowResponse>}
|
|
116
299
|
*/
|
|
117
|
-
export async function run(event, {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
ignoreAppRequire: true
|
|
300
|
+
export async function run(event, {eventSource} = {}) {
|
|
301
|
+
const result = WorkflowEventSchema.safeParse(event);
|
|
302
|
+
if (!result.success) {
|
|
303
|
+
logUserValidationError(result.error, eventSource);
|
|
304
|
+
throw result.error;
|
|
305
|
+
}
|
|
306
|
+
const configuration = new Configuration({
|
|
307
|
+
apiKey: process.env.SCOUT9_API_KEY
|
|
126
308
|
});
|
|
309
|
+
const scout9 = new Scout9Api(configuration);
|
|
310
|
+
const response = await scout9.runPlatform(event)
|
|
311
|
+
.catch((err) => {
|
|
312
|
+
err.message = `Error running platform: ${err.message}`;
|
|
313
|
+
throw err;
|
|
314
|
+
});
|
|
315
|
+
return response.data;
|
|
316
|
+
}
|
|
127
317
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
318
|
+
/**
|
|
319
|
+
* Calls scout9 backend to get project config file
|
|
320
|
+
*/
|
|
321
|
+
export async function runConfig() {
|
|
322
|
+
if (!process.env.SCOUT9_API_KEY) {
|
|
323
|
+
throw new Error('Missing SCOUT9_API_KEY, please add your Scout9 API key to your .env file');
|
|
324
|
+
}
|
|
325
|
+
const configuration = new Configuration({
|
|
326
|
+
apiKey: process.env.SCOUT9_API_KEY
|
|
327
|
+
});
|
|
328
|
+
const scout9 = new Scout9Api(configuration);
|
|
329
|
+
const response = await scout9.config()
|
|
330
|
+
.catch((err) => {
|
|
331
|
+
err.message = `Error running platform: ${err.message}`;
|
|
332
|
+
throw err;
|
|
333
|
+
});
|
|
334
|
+
return response.data;
|
|
132
335
|
}
|
|
133
336
|
|
|
337
|
+
|
|
134
338
|
/**
|
|
135
339
|
* Builds a local project
|
|
340
|
+
* @param {{cwd: string; src: string; dest: string; logger: ProgressLogger; mode: string;}} - build options
|
|
341
|
+
* @param {import('../runtime/client/').IScout9ProjectBuildConfig} config
|
|
342
|
+
* @returns {messages: string[]}
|
|
136
343
|
*/
|
|
137
|
-
export async function build({
|
|
344
|
+
export async function build({
|
|
345
|
+
cwd = process.cwd(),
|
|
346
|
+
src = './src',
|
|
347
|
+
dest = '/tmp/project',
|
|
348
|
+
logger = new ProgressLogger(),
|
|
349
|
+
mode
|
|
350
|
+
} = {}, config) {
|
|
351
|
+
const messages = [];
|
|
138
352
|
// 1. Lint: Run validation checks
|
|
139
353
|
|
|
140
354
|
// Check if app looks good
|
|
141
|
-
await getApp({cwd,
|
|
355
|
+
await getApp({cwd, src});
|
|
142
356
|
|
|
143
357
|
// Check if workflows look good
|
|
144
|
-
console.log('@TODO check if workflows are properly written');
|
|
358
|
+
// console.log('@TODO check if workflows are properly written');
|
|
359
|
+
|
|
360
|
+
// 2. Build app
|
|
361
|
+
await buildApp(cwd, src, dest, config);
|
|
145
362
|
|
|
146
363
|
// 2. Build code in user's project
|
|
147
|
-
await runNpmRunBuild({cwd});
|
|
364
|
+
// const buildDir = await runNpmRunBuild({cwd, src: src});
|
|
365
|
+
// const buildPath = buildDir.split('/');
|
|
366
|
+
|
|
367
|
+
// Check if directory "build" exists
|
|
368
|
+
// if (!fss.existsSync(buildDir)) {
|
|
369
|
+
// throw new Error(`Missing required "${buildPath[buildPath.length - 1]}" directory, make sure your build script outputs to a "${buildPath[buildPath.length - 1]}" directory or modify your scout9 config`);
|
|
370
|
+
// }
|
|
148
371
|
|
|
149
372
|
|
|
150
373
|
// 3. Remove unnecessary files
|
|
151
|
-
const files = globSync(path.resolve(cwd,
|
|
374
|
+
// const files = globSync(path.resolve(cwd, `${dest}/**/*(*.test.*|*.spec.*)`), {cwd, absolute: true});
|
|
375
|
+
const files = globSync(`${dest}/**/*(*.test.*|*.spec.*)`, {cwd, absolute: true});
|
|
152
376
|
for (const file of files) {
|
|
153
377
|
await fs.unlink(file);
|
|
154
378
|
}
|
|
155
379
|
|
|
156
380
|
// 3. Run tests
|
|
157
381
|
// console.log('@TODO run tests');
|
|
382
|
+
return {messages};
|
|
158
383
|
}
|
|
159
384
|
|
|
385
|
+
|
|
160
386
|
/**
|
|
161
387
|
* Deploys a local project to scout9
|
|
388
|
+
* @param {{cwd: string; src: string, dest: string}} - build options
|
|
389
|
+
* @param {Scout9ProjectBuildConfig} config
|
|
390
|
+
* @return {Promise<{deploy: Object, contacts: any}>}
|
|
162
391
|
*/
|
|
163
|
-
export async function deploy(
|
|
164
|
-
|
|
165
|
-
|
|
392
|
+
export async function deploy(
|
|
393
|
+
{cwd = process.cwd(), src = './src', dest = '/tmp/project', logger = new ProgressLogger()},
|
|
394
|
+
config
|
|
395
|
+
) {
|
|
166
396
|
|
|
167
|
-
|
|
397
|
+
// Check if app looks good
|
|
398
|
+
await getApp({cwd, src});
|
|
168
399
|
|
|
400
|
+
await buildApp(cwd, src, dest, config);
|
|
401
|
+
logger.info(`App built ${dest}`);
|
|
402
|
+
|
|
403
|
+
await test({cwd, src, dest, logger}, config);
|
|
404
|
+
|
|
405
|
+
const destPaths = dest.split('/');
|
|
406
|
+
const zipFilePath = path.resolve(dest, `${destPaths[destPaths.length - 1]}.tar.gz`);
|
|
407
|
+
if (fss.existsSync(zipFilePath)) {
|
|
408
|
+
fss.unlinkSync(zipFilePath);
|
|
409
|
+
}
|
|
410
|
+
await zipDirectory(dest, zipFilePath);
|
|
411
|
+
|
|
412
|
+
logger.info('Project zipped successfully.', zipFilePath);
|
|
413
|
+
|
|
414
|
+
logger.log(`Uploading ${zipFilePath} to Scout9...`);
|
|
169
415
|
const response = await deployZipDirectory(zipFilePath, config);
|
|
170
|
-
|
|
416
|
+
|
|
417
|
+
if (response.status !== 200) {
|
|
418
|
+
logger.error(`Error uploading project to Scout9 ${response.status} - ${response.statusText}`);
|
|
419
|
+
} else {
|
|
420
|
+
logger.info(`File sent successfully: ${response.status} - ${response.statusText}`);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
logger.log(`Fetching agent contacts...`);
|
|
424
|
+
const contacts = await getAgentContacts();
|
|
425
|
+
logger.info(`Fetched agent contacts`);
|
|
426
|
+
|
|
427
|
+
// Remove temporary directory
|
|
428
|
+
// logger.log(`Cleaning up ${dest}...`);
|
|
429
|
+
// await fs.rmdir(dest, { recursive: true });
|
|
430
|
+
// logger.info(`Cleaned up ${dest}`);
|
|
431
|
+
|
|
432
|
+
return {deploy: response, contacts};
|
|
171
433
|
}
|
|
172
434
|
|
|
173
435
|
/**
|
|
174
|
-
*
|
|
175
|
-
* @param {
|
|
176
|
-
* @param {
|
|
177
|
-
* @returns {Promise<void>}
|
|
436
|
+
* Tests a local project to scout9 by running a dummy parse command with the project's local entities
|
|
437
|
+
* @param {{cwd: string; src: string, dest: string}} - build options
|
|
438
|
+
* @param {import('../runtime/client/config.js').IScout9ProjectBuildConfig} config
|
|
178
439
|
*/
|
|
179
|
-
export async function
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
config.agents = agents.reduce((accumulator, agent) => {
|
|
189
|
-
// Check if agent already exists
|
|
190
|
-
const existingAgentIndex = accumulator.findIndex(a => a.id === agent.id);
|
|
191
|
-
if (existingAgentIndex === -1) {
|
|
192
|
-
accumulator.push(agent);
|
|
193
|
-
} else {
|
|
194
|
-
// Merge agent
|
|
195
|
-
accumulator[existingAgentIndex] = {
|
|
196
|
-
...accumulator[existingAgentIndex],
|
|
197
|
-
...agent
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
return accumulator;
|
|
201
|
-
}, config.agents);
|
|
202
|
-
|
|
203
|
-
config.entities = entities.reduce((accumulator, entity) => {
|
|
204
|
-
// Check if agent already exists
|
|
205
|
-
const existingEntityIndex = accumulator.findIndex(a => a.id === entity.id);
|
|
206
|
-
if (existingEntityIndex === -1) {
|
|
207
|
-
accumulator.push(entity);
|
|
208
|
-
} else {
|
|
209
|
-
// Merge agent
|
|
210
|
-
accumulator[existingEntityIndex] = {
|
|
211
|
-
...accumulator[existingEntityIndex],
|
|
212
|
-
...entity
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
return accumulator;
|
|
216
|
-
}, config.entities);
|
|
217
|
-
|
|
218
|
-
// Write to src/agents
|
|
219
|
-
const paths = globSync(path.resolve(cwd, `${folder}/entities/agents/{index,config}.{ts,js}`));
|
|
220
|
-
if (paths.length === 0) {
|
|
221
|
-
throw new Error(`Missing required agents entity file, rerun "scout9 sync" to fix`);
|
|
440
|
+
export async function test(
|
|
441
|
+
{cwd = process.cwd(), src = './src', dest = '/tmp/project', logger = new ProgressLogger()},
|
|
442
|
+
config
|
|
443
|
+
) {
|
|
444
|
+
|
|
445
|
+
const testableEntities = config.entities.filter(e => e?.definitions?.length > 0 || e?.training?.length > 0);
|
|
446
|
+
if (testableEntities.length === 0) {
|
|
447
|
+
throw new Error(
|
|
448
|
+
'No testable entities found - make sure you have at least one entity with definitions or training data - learn more at https://scout9.com/docs');
|
|
222
449
|
}
|
|
223
|
-
|
|
224
|
-
|
|
450
|
+
|
|
451
|
+
const tests = testableEntities.reduce((accumulator, entity) => accumulator += (entity?.tests || []).length, 0);
|
|
452
|
+
|
|
453
|
+
if (tests === 0) {
|
|
454
|
+
logger.warn('No tests found for any entities, skipping test run');
|
|
455
|
+
return;
|
|
225
456
|
}
|
|
226
|
-
const [filePath] = paths;
|
|
227
457
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
458
|
+
// @TODO format errors
|
|
459
|
+
logger.log(`Running ${tests} entity test points...`);
|
|
460
|
+
await new Scout9Api(new Configuration({apiKey: process.env.SCOUT9_API_KEY || ''})).parse({
|
|
461
|
+
message: 'Dummy message to parse',
|
|
462
|
+
language: 'en',
|
|
463
|
+
entities: testableEntities
|
|
464
|
+
});
|
|
235
465
|
}
|
|
236
|
-
`);
|
|
237
466
|
|
|
238
|
-
for (const entity of config.entities) {
|
|
239
|
-
const {entity: _entity, entities, ...rest} = entity;
|
|
240
|
-
const fileContent = `
|
|
241
467
|
/**
|
|
242
|
-
*
|
|
243
|
-
* @
|
|
468
|
+
*
|
|
469
|
+
* @param {{cwd: string; src: string; projectFiles: ProjectFiles; logger: ProgressLogger}} options
|
|
470
|
+
* @param {import('../runtime/client/config.js').IScout9ProjectBuildConfig} config
|
|
471
|
+
* @returns {Promise<{success: boolean; config: import('../runtime/client/config.js').IScout9ProjectBuildConfig}>}
|
|
244
472
|
*/
|
|
245
|
-
export
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
473
|
+
export async function sync({
|
|
474
|
+
cwd = process.cwd(), src = 'src',
|
|
475
|
+
projectFiles = new ProjectFiles({src, autoSave: true, cwd}),
|
|
476
|
+
logger = new ProgressLogger()} = {},
|
|
477
|
+
config
|
|
478
|
+
) {
|
|
479
|
+
if (!process.env.SCOUT9_API_KEY) {
|
|
480
|
+
throw new Error('Missing required environment variable "SCOUT9_API_KEY"');
|
|
252
481
|
}
|
|
253
|
-
|
|
254
|
-
|
|
482
|
+
logger.log('Fetching project data...');
|
|
483
|
+
// Grabs saved server data on Scout9
|
|
484
|
+
config = await syncData(config);
|
|
485
|
+
logger.log(`Syncing project`);
|
|
486
|
+
|
|
487
|
+
// Uses saved server data to sync project with local data
|
|
488
|
+
await projectFiles.sync(config, (message, type) => {
|
|
489
|
+
switch (type) {
|
|
490
|
+
case 'info':
|
|
491
|
+
logger.info(message);
|
|
492
|
+
break;
|
|
493
|
+
case 'warn':
|
|
494
|
+
logger.warn(message);
|
|
495
|
+
break;
|
|
496
|
+
case 'error':
|
|
497
|
+
logger.error(message);
|
|
498
|
+
break;
|
|
499
|
+
default:
|
|
500
|
+
logger.info(message);
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
return {success: true, config};
|
|
255
504
|
}
|