@kumologica/sdk 3.0.27 → 3.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+ class KumologicaError extends Error {
4
+ constructor(message, code, options = {}) {
5
+ super(message);
6
+ this.code = code;
7
+ this.decoratedMessage = options.decoratedMessage;
8
+ }
9
+ }
10
+
11
+ Object.defineProperty(KumologicaError.prototype, 'name', {
12
+ value: KumologicaError.name,
13
+ configurable: true,
14
+ writable: true,
15
+ });
16
+
17
+ module.exports = KumologicaError;
package/cli/cli.js CHANGED
@@ -1,6 +1,9 @@
1
+ const chalk = require('chalk');
2
+
1
3
  require('yargs/yargs')(process.argv.slice(2))
2
4
  .commandDir('commands')
3
5
  .demandCommand()
4
- .help()
6
+ .help('help', 'Show usage information & exit')
7
+ .alias('help', 'h')
5
8
  // .epilog("copyright 2021")
6
9
  .argv
@@ -0,0 +1,42 @@
1
+ const pathlib = require('path');
2
+ const { prompt, Select } = require('enquirer');
3
+ const { logNotice, logError } = require('../../utils/logger');
4
+ const { downloadTemplateFromRepo, listAvailableTemplates } = require('../../utils/download-template-from-repo');
5
+
6
+ async function createProjectIteratively(){
7
+ const availableTemplates = await listAvailableTemplates();
8
+ // Project name
9
+ const projectNameResponse = await prompt({
10
+ type: 'input',
11
+ name: 'projectName',
12
+ message: 'Enter the name for the project'
13
+ });
14
+ let aProjectName = projectNameResponse.projectName;
15
+
16
+ // Target Path
17
+ const directoryResponse = await prompt({
18
+ type: 'input',
19
+ name: 'directory',
20
+ message: 'Enter the path where the project will be created '
21
+ });
22
+ let aProjectPath = directoryResponse.directory;
23
+
24
+ // Template name
25
+ const promptTemplate = new Select({
26
+ name: 'template',
27
+ message: 'Pick one of the available templates',
28
+ choices: availableTemplates
29
+ });
30
+
31
+ let aTemplateName = await promptTemplate.run();
32
+
33
+ try{
34
+ await downloadTemplateFromRepo(undefined, aTemplateName, aProjectPath, aProjectName);
35
+ logNotice(`Successfully created project: ${pathlib.join(aProjectPath, aProjectName)}`)
36
+ } catch (err){
37
+ logError(`Error found while creating project due to: ${err.message}`)
38
+ }
39
+ }
40
+
41
+
42
+ module.exports = createProjectIteratively;
@@ -0,0 +1,5 @@
1
+ const createProjectIteratively = require('./create-project-iteratively');
2
+
3
+ module.exports = {
4
+ createProjectIteratively
5
+ }
@@ -1,32 +1,61 @@
1
1
  const pathlib = require('path');
2
- const { logNotice, logError } = require('./utils');
3
2
  const { codegen } = require('@kumologica/builder');
4
3
 
5
- exports.command = "create"
4
+ const { logNotice, logError } = require('../utils/logger');
5
+ const { downloadTemplateFromRepo } = require('../utils/download-template-from-repo');
6
+ const { createProjectIteratively } = require('./create-commands');
7
+
8
+ exports.command = 'create [options]'
6
9
  exports.builder = (yargs) => {
7
10
  yargs.option(`path`, {
8
- describe: "The path where project will be created.",
9
- demandOption: "The path is required.",
10
- type: 'string',
11
- alias: 'p',
12
- nargs: 1
13
- });
11
+ describe: "The project path where the project will be created",
12
+ // demandOption: "The path is required.",
13
+ type: 'string',
14
+ alias: 'p',
15
+ nargs: 1
16
+ })
17
+ yargs.option(`template`, {
18
+ describe: `Template for the project`,
19
+ type: 'string',
20
+ alias: 't',
21
+ nargs: 1
22
+ })
14
23
  }
15
24
 
16
- exports.desc = 'Create a kumologica project'
17
- exports.handler = ({ path }) => {
25
+
26
+ exports.desc = 'Create a kumologica project. Do not pass any options to create iteratively.'
27
+ exports.handler = async ({ path, template }) => {
18
28
  const { version } = require('../../package.json');
19
- try{
20
- codegen.generateBoilerplateProject({
21
- basePath :process.cwd(),
22
- projectName: path,
23
- runtimeVersion: version });
24
-
25
- logNotice(`Successfully created project: ${ pathlib.join(process.cwd(), path)}`);
26
- } catch(e) {
27
- logError(e.toString());
28
- process.exit(1);
29
+
30
+ if (!path && !template) {
31
+ await createProjectIteratively();
32
+ } else if (!path && template) {
33
+ try{
34
+ await downloadTemplateFromRepo(undefined, template, process.cwd(), template);
35
+ logNotice(`Successfully created project: ${ pathlib.join(process.cwd(), template)}`);
36
+ }catch(e){
37
+ logError(e.message);
38
+ }
39
+ } else if (path && !template){
40
+ try{
41
+ codegen.generateBoilerplateProject({
42
+ basePath :process.cwd(),
43
+ projectName: path,
44
+ runtimeVersion: version });
45
+ logNotice(`Successfully created project: ${ pathlib.join(process.cwd(), path)}`);
46
+ }catch(e){
47
+ logError(e.message);
48
+ }
49
+ } else if (path && template) {
50
+ path = pathlib.resolve(path);
51
+ let projectPath = pathlib.dirname(path);
52
+ let projectName = pathlib.basename(path);
53
+ try{
54
+ await downloadTemplateFromRepo(undefined, template, projectPath, projectName);
55
+ logNotice(`Successfully created project: ${path}`);
56
+ }catch(e){
57
+ logError(e.message);
58
+ }
29
59
  }
30
-
31
-
60
+
32
61
  }
@@ -0,0 +1,24 @@
1
+ const { listAvailableTemplates } = require('../utils/download-template-from-repo');
2
+ const { logNotice, logError, logInfo } = require('../utils/logger');
3
+
4
+ exports.command = 'list-templates'
5
+ exports.desc = 'List of available templates'
6
+ exports.handler = async function (argv) {
7
+ try {
8
+ logInfo('List of available templates:');
9
+ let templates = await readerFriendly(listAvailableTemplates())
10
+ logNotice(templates);
11
+ }catch(e){
12
+ logError(e.message);
13
+ }
14
+ }
15
+
16
+ // Utils
17
+ async function readerFriendly(templates) {
18
+ let result = [];
19
+ templates = await templates;
20
+ templates.forEach(t => {
21
+ result.push(`"${t}"`);
22
+ });
23
+ return result.join(',')
24
+ }
@@ -1,5 +1,5 @@
1
1
  const path = require('path');
2
- const { logError } = require('./utils');
2
+ const { logError } = require('../utils/logger');
3
3
 
4
4
  const rootPath = path.join(__dirname, '..', '..',);
5
5
 
@@ -5,7 +5,6 @@ const path = require('path');
5
5
  const chalk = require('chalk');
6
6
  const { error } = require('./util/output');
7
7
  const got = require('got');
8
- const { logError } = require('../utils');
9
8
  const log = console.log;
10
9
 
11
10
 
@@ -1,14 +1,15 @@
1
1
  const chalk = require('chalk');
2
2
  const { Select } = require('enquirer');
3
3
  const fs = require('fs');
4
- const { logError } = require('./utils');
5
4
  const path = require('path');
6
5
  const wcmatch = require('wildcard-match');
7
6
 
8
7
  const { AppServer } = require('@kumologica/runtime');
9
- const { TestSuiteController } = require('./test-utils/TestSuiteController');
10
8
  const { codegen } = require('@kumologica/builder');
11
9
 
10
+ const { TestSuiteController } = require('./test-utils/TestSuiteController');
11
+ const { logError } = require('../utils/logger');
12
+
12
13
  const log = console.log;
13
14
  const APP_SERVER_PORT = 1990;
14
15
 
@@ -0,0 +1,338 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const URL = require('url');
5
+ const BbPromise = require('bluebird');
6
+ const fse = require('fs-extra');
7
+ const qs = require('querystring');
8
+ const fetch = require('node-fetch');
9
+ const spawn = require('child-process-ext/spawn');
10
+ const renameService = require('./rename-service').renameService;
11
+ const KumologicaError = require('../KumologicaError');
12
+ const copyDirContentsSync = require('./fs/copy-dir-contents-sync');
13
+ const dirExistsSync = require('./fs/dir-exists-sync');
14
+ const walkDirSync = require('./fs/walk-dir-sync');
15
+ const { getTmpDirPath, getBaseTmpDirPath } = require('./fs/get-tmp-dir-path');
16
+
17
+
18
+ const OFFICAL_TEMPLATES_REPO = 'https://github.com/KumologicaHQ/kumologica-templates.git';
19
+ /**
20
+ * Returns directory path
21
+ * @param {Number} length
22
+ * @param {Array} parts
23
+ * @returns {String} directory path
24
+ */
25
+ function getPathDirectory(length, parts) {
26
+ if (!parts) {
27
+ return '';
28
+ }
29
+ return parts.slice(length).filter(Boolean).join(path.sep);
30
+ }
31
+
32
+ /**
33
+ * Validates URL
34
+ * @param {Object} url
35
+ * @param {String} hostname
36
+ * @param {String} service
37
+ * @param {String} owner
38
+ * @param {String} repo
39
+ */
40
+ function validateUrl({ url, hostname, service, owner, repo }) {
41
+ // validate if given url is a valid url
42
+ if (url.hostname !== hostname || !owner || !repo) {
43
+ const errorMessage = `The URL must be a valid ${service} URL in the following format: https://${hostname}/serverless/serverless`;
44
+ throw new KumologicaError(errorMessage, 'INVALID_TEMPLATE_URL');
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Check if the URL is pointing to a Git repository
50
+ * @param {String} url
51
+ */
52
+ function isPlainGitURL(url) {
53
+ return (url.startsWith('https') || url.startsWith('git@')) && url.endsWith('.git');
54
+ }
55
+
56
+ /**
57
+ * @param {Object} url
58
+ * @returns {Object}
59
+ */
60
+ function parseGitHubURL(url) {
61
+ const pathLength = 4;
62
+ const parts = url.pathname.split('/');
63
+ const isSubdirectory = parts.length > pathLength;
64
+ const owner = parts[1];
65
+ const repo = parts[2];
66
+ const branch = isSubdirectory ? parts[pathLength] : 'master';
67
+ const isGitHubEnterprise = url.hostname !== 'github.com';
68
+
69
+ if (!isGitHubEnterprise) {
70
+ // validate if given url is a valid GitHub url
71
+ validateUrl({ url, hostname: 'github.com', service: 'GitHub', owner, repo });
72
+ }
73
+
74
+ const downloadUrl = `https://${
75
+ isGitHubEnterprise ? url.hostname : 'github.com'
76
+ }/${owner}/${repo}/archive/${branch}.zip`;
77
+
78
+ return {
79
+ owner,
80
+ repo,
81
+ branch,
82
+ downloadUrl,
83
+ isSubdirectory,
84
+ pathToDirectory: getPathDirectory(pathLength + 1, parts),
85
+ username: url.username || '',
86
+ password: url.password || '',
87
+ };
88
+ }
89
+
90
+ /**
91
+ * @param {Object} url
92
+ * @returns {Object}
93
+ */
94
+ function parseBitbucketURL(url) {
95
+ const pathLength = 4;
96
+ const parts = url.pathname.split('/');
97
+ const isSubdirectory = parts.length > pathLength;
98
+ const owner = parts[1];
99
+ const repo = parts[2];
100
+
101
+ const query = qs.parse(url.query);
102
+ const branch = 'at' in query ? query.at : 'master';
103
+
104
+ // validate if given url is a valid Bitbucket url
105
+ validateUrl({ url, hostname: 'bitbucket.org', service: 'Bitbucket', owner, repo });
106
+
107
+ const downloadUrl = `https://bitbucket.org/${owner}/${repo}/get/${branch}.zip`;
108
+
109
+ return {
110
+ owner,
111
+ repo,
112
+ branch,
113
+ downloadUrl,
114
+ isSubdirectory,
115
+ pathToDirectory: getPathDirectory(pathLength + 1, parts),
116
+ username: url.username || '',
117
+ password: url.password || '',
118
+ };
119
+ }
120
+
121
+ function parseBitbucketServerURL(url) {
122
+ const pathLength = 9;
123
+ const parts = url.pathname.split('/');
124
+ const isSubdirectory = parts.length > pathLength;
125
+ const owner = parts[5];
126
+ const repo = parts[7];
127
+
128
+ const query = qs.parse(url.query);
129
+ const branch = 'at' in query ? decodeURIComponent(query.at) : 'master';
130
+
131
+ const downloadUrl = `${url.protocol}//${url.hostname}/rest/api/latest/projects/${owner}/repos/${repo}/archive${url.search}&format=zip`;
132
+
133
+ return {
134
+ owner,
135
+ repo,
136
+ branch,
137
+ downloadUrl,
138
+ isSubdirectory,
139
+ pathToDirectory: getPathDirectory(pathLength + 1, parts),
140
+ username: url.username || '',
141
+ password: url.password || '',
142
+ };
143
+ }
144
+
145
+ /**
146
+ * Call `/rest/api/1.0/application-properties` to retrieve server info
147
+ * @param {Object} url
148
+ * @returns {Boolean}
149
+ */
150
+ async function retrieveBitbucketServerInfo(url) {
151
+ const versionInfoPath = `${url.protocol}//${url.hostname}/rest/api/1.0/application-properties`;
152
+
153
+ return fetch(versionInfoPath)
154
+ .then((resp) => resp.json())
155
+ .then((body) => body.displayName === 'Bitbucket');
156
+ }
157
+
158
+ /**
159
+ * @param {Object} url
160
+ * @returns {Object}
161
+ */
162
+ function parseGitlabURL(url) {
163
+ const pathLength = 4;
164
+ const parts = url.pathname.split('/');
165
+ const isSubdirectory = parts.length > pathLength;
166
+ const owner = parts[1];
167
+ const repo = parts[2];
168
+
169
+ const branch = isSubdirectory ? parts[pathLength] : 'master';
170
+
171
+ // validate if given url is a valid GitLab url
172
+ validateUrl({ url, hostname: 'gitlab.com', service: 'Bitbucket', owner, repo });
173
+
174
+ const downloadUrl = `https://gitlab.com/${owner}/${repo}/-/archive/${branch}/${repo}-${branch}.zip`;
175
+
176
+ return {
177
+ owner,
178
+ repo,
179
+ branch,
180
+ downloadUrl,
181
+ isSubdirectory,
182
+ pathToDirectory: getPathDirectory(pathLength + 1, parts),
183
+ username: url.username || '',
184
+ password: url.password || '',
185
+ };
186
+ }
187
+
188
+ /**
189
+ * Parses a URL which points to a plain Git repository
190
+ * such as https://example.com/jdoe/project.git
191
+ *
192
+ * @param {String} url
193
+ * @returns {Object}
194
+ */
195
+ function parsePlainGitURL(url) {
196
+ const branch = 'master';
197
+ const downloadUrl = url;
198
+ const isSubdirectory = false;
199
+ const repo = url.match(/.+\/(.+)\.git/)[1];
200
+ return {
201
+ repo,
202
+ branch,
203
+ downloadUrl,
204
+ isSubdirectory,
205
+ username: url.username || '',
206
+ password: url.password || '',
207
+ };
208
+ }
209
+
210
+ /**
211
+ * Parse URL and call the appropriate adaptor
212
+ *
213
+ * @param {string} inputUrl
214
+ * @throws {KumologicaError}
215
+ * @returns {Promise}
216
+ */
217
+ async function parseRepoURL(inputUrl) {
218
+ return new BbPromise((resolve, reject) => {
219
+ if (!inputUrl) {
220
+ return reject(new KumologicaError('URL is required', 'MISSING_TEMPLATE_URL'));
221
+ }
222
+
223
+ const url = URL.parse(inputUrl.replace(/\/$/, ''));
224
+ if (url.auth) {
225
+ const [username, password] = url.auth.split(':');
226
+ url.username = username;
227
+ url.password = password;
228
+ }
229
+
230
+ // check if url parameter is a valid url
231
+ if (!url.host && !url.href.startsWith('git@')) {
232
+ return reject(new KumologicaError('The URL you passed is not valid', 'INVALID_TEMPLATE_URL'));
233
+ }
234
+
235
+ if (isPlainGitURL(url.href)) {
236
+ return resolve(parsePlainGitURL(inputUrl));
237
+ } else if (url.hostname === 'github.com' || url.hostname.indexOf('github.') !== -1) {
238
+ return resolve(parseGitHubURL(url));
239
+ } else if (url.hostname === 'bitbucket.org') {
240
+ return resolve(parseBitbucketURL(url));
241
+ } else if (url.hostname === 'gitlab.com') {
242
+ return resolve(parseGitlabURL(url));
243
+ }
244
+
245
+ const msg =
246
+ 'The URL you passed is not one of the valid providers: "GitHub", "GitHub Entreprise", "Bitbucket", "Bitbucket Server" or "GitLab".';
247
+ const err = new KumologicaError(msg, 'INVALID_TEMPLATE_PROVIDER');
248
+ // test if it's a private bitbucket server
249
+ return retrieveBitbucketServerInfo(url)
250
+ .then((isBitbucket) => {
251
+ if (!isBitbucket) {
252
+ return reject(err);
253
+ }
254
+
255
+ // build download URL
256
+ return resolve(parseBitbucketServerURL(url));
257
+ })
258
+ .catch(() => reject(err));
259
+ });
260
+ }
261
+
262
+ /**
263
+ * Downloads the content of the template into the projectName directory
264
+ * @param {string} [repoUrl] - the git repository
265
+ * @param {string} [templateName] - the name of the project (optional), in case we are only interested in a particular directory within the repo
266
+ * @param {string} projectName - the local project name / directory to be created
267
+ * @returns {Promise}
268
+ */
269
+ async function downloadTemplateFromRepo(repoUrl, templateName, projectPath, projectName) {
270
+ try{
271
+ repoUrl = repoUrl || OFFICAL_TEMPLATES_REPO;
272
+ const repoInformation = await parseRepoURL(repoUrl);
273
+
274
+ const tempBaseDirectory = getBaseTmpDirPath();
275
+ // clean up temp directory
276
+ fse.removeSync(tempBaseDirectory);
277
+ const tempRepoDirectory = getTmpDirPath();
278
+ // const { username, password } = repoInformation;
279
+
280
+ const renamed = templateName !== projectName;
281
+
282
+ // Source project directory
283
+ const srcProjectDir = templateName? path.join(tempRepoDirectory, templateName) : tempRepoDirectory;
284
+
285
+ // Target project directory
286
+ const targetProjectDir = path.join(projectPath, projectName);
287
+
288
+ if (dirExistsSync(targetProjectDir)) {
289
+ const errorMessage = `A project already exist in path: "${targetProjectDir}".`;
290
+ throw new KumologicaError(errorMessage, 'TARGET_FOLDER_ALREADY_EXISTS');
291
+ }
292
+
293
+ if (isPlainGitURL(repoUrl)) {
294
+ return spawn('git', ['clone', repoUrl, tempRepoDirectory]).then(() => {
295
+ try {
296
+ copyDirContentsSync(srcProjectDir, targetProjectDir);
297
+ fse.removeSync(tempBaseDirectory);
298
+ if (renamed) renameService(targetProjectDir, projectName);
299
+ }catch(err){
300
+ throw new KumologicaError(`Template name: "${templateName}" does not exist. Run "kl list-templates" to see list of available templates.`);
301
+ }
302
+ return "";
303
+ });
304
+ } else {
305
+ throw new KumologicaError(`Git repo URL is incorrect`);
306
+ }
307
+ }catch(err){
308
+ throw new KumologicaError(err.message);
309
+ }
310
+ }
311
+
312
+ async function listAvailableTemplates() {
313
+ try{
314
+ const tempBaseDirectory = getBaseTmpDirPath();
315
+ const tempRepoDirectory = getTmpDirPath();
316
+
317
+ await parseRepoURL(OFFICAL_TEMPLATES_REPO);
318
+
319
+ const listTemplates = spawn('git', ['clone', OFFICAL_TEMPLATES_REPO, tempRepoDirectory]).then(() => {
320
+ try {
321
+ const projects = walkDirSync(tempRepoDirectory);
322
+ fse.removeSync(tempBaseDirectory);
323
+ return projects;
324
+ }catch(err){
325
+ throw new KumologicaError(err.message);
326
+ }
327
+ });
328
+ // ListTemplates: ["t1", "t2"]
329
+ return listTemplates;
330
+ } catch(err){
331
+ throw new KumologicaError(err.message);
332
+ }
333
+ }
334
+
335
+ module.exports = {
336
+ downloadTemplateFromRepo,
337
+ listAvailableTemplates,
338
+ };
@@ -0,0 +1,12 @@
1
+ const { readerFriendly } = require('../commands/utils');
2
+ const { downloadTemplateFromRepo, listAvailableTemplates } = require('./download-template-from-repo');
3
+
4
+ (async()=> {
5
+ try{
6
+ // await downloadTemplateFromRepo(undefined, 'aws-api-helloworldWRONG', 'kk');
7
+ const result = await readerFriendly(listAvailableTemplates());
8
+ console.log(result);
9
+ } catch(err){
10
+ console.log('Error creating template due to: ', err.message);
11
+ }
12
+ })();
@@ -0,0 +1,119 @@
1
+ // This file is mostly adapted from https://github.com/kevva/download repository
2
+
3
+ // License of the original module - https://github.com/kevva/download/blob/master/license
4
+
5
+ // MIT License
6
+
7
+ // Copyright (c) Kevin Mårtensson <kevinmartensson@gmail.com> (github.com/kevva)
8
+
9
+ // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
10
+
11
+ // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
12
+
13
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
14
+
15
+ 'use strict';
16
+
17
+ const fsp = require('fs').promises;
18
+ const path = require('path');
19
+ const { URL } = require('url');
20
+ const contentDisposition = require('content-disposition');
21
+ const archiveType = require('archive-type');
22
+ const decompress = require('decompress');
23
+ const filenamify = require('filenamify');
24
+ const getStream = require('get-stream');
25
+ const got = require('got');
26
+ const makeDir = require('make-dir');
27
+ const pEvent = require('p-event');
28
+ const FileType = require('file-type');
29
+ const extName = require('ext-name');
30
+
31
+ const filenameFromPath = (res) => path.basename(new URL(res.requestUrl).pathname);
32
+
33
+ const getExtFromMime = (res) => {
34
+ const header = res.headers['content-type'];
35
+
36
+ if (!header) {
37
+ return null;
38
+ }
39
+
40
+ const exts = extName.mime(header);
41
+
42
+ if (exts.length !== 1) {
43
+ return null;
44
+ }
45
+
46
+ return exts[0].ext;
47
+ };
48
+
49
+ const getFilename = async (res, data) => {
50
+ const header = res.headers['content-disposition'];
51
+
52
+ if (header) {
53
+ const parsed = contentDisposition.parse(header);
54
+
55
+ if (parsed.parameters && parsed.parameters.filename) {
56
+ return parsed.parameters.filename;
57
+ }
58
+ }
59
+
60
+ let filename = filenameFromPath(res);
61
+
62
+ if (!path.extname(filename)) {
63
+ const ext = ((await FileType.fromBuffer(data)) || {}).ext || getExtFromMime(res);
64
+
65
+ if (ext) {
66
+ filename = `${filename}.${ext}`;
67
+ }
68
+ }
69
+
70
+ return filename;
71
+ };
72
+
73
+ module.exports = (uri, output, opts) => {
74
+ if (typeof output === 'object') {
75
+ opts = output;
76
+ output = null;
77
+ }
78
+
79
+ opts = Object.assign(
80
+ {
81
+ https: {
82
+ rejectUnauthorized: process.env.npm_config_strict_ssl !== 'false',
83
+ },
84
+ responseType: 'buffer',
85
+ },
86
+ opts
87
+ );
88
+
89
+ const stream = got.stream(uri, opts);
90
+
91
+ const promise = pEvent(stream, 'response')
92
+ .then((res) => {
93
+ const encoding = opts.responseType === 'buffer' ? 'buffer' : opts.encoding;
94
+ return Promise.all([getStream(stream, { encoding }), res]);
95
+ })
96
+ .then(async (result) => {
97
+ const [data, res] = result;
98
+
99
+ if (!output) {
100
+ return opts.extract && archiveType(data) ? decompress(data, opts) : data;
101
+ }
102
+
103
+ const filename = opts.filename || filenamify(await getFilename(res, data));
104
+ const outputFilepath = path.join(output, filename);
105
+
106
+ if (opts.extract && archiveType(data)) {
107
+ return decompress(data, path.dirname(outputFilepath), opts);
108
+ }
109
+
110
+ return makeDir(path.dirname(outputFilepath))
111
+ .then(() => fsp.writeFile(outputFilepath, data))
112
+ .then(() => data);
113
+ });
114
+
115
+ stream.then = promise.then.bind(promise);
116
+ stream.catch = promise.catch.bind(promise);
117
+
118
+ return stream;
119
+ };
@@ -0,0 +1,15 @@
1
+ 'use strict';
2
+
3
+ const fse = require('fs-extra');
4
+
5
+ const isNotSymbolicLink = (src) => !fse.lstatSync(src).isSymbolicLink();
6
+
7
+ function copyDirContentsSync(srcDir, destDir, { noLinks = false } = {}) {
8
+ const copySyncOptions = {
9
+ dereference: true,
10
+ filter: noLinks ? isNotSymbolicLink : null,
11
+ };
12
+ fse.copySync(srcDir, destDir, copySyncOptions);
13
+ }
14
+
15
+ module.exports = copyDirContentsSync;
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const archiver = require('archiver');
6
+ const BbPromise = require('bluebird');
7
+ const walkDirSync = require('./walk-dir-sync');
8
+
9
+ async function createZipFile(srcDirPath, outputFilePath) {
10
+ const files = walkDirSync(srcDirPath).map((file) => ({
11
+ input: file,
12
+ output: file.replace(path.join(srcDirPath, path.sep), ''),
13
+ }));
14
+
15
+ return new BbPromise((resolve, reject) => {
16
+ const output = fs.createWriteStream(outputFilePath);
17
+ const archive = archiver('zip', {
18
+ zlib: { level: 9 },
19
+ });
20
+
21
+ output.on('open', () => {
22
+ archive.pipe(output);
23
+
24
+ files.forEach((file) => {
25
+ // TODO: update since this is REALLY slow
26
+ if (fs.lstatSync(file.input).isFile()) {
27
+ archive.append(fs.createReadStream(file.input), { name: file.output });
28
+ }
29
+ });
30
+
31
+ archive.finalize();
32
+ });
33
+
34
+ archive.on('error', (err) => reject(err));
35
+ output.on('close', () => resolve(outputFilePath));
36
+ });
37
+ }
38
+
39
+ module.exports = createZipFile;
@@ -0,0 +1,14 @@
1
+ 'use strict';
2
+
3
+ const fse = require('fs-extra');
4
+
5
+ function dirExistsSync(dirPath) {
6
+ try {
7
+ const stats = fse.statSync(dirPath);
8
+ return stats.isDirectory();
9
+ } catch (e) {
10
+ return false;
11
+ }
12
+ }
13
+
14
+ module.exports = dirExistsSync;
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+ const fsp = require('fs').promises;
4
+
5
+ async function dirExists(path) {
6
+ return fsp.lstat(path).then(
7
+ (stats) => stats.isDirectory(),
8
+ (error) => {
9
+ if (error.code === 'ENOENT') {
10
+ return false;
11
+ }
12
+ throw error;
13
+ }
14
+ );
15
+ }
16
+
17
+ module.exports = dirExists;
@@ -0,0 +1,14 @@
1
+ 'use strict';
2
+
3
+ const fse = require('fs-extra');
4
+
5
+ function fileExistsSync(filePath) {
6
+ try {
7
+ const stats = fse.statSync(filePath);
8
+ return stats.isFile();
9
+ } catch (e) {
10
+ return false;
11
+ }
12
+ }
13
+
14
+ module.exports = fileExistsSync;
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ const fsp = require('fs').promises;
4
+
5
+ async function fileExists(filePath) {
6
+ return fsp
7
+ .lstat(filePath)
8
+ .then((stats) => stats.isFile())
9
+ .catch(() => false);
10
+ }
11
+
12
+ module.exports = fileExists;
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ const os = require('os');
4
+ const path = require('path');
5
+ const crypto = require('crypto');
6
+
7
+
8
+ function getBaseTmpDirPath() {
9
+ return path.join(os.tmpdir(), 'tmp-templates-kumologica');
10
+ }
11
+
12
+ function getTmpDirPath() {
13
+ return path.join(
14
+ getBaseTmpDirPath(),
15
+ crypto.randomBytes(8).toString('hex')
16
+ );
17
+ }
18
+
19
+ module.exports = {
20
+ getBaseTmpDirPath,
21
+ getTmpDirPath
22
+ }
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+
3
+ const jc = require('json-cycle');
4
+ const yaml = require('js-yaml');
5
+ const _ = require('lodash');
6
+ // const cloudformationSchema = require('@serverless/utils/cloudformation-schema');
7
+
8
+ const loadYaml = (contents, options) => {
9
+ let data;
10
+ let error;
11
+ try {
12
+ data = yaml.load(contents.toString(), options || {});
13
+ } catch (exception) {
14
+ error = exception;
15
+ }
16
+ return { data, error };
17
+ };
18
+
19
+ function parse(filePath, contents) {
20
+ // Auto-parse JSON
21
+ if (filePath.endsWith('.json') || filePath.endsWith('.tfstate')) {
22
+ return jc.parse(contents);
23
+ } else if (filePath.endsWith('.yml') || filePath.endsWith('.yaml')) {
24
+ const options = {
25
+ filename: filePath,
26
+ };
27
+ let result = loadYaml(contents.toString(), options);
28
+ // if (result.error && result.error.name === 'YAMLException') {
29
+ // _.merge(options, { schema: cloudformationSchema });
30
+ // result = loadYaml(contents.toString(), options);
31
+ // }
32
+ if (result.error) {
33
+ throw result.error;
34
+ }
35
+ return result.data;
36
+ }
37
+ return contents.toString().trim();
38
+ }
39
+
40
+ module.exports = parse;
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+
3
+ const fse = require('fs-extra');
4
+ const parse = require('./parse');
5
+
6
+ function readFileSync(filePath) {
7
+ const contents = fse.readFileSync(filePath);
8
+ return parse(filePath, contents);
9
+ }
10
+
11
+ module.exports = readFileSync;
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ const fsp = require('fs').promises;
4
+ const parse = require('./parse');
5
+
6
+ async function readFile(filePath) {
7
+ return fsp.readFile(filePath, 'utf8').then((contents) => parse(filePath, contents));
8
+ }
9
+
10
+ module.exports = readFile;
@@ -0,0 +1,58 @@
1
+ 'use strict';
2
+
3
+ const fsp = require('fs').promises;
4
+ const fse = require('fs-extra');
5
+ const crypto = require('crypto');
6
+ const path = require('path');
7
+
8
+ /**
9
+ * Given a path that designates a location of a file on another device,
10
+ * will return a path to file in the same folder, but with a unique name
11
+ * to avoid collisions.
12
+ *
13
+ * @param {*} destPath the path to the final location of the file being moved
14
+ * @returns a unique path to a file on the same device as the file being moved
15
+ */
16
+ const generateTemporaryPathOnDestinationDevice = (destPath) => {
17
+ const dirName = path.dirname(destPath);
18
+ // Generate a unique destination file name to get the file onto the destination filesystem
19
+ const tempName = path.basename(destPath) + crypto.randomBytes(8).toString('hex');
20
+ return path.join(dirName, tempName);
21
+ };
22
+
23
+ /**
24
+ * Allows a file to be moved (renamed) even across filesystem boundaries.
25
+ *
26
+ * If the rename fails because the file is getting renamed across file system boundaries,
27
+ * the file is first copied to the destination file system under a temporary name,
28
+ * and then renamed from there.
29
+ *
30
+ * This is done because rename is atomic but copy is not, and can leave partially copied files.
31
+ *
32
+ * @param {*} oldPath the original file that should be moved
33
+ * @param {*} newPath the path to move the file to
34
+ */
35
+ async function safeMoveFile(oldPath, newPath) {
36
+ try {
37
+ // Golden path, we simply rename the file in an atomic operation
38
+ await fsp.rename(oldPath, newPath);
39
+ } catch (err) {
40
+ // The EXDEV error indicates that the rename failed because the rename was across filesystem boundaries
41
+ // This might occur if a distro uses tmpfs for temporary directories
42
+ if (err.code === 'EXDEV') {
43
+ // Generate a unique destination file name to get the file onto the destination filesystem
44
+ const tempPath = generateTemporaryPathOnDestinationDevice(newPath);
45
+
46
+ // Copy onto the destination filesystem (not guaranteed to be atomic)
47
+ await fse.copy(oldPath, tempPath);
48
+ // Atomically move the file onto the destination path, overwriting it
49
+ await fsp.rename(tempPath, newPath);
50
+ // Delete the old file once both the above operations succeed
51
+ await fse.remove(oldPath);
52
+ } else {
53
+ throw err;
54
+ }
55
+ }
56
+ }
57
+
58
+ module.exports = safeMoveFile;
@@ -0,0 +1,34 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const fs = require('fs');
5
+
6
+ function walkDirSync(dirPath, opts) {
7
+ const options = Object.assign(
8
+ {
9
+ noLinks: false,
10
+ gitIgnore: true,
11
+ recursive: false
12
+ },
13
+ opts
14
+ );
15
+ let filePaths = [];
16
+ const list = fs.readdirSync(dirPath);
17
+ list.forEach((filePathParam) => {
18
+ let filePath = filePathParam;
19
+ filePath = path.join(dirPath, filePath);
20
+ const stat = options.noLinks ? fs.lstatSync(filePath) : fs.statSync(filePath);
21
+ // skipping symbolic links when noLinks option
22
+ if ((options.noLinks && stat && stat.isSymbolicLink()) || (options.gitIgnore && filePathParam === '.git')){
23
+ return;
24
+ } else if (stat && stat.isDirectory() && options.recursive) {
25
+ filePaths = filePaths.concat(walkDirSync(filePath, opts));
26
+ } else {
27
+ filePaths.push(filePathParam);
28
+ }
29
+ });
30
+
31
+ return filePaths;
32
+ }
33
+
34
+ module.exports = walkDirSync;
@@ -0,0 +1,31 @@
1
+ 'use strict';
2
+
3
+ const fse = require('fs-extra');
4
+ const path = require('path');
5
+ const jc = require('json-cycle');
6
+ const yaml = require('js-yaml');
7
+
8
+ function writeFileSync(filePath, conts, cycles) {
9
+ let contents = conts || '';
10
+
11
+ fse.mkdirsSync(path.dirname(filePath));
12
+
13
+ if (filePath.indexOf('.json') !== -1 && typeof contents !== 'string') {
14
+ if (cycles) {
15
+ contents = jc.stringify(contents, null, 2);
16
+ } else {
17
+ contents = JSON.stringify(contents, null, 2);
18
+ }
19
+ }
20
+
21
+ const yamlFileExists = filePath.indexOf('.yaml') !== -1;
22
+ const ymlFileExists = filePath.indexOf('.yml') !== -1;
23
+
24
+ if ((yamlFileExists || ymlFileExists) && typeof contents !== 'string') {
25
+ contents = yaml.dump(contents);
26
+ }
27
+
28
+ return fse.writeFileSync(filePath, contents);
29
+ }
30
+
31
+ module.exports = writeFileSync;
@@ -0,0 +1,32 @@
1
+ 'use strict';
2
+
3
+ const fsp = require('fs').promises;
4
+ const fse = require('fs-extra');
5
+ const path = require('path');
6
+ const jc = require('json-cycle');
7
+ const yaml = require('js-yaml');
8
+
9
+ async function writeFile(filePath, conts, cycles) {
10
+ let contents = conts || '';
11
+
12
+ return fse.mkdirs(path.dirname(filePath)).then(() => {
13
+ if (filePath.indexOf('.json') !== -1 && typeof contents !== 'string') {
14
+ if (cycles) {
15
+ contents = jc.stringify(contents, null, 2);
16
+ } else {
17
+ contents = JSON.stringify(contents, null, 2);
18
+ }
19
+ }
20
+
21
+ const yamlFileExists = filePath.indexOf('.yaml') !== -1;
22
+ const ymlFileExists = filePath.indexOf('.yml') !== -1;
23
+
24
+ if ((yamlFileExists || ymlFileExists) && typeof contents !== 'string') {
25
+ contents = yaml.dump(contents);
26
+ }
27
+
28
+ return fsp.writeFile(filePath, contents);
29
+ });
30
+ }
31
+
32
+ module.exports = writeFile;
@@ -1,5 +1,6 @@
1
1
  const chalk = require('chalk');
2
2
 
3
+ /* Loggers */
3
4
  function logError(message) {
4
5
  console.log(chalk.red(message));
5
6
  }
@@ -8,7 +9,12 @@ function logNotice(message) {
8
9
  console.log(chalk.yellow(message));
9
10
  }
10
11
 
12
+ function logInfo(message) {
13
+ console.log(message);
14
+ }
15
+
11
16
  module.exports = {
12
17
  logError,
13
- logNotice
18
+ logNotice,
19
+ logInfo
14
20
  }
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const fse = require('fs-extra');
5
+
6
+ const fileExistsSync = require('./fs/file-exists-sync');
7
+ const readFileSync = require('./fs/read-file-sync');
8
+ const writeFileSync = require('./fs/write-file-sync');
9
+ const KumologicaError = require('../KumologicaError');
10
+
11
+ function renameYmlService(name, ymlServiceFile) {
12
+ const serverlessYml = fse
13
+ .readFileSync(ymlServiceFile, 'utf-8')
14
+ .replace(/(^|\s|#)service\s*:.+/, (ignore, prefix) => `${prefix}service: ${name}`)
15
+ .replace(
16
+ /(^|\s|#)service\s*:\s*\n(\s+)name:.+/,
17
+ (match, prefix, indent) => `${prefix}service:\n${indent}name: ${name}`
18
+ );
19
+
20
+ fse.writeFileSync(ymlServiceFile, serverlessYml);
21
+ }
22
+
23
+ function renameTsService(name, tsServicefile) {
24
+ const serverlessTs = fse
25
+ .readFileSync(tsServicefile, 'utf-8')
26
+ .replace(/(^|\s)service\s*:\s*('|").+('|")/, (ignore, prefix) => `${prefix}service: '${name}'`)
27
+ .replace(
28
+ /(^|\s)service\s*:\s*{\s*\n(\s+)name:\s*('|").+('|")/,
29
+ (match, prefix, indent) => `${prefix}service: {\n${indent}name: '${name}'`
30
+ );
31
+
32
+ fse.writeFileSync(tsServicefile, serverlessTs);
33
+ }
34
+
35
+ function renameService(targetProjectDir, projectName) {
36
+ const name = projectName;
37
+ const packageFile = path.join(targetProjectDir, 'package.json');
38
+ if (fileExistsSync(packageFile)) {
39
+ const json = readFileSync(packageFile);
40
+ writeFileSync(packageFile, Object.assign(json, { name }));
41
+ }
42
+ const packageLockFile = path.join(targetProjectDir, 'package-lock.json');
43
+ if (fileExistsSync(packageLockFile)) {
44
+ const json = readFileSync(packageLockFile);
45
+ writeFileSync(packageLockFile, Object.assign(json, { name }));
46
+ }
47
+ }
48
+
49
+ module.exports.renameService = renameService;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "productName": "Kumologica Designer",
4
4
  "copyright": "Copyright 2020 Kumologica Pty Ltd, All Rights Reserved.",
5
5
  "author": "Kumologica Pty Ltd <contact@kumologica.com>",
6
- "version": "3.0.27",
6
+ "version": "3.0.30",
7
7
  "description": "Kumologica Designer, harnessing Serverless for your cloud integration needs",
8
8
  "main": "src/app/main.js",
9
9
  "files": [
@@ -65,30 +65,41 @@
65
65
  "license": "Proprietary",
66
66
  "dependencies": {
67
67
  "@electron/remote": "^2.0.8",
68
- "@kumologica/builder": "3.0.27",
69
- "@kumologica/devkit": "3.0.27",
70
- "@kumologica/runtime": "3.0.27",
68
+ "@kumologica/builder": "3.0.30",
69
+ "@kumologica/devkit": "3.0.30",
70
+ "@kumologica/runtime": "3.0.30",
71
71
  "adm-zip": "0.4.13",
72
72
  "ajv": "8.10.0",
73
+ "archive-type": "^4.0.0",
74
+ "archiver": "^1.3.0",
73
75
  "aws-sdk": "2.513.0",
74
76
  "basic-auth": "2.0.1",
75
77
  "bcryptjs": "2.4.3",
78
+ "bluebird": "^3.7.2",
76
79
  "body-parser": "1.19.0",
77
80
  "chalk": "2.4.2",
78
81
  "child_process": "1.0.2",
82
+ "child-process-ext": "^2.1.1",
79
83
  "clone": "2.1.2",
84
+ "content-disposition": "0.5.4",
80
85
  "cookie-parser": "1.4.4",
81
86
  "cors": "^2.8.5",
82
87
  "dagre": "^0.8.5",
83
- "debounce": "^1.2.0",
88
+ "debounce": "1.2.1",
89
+ "decompress": "4.2.1",
84
90
  "electron": "19.0.0",
85
91
  "electron-tabs": "^1.0.1",
86
92
  "electron-updater": "4.3.9",
87
- "enquirer": "^2.3.1",
93
+ "enquirer": "2.3.6",
88
94
  "express": "4.17.3",
89
95
  "express-session": "1.16.2",
96
+ "ext-name": "5.0.0",
90
97
  "extract-json-from-string": "1.0.1",
98
+ "file-type": "16.5.4",
99
+ "filenamify": "4.0.0",
100
+ "fs": "^0.0.1-security",
91
101
  "fs-extra": "10.1.0",
102
+ "get-stream": "^6.0.1",
92
103
  "glob": "7.1.6",
93
104
  "got": "11.8.2",
94
105
  "hash-sum": "2.0.0",
@@ -96,18 +107,24 @@
96
107
  "iconv-lite": "0.5.0",
97
108
  "ini": "^2.0.0",
98
109
  "js-yaml": "3.13.1",
110
+ "json-cycle": "^1.3.0",
99
111
  "jsonata": "1.7.0",
100
112
  "jsonpath": "1.0.2",
113
+ "lazystream": "^1.0.1",
114
+ "make-dir": "^3.1.0",
101
115
  "memorystore": "1.6.1",
102
116
  "mime": "2.4.4",
103
117
  "minimist": "^1.2.3",
104
118
  "multer": "1.4.1",
105
119
  "mustache": "3.0.1",
120
+ "node-fetch": "2.6.7",
106
121
  "oauth2orize": "1.11.0",
107
122
  "os": "0.1.1",
123
+ "p-event": "4.2.0",
108
124
  "passport": "0.4.0",
109
125
  "passport-http-bearer": "1.0.1",
110
126
  "passport-oauth2-client-password": "0.1.2",
127
+ "querystring": "^0.2.1",
111
128
  "request": "2.88.0",
112
129
  "rimraf": "3.0.0",
113
130
  "shell-path": "2.1.0",
@@ -117,6 +134,7 @@
117
134
  "socket.io": "2.3.0",
118
135
  "tcp-port-used": "1.0.2",
119
136
  "util": "0.12.1",
137
+ "whatwg-url": "^11.0.0",
120
138
  "when": "3.7.8",
121
139
  "wide-align": "^1.1.5",
122
140
  "wildcard-match": "^5.1.2",
@@ -160,5 +178,8 @@
160
178
  },
161
179
  "engines": {
162
180
  "node": ">=8"
181
+ },
182
+ "kumologica": {
183
+ "dev": false
163
184
  }
164
185
  }