@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
@@ -4,14 +4,19 @@ import * as lambda from '@aws-sdk/client-lambda';
4
4
  import * as s3 from '@aws-sdk/client-s3';
5
5
  import * as sts from '@aws-sdk/client-sts';
6
6
  import { Command, flags as flagsParser } from '@oclif/command';
7
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
8
+ const pMap = require('p-map');
7
9
  import * as path from 'path';
8
10
  import { pathExists, createReadStream } from 'fs-extra';
9
11
  import { Listr, ListrTask } from 'listr2';
10
12
  import { createVersions, IVersions } from '../lib/Versions';
11
13
  import { Config, IConfig } from '../config/Config';
12
- import DeployClient, { IDeployVersionPreflightResult } from '../lib/DeployClient';
13
- import S3Uploader from '../lib/S3Uploader';
14
- import S3TransferUtility from '../lib/S3TransferUtility';
14
+ import DeployClient, {
15
+ DeployVersionArgs,
16
+ IDeployVersionPreflightResult,
17
+ } from '../lib/DeployClient';
18
+ import { S3Uploader } from '../lib/S3Uploader';
19
+ import { S3TransferUtility } from '../lib/S3TransferUtility';
15
20
  import { Upload } from '@aws-sdk/lib-storage';
16
21
  import { contentType } from 'mime-types';
17
22
  import { TaskWrapper } from 'listr2/dist/lib/task-wrapper';
@@ -22,8 +27,6 @@ const lambdaClient = new lambda.LambdaClient({
22
27
  maxAttempts: 8,
23
28
  });
24
29
 
25
- type DeployVersionArgs = Parameters<typeof DeployClient.DeployVersionLite>[0];
26
-
27
30
  interface IContext {
28
31
  preflightResult: IDeployVersionPreflightResult;
29
32
  files: string[];
@@ -66,38 +69,78 @@ export class PublishCommand extends Command {
66
69
  char: 'v',
67
70
  }),
68
71
  help: flagsParser.help(),
72
+ // Deprecated
69
73
  deployerLambdaName: flagsParser.string({
74
+ multiple: false,
75
+ required: false,
76
+ hidden: true,
77
+ }),
78
+ 'deployer-lambda-name': flagsParser.string({
70
79
  char: 'd',
71
80
  multiple: false,
72
- required: true,
81
+ exactlyOne: ['deployer-lambda-name', 'deployerLambdaName'],
73
82
  description: 'Name of the deployer lambda function',
74
83
  }),
84
+ // Deprecated
75
85
  newVersion: flagsParser.string({
86
+ multiple: false,
87
+ required: false,
88
+ hidden: true,
89
+ }),
90
+ 'new-version': flagsParser.string({
76
91
  char: 'n',
77
92
  multiple: false,
78
- required: true,
93
+ exactlyOne: ['new-version', 'newVersion'],
79
94
  description: 'New semantic version to apply',
80
95
  }),
96
+ // Deprecated
81
97
  appLambdaName: flagsParser.string({
98
+ multiple: false,
99
+ required: false,
100
+ hidden: true,
101
+ exclusive: ['app-lambda-name'],
102
+ }),
103
+ 'app-lambda-name': flagsParser.string({
82
104
  char: 'l',
83
105
  multiple: false,
84
106
  required: false,
85
107
  description: 'ARN of lambda version, alias, or function (name or ARN) to deploy',
86
108
  }),
109
+ // Deprecated
87
110
  appName: flagsParser.string({
88
- char: 'a',
89
111
  multiple: false,
90
112
  required: false,
113
+ hidden: true,
114
+ exclusive: ['app-name'],
115
+ }),
116
+ 'app-name': flagsParser.string({
117
+ char: 'a',
118
+ multiple: false,
119
+ exactlyOne: ['app-name', 'appName'],
91
120
  description: 'MicroApps app name (this becomes the path the app is rooted at)',
92
121
  }),
122
+ // Deprecated
93
123
  staticAssetsPath: flagsParser.string({
124
+ multiple: false,
125
+ required: false,
126
+ hidden: true,
127
+ exclusive: ['static-assets-path'],
128
+ }),
129
+ 'static-assets-path': flagsParser.string({
94
130
  char: 's',
95
131
  multiple: false,
96
132
  required: false,
97
133
  description:
98
134
  '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.',
99
135
  }),
136
+ // Deprecated
100
137
  defaultFile: flagsParser.string({
138
+ multiple: false,
139
+ required: false,
140
+ hidden: true,
141
+ exclusive: ['default-file'],
142
+ }),
143
+ 'default-file': flagsParser.string({
101
144
  char: 'i',
102
145
  multiple: false,
103
146
  required: false,
@@ -111,7 +154,13 @@ export class PublishCommand extends Command {
111
154
  description:
112
155
  'Allow overwrite - Warn but do not fail if version exists. Discouraged outside of test envs if cacheable static files have changed.',
113
156
  }),
157
+ // Deprecated
114
158
  noCache: flagsParser.boolean({
159
+ required: false,
160
+ default: false,
161
+ hidden: true,
162
+ }),
163
+ 'no-cache': flagsParser.boolean({
115
164
  required: false,
116
165
  default: false,
117
166
  description: 'Force revalidation of CloudFront and browser caching of static assets',
@@ -149,14 +198,22 @@ export class PublishCommand extends Command {
149
198
  const RUNNING = ''; //chalk.reset.inverse.yellow.bold(RUNNING_TEXT) + ' ';
150
199
 
151
200
  const { flags: parsedFlags } = this.parse(PublishCommand);
152
- const appLambdaName = parsedFlags.appLambdaName ?? config.app.lambdaName;
153
- const appName = parsedFlags.appName ?? config.app.name;
154
- const deployerLambdaName = parsedFlags.deployerLambdaName ?? config.deployer.lambdaName;
155
- const semVer = parsedFlags.newVersion ?? config.app.semVer;
156
- const staticAssetsPath = parsedFlags.staticAssetsPath ?? config.app.staticAssetsPath;
157
- const defaultFile = parsedFlags.defaultFile ?? config.app.defaultFile;
201
+ const appLambdaName =
202
+ parsedFlags.appLambdaName ?? parsedFlags['app-lambda-name'] ?? config.app.lambdaName;
203
+ const appName = parsedFlags.appName ?? parsedFlags['app-name'] ?? config.app.name;
204
+ const deployerLambdaName =
205
+ parsedFlags.deployerLambdaName ??
206
+ parsedFlags['deployer-lambda-name'] ??
207
+ config.deployer.lambdaName;
208
+ const semVer = parsedFlags.newVersion ?? parsedFlags['new-version'] ?? config.app.semVer;
209
+ const staticAssetsPath =
210
+ parsedFlags.staticAssetsPath ??
211
+ parsedFlags['static-assets-path'] ??
212
+ config.app.staticAssetsPath;
213
+ const defaultFile =
214
+ parsedFlags.defaultFile ?? parsedFlags['default-file'] ?? config.app.defaultFile;
158
215
  const overwrite = parsedFlags.overwrite;
159
- const noCache = parsedFlags.noCache;
216
+ const noCache = parsedFlags.noCache ?? parsedFlags['no-cache'];
160
217
 
161
218
  // Override the config value
162
219
  config.deployer.lambdaName = deployerLambdaName;
@@ -331,11 +388,11 @@ export class PublishCommand extends Command {
331
388
  {
332
389
  enabled: () => !!config.app.staticAssetsPath,
333
390
  title: 'Enumerate Files to Upload to S3',
334
- task: async (ctx, task) => {
391
+ task: (ctx, task) => {
335
392
  const origTitle = task.title;
336
393
  task.title = RUNNING + origTitle;
337
394
 
338
- ctx.files = (await S3TransferUtility.GetFiles(S3Uploader.TempDir)) as string[];
395
+ ctx.files = S3TransferUtility.GetFiles(S3Uploader.TempDir);
339
396
 
340
397
  task.title = origTitle;
341
398
  },
@@ -343,7 +400,7 @@ export class PublishCommand extends Command {
343
400
  {
344
401
  enabled: () => !!config.app.staticAssetsPath,
345
402
  title: 'Upload Static Files to S3',
346
- task: (ctx, task) => {
403
+ task: async (ctx, task) => {
347
404
  const origTitle = task.title;
348
405
  task.title = RUNNING + origTitle;
349
406
 
@@ -363,50 +420,88 @@ export class PublishCommand extends Command {
363
420
 
364
421
  // Setup caching on static assets
365
422
  // NoCache - Only used for test deploys, requires browser and CloudFront to refetch every time
366
- // Overwrite - Reduces default cache time period from 24 hours to 15 minutes
367
423
  // Default - 24 hours
368
424
  const CacheControl = noCache
369
425
  ? 'max-age=0, must-revalidate, public'
370
- : overwrite
371
- ? `max-age=${15 * 60}, public`
372
426
  : `max-age=${24 * 60 * 60}, public`;
373
427
 
374
428
  const pathWithoutAppAndVer = path.join(S3Uploader.TempDir, destinationPrefix);
375
429
 
376
- const tasks: ListrTask<IContext>[] = ctx.files.map((filePath) => ({
377
- task: async (ctx: IContext, subtask) => {
378
- const relFilePath = path.relative(pathWithoutAppAndVer, filePath);
379
-
380
- const origTitle = relFilePath;
381
- subtask.title = RUNNING + origTitle;
382
-
383
- const upload = new Upload({
384
- client: s3Client,
385
- leavePartsOnError: false,
386
- params: {
387
- Bucket: bucketName,
388
- Key: path.relative(S3Uploader.TempDir, filePath),
389
- Body: createReadStream(filePath),
390
- ContentType: contentType(path.basename(filePath)) || 'application/octet-stream',
391
- CacheControl,
392
- },
393
- });
394
- await upload.done();
395
-
396
- subtask.title = origTitle;
397
- },
398
- }));
399
-
400
- task.title = origTitle;
401
-
402
- return task.newListr(tasks, {
403
- concurrent: 8,
404
- rendererOptions: {
405
- clearOutput: false,
406
- showErrorMessage: true,
407
- showTimer: true,
408
- },
409
- });
430
+ // Listr causes OOM if passes a list of, say, 5,000 to 20,000 files
431
+ if (ctx.files.length > 200) {
432
+ const fileCountMsgInterval = Math.floor(ctx.files.length / 10);
433
+ let filesPublished = 0;
434
+
435
+ await pMap(
436
+ ctx.files,
437
+ async (filePath: string) => {
438
+ // Can't use tasks for each file
439
+ const relFilePath = path.relative(pathWithoutAppAndVer, filePath);
440
+
441
+ if (
442
+ ctx.files.length > 1000 &&
443
+ (filesPublished % fileCountMsgInterval === 0 ||
444
+ filesPublished === ctx.files.length)
445
+ ) {
446
+ task.output = `Uploaded ${filesPublished} of ${ctx.files.length} files`;
447
+ } else if (ctx.files.length <= 1000) {
448
+ task.output = `Uploading ${relFilePath}`;
449
+ }
450
+
451
+ const upload = new Upload({
452
+ client: s3Client,
453
+ leavePartsOnError: false,
454
+ params: {
455
+ Bucket: bucketName,
456
+ Key: path.relative(S3Uploader.TempDir, filePath),
457
+ Body: createReadStream(filePath),
458
+ ContentType:
459
+ contentType(path.basename(filePath)) || 'application/octet-stream',
460
+ CacheControl,
461
+ },
462
+ });
463
+ await upload.done();
464
+ filesPublished++;
465
+ },
466
+ { concurrency: 40 },
467
+ );
468
+ } else {
469
+ const tasks: ListrTask<IContext>[] = ctx.files.map((filePath) => ({
470
+ task: async (ctx: IContext, subtask) => {
471
+ const relFilePath = path.relative(pathWithoutAppAndVer, filePath);
472
+
473
+ const origTitle = relFilePath;
474
+ subtask.title = RUNNING + origTitle;
475
+
476
+ const upload = new Upload({
477
+ client: s3Client,
478
+ leavePartsOnError: false,
479
+ params: {
480
+ Bucket: bucketName,
481
+ Key: path.relative(S3Uploader.TempDir, filePath),
482
+ Body: createReadStream(filePath),
483
+ ContentType:
484
+ contentType(path.basename(filePath)) || 'application/octet-stream',
485
+ CacheControl,
486
+ },
487
+ });
488
+ await upload.done();
489
+
490
+ subtask.title = origTitle;
491
+ },
492
+ }));
493
+
494
+ task.title = origTitle;
495
+
496
+ return task.newListr(tasks, {
497
+ concurrent: 20,
498
+ rendererOptions: {
499
+ clearOutput: false,
500
+ showErrorMessage: true,
501
+ showTimer: true,
502
+ },
503
+ });
504
+ }
410
505
  },
411
506
  },
412
507
  {
@@ -19,7 +19,7 @@ export interface IDeployVersionPreflightResult {
19
19
  response: IDeployVersionPreflightResponse;
20
20
  }
21
21
 
22
- export type DeployVersionArgs = Parameters<typeof DeployClient.DeployVersionLite>;
22
+ export type DeployVersionArgs = Parameters<typeof DeployClient.DeployVersionLite>[0];
23
23
 
24
24
  export default class DeployClient {
25
25
  static readonly _client = new lambda.LambdaClient({
@@ -0,0 +1,15 @@
1
+ import { S3TransferUtility } from './S3TransferUtility';
2
+ import path from 'path';
3
+
4
+ describe('S3TransferUtility', () => {
5
+ it('collects the files', () => {
6
+ const testFilesRoot = path.join(__dirname, '../../tests/files');
7
+ const files = S3TransferUtility.GetFiles(path.join(__dirname, '../../tests/files'));
8
+
9
+ expect(files).toBeDefined();
10
+ expect(files).toHaveLength(6);
11
+
12
+ const relativeFiles = files.map((file) => path.relative(testFilesRoot, file));
13
+ expect(relativeFiles).toMatchSnapshot();
14
+ });
15
+ });
@@ -2,7 +2,7 @@
2
2
  // From: https://stackoverflow.com/a/65862128/878903
3
3
  //
4
4
 
5
- import { promises as fs, createReadStream } from 'fs';
5
+ import { createReadStream, readdirSync } from 'fs';
6
6
  import * as path from 'path';
7
7
  import * as s3 from '@aws-sdk/client-s3';
8
8
  import { Upload } from '@aws-sdk/lib-storage';
@@ -10,7 +10,7 @@ import type { IDeployVersionPreflightResponse } from '@pwrdrvr/microapps-deploye
10
10
  import { contentType } from 'mime-types';
11
11
  import pMap from 'p-map';
12
12
 
13
- export default class S3TransferUtility {
13
+ export class S3TransferUtility {
14
14
  /**
15
15
  * @deprecated 2021-11-27
16
16
  *
@@ -36,7 +36,7 @@ export default class S3TransferUtility {
36
36
  });
37
37
 
38
38
  // console.log('Uploading files to S3');
39
- const files = (await S3TransferUtility.GetFiles(s3Path)) as string[];
39
+ const files = S3TransferUtility.GetFiles(s3Path);
40
40
  // const pathWithoutAppAndVer = path.join(s3Path, destPrefixPath);
41
41
  // for (const filePath of files) {
42
42
  // const relFilePath = path.relative(pathWithoutAppAndVer, filePath);
@@ -82,14 +82,32 @@ export default class S3TransferUtility {
82
82
  * @param dir
83
83
  * @returns
84
84
  */
85
- public static async GetFiles(dir: string): Promise<string | string[]> {
86
- const dirents = await fs.readdir(dir, { withFileTypes: true });
87
- const files = await Promise.all(
88
- dirents.map((dirent) => {
89
- const res = path.resolve(dir, dirent.name);
90
- return dirent.isDirectory() ? S3TransferUtility.GetFiles(res) : res;
91
- }),
92
- );
93
- return Array.prototype.concat(...files);
85
+ public static GetFiles(dir: string): string[] {
86
+ const dirents = readdirSync(dir, { withFileTypes: true });
87
+ const files: string[] = [];
88
+ const toScan = dirents.map((dirent) => ({
89
+ path: path.resolve(dir, dirent.name),
90
+ isDirectory: dirent.isDirectory(),
91
+ }));
92
+
93
+ // Iteratively collect all the files in the directories
94
+ // Do not use function call recursion
95
+ while (toScan.length > 0) {
96
+ const dirOrFile = toScan.pop();
97
+
98
+ if (dirOrFile.isDirectory) {
99
+ const direntsChild = readdirSync(dirOrFile.path, { withFileTypes: true });
100
+ const toScanChild = direntsChild.map((dirent) => ({
101
+ path: path.resolve(dirOrFile.path, dirent.name),
102
+ isDirectory: dirent.isDirectory(),
103
+ }));
104
+ Array.prototype.push.apply(toScan, toScanChild);
105
+ } else {
106
+ files.push(dirOrFile.path);
107
+ }
108
+ }
109
+
110
+ // Reverse the array so that the files are in the same order as the original
111
+ return files.reverse();
94
112
  }
95
113
  }
@@ -2,9 +2,9 @@ import * as path from 'path';
2
2
  import type { IDeployVersionPreflightResponse } from '@pwrdrvr/microapps-deployer-lib';
3
3
  import * as fs from 'fs-extra';
4
4
  import { IConfig } from '../config/Config';
5
- import S3TransferUtility from './S3TransferUtility';
5
+ import { S3TransferUtility } from './S3TransferUtility';
6
6
 
7
- export default class S3Uploader {
7
+ export class S3Uploader {
8
8
  /**
9
9
  * Copy files to local upload directory
10
10
  * @param config
@@ -0,0 +1,12 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`S3TransferUtility collects the files 1`] = `
4
+ [
5
+ "dir1/.touch",
6
+ "dir2/dir21/dir211/some211-1.json",
7
+ "dir2/dir21/dir211/some211-2.json",
8
+ "dir2/dir21/some21.json",
9
+ "dir2/dir22/some22.json",
10
+ "some.json",
11
+ ]
12
+ `;