@pwrdrvr/microapps-publish 0.0.20 → 0.0.21

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 (68) hide show
  1. package/bin/run +5 -0
  2. package/dist/commands/nextjs-docker-auto.d.ts +38 -0
  3. package/dist/commands/nextjs-docker-auto.d.ts.map +1 -0
  4. package/dist/commands/nextjs-docker-auto.js +415 -0
  5. package/dist/commands/nextjs-docker-auto.js.map +1 -0
  6. package/dist/commands/nextjs-version-restore.d.ts +14 -0
  7. package/dist/commands/nextjs-version-restore.d.ts.map +1 -0
  8. package/dist/commands/nextjs-version-restore.js +58 -0
  9. package/dist/commands/nextjs-version-restore.js.map +1 -0
  10. package/dist/commands/nextjs-version.d.ts +16 -0
  11. package/dist/commands/nextjs-version.d.ts.map +1 -0
  12. package/dist/commands/nextjs-version.js +94 -0
  13. package/dist/commands/nextjs-version.js.map +1 -0
  14. package/dist/commands/preflight.d.ts +15 -0
  15. package/dist/commands/preflight.d.ts.map +1 -0
  16. package/dist/commands/preflight.js +111 -0
  17. package/dist/commands/preflight.js.map +1 -0
  18. package/dist/commands/publish.d.ts +23 -0
  19. package/dist/commands/publish.d.ts.map +1 -0
  20. package/dist/commands/publish.js +300 -0
  21. package/dist/commands/publish.js.map +1 -0
  22. package/dist/config/Config.d.ts +0 -2
  23. package/dist/config/Config.d.ts.map +1 -1
  24. package/dist/config/Config.js +0 -5
  25. package/dist/config/Config.js.map +1 -1
  26. package/dist/index.d.ts +1 -64
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +3 -480
  29. package/dist/index.js.map +1 -1
  30. package/dist/{DeployClient.d.ts → lib/DeployClient.d.ts} +14 -6
  31. package/dist/lib/DeployClient.d.ts.map +1 -0
  32. package/dist/{DeployClient.js → lib/DeployClient.js} +15 -5
  33. package/dist/lib/DeployClient.js.map +1 -0
  34. package/dist/{S3TransferUtility.d.ts → lib/S3TransferUtility.d.ts} +0 -0
  35. package/dist/lib/S3TransferUtility.d.ts.map +1 -0
  36. package/dist/{S3TransferUtility.js → lib/S3TransferUtility.js} +0 -0
  37. package/dist/lib/S3TransferUtility.js.map +1 -0
  38. package/dist/{S3Uploader.d.ts → lib/S3Uploader.d.ts} +1 -1
  39. package/dist/lib/S3Uploader.d.ts.map +1 -0
  40. package/dist/{S3Uploader.js → lib/S3Uploader.js} +10 -10
  41. package/dist/lib/S3Uploader.js.map +1 -0
  42. package/dist/lib/Versions.d.ts +27 -0
  43. package/dist/lib/Versions.d.ts.map +1 -0
  44. package/dist/lib/Versions.js +76 -0
  45. package/dist/lib/Versions.js.map +1 -0
  46. package/package.json +11 -2
  47. package/src/commands/nextjs-docker-auto.ts +498 -0
  48. package/src/commands/nextjs-version-restore.ts +73 -0
  49. package/src/commands/nextjs-version.ts +113 -0
  50. package/src/commands/preflight.ts +121 -0
  51. package/src/commands/publish.ts +358 -0
  52. package/src/config/Config.ts +0 -4
  53. package/src/index.ts +1 -596
  54. package/src/{DeployClient.ts → lib/DeployClient.ts} +21 -12
  55. package/src/{S3TransferUtility.ts → lib/S3TransferUtility.ts} +0 -0
  56. package/src/{S3Uploader.ts → lib/S3Uploader.ts} +3 -3
  57. package/src/lib/Versions.ts +95 -0
  58. package/dist/DeployClient.d.ts.map +0 -1
  59. package/dist/DeployClient.js.map +0 -1
  60. package/dist/S3TransferUtility.d.ts.map +0 -1
  61. package/dist/S3TransferUtility.js.map +0 -1
  62. package/dist/S3Uploader.d.ts.map +0 -1
  63. package/dist/S3Uploader.js.map +0 -1
  64. package/dist/config/FileStore.d.ts +0 -7
  65. package/dist/config/FileStore.d.ts.map +0 -1
  66. package/dist/config/FileStore.js +0 -17
  67. package/dist/config/FileStore.js.map +0 -1
  68. package/src/config/FileStore.ts +0 -14
@@ -0,0 +1,121 @@
1
+ import 'reflect-metadata';
2
+ import * as sts from '@aws-sdk/client-sts';
3
+ import { Command, flags as flagsParser } from '@oclif/command';
4
+ import * as chalk from 'chalk';
5
+ import { Listr } from 'listr2';
6
+ import { Config } from '../config/Config';
7
+ import DeployClient from '../lib/DeployClient';
8
+
9
+ const RUNNING_TEXT = ' RUNS ';
10
+ const RUNNING = chalk.reset.inverse.yellow.bold(RUNNING_TEXT) + ' ';
11
+
12
+ export class PreflightCommand extends Command {
13
+ static description = 'Check if app/version are available';
14
+
15
+ static examples = [
16
+ `$ microapps-publish preflight -a release -n 0.0.13
17
+ ✔ Preflight Version Check [0.2s]
18
+ `,
19
+ ];
20
+
21
+ static flags = {
22
+ version: flagsParser.version({
23
+ char: 'v',
24
+ }),
25
+ help: flagsParser.help(),
26
+ appName: flagsParser.string({
27
+ char: 'a',
28
+ multiple: false,
29
+ required: false,
30
+ description: 'Name of the MicroApp',
31
+ }),
32
+ newVersion: flagsParser.string({
33
+ char: 'n',
34
+ multiple: false,
35
+ required: true,
36
+ description: 'New semantic version to apply',
37
+ }),
38
+ deployerLambdaName: flagsParser.string({
39
+ char: 'd',
40
+ multiple: false,
41
+ required: true,
42
+ description: 'Name of the deployer lambda function',
43
+ }),
44
+ };
45
+
46
+ async run(): Promise<void> {
47
+ const config = Config.instance;
48
+
49
+ const { flags: parsedFlags } = this.parse(PreflightCommand);
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;
54
+
55
+ // Override the config value
56
+ config.deployer.lambdaName = deployerLambdaName;
57
+ config.app.name = appName;
58
+ config.app.semVer = semVer;
59
+
60
+ // TODO: Pick and validate the appname/semver from the config and flags
61
+
62
+ // Get the account ID and region from STS
63
+ // TODO: Move this to the right place
64
+ if (config.app.awsAccountID === 0 || config.app.awsRegion === '') {
65
+ const stsClient = new sts.STSClient({
66
+ maxAttempts: 8,
67
+ });
68
+ const stsResponse = await stsClient.send(new sts.GetCallerIdentityCommand({}));
69
+ if (config.app.awsAccountID === 0) {
70
+ config.app.awsAccountID = parseInt(stsResponse.Account, 10);
71
+ }
72
+ if (config.app.awsRegion === '') {
73
+ config.app.awsRegion = stsClient.config.region as string;
74
+ }
75
+ }
76
+
77
+ if (config === undefined) {
78
+ this.error('Failed to load the config file');
79
+ }
80
+
81
+ //
82
+ // Setup Tasks
83
+ //
84
+
85
+ const tasks = new Listr(
86
+ [
87
+ {
88
+ title: 'Preflight Version Check',
89
+ task: async (ctx, task) => {
90
+ const origTitle = task.title;
91
+ task.title = RUNNING + origTitle;
92
+
93
+ // Confirm the Version Does Not Exist in Published State
94
+ task.output = `Checking if deployed app/version already exists for ${config.app.name}/${version}`;
95
+ const preflightResult = await DeployClient.DeployVersionPreflight({
96
+ config,
97
+ needS3Creds: false,
98
+ output: (message: string) => (task.output = message),
99
+ });
100
+ if (preflightResult.exists) {
101
+ task.output = `Warning: App/Version already exists: ${config.app.name}/${config.app.semVer}`;
102
+ }
103
+
104
+ task.title = origTitle;
105
+ },
106
+ },
107
+ ],
108
+ {
109
+ rendererOptions: {
110
+ showTimer: true,
111
+ },
112
+ },
113
+ );
114
+
115
+ try {
116
+ await tasks.run();
117
+ } catch (error) {
118
+ this.log(`Caught exception: ${error.message}`);
119
+ }
120
+ }
121
+ }
@@ -0,0 +1,358 @@
1
+ import 'reflect-metadata';
2
+ import * as util from 'util';
3
+ import * as lambda from '@aws-sdk/client-lambda';
4
+ import * as s3 from '@aws-sdk/client-s3';
5
+ import * as sts from '@aws-sdk/client-sts';
6
+ import { Command, flags as flagsParser } from '@oclif/command';
7
+ import * as chalk from 'chalk';
8
+ import * as path from 'path';
9
+ import { pathExists, createReadStream } from 'fs-extra';
10
+ import { Listr, ListrTask } from 'listr2';
11
+ 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';
15
+ import { Upload } from '@aws-sdk/lib-storage';
16
+ import { contentType } from 'mime-types';
17
+ import { TaskWrapper } from 'listr2/dist/lib/task-wrapper';
18
+ import { DefaultRenderer } from 'listr2/dist/renderer/default.renderer';
19
+ import { createVersions, IVersions } from '../lib/Versions';
20
+ const asyncSetTimeout = util.promisify(setTimeout);
21
+
22
+ const lambdaClient = new lambda.LambdaClient({
23
+ maxAttempts: 8,
24
+ });
25
+
26
+ interface IContext {
27
+ preflightResult: IDeployVersionPreflightResult;
28
+ files: string[];
29
+ }
30
+
31
+ export class PublishCommand extends Command {
32
+ static description =
33
+ 'Publish arbitrary framework app - deploy static assets to S3, alias the $LATEST Lambda function, and add route to API Gateway.';
34
+
35
+ static examples = [
36
+ `$ microapps-publish publish -d microapps-deployer-dev -n 0.0.21 -l microapps-app-release-dev -a release
37
+ ✔ Get S3 Temp Credentials [1s]
38
+ ✔ Deploy to Lambda [0.6s]
39
+ ✔ Confirm Static Assets Folder Exists [0.0s]
40
+ ✔ Copy Static Files to Local Upload Dir [0.0s]
41
+ ✔ Enumerate Files to Upload to S3 [0.0s]
42
+ ✔ Upload Static Files to S3 [1s]
43
+ ✔ Creating MicroApp Application: release [0.0s]
44
+ ✔ Creating MicroApp Version: 0.0.21 [1s]
45
+ `,
46
+ ];
47
+
48
+ static flags = {
49
+ version: flagsParser.version({
50
+ char: 'v',
51
+ }),
52
+ help: flagsParser.help(),
53
+ deployerLambdaName: flagsParser.string({
54
+ char: 'd',
55
+ multiple: false,
56
+ required: true,
57
+ description: 'Name of the deployer lambda function',
58
+ }),
59
+ newVersion: flagsParser.string({
60
+ char: 'n',
61
+ multiple: false,
62
+ required: true,
63
+ description: 'New semantic version to apply',
64
+ }),
65
+ appLambdaName: flagsParser.string({
66
+ char: 'l',
67
+ multiple: false,
68
+ required: false,
69
+ description: 'Name of the application lambda function',
70
+ }),
71
+ appName: flagsParser.string({
72
+ char: 'a',
73
+ multiple: false,
74
+ required: false,
75
+ description: 'MicroApps app name',
76
+ }),
77
+ };
78
+
79
+ private VersionAndAlias: IVersions;
80
+
81
+ async run(): Promise<void> {
82
+ const config = Config.instance;
83
+
84
+ const RUNNING_TEXT = ' RUNS ';
85
+ const RUNNING = chalk.reset.inverse.yellow.bold(RUNNING_TEXT) + ' ';
86
+
87
+ const { flags: parsedFlags } = this.parse(PublishCommand);
88
+ const appLambdaName = parsedFlags.appLambdaName ?? config.app.lambdaName;
89
+ const appName = parsedFlags.appName ?? config.app.name;
90
+ const deployerLambdaName = parsedFlags.deployerLambdaName ?? config.deployer.lambdaName;
91
+ const semVer = parsedFlags.newVersion ?? config.app.semVer;
92
+
93
+ // Override the config value
94
+ config.deployer.lambdaName = deployerLambdaName;
95
+ config.app.lambdaName = appLambdaName;
96
+ config.app.name = appName;
97
+ config.app.semVer = semVer;
98
+
99
+ // Get the account ID and region from STS
100
+ // TODO: Move this to the right place
101
+ if (config.app.awsAccountID === 0 || config.app.awsRegion === '') {
102
+ const stsClient = new sts.STSClient({
103
+ maxAttempts: 8,
104
+ });
105
+ const stsResponse = await stsClient.send(new sts.GetCallerIdentityCommand({}));
106
+ if (config.app.awsAccountID === 0) {
107
+ config.app.awsAccountID = parseInt(stsResponse.Account, 10);
108
+ }
109
+ if (config.app.awsRegion === '') {
110
+ config.app.awsRegion = stsClient.config.region as string;
111
+ }
112
+ }
113
+
114
+ this.VersionAndAlias = createVersions(semVer);
115
+
116
+ if (config === undefined) {
117
+ this.error('Failed to load the config file');
118
+ }
119
+ if (config.app.staticAssetsPath === undefined) {
120
+ this.error('StaticAssetsPath must be specified in the config file');
121
+ }
122
+
123
+ //
124
+ // Setup Tasks
125
+ //
126
+
127
+ const tasks = new Listr<IContext>(
128
+ [
129
+ {
130
+ // TODO: Disable this task if no static assets path
131
+ title: 'Get S3 Temp Credentials',
132
+ task: async (ctx, task) => {
133
+ const origTitle = task.title;
134
+ task.title = RUNNING + origTitle;
135
+
136
+ // Confirm the Version Does Not Exist in Published State
137
+ task.output = `Checking if deployed app/version already exists for ${config.app.name}/${semVer}`;
138
+ ctx.preflightResult = await DeployClient.DeployVersionPreflight({
139
+ config,
140
+ output: (message: string) => (task.output = message),
141
+ });
142
+ if (ctx.preflightResult.exists) {
143
+ task.output = `Warning: App/Version already exists: ${config.app.name}/${config.app.semVer}`;
144
+ }
145
+
146
+ task.title = origTitle;
147
+ },
148
+ },
149
+ {
150
+ // TODO: Disable this task if no Lambda function
151
+ title: 'Deploy to Lambda',
152
+ task: async (ctx, task) => {
153
+ const origTitle = task.title;
154
+ task.title = RUNNING + origTitle;
155
+
156
+ // Update the Lambda function
157
+ await this.deployToLambda(config, this.VersionAndAlias, task);
158
+
159
+ task.title = origTitle;
160
+ },
161
+ },
162
+ {
163
+ // TODO: Disable this task if no static assets path
164
+ title: 'Confirm Static Assets Folder Exists',
165
+ task: async (ctx, task) => {
166
+ const origTitle = task.title;
167
+ task.title = RUNNING + origTitle;
168
+
169
+ // Check that Static Assets Folder exists
170
+ if (!(await pathExists(config.app.staticAssetsPath))) {
171
+ this.error(`Static asset path does not exist: ${config.app.staticAssetsPath}`);
172
+ }
173
+
174
+ task.title = origTitle;
175
+ },
176
+ },
177
+ {
178
+ // TODO: Disable this task if no static assets path
179
+ title: 'Copy Static Files to Local Upload Dir',
180
+ task: async (ctx, task) => {
181
+ const origTitle = task.title;
182
+ task.title = RUNNING + origTitle;
183
+
184
+ // Copy files to local dir to be uploaded
185
+ await S3Uploader.CopyToUploadDir(config, ctx.preflightResult.response.s3UploadUrl);
186
+
187
+ task.title = origTitle;
188
+ },
189
+ },
190
+ {
191
+ // TODO: Disable this task if no static assets path
192
+ title: 'Enumerate Files to Upload to S3',
193
+ task: async (ctx, task) => {
194
+ const origTitle = task.title;
195
+ task.title = RUNNING + origTitle;
196
+
197
+ ctx.files = (await S3TransferUtility.GetFiles(S3Uploader.TempDir)) as string[];
198
+
199
+ task.title = origTitle;
200
+ },
201
+ },
202
+ {
203
+ // TODO: Disable this task if no static assets path
204
+ title: 'Upload Static Files to S3',
205
+ task: (ctx, task) => {
206
+ const origTitle = task.title;
207
+ task.title = RUNNING + origTitle;
208
+
209
+ const { bucketName, destinationPrefix } = S3Uploader.ParseUploadPath(
210
+ ctx.preflightResult.response.s3UploadUrl,
211
+ );
212
+
213
+ // Use temp credentials for S3
214
+ const s3Client = new s3.S3Client({
215
+ maxAttempts: 16,
216
+ credentials: {
217
+ accessKeyId: ctx.preflightResult.response.awsCredentials.accessKeyId,
218
+ secretAccessKey: ctx.preflightResult.response.awsCredentials.secretAccessKey,
219
+ sessionToken: ctx.preflightResult.response.awsCredentials.sessionToken,
220
+ },
221
+ });
222
+
223
+ const pathWithoutAppAndVer = path.join(S3Uploader.TempDir, destinationPrefix);
224
+
225
+ const tasks: ListrTask<IContext>[] = ctx.files.map((filePath) => ({
226
+ task: async (ctx: IContext, subtask) => {
227
+ const relFilePath = path.relative(pathWithoutAppAndVer, filePath);
228
+
229
+ const origTitle = relFilePath;
230
+ subtask.title = RUNNING + origTitle;
231
+
232
+ const upload = new Upload({
233
+ client: s3Client,
234
+ leavePartsOnError: false,
235
+ params: {
236
+ Bucket: bucketName,
237
+ Key: path.relative(S3Uploader.TempDir, filePath),
238
+ Body: createReadStream(filePath),
239
+ ContentType: contentType(path.basename(filePath)) || 'application/octet-stream',
240
+ CacheControl: 'max-age=86400; public',
241
+ },
242
+ });
243
+ await upload.done();
244
+
245
+ subtask.title = origTitle;
246
+ },
247
+ }));
248
+
249
+ task.title = origTitle;
250
+
251
+ return task.newListr(tasks, {
252
+ concurrent: 8,
253
+ rendererOptions: {
254
+ clearOutput: false,
255
+ showErrorMessage: true,
256
+ showTimer: true,
257
+ },
258
+ });
259
+ },
260
+ },
261
+ {
262
+ title: `Creating MicroApp Application: ${config.app.name}`,
263
+ task: async (ctx, task) => {
264
+ const origTitle = task.title;
265
+ task.title = RUNNING + origTitle;
266
+
267
+ // Call Deployer to Create App if Not Exists
268
+ await DeployClient.CreateApp(config);
269
+
270
+ task.title = origTitle;
271
+ },
272
+ },
273
+ {
274
+ title: `Creating MicroApp Version: ${config.app.semVer}`,
275
+ task: async (ctx, task) => {
276
+ const origTitle = task.title;
277
+ task.title = RUNNING + origTitle;
278
+
279
+ // Call Deployer to Deploy AppName/Version
280
+ await DeployClient.DeployVersion(config, (message: string) => (task.output = message));
281
+
282
+ task.title = origTitle;
283
+ },
284
+ },
285
+ ],
286
+ {
287
+ rendererOptions: {
288
+ showTimer: true,
289
+ },
290
+ },
291
+ );
292
+
293
+ try {
294
+ await tasks.run();
295
+ // this.log(`Published: ${config.app.name}/${config.app.semVer}`);
296
+ } catch (error) {
297
+ this.log(`Caught exception: ${error.message}`);
298
+ } finally {
299
+ await S3Uploader.removeTempDirIfExists();
300
+ }
301
+ }
302
+
303
+ /**
304
+ * Publish an app version to Lambda
305
+ * @param config
306
+ * @param versions
307
+ */
308
+ private async deployToLambda(
309
+ config: IConfig,
310
+ versions: IVersions,
311
+ task: TaskWrapper<IContext, typeof DefaultRenderer>,
312
+ ): Promise<void> {
313
+ // Create Lambda version
314
+ task.output = 'Creating version for Lambda $LATEST';
315
+ const resultUpdate = await lambdaClient.send(
316
+ new lambda.PublishVersionCommand({
317
+ FunctionName: config.app.lambdaName,
318
+ }),
319
+ );
320
+ const lambdaVersion = resultUpdate.Version;
321
+ task.output = `Lambda version created: ${resultUpdate.Version}`;
322
+
323
+ let lastUpdateStatus = resultUpdate.LastUpdateStatus;
324
+ for (let i = 0; i < 5; i++) {
325
+ // When the function is created the status will be "Pending"
326
+ // and we have to wait until it's done creating
327
+ // before we can point an alias to it
328
+ if (lastUpdateStatus === 'Successful') {
329
+ task.output = `Lambda function updated, version: ${lambdaVersion}`;
330
+ break;
331
+ }
332
+
333
+ // If it didn't work, wait and try again
334
+ await asyncSetTimeout(1000 * i);
335
+
336
+ const resultGet = await lambdaClient.send(
337
+ new lambda.GetFunctionCommand({
338
+ FunctionName: config.app.lambdaName,
339
+ Qualifier: lambdaVersion,
340
+ }),
341
+ );
342
+
343
+ // Save the last update status so we can check on re-loop
344
+ lastUpdateStatus = resultGet?.Configuration?.LastUpdateStatus;
345
+ }
346
+
347
+ // Create Lambda alias point
348
+ task.output = `Creating the lambda alias for the new version: ${lambdaVersion}`;
349
+ const resultLambdaAlias = await lambdaClient.send(
350
+ new lambda.CreateAliasCommand({
351
+ FunctionName: config.app.lambdaName,
352
+ Name: versions.alias,
353
+ FunctionVersion: lambdaVersion,
354
+ }),
355
+ );
356
+ task.output = `Lambda alias created, name: ${resultLambdaAlias.Name}`;
357
+ }
358
+ }
@@ -5,7 +5,6 @@ import { TSConvict } from 'ts-convict';
5
5
  import { FilesExist } from '../lib/FilesExist';
6
6
  import { ApplicationConfig, IApplicationConfig } from './Application';
7
7
  import { DeployerConfig, IDeployerConfig } from './Deployer';
8
- import { FileStoreConfig, IFileStoreRename } from './FileStore';
9
8
 
10
9
  export interface IConfig {
11
10
  deployer: IDeployerConfig;
@@ -76,7 +75,4 @@ export class Config implements IConfig {
76
75
 
77
76
  @convict.Property(DeployerConfig)
78
77
  public deployer!: IDeployerConfig;
79
-
80
- @convict.Property(FileStoreConfig)
81
- public filestore!: IFileStoreRename;
82
78
  }