Skip to content

Commit e81ceec

Browse files
Merge pull request #24 from contentstack/development
fix: --redeploy-latest and --redeploy-last-upload, warning, and prompts
2 parents c201eb3 + b1b5dd6 commit e81ceec

File tree

21 files changed

+913
-176
lines changed

21 files changed

+913
-176
lines changed

README.md

Lines changed: 8 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ With Launch CLI, you can interact with the Contentstack Launch platform using th
88
* [Launch CLI plugin](#launch-cli-plugin)
99
* [Installation steps](#installation-steps)
1010
* [Commands](#commands)
11-
* [How to test Changes Locally?](#how-to-test-changes-locally)
12-
* [Release & SRE Process:-](#release--sre-process-)
11+
* [How to do development Locally?](#how-to-do-development-locally)
12+
* [How to run tests Locally?](#how-to-run-tests-locally)
1313
<!-- tocstop -->
1414

1515
# Installation steps
@@ -43,7 +43,7 @@ Run cloud functions locally
4343

4444

4545

46-
# How to test Changes Locally?
46+
# How to do development Locally?
4747
- Branch out from development for development.
4848
- Install npm: @contentstack/cli
4949
- Set region and log in using csdx config:set:region & csdx login
@@ -68,42 +68,16 @@ csdx plugins:link <plugin local path>
6868
csdx <command-name>
6969
```
7070

71+
# How to run tests Locally?
72+
Step 1:- csdx config:set:region <region> Mentioned project should exists in provided org
7173

72-
# Release & SRE Process:-
74+
Step 2:- csdx login
7375

74-
Version Increment:
76+
Step 3:- Create env on root level (refer: example.env file)
7577

76-
Patch version update (fixes): 1.0.0 → 1.0.1
78+
Step 4:- run test cases (`npm run test:unit` or `npm run test:unit:report`)
7779

78-
Minor version update (enhancements): 1.0.0 → 1.1.0
7980

80-
Major version update (breaking changes): 1.0.0 → 2.0.0
81-
82-
## For release:
83-
84-
- Raise a draft pull request (PR) from the development branch to the main branch.
85-
86-
### Pre-release SRE Preparation:
87-
88-
- Create an SRE ticket a week before the release date, including the PR.
89-
90-
- After the SRE review, address any identified issues and create tickets for any new issues.
91-
92-
- Request SRE approval if no issues are identified or after fixing all the SRE-raised issues.
93-
94-
### CAB Approval:
95-
96-
- For CAB, prepare deployment plan sheets(including publish & rollback plan).
97-
98-
- Once SRE approves, raise the request for CAB(SRE ticket, deployment plan, release tickets, release notes). At least two CAB approvals are required.
99-
100-
- Obtain approval for the PR from the security admin (Aravind) and launch admin.
101-
102-
### Merge and Release:
103-
104-
- After getting the necessary approvals, merge the PR. This will trigger the publishing process on npm and GitHub, which can be tracked through the actions & github released tags.
105-
106-
10781

10882
### How will changes be reflected in the CLI ?
10983
If a patch or minor version of the launch is released, users will need to update or install the latest CLI version, which will automatically include the latest launch version.

bin/dev

Lines changed: 0 additions & 17 deletions
This file was deleted.

bin/dev.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env node_modules/.bin/ts-node
2+
// eslint-disable-next-line node/shebang, unicorn/prefer-top-level-await
3+
(async () => {
4+
const oclif = await import('@oclif/core');
5+
await oclif.execute({ development: true, dir: __dirname });
6+
})();

bin/run

Lines changed: 0 additions & 5 deletions
This file was deleted.

bin/run.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/env node
2+
3+
// eslint-disable-next-line unicorn/prefer-top-level-await
4+
(async () => {
5+
const oclif = await import('@oclif/core');
6+
await oclif.execute({ development: false, dir: __dirname });
7+
})();

example.env

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
ENVIRONMENT=<DEPLOYMENT_ENVIRONMENT>
2-
ORG=<ORG_UID>
3-
PROJECT=<PROJECT_NAME>
1+
ORG=organizationuid
2+
ENVIRONMENT=environmentname
3+
PROJECT=projectname

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@contentstack/cli-launch",
3-
"version": "1.5.2",
3+
"version": "1.6.0",
44
"description": "Launch related operations",
55
"author": "Contentstack CLI",
66
"bin": {

src/adapters/base-class.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ export default class BaseClass {
394394
data.project = this.config.currentConfig;
395395
}
396396

397-
writeFileSync(`${this.config.projectBasePath}/${this.config.configName}`, JSON.stringify(data), {
397+
writeFileSync(this.config.config, JSON.stringify(data), {
398398
encoding: 'utf8',
399399
flag: 'w',
400400
});

src/adapters/file-upload.ts

Lines changed: 94 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ import { print } from '../util';
1414
import BaseClass from './base-class';
1515
import { getFileList } from '../util/fs';
1616
import { createSignedUploadUrlMutation, importProjectMutation } from '../graphql';
17+
import { SignedUploadUrlData, FileUploadMethod } from '../types/launch';
18+
import config from '../config';
1719

1820
export default class FileUpload extends BaseClass {
19-
private signedUploadUrlData!: Record<string, any>;
20-
2121
/**
2222
* @method run
2323
*
@@ -26,28 +26,9 @@ export default class FileUpload extends BaseClass {
2626
*/
2727
async run(): Promise<void> {
2828
if (this.config.isExistingProject) {
29-
await this.initApolloClient();
30-
const uploadLastFile =
31-
this.config['redeploy-last-upload'] ||
32-
(await cliux.inquire({
33-
type: 'confirm',
34-
default: false,
35-
name: 'uploadLastFile',
36-
message: 'Redeploy with last file upload?',
37-
}));
38-
if (!uploadLastFile) {
39-
await this.createSignedUploadUrl();
40-
const { zipName, zipPath } = await this.archive();
41-
await this.uploadFile(zipName, zipPath);
42-
}
43-
44-
const { uploadUid } = this.signedUploadUrlData || {
45-
uploadUid: undefined,
46-
};
47-
await this.createNewDeployment(true, uploadUid);
29+
await this.handleExistingProject();
4830
} else {
49-
await this.prepareForNewProjectCreation();
50-
await this.createNewProject();
31+
await this.handleNewProject();
5132
}
5233

5334
this.prepareLaunchConfig();
@@ -56,13 +37,79 @@ export default class FileUpload extends BaseClass {
5637
this.showSuggestion();
5738
}
5839

40+
private async handleExistingProject(): Promise<void> {
41+
await this.initApolloClient();
42+
43+
let redeployLatest = this.config['redeploy-latest'];
44+
let redeployLastUpload = this.config['redeploy-last-upload'];
45+
46+
if (!redeployLatest && !redeployLastUpload) {
47+
await this.confirmRedeployment();
48+
const latestRedeploymentConfirmed = await this.confirmLatestRedeployment();
49+
redeployLatest = latestRedeploymentConfirmed;
50+
redeployLastUpload = !latestRedeploymentConfirmed;
51+
}
52+
53+
if (redeployLastUpload && redeployLatest) {
54+
this.log('redeploy-last-upload and redeploy-latest flags are not supported together.', 'error');
55+
this.exit(1);
56+
}
57+
58+
let uploadUid;
59+
if (redeployLatest) {
60+
const signedUploadUrlData = await this.createSignedUploadUrl();
61+
uploadUid = signedUploadUrlData.uploadUid;
62+
const { zipName, zipPath } = await this.archive();
63+
await this.uploadFile(zipName, zipPath, signedUploadUrlData);
64+
}
65+
66+
await this.createNewDeployment(true, uploadUid);
67+
}
68+
69+
private async confirmRedeployment(): Promise<void> {
70+
const redeploy = await cliux.inquire({
71+
type: 'confirm',
72+
name: 'deployLatestCommit',
73+
message: 'Do you want to redeploy this existing Launch project?',
74+
});
75+
if (!redeploy) {
76+
this.log('Project redeployment aborted.', 'info');
77+
this.exit(1);
78+
}
79+
}
80+
81+
private async confirmLatestRedeployment(): Promise<boolean | void> {
82+
const choices = [
83+
...map(config.supportedFileUploadMethods, (fileUploadMethod) => ({
84+
value: fileUploadMethod,
85+
name: `Redeploy with ${fileUploadMethod}`,
86+
}))
87+
];
88+
89+
const selectedFileUploadMethod: FileUploadMethod = await cliux.inquire({
90+
choices: choices,
91+
type: 'search-list',
92+
name: 'fileUploadMethod',
93+
message: 'Choose a redeploy method to proceed',
94+
});
95+
if (selectedFileUploadMethod === FileUploadMethod.LastFileUpload) {
96+
return false;
97+
}
98+
return true;
99+
}
100+
101+
private async handleNewProject(): Promise<void> {
102+
const uploadUid = await this.prepareAndUploadNewProjectFile();
103+
await this.createNewProject(uploadUid);
104+
}
105+
59106
/**
60107
* @method createNewProject - Create new launch project
61108
*
62109
* @return {*} {Promise<void>}
63110
* @memberof FileUpload
64111
*/
65-
async createNewProject(): Promise<void> {
112+
async createNewProject(uploadUid: string): Promise<void> {
66113
const { framework, projectName, buildCommand, outputDirectory, environmentName, serverCommand } = this.config;
67114
await this.apolloClient
68115
.mutate({
@@ -71,7 +118,7 @@ export default class FileUpload extends BaseClass {
71118
project: {
72119
projectType: 'FILEUPLOAD',
73120
name: projectName,
74-
fileUpload: { uploadUid: this.signedUploadUrlData.uploadUid },
121+
fileUpload: { uploadUid },
75122
environment: {
76123
frameworkPreset: framework,
77124
outputDirectory: outputDirectory,
@@ -95,7 +142,7 @@ export default class FileUpload extends BaseClass {
95142
const canRetry = await this.handleNewProjectCreationError(error);
96143

97144
if (canRetry) {
98-
return this.createNewProject();
145+
return this.createNewProject(uploadUid);
99146
}
100147
});
101148
}
@@ -106,7 +153,7 @@ export default class FileUpload extends BaseClass {
106153
* @return {*} {Promise<void>}
107154
* @memberof FileUpload
108155
*/
109-
async prepareForNewProjectCreation(): Promise<void> {
156+
async prepareAndUploadNewProjectFile(): Promise<string> {
110157
const {
111158
name,
112159
framework,
@@ -123,9 +170,9 @@ export default class FileUpload extends BaseClass {
123170
this.config.deliveryToken = token;
124171
// this.fileValidation();
125172
await this.selectOrg();
126-
await this.createSignedUploadUrl();
173+
const signedUploadUrlData = await this.createSignedUploadUrl();
127174
const { zipName, zipPath, projectName } = await this.archive();
128-
await this.uploadFile(zipName, zipPath);
175+
await this.uploadFile(zipName, zipPath, signedUploadUrlData);
129176
this.config.projectName =
130177
name ||
131178
(await cliux.inquire({
@@ -186,6 +233,7 @@ export default class FileUpload extends BaseClass {
186233
this.config.variableType = variableType as unknown as string;
187234
this.config.envVariables = envVariables;
188235
await this.handleEnvImportFlow();
236+
return signedUploadUrlData.uploadUid;
189237
}
190238

191239
/**
@@ -251,19 +299,23 @@ export default class FileUpload extends BaseClass {
251299
/**
252300
* @method createSignedUploadUrl - create pre signed url for file upload
253301
*
254-
* @return {*} {Promise<void>}
302+
* @return {*} {Promise<SignedUploadUrlData>}
255303
* @memberof FileUpload
256304
*/
257-
async createSignedUploadUrl(): Promise<void> {
258-
this.signedUploadUrlData = await this.apolloClient
259-
.mutate({ mutation: createSignedUploadUrlMutation })
260-
.then(({ data: { signedUploadUrl } }) => signedUploadUrl)
261-
.catch((error) => {
262-
this.log('Something went wrong. Please try again.', 'warn');
263-
this.log(error, 'error');
264-
this.exit(1);
265-
});
266-
this.config.uploadUid = this.signedUploadUrlData.uploadUid;
305+
async createSignedUploadUrl(): Promise<SignedUploadUrlData> {
306+
try {
307+
const result = await this.apolloClient.mutate({ mutation: createSignedUploadUrlMutation });
308+
const signedUploadUrlData = result.data.signedUploadUrl;
309+
this.config.uploadUid = signedUploadUrlData.uploadUid;
310+
return signedUploadUrlData;
311+
} catch (error) {
312+
this.log('Something went wrong. Please try again.', 'warn');
313+
if (error instanceof Error) {
314+
this.log(error.message, 'error');
315+
}
316+
this.exit(1);
317+
return {} as SignedUploadUrlData;
318+
}
267319
}
268320

269321
/**
@@ -274,8 +326,8 @@ export default class FileUpload extends BaseClass {
274326
* @return {*} {Promise<void>}
275327
* @memberof FileUpload
276328
*/
277-
async uploadFile(fileName: string, filePath: PathLike): Promise<void> {
278-
const { uploadUrl, fields, headers, method } = this.signedUploadUrlData;
329+
async uploadFile(fileName: string, filePath: PathLike, signedUploadUrlData: SignedUploadUrlData): Promise<void> {
330+
const { uploadUrl, fields, headers, method } = signedUploadUrlData;
279331
const formData = new FormData();
280332

281333
if (!isEmpty(fields)) {

0 commit comments

Comments
 (0)