@pwrdrvr/microapps-publish 0.3.5 → 0.4.0-alpha.10

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.
Files changed (45) hide show
  1. package/README.md +67 -72
  2. package/dist/commands/delete.d.ts +3 -0
  3. package/dist/commands/delete.d.ts.map +1 -1
  4. package/dist/commands/delete.js +27 -10
  5. package/dist/commands/delete.js.map +1 -1
  6. package/dist/commands/nextjs-version.d.ts +2 -0
  7. package/dist/commands/nextjs-version.d.ts.map +1 -1
  8. package/dist/commands/nextjs-version.js +20 -5
  9. package/dist/commands/nextjs-version.js.map +1 -1
  10. package/dist/commands/preflight.d.ts +3 -0
  11. package/dist/commands/preflight.d.ts.map +1 -1
  12. package/dist/commands/preflight.js +27 -10
  13. package/dist/commands/preflight.js.map +1 -1
  14. package/dist/commands/publish-static.d.ts +6 -1
  15. package/dist/commands/publish-static.d.ts.map +1 -1
  16. package/dist/commands/publish-static.js +125 -48
  17. package/dist/commands/publish-static.js.map +1 -1
  18. package/dist/commands/publish.d.ts +7 -0
  19. package/dist/commands/publish.d.ts.map +1 -1
  20. package/dist/commands/publish.js +118 -41
  21. package/dist/commands/publish.js.map +1 -1
  22. package/dist/config/Application.js.map +1 -1
  23. package/dist/config/Config.js.map +1 -1
  24. package/dist/config/Deployer.js.map +1 -1
  25. package/dist/lib/DeployClient.d.ts +1 -1
  26. package/dist/lib/DeployClient.d.ts.map +1 -1
  27. package/dist/lib/S3TransferUtility.d.ts +2 -2
  28. package/dist/lib/S3TransferUtility.d.ts.map +1 -1
  29. package/dist/lib/S3TransferUtility.js +27 -8
  30. package/dist/lib/S3TransferUtility.js.map +1 -1
  31. package/dist/lib/S3Uploader.d.ts +1 -1
  32. package/dist/lib/S3Uploader.d.ts.map +1 -1
  33. package/dist/lib/S3Uploader.js +4 -3
  34. package/dist/lib/S3Uploader.js.map +1 -1
  35. package/package.json +13 -7
  36. package/src/commands/delete.ts +29 -9
  37. package/src/commands/nextjs-version.ts +19 -5
  38. package/src/commands/preflight.ts +29 -9
  39. package/src/commands/publish-static.ts +155 -64
  40. package/src/commands/publish.ts +150 -55
  41. package/src/lib/DeployClient.ts +1 -1
  42. package/src/lib/S3TransferUtility.spec.ts +15 -0
  43. package/src/lib/S3TransferUtility.ts +30 -12
  44. package/src/lib/S3Uploader.ts +2 -2
  45. package/src/lib/__snapshots__/S3TransferUtility.spec.ts.snap +12 -0
@@ -19,22 +19,40 @@ export class DeleteCommand extends Command {
19
19
  char: 'v',
20
20
  }),
21
21
  help: flagsParser.help(),
22
+ // Deprecated
22
23
  appName: flagsParser.string({
23
- char: 'a',
24
24
  multiple: false,
25
25
  required: false,
26
- description: 'Name of the MicroApp',
26
+ hidden: true,
27
27
  }),
28
+ 'app-name': flagsParser.string({
29
+ char: 'a',
30
+ multiple: false,
31
+ exactlyOne: ['app-name', 'appName'],
32
+ description: 'MicroApps app name (this becomes the path the app is rooted at)',
33
+ }),
34
+ // Deprecated
28
35
  newVersion: flagsParser.string({
36
+ multiple: false,
37
+ required: false,
38
+ hidden: true,
39
+ }),
40
+ 'new-version': flagsParser.string({
29
41
  char: 'n',
30
42
  multiple: false,
31
- required: true,
43
+ exactlyOne: ['new-version', 'newVersion'],
32
44
  description: 'New semantic version to apply',
33
45
  }),
46
+ // Deprecated
34
47
  deployerLambdaName: flagsParser.string({
48
+ multiple: false,
49
+ required: false,
50
+ hidden: true,
51
+ }),
52
+ 'deployer-lambda-name': flagsParser.string({
35
53
  char: 'd',
36
54
  multiple: false,
37
- required: true,
55
+ exactlyOne: ['deployer-lambda-name', 'deployerLambdaName'],
38
56
  description: 'Name of the deployer lambda function',
39
57
  }),
40
58
  };
@@ -47,10 +65,12 @@ export class DeleteCommand extends Command {
47
65
  const RUNNING = ''; //chalk.reset.inverse.yellow.bold(RUNNING_TEXT) + ' ';
48
66
 
49
67
  const { flags: parsedFlags } = this.parse(DeleteCommand);
50
- const version = parsedFlags.newVersion;
51
- const appName = parsedFlags.appName ?? config.app.name;
52
- const deployerLambdaName = parsedFlags.deployerLambdaName ?? config.deployer.lambdaName;
53
- const semVer = parsedFlags.newVersion ?? config.app.semVer;
68
+ const semVer = parsedFlags.newVersion ?? parsedFlags['new-version'] ?? config.app.semVer;
69
+ const appName = parsedFlags.appName ?? parsedFlags['app-name'] ?? config.app.name;
70
+ const deployerLambdaName =
71
+ parsedFlags.deployerLambdaName ??
72
+ parsedFlags['deployer-lambda-name'] ??
73
+ config.deployer.lambdaName;
54
74
 
55
75
  // Override the config value
56
76
  config.deployer.lambdaName = deployerLambdaName;
@@ -87,7 +107,7 @@ export class DeleteCommand extends Command {
87
107
  task.title = RUNNING + origTitle;
88
108
 
89
109
  // Confirm the Version Does Not Exist in Published State
90
- task.output = `Deleting app/version ${config.app.name}/${version}`;
110
+ task.output = `Deleting app/version ${config.app.name}/${semVer}`;
91
111
  const result = await DeployClient.DeleteVersion({
92
112
  config,
93
113
  output: (message: string) => (task.output = message),
@@ -18,13 +18,27 @@ export class NextJSVersionCommand extends Command {
18
18
  char: 'v',
19
19
  }),
20
20
  help: flagsParser.help(),
21
+ // Deprecated
21
22
  newVersion: flagsParser.string({
23
+ multiple: false,
24
+ required: false,
25
+ hidden: true,
26
+ }),
27
+ 'new-version': flagsParser.string({
22
28
  char: 'n',
23
29
  multiple: false,
24
- required: true,
30
+ exactlyOne: ['new-version', 'newVersion'],
25
31
  description: 'New semantic version to apply',
26
32
  }),
33
+ // Deprecated
27
34
  leaveCopy: flagsParser.boolean({
35
+ char: 'l',
36
+ default: false,
37
+ required: false,
38
+ hidden: true,
39
+ exclusive: ['leave-copy'],
40
+ }),
41
+ 'leave-copy': flagsParser.boolean({
28
42
  char: 'l',
29
43
  default: false,
30
44
  required: false,
@@ -44,14 +58,14 @@ export class NextJSVersionCommand extends Command {
44
58
  const RUNNING = ''; //chalk.reset.inverse.yellow.bold(RUNNING_TEXT) + ' ';
45
59
 
46
60
  const { flags: parsedFlags } = this.parse(NextJSVersionCommand);
47
- const version = parsedFlags.newVersion;
48
- const leaveFiles = parsedFlags.leaveCopy;
61
+ const semVer = parsedFlags.newVersion ?? parsedFlags['new-version'];
62
+ const leaveFiles = parsedFlags.leaveCopy ?? parsedFlags['leave-copy'];
49
63
 
50
64
  // Override the config value
51
65
  const config = Config.instance;
52
- config.app.semVer = version;
66
+ config.app.semVer = semVer;
53
67
 
54
- this.VersionAndAlias = createVersions(version);
68
+ this.VersionAndAlias = createVersions(semVer);
55
69
  const versionOnly = { version: this.VersionAndAlias.version };
56
70
 
57
71
  this.FILES_TO_MODIFY = [{ path: 'next.config.js', versions: versionOnly }];
@@ -19,22 +19,40 @@ export class PreflightCommand extends Command {
19
19
  char: 'v',
20
20
  }),
21
21
  help: flagsParser.help(),
22
+ // Deprecated
22
23
  appName: flagsParser.string({
23
- char: 'a',
24
24
  multiple: false,
25
25
  required: false,
26
- description: 'Name of the MicroApp',
26
+ hidden: true,
27
27
  }),
28
+ 'app-name': flagsParser.string({
29
+ char: 'a',
30
+ multiple: false,
31
+ exactlyOne: ['app-name', 'appName'],
32
+ description: 'MicroApps app name (this becomes the path the app is rooted at)',
33
+ }),
34
+ // Deprecated
28
35
  newVersion: flagsParser.string({
36
+ multiple: false,
37
+ required: false,
38
+ hidden: true,
39
+ }),
40
+ 'new-version': flagsParser.string({
29
41
  char: 'n',
30
42
  multiple: false,
31
- required: true,
43
+ exactlyOne: ['new-version', 'newVersion'],
32
44
  description: 'New semantic version to apply',
33
45
  }),
46
+ // Deprecated
34
47
  deployerLambdaName: flagsParser.string({
48
+ multiple: false,
49
+ required: false,
50
+ hidden: true,
51
+ }),
52
+ 'deployer-lambda-name': flagsParser.string({
35
53
  char: 'd',
36
54
  multiple: false,
37
- required: true,
55
+ exactlyOne: ['deployer-lambda-name', 'deployerLambdaName'],
38
56
  description: 'Name of the deployer lambda function',
39
57
  }),
40
58
  overwrite: flagsParser.boolean({
@@ -54,10 +72,12 @@ export class PreflightCommand extends Command {
54
72
  const RUNNING = ''; //chalk.reset.inverse.yellow.bold(RUNNING_TEXT) + ' ';
55
73
 
56
74
  const { flags: parsedFlags } = this.parse(PreflightCommand);
57
- const version = parsedFlags.newVersion;
58
- const appName = parsedFlags.appName ?? config.app.name;
59
- const deployerLambdaName = parsedFlags.deployerLambdaName ?? config.deployer.lambdaName;
60
- const semVer = parsedFlags.newVersion ?? config.app.semVer;
75
+ const appName = parsedFlags.appName ?? parsedFlags['app-name'] ?? config.app.name;
76
+ const deployerLambdaName =
77
+ parsedFlags.deployerLambdaName ??
78
+ parsedFlags['deployer-lambda-name'] ??
79
+ config.deployer.lambdaName;
80
+ const semVer = parsedFlags.newVersion ?? parsedFlags['new-version'] ?? config.app.semVer;
61
81
  const overwrite = parsedFlags.overwrite;
62
82
 
63
83
  // Override the config value
@@ -95,7 +115,7 @@ export class PreflightCommand extends Command {
95
115
  task.title = RUNNING + origTitle;
96
116
 
97
117
  // Confirm the Version Does Not Exist in Published State
98
- task.output = `Checking if deployed app/version already exists for ${config.app.name}/${version}`;
118
+ task.output = `Checking if deployed app/version already exists for ${config.app.name}/${semVer}`;
99
119
  const preflightResult = await DeployClient.DeployVersionPreflight({
100
120
  config,
101
121
  needS3Creds: false,
@@ -1,17 +1,21 @@
1
1
  import 'reflect-metadata';
2
2
  import * as s3 from '@aws-sdk/client-s3';
3
3
  import * as sts from '@aws-sdk/client-sts';
4
+ import { Upload } from '@aws-sdk/lib-storage';
4
5
  import { Command, flags as flagsParser } from '@oclif/command';
6
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
7
+ const pMap = require('p-map');
5
8
  import * as path from 'path';
6
9
  import { pathExists, createReadStream } from 'fs-extra';
7
10
  import { Listr, ListrTask } from 'listr2';
8
- import { createVersions, IVersions } from '../lib/Versions';
9
- import { Config } from '../config/Config';
10
- import DeployClient, { IDeployVersionPreflightResult } from '../lib/DeployClient';
11
- import S3Uploader from '../lib/S3Uploader';
12
- import S3TransferUtility from '../lib/S3TransferUtility';
13
- import { Upload } from '@aws-sdk/lib-storage';
14
11
  import { contentType } from 'mime-types';
12
+ import { Config } from '../config/Config';
13
+ import DeployClient, {
14
+ DeployVersionArgs,
15
+ IDeployVersionPreflightResult,
16
+ } from '../lib/DeployClient';
17
+ import { S3Uploader } from '../lib/S3Uploader';
18
+ import { S3TransferUtility } from '../lib/S3TransferUtility';
15
19
 
16
20
  interface IContext {
17
21
  preflightResult: IDeployVersionPreflightResult;
@@ -38,32 +42,64 @@ export class PublishCommand extends Command {
38
42
  char: 'v',
39
43
  }),
40
44
  help: flagsParser.help(),
45
+ // Deprecated
41
46
  deployerLambdaName: flagsParser.string({
47
+ multiple: false,
48
+ required: false,
49
+ hidden: true,
50
+ }),
51
+ 'deployer-lambda-name': flagsParser.string({
42
52
  char: 'd',
43
53
  multiple: false,
44
- required: true,
54
+ exactlyOne: ['deployer-lambda-name', 'deployerLambdaName'],
45
55
  description: 'Name of the deployer lambda function',
46
56
  }),
57
+ // Deprecated
47
58
  newVersion: flagsParser.string({
59
+ multiple: false,
60
+ required: false,
61
+ hidden: true,
62
+ }),
63
+ 'new-version': flagsParser.string({
48
64
  char: 'n',
49
65
  multiple: false,
50
- required: true,
66
+ exactlyOne: ['new-version', 'newVersion'],
51
67
  description: 'New semantic version to apply',
52
68
  }),
69
+ // Deprecated
53
70
  appName: flagsParser.string({
54
- char: 'a',
55
71
  multiple: false,
56
72
  required: false,
57
- description: 'MicroApps app name',
73
+ hidden: true,
58
74
  }),
75
+ 'app-name': flagsParser.string({
76
+ char: 'a',
77
+ multiple: false,
78
+ exactlyOne: ['app-name', 'appName'],
79
+ description: 'MicroApps app name (this becomes the path the app is rooted at)',
80
+ }),
81
+ // Deprecated
59
82
  staticAssetsPath: flagsParser.string({
83
+ multiple: false,
84
+ required: false,
85
+ hidden: true,
86
+ }),
87
+ 'static-assets-path': flagsParser.string({
60
88
  char: 's',
61
89
  multiple: false,
62
90
  required: false,
91
+ exactlyOne: ['static-assets-path', 'staticAssetsPath'],
63
92
  description:
64
93
  'Path to files to be uploaded to S3 static bucket at app/version/ path. Do include app/version/ in path if files are already "rooted" under that path locally.',
65
94
  }),
95
+ // Deprecated
66
96
  defaultFile: flagsParser.string({
97
+ multiple: false,
98
+ required: false,
99
+ hidden: true,
100
+ exclusive: ['default-file'],
101
+ }),
102
+ 'default-file': flagsParser.string({
67
103
  char: 'i',
68
104
  multiple: false,
69
105
  required: false,
@@ -77,15 +113,19 @@ export class PublishCommand extends Command {
77
113
  description:
78
114
  'Allow overwrite - Warn but do not fail if version exists. Discouraged outside of test envs if cacheable static files have changed.',
79
115
  }),
116
+ // Deprecated
80
117
  noCache: flagsParser.boolean({
118
+ required: false,
119
+ default: false,
120
+ hidden: true,
121
+ }),
122
+ 'no-cache': flagsParser.boolean({
81
123
  required: false,
82
124
  default: false,
83
125
  description: 'Force revalidation of CloudFront and browser caching of static assets',
84
126
  }),
85
127
  };
86
128
 
87
- private VersionAndAlias: IVersions;
88
-
89
129
  async run(): Promise<void> {
90
130
  const config = Config.instance;
91
131
 
@@ -94,13 +134,20 @@ export class PublishCommand extends Command {
94
134
  const RUNNING = ''; //chalk.reset.inverse.yellow.bold(RUNNING_TEXT) + ' ';
95
135
 
96
136
  const { flags: parsedFlags } = this.parse(PublishCommand);
97
- const appName = parsedFlags.appName ?? config.app.name;
98
- const deployerLambdaName = parsedFlags.deployerLambdaName ?? config.deployer.lambdaName;
99
- const semVer = parsedFlags.newVersion ?? config.app.semVer;
100
- const staticAssetsPath = parsedFlags.staticAssetsPath ?? config.app.staticAssetsPath;
101
- const defaultFile = parsedFlags.defaultFile ?? config.app.defaultFile;
137
+ const appName = parsedFlags.appName ?? parsedFlags['app-name'] ?? config.app.name;
138
+ const deployerLambdaName =
139
+ parsedFlags.deployerLambdaName ??
140
+ parsedFlags['deployer-lambda-name'] ??
141
+ config.deployer.lambdaName;
142
+ const semVer = parsedFlags.newVersion ?? parsedFlags['new-version'] ?? config.app.semVer;
143
+ const staticAssetsPath =
144
+ parsedFlags.staticAssetsPath ??
145
+ parsedFlags['static-assets-path'] ??
146
+ config.app.staticAssetsPath;
147
+ const defaultFile =
148
+ parsedFlags.defaultFile ?? parsedFlags['default-file'] ?? config.app.defaultFile;
102
149
  const overwrite = parsedFlags.overwrite;
103
- const noCache = parsedFlags.noCache;
150
+ const noCache = parsedFlags.noCache ?? parsedFlags['no-cache'];
104
151
 
105
152
  // Override the config value
106
153
  config.deployer.lambdaName = deployerLambdaName;
@@ -125,8 +172,6 @@ export class PublishCommand extends Command {
125
172
  }
126
173
  }
127
174
 
128
- this.VersionAndAlias = createVersions(semVer);
129
-
130
175
  if (config.app.staticAssetsPath === undefined) {
131
176
  this.error('staticAssetsPath must be specified');
132
177
  }
@@ -198,11 +243,11 @@ export class PublishCommand extends Command {
198
243
  {
199
244
  // TODO: Disable this task if no static assets path
200
245
  title: 'Enumerate Files to Upload to S3',
201
- task: async (ctx, task) => {
246
+ task: (ctx, task) => {
202
247
  const origTitle = task.title;
203
248
  task.title = RUNNING + origTitle;
204
249
 
205
- ctx.files = (await S3TransferUtility.GetFiles(S3Uploader.TempDir)) as string[];
250
+ ctx.files = S3TransferUtility.GetFiles(S3Uploader.TempDir);
206
251
 
207
252
  task.title = origTitle;
208
253
  },
@@ -210,7 +255,7 @@ export class PublishCommand extends Command {
210
255
  {
211
256
  // TODO: Disable this task if no static assets path
212
257
  title: 'Upload Static Files to S3',
213
- task: (ctx, task) => {
258
+ task: async (ctx, task) => {
214
259
  const origTitle = task.title;
215
260
  task.title = RUNNING + origTitle;
216
261
 
@@ -230,50 +275,87 @@ export class PublishCommand extends Command {
230
275
 
231
276
  // Setup caching on static assets
232
277
  // NoCache - Only used for test deploys, requires browser and CloudFront to refetch every time
233
- // Overwrite - Reduces default cache time period from 24 hours to 15 minutes
234
278
  // Default - 24 hours
235
279
  const CacheControl = noCache
236
280
  ? 'max-age=0, must-revalidate, public'
237
- : overwrite
238
- ? `max-age=${15 * 60}, public`
239
281
  : `max-age=${24 * 60 * 60}, public`;
240
282
 
241
283
  const pathWithoutAppAndVer = path.join(S3Uploader.TempDir, destinationPrefix);
242
-
243
- const tasks: ListrTask<IContext>[] = ctx.files.map((filePath) => ({
244
- task: async (ctx: IContext, subtask) => {
245
- const relFilePath = path.relative(pathWithoutAppAndVer, filePath);
246
-
247
- const origTitle = relFilePath;
248
- subtask.title = RUNNING + origTitle;
249
-
250
- const upload = new Upload({
251
- client: s3Client,
252
- leavePartsOnError: false,
253
- params: {
254
- Bucket: bucketName,
255
- Key: path.relative(S3Uploader.TempDir, filePath),
256
- Body: createReadStream(filePath),
257
- ContentType: contentType(path.basename(filePath)) || 'application/octet-stream',
258
- CacheControl,
259
- },
260
- });
261
- await upload.done();
262
-
263
- subtask.title = origTitle;
264
- },
265
- }));
266
-
267
- task.title = origTitle;
268
-
269
- return task.newListr(tasks, {
270
- concurrent: 8,
271
- rendererOptions: {
272
- clearOutput: false,
273
- showErrorMessage: true,
274
- showTimer: true,
275
- },
276
- });
284
+ // Listr causes OOM if passes a list of, say, 5,000 to 20,000 files
285
+ if (ctx.files.length > 200) {
286
+ const fileCountMsgInterval = Math.floor(ctx.files.length / 10);
287
+ let filesPublished = 0;
288
+
289
+ await pMap(
290
+ ctx.files,
291
+ async (filePath: string) => {
292
+ // Can't use tasks for each file
293
+ const relFilePath = path.relative(pathWithoutAppAndVer, filePath);
294
+
295
+ if (
296
+ ctx.files.length > 1000 &&
297
+ (filesPublished % fileCountMsgInterval === 0 ||
298
+ filesPublished === ctx.files.length)
299
+ ) {
300
+ task.output = `Uploaded ${filesPublished} of ${ctx.files.length} files`;
301
+ } else if (ctx.files.length <= 1000) {
302
+ task.output = `Uploading ${relFilePath}`;
303
+ }
304
+
305
+ const upload = new Upload({
306
+ client: s3Client,
307
+ leavePartsOnError: false,
308
+ params: {
309
+ Bucket: bucketName,
310
+ Key: path.relative(S3Uploader.TempDir, filePath),
311
+ Body: createReadStream(filePath),
312
+ ContentType:
313
+ contentType(path.basename(filePath)) || 'application/octet-stream',
314
+ CacheControl,
315
+ },
316
+ });
317
+ await upload.done();
318
+ filesPublished++;
319
+ },
320
+ { concurrency: 40 },
321
+ );
322
+ } else {
323
+ const tasks: ListrTask<IContext>[] = ctx.files.map((filePath) => ({
324
+ task: async (ctx: IContext, subtask) => {
325
+ const relFilePath = path.relative(pathWithoutAppAndVer, filePath);
326
+
327
+ const origTitle = relFilePath;
328
+ subtask.title = RUNNING + origTitle;
329
+
330
+ const upload = new Upload({
331
+ client: s3Client,
332
+ leavePartsOnError: false,
333
+ params: {
334
+ Bucket: bucketName,
335
+ Key: path.relative(S3Uploader.TempDir, filePath),
336
+ Body: createReadStream(filePath),
337
+ ContentType:
338
+ contentType(path.basename(filePath)) || 'application/octet-stream',
339
+ CacheControl,
340
+ },
341
+ });
342
+ await upload.done();
343
+
344
+ subtask.title = origTitle;
345
+ },
346
+ }));
347
+
348
+ task.title = origTitle;
349
+
350
+ return task.newListr(tasks, {
351
+ concurrent: 8,
352
+ rendererOptions: {
353
+ clearOutput: false,
354
+ showErrorMessage: true,
355
+ showTimer: true,
356
+ },
357
+ });
358
+ }
277
359
  },
278
360
  },
279
361
  {
@@ -294,8 +376,7 @@ export class PublishCommand extends Command {
294
376
  const origTitle = task.title;
295
377
  task.title = RUNNING + origTitle;
296
378
 
297
- // Call Deployer to Deploy AppName/Version
298
- await DeployClient.DeployVersion({
379
+ const request: DeployVersionArgs = {
299
380
  appName: config.app.name,
300
381
  semVer: config.app.semVer,
301
382
  deployerLambdaName: config.deployer.lambdaName,
@@ -303,7 +384,17 @@ export class PublishCommand extends Command {
303
384
  appType: 'static',
304
385
  overwrite,
305
386
  output: (message: string) => (task.output = message),
306
- });
387
+ };
388
+
389
+ // Use DeployVersionLite if createAlias is supported
390
+ if (ctx.preflightResult.response.capabilities?.['createAlias'] === 'true') {
391
+ task.output = 'Using DeployVersionLite';
392
+ await DeployClient.DeployVersionLite(request);
393
+ } else {
394
+ // Use legacy DeployVersion if createAlias is not supported
395
+ task.output = 'Using DeployVersion';
396
+ await DeployClient.DeployVersion(request);
397
+ }
307
398
 
308
399
  task.title = origTitle;
309
400
  },