@dynamicweb/cli 1.0.0 → 1.0.2

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.
@@ -76,10 +76,8 @@ async function handleEnv(argv) {
76
76
  environment: {
77
77
  type: 'input'
78
78
  },
79
- protocol: {
80
- type: 'input'
81
- },
82
79
  host: {
80
+ describe: 'Enter your host including protocol, i.e "https://yourHost.com":',
83
81
  type: 'input'
84
82
  },
85
83
  interactive: {
@@ -96,15 +94,26 @@ export async function interactiveEnv(argv, options) {
96
94
  .then(async (result) => {
97
95
  getConfig().env = getConfig().env || {};
98
96
  getConfig().env[result.environment] = getConfig().env[result.environment] || {};
99
- getConfig().env[result.environment].protocol = result.protocol || 'https';
100
- if (result.host)
101
- getConfig().env[result.environment].host = result.host;
97
+ if (result.host) {
98
+ var hostSplit = result.host.split("://");
99
+ if (hostSplit.length == 1) {
100
+ getConfig().env[result.environment].protocol = 'https';
101
+ getConfig().env[result.environment].host = hostSplit[0];
102
+ } else if (hostSplit.length == 2) {
103
+ getConfig().env[result.environment].protocol = hostSplit[0];
104
+ getConfig().env[result.environment].host = hostSplit[1];
105
+ } else {
106
+ console.log(`Issues resolving host ${result.host}`);
107
+ return;
108
+ }
109
+ }
102
110
  if (result.environment) {
103
111
  getConfig().current = getConfig().current || {};
104
112
  getConfig().current.env = result.environment;
105
113
  }
106
114
  updateConfig();
107
115
  console.log(`Your current environment is now ${getConfig().current.env}`);
116
+ console.log(`To change the host of your environment, use the command 'dw env'`)
108
117
  });
109
118
  }
110
119
 
@@ -118,6 +127,7 @@ async function changeEnv(argv) {
118
127
  prompt: 'never'
119
128
  },
120
129
  host: {
130
+ describe: 'Enter your host including protocol, i.e "https://yourHost.com":',
121
131
  type: 'input',
122
132
  prompt: 'always'
123
133
  },
@@ -28,26 +28,35 @@ export function filesCommand() {
28
28
  .option('export', {
29
29
  alias: 'e',
30
30
  type: 'boolean',
31
- describe: 'Exports the directory at [dirPath] to [outPath]'
31
+ describe: 'Exports the specified directory and all subdirectories at [dirPath] to [outPath]'
32
32
  })
33
33
  .option('import', {
34
34
  alias: 'i',
35
35
  type: 'boolean',
36
36
  describe: 'Imports the file at [dirPath] to [outPath]'
37
37
  })
38
+ .option('overwrite', {
39
+ alias: 'o',
40
+ type: 'boolean',
41
+ describe: 'Used with import, will overwrite existing files at destrination if set to true'
42
+ })
43
+ .option('createEmpty', {
44
+ type: 'boolean',
45
+ describe: 'Used with import, will create a file even if its empty'
46
+ })
38
47
  .option('includeFiles', {
39
48
  alias: 'f',
40
49
  type: 'boolean',
41
- describe: 'Includes files in list of directories and files'
50
+ describe: 'Used with export, includes files in list of directories and files'
42
51
  })
43
52
  .option('recursive', {
44
53
  alias: 'r',
45
54
  type: 'boolean',
46
- describe: 'Handles all directories recursively'
55
+ describe: 'Used with list, import and export, handles all directories recursively'
47
56
  })
48
57
  .option('raw', {
49
58
  type: 'boolean',
50
- describe: 'Keeps zip file instead of unpacking it'
59
+ describe: 'Used with export, keeps zip file instead of unpacking it'
51
60
  })
52
61
  .option('iamstupid', {
53
62
  type: 'boolean',
@@ -75,11 +84,11 @@ async function handleFiles(argv) {
75
84
 
76
85
  if (argv.export) {
77
86
  if (argv.dirPath) {
78
- await download(env, user, argv.dirPath, argv.outPath, argv.recursive, null, argv.raw, []);
87
+ await download(env, user, argv.dirPath, argv.outPath, true, null, argv.raw, argv.iamstupid, []);
79
88
  } else {
80
89
  await interactiveConfirm('Are you sure you want a full export of files?', async () => {
81
90
  console.log('Full export is starting')
82
- let filesStructure = (await getFilesStructure(env, user, '/', false, argv.includeFiles)).model;
91
+ let filesStructure = (await getFilesStructure(env, user, '/', false, true)).model;
83
92
  let dirs = filesStructure.directories;
84
93
  for (let id = 0; id < dirs.length; id++) {
85
94
  const dir = dirs[id];
@@ -91,12 +100,61 @@ async function handleFiles(argv) {
91
100
  }
92
101
  } else if (argv.import) {
93
102
  if (argv.dirPath && argv.outPath) {
94
- let resolvedPath = path.resolve(argv.dirPath)
95
- await uploadFile(env, user, resolvedPath, argv.outPath);
103
+ let resolvedPath = path.resolve(argv.dirPath);
104
+ let files;
105
+ if (!argv.overwrite) {
106
+ files = (await getFilesStructure(env, user, argv.outPath, argv.recursive, true)).model;
107
+ }
108
+ if (argv.recursive) {
109
+ await processDirectory(env, user, resolvedPath, argv.outPath, files, resolvedPath, argv.createEmpty, true);
110
+ } else {
111
+ let filesInDir = getFilesInDirectory(resolvedPath);
112
+ if (files)
113
+ filesInDir = getFilesNotInData(filesInDir, files.files.data, resolvedPath);
114
+ await uploadFiles(env, user, filesInDir, argv.outPath, argv.createEmpty);
115
+ }
96
116
  }
97
117
  }
98
118
  }
99
119
 
120
+ function convertToDataFormat(filePath, resolvedPath) {
121
+ const relativePath = `/Files${filePath.substring(resolvedPath.length)}`;
122
+ return path.format(path.parse(relativePath)).replace(/\\/g, '/');
123
+ }
124
+
125
+ function getFilesNotInData(filesInDir, data, resolvedPath) {
126
+ const existingPaths = data.map(file => file.filePath);
127
+ return filesInDir.filter(filePath => {
128
+ const convertedPath = convertToDataFormat(filePath, resolvedPath);
129
+ return !existingPaths.includes(convertedPath);
130
+ });
131
+ }
132
+
133
+ function getFilesInDirectory(dirPath) {
134
+ return fs.readdirSync(dirPath)
135
+ .map(file => path.join(dirPath, file))
136
+ .filter(file => fs.statSync(file).isFile());
137
+ }
138
+
139
+ async function processDirectory(env, user, dirPath, outPath, files, originalDir, createEmpty, isRoot = false) {
140
+ let filesInDir = getFilesInDirectory(dirPath);
141
+ let missingFiles;
142
+ if (files === undefined)
143
+ missingFiles = filesInDir;
144
+ else
145
+ missingFiles = getFilesNotInData(filesInDir, files.files.data, originalDir);
146
+ if (missingFiles.length > 0)
147
+ await uploadFiles(env, user, missingFiles, isRoot ? outPath : path.join(outPath, path.basename(dirPath)), createEmpty);
148
+
149
+ const subDirectories = fs.readdirSync(dirPath)
150
+ .map(subDir => path.join(dirPath, subDir))
151
+ .filter(subDir => fs.statSync(subDir).isDirectory());
152
+ for (let subDir of subDirectories) {
153
+ const remoteSubDir = files?.directories.find(dir => dir.name === path.basename(subDir));
154
+ await processDirectory(env, user, subDir, isRoot ? outPath : path.join(outPath, path.basename(dirPath)), remoteSubDir, originalDir, createEmpty);
155
+ }
156
+ }
157
+
100
158
  function resolveTree(dirs, indentLevel, parentHasFiles) {
101
159
  let end = `└──`
102
160
  let mid = `├──`
@@ -208,12 +266,15 @@ async function getFilesStructure(env, user, dirPath, recursive, includeFiles) {
208
266
  }
209
267
  }
210
268
 
211
- export async function uploadFile(env, user, localFilePath, destinationPath) {
212
- console.log('Uploading file')
269
+ export async function uploadFiles(env, user, localFilePaths, destinationPath, createEmpty = false) {
270
+ console.log('Uploading files')
213
271
  let form = new FormData();
214
272
  form.append('path', destinationPath);
215
- form.append('files', fs.createReadStream(localFilePath));
216
- let res = await fetch(`${env.protocol}://${env.host}/Admin/Api/Upload`, {
273
+ localFilePaths.forEach((localPath, index) => {
274
+ console.log(localPath)
275
+ form.append('files', fs.createReadStream(path.resolve(localPath)));
276
+ });
277
+ let res = await fetch(`${env.protocol}://${env.host}/Admin/Api/Upload?` + new URLSearchParams({"createEmptyFiles": createEmpty}), {
217
278
  method: 'POST',
218
279
  body: form,
219
280
  headers: {
@@ -223,7 +284,7 @@ export async function uploadFile(env, user, localFilePath, destinationPath) {
223
284
  });
224
285
  if (res.ok) {
225
286
  if (env.verbose) console.log(await res.json())
226
- console.log(`File uploaded`)
287
+ console.log(`Files uploaded`)
227
288
  }
228
289
  else {
229
290
  console.log(res)
@@ -2,7 +2,7 @@ import fetch from 'node-fetch';
2
2
  import path from 'path';
3
3
  import { setupEnv, getAgent } from './env.js';
4
4
  import { setupUser } from './login.js';
5
- import { uploadFile } from './files.js';
5
+ import { uploadFiles } from './files.js';
6
6
 
7
7
  export function installCommand() {
8
8
  return {
@@ -25,15 +25,17 @@ async function handleInstall(argv) {
25
25
  let env = await setupEnv(argv);
26
26
  let user = await setupUser(argv, env);
27
27
  let resolvedPath = path.resolve(argv.filePath)
28
- await uploadFile(env, user, resolvedPath, 'System/AddIns/Local');
28
+ await uploadFiles(env, user, [resolvedPath], 'System/AddIns/Local');
29
29
  await installAddin(env, user, resolvedPath)
30
30
  }
31
31
 
32
32
  async function installAddin(env, user, resolvedPath) {
33
33
  console.log('Installing addin')
34
+ let filename = path.basename(resolvedPath);
34
35
  let data = {
35
- 'AddinProvider': 'Dynamicweb.Marketplace.Providers.LocalAddinProvider',
36
- 'Package': path.basename(resolvedPath)
36
+ 'Ids': [
37
+ `${filename.substring(0, filename.lastIndexOf('.')) || filename}|${path.extname(resolvedPath)}`
38
+ ]
37
39
  }
38
40
  let res = await fetch(`${env.protocol}://${env.host}/Admin/Api/AddinInstall`, {
39
41
  method: 'POST',
@@ -50,6 +52,7 @@ async function installAddin(env, user, resolvedPath) {
50
52
  console.log(`Addin installed`)
51
53
  }
52
54
  else {
53
- console.log(res)
55
+ console.log('Request failed, returned error:')
56
+ console.log(await res.json())
54
57
  }
55
58
  }
@@ -70,7 +70,7 @@ export async function interactiveLogin(argv, options) {
70
70
  .interactive(options)
71
71
  .then(async (result) => {
72
72
  if (!getConfig().env || !getConfig().env[result.environment] || !getConfig().env[result.environment].host || !getConfig().env[result.environment].protocol) {
73
- if (!argv.host || !argv.protocol)
73
+ if (!argv.host)
74
74
  console.log(`The environment specified is missing parameters, please specify them`)
75
75
  await interactiveEnv(argv, {
76
76
  environment: {
@@ -78,11 +78,8 @@ export async function interactiveLogin(argv, options) {
78
78
  default: result.environment,
79
79
  prompt: 'never'
80
80
  },
81
- protocol: {
82
- type: 'input',
83
- prompt: 'if-no-arg'
84
- },
85
81
  host: {
82
+ describe: 'Enter your host including protocol, i.e "https://yourHost.com":',
86
83
  type: 'input',
87
84
  prompt: 'if-no-arg'
88
85
  },
@@ -91,14 +88,16 @@ export async function interactiveLogin(argv, options) {
91
88
  }
92
89
  })
93
90
  }
94
- await loginInteractive(result);
91
+ await loginInteractive(result, argv.verbose);
95
92
  });
96
93
  }
97
94
 
98
- async function loginInteractive(result) {
95
+ async function loginInteractive(result, verbose) {
99
96
  var protocol = getConfig().env[result.environment].protocol;
100
- var token = await login(result.username, result.password, result.environment, protocol);
101
- var apiKey = await getApiKey(token, result.environment, protocol)
97
+ var token = await login(result.username, result.password, result.environment, protocol, verbose);
98
+ if (!token) return;
99
+ var apiKey = await getApiKey(token, result.environment, protocol, verbose)
100
+ if (!apiKey) return;
102
101
  getConfig().env = getConfig().env || {};
103
102
  getConfig().env[result.environment].users = getConfig().env[result.environment].users || {};
104
103
  getConfig().env[result.environment].users[result.username] = getConfig().env[result.environment].users[result.username] || {};
@@ -108,7 +107,7 @@ async function loginInteractive(result) {
108
107
  updateConfig();
109
108
  }
110
109
 
111
- async function login(username, password, env, protocol) {
110
+ async function login(username, password, env, protocol, verbose) {
112
111
  let data = new URLSearchParams();
113
112
  data.append('Username', username);
114
113
  data.append('Password', password);
@@ -123,16 +122,21 @@ async function login(username, password, env, protocol) {
123
122
 
124
123
  if (res.ok) {
125
124
  let user = parseCookies(res.headers.get('set-cookie')).user;
126
- return await getToken(user, env, protocol)
125
+ if (!user) return;
126
+ return await getToken(user, env, protocol, verbose)
127
127
  }
128
128
  else {
129
- console.log(res)
129
+ if (verbose) console.info(res)
130
+ console.log(`Login attempt failed with username ${username}, please verify its a valid user in your Dynamicweb solution.`)
130
131
  }
131
132
  }
132
133
 
133
134
  function parseCookies (cookieHeader) {
134
135
  const list = {};
135
- if (!cookieHeader) return list;
136
+ if (!cookieHeader) {
137
+ console.log(`Could not get the necessary information from the login request, please verify its a valid user in your Dynamicweb solution.`)
138
+ return list;
139
+ }
136
140
 
137
141
  cookieHeader.replace('httponly, ', '').replace('Dynamicweb.Admin', 'user').split(`;`).forEach(cookie => {
138
142
  let [ name, ...rest] = cookie.split(`=`);
@@ -143,11 +147,15 @@ function parseCookies (cookieHeader) {
143
147
  list[name] = decodeURIComponent(value);
144
148
  });
145
149
 
150
+ if (!list.user) {
151
+ console.log(`Could not get the necessary information from the login request, please verify its a valid user in your Dynamicweb solution.`)
152
+ }
153
+
146
154
  return list;
147
155
  }
148
156
 
149
- async function getToken(user, env, protocol) {
150
- var res = await fetch(`${getConfig().env[env].protocol}://${getConfig().env[env].host}/Admin/Authentication/Token`, {
157
+ async function getToken(user, env, protocol, verbose) {
158
+ var res = await fetch(`${protocol}://${getConfig().env[env].host}/Admin/Authentication/Token`, {
151
159
  method: 'GET',
152
160
  headers: {
153
161
  'cookie': `Dynamicweb.Admin=${user}`
@@ -157,12 +165,16 @@ async function getToken(user, env, protocol) {
157
165
  if (res.ok) {
158
166
  return (await res.json()).token
159
167
  }
168
+ else {
169
+ if (verbose) console.info(res)
170
+ console.log(`Could not fetch the token for the logged in user ${user}, please verify its a valid user in your Dynamicweb solution.`)
171
+ }
160
172
  }
161
173
 
162
- async function getApiKey(token, env, protocol) {
174
+ async function getApiKey(token, env, protocol, verbose) {
163
175
  let data = {
164
- 'Name': 'addin',
165
- 'Prefix': 'addin',
176
+ 'Name': 'DW CLI',
177
+ 'Prefix': 'CLI',
166
178
  'Description': 'Auto-generated ApiKey by DW CLI'
167
179
  };
168
180
  var res = await fetch(`${protocol}://${getConfig().env[env].host}/Admin/Api/ApiKeySave`, {
@@ -179,7 +191,8 @@ async function getApiKey(token, env, protocol) {
179
191
  return (await res.json()).message
180
192
  }
181
193
  else {
182
- console.log(await res.json())
194
+ if (verbose) console.info(res)
195
+ console.log(`Could not create an API Key for the logged in user, please verify its a valid user in your Dynamicweb solution.`)
183
196
  }
184
197
  }
185
198
 
package/bin/index.js CHANGED
@@ -39,6 +39,10 @@ function baseCommand() {
39
39
  command: '$0',
40
40
  describe: 'Shows the current env and user being used',
41
41
  handler: () => {
42
+ if (Object.keys(getConfig()).length === 0) {
43
+ console.log('To login to a solution use `dw login`')
44
+ return;
45
+ }
42
46
  console.log(`Environment: ${getConfig()?.current?.env}`)
43
47
  console.log(`User: ${getConfig()?.env[getConfig()?.current?.env]?.current?.user}`)
44
48
  console.log(`Protocol: ${getConfig()?.env[getConfig()?.current?.env]?.protocol}`)
package/bin/utils.js CHANGED
@@ -5,7 +5,7 @@ export async function interactiveConfirm(question, func) {
5
5
  .interactive({
6
6
  confirm: {
7
7
  type: 'confirm',
8
- default: false,
8
+ default: true,
9
9
  describe: question,
10
10
  prompt: 'always'
11
11
  },
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@dynamicweb/cli",
3
3
  "type": "module",
4
4
  "description": "Dynamicweb CLI is a commandline tool for interacting with Dynamicweb 10 solutions.",
5
- "version": "1.0.0",
5
+ "version": "1.0.2",
6
6
  "main": "bin/index.js",
7
7
  "files": [
8
8
  "bin/*"