Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 8 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ With Launch CLI, you can interact with the Contentstack Launch platform using th
* [Launch CLI plugin](#launch-cli-plugin)
* [Installation steps](#installation-steps)
* [Commands](#commands)
* [How to test Changes Locally?](#how-to-test-changes-locally)
* [Release & SRE Process:-](#release--sre-process-)
* [How to do development Locally?](#how-to-do-development-locally)
* [How to run tests Locally?](#how-to-run-tests-locally)
<!-- tocstop -->

# Installation steps
Expand Down Expand Up @@ -43,7 +43,7 @@ Run cloud functions locally



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

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

# Release & SRE Process:-
Step 2:- csdx login

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

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

Minor version update (enhancements): 1.0.0 → 1.1.0

Major version update (breaking changes): 1.0.0 → 2.0.0

## For release:

- Raise a draft pull request (PR) from the development branch to the main branch.

### Pre-release SRE Preparation:

- Create an SRE ticket a week before the release date, including the PR.

- After the SRE review, address any identified issues and create tickets for any new issues.

- Request SRE approval if no issues are identified or after fixing all the SRE-raised issues.

### CAB Approval:

- For CAB, prepare deployment plan sheets(including publish & rollback plan).

- Once SRE approves, raise the request for CAB(SRE ticket, deployment plan, release tickets, release notes). At least two CAB approvals are required.

- Obtain approval for the PR from the security admin (Aravind) and launch admin.

### Merge and Release:

- 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.



### How will changes be reflected in the CLI ?
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.
Expand Down
17 changes: 0 additions & 17 deletions bin/dev

This file was deleted.

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

This file was deleted.

7 changes: 7 additions & 0 deletions bin/run.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env node

// eslint-disable-next-line unicorn/prefer-top-level-await
(async () => {
const oclif = await import('@oclif/core');
await oclif.execute({ development: false, dir: __dirname });
})();
6 changes: 3 additions & 3 deletions example.env
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
ENVIRONMENT=<DEPLOYMENT_ENVIRONMENT>
ORG=<ORG_UID>
PROJECT=<PROJECT_NAME>
ORG=organizationuid
ENVIRONMENT=environmentname
PROJECT=projectname
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@contentstack/cli-launch",
"version": "1.5.2",
"version": "1.6.0",
"description": "Launch related operations",
"author": "Contentstack CLI",
"bin": {
Expand Down
2 changes: 1 addition & 1 deletion src/adapters/base-class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ export default class BaseClass {
data.project = this.config.currentConfig;
}

writeFileSync(`${this.config.projectBasePath}/${this.config.configName}`, JSON.stringify(data), {
writeFileSync(this.config.config, JSON.stringify(data), {
encoding: 'utf8',
flag: 'w',
});
Expand Down
136 changes: 94 additions & 42 deletions src/adapters/file-upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import { print } from '../util';
import BaseClass from './base-class';
import { getFileList } from '../util/fs';
import { createSignedUploadUrlMutation, importProjectMutation } from '../graphql';
import { SignedUploadUrlData, FileUploadMethod } from '../types/launch';
import config from '../config';

export default class FileUpload extends BaseClass {
private signedUploadUrlData!: Record<string, any>;

/**
* @method run
*
Expand All @@ -26,28 +26,9 @@ export default class FileUpload extends BaseClass {
*/
async run(): Promise<void> {
if (this.config.isExistingProject) {
await this.initApolloClient();
const uploadLastFile =
this.config['redeploy-last-upload'] ||
(await cliux.inquire({
type: 'confirm',
default: false,
name: 'uploadLastFile',
message: 'Redeploy with last file upload?',
}));
if (!uploadLastFile) {
await this.createSignedUploadUrl();
const { zipName, zipPath } = await this.archive();
await this.uploadFile(zipName, zipPath);
}

const { uploadUid } = this.signedUploadUrlData || {
uploadUid: undefined,
};
await this.createNewDeployment(true, uploadUid);
await this.handleExistingProject();
} else {
await this.prepareForNewProjectCreation();
await this.createNewProject();
await this.handleNewProject();
}

this.prepareLaunchConfig();
Expand All @@ -56,13 +37,79 @@ export default class FileUpload extends BaseClass {
this.showSuggestion();
}

private async handleExistingProject(): Promise<void> {
await this.initApolloClient();

let redeployLatest = this.config['redeploy-latest'];
let redeployLastUpload = this.config['redeploy-last-upload'];

if (!redeployLatest && !redeployLastUpload) {
await this.confirmRedeployment();
const latestRedeploymentConfirmed = await this.confirmLatestRedeployment();
redeployLatest = latestRedeploymentConfirmed;
redeployLastUpload = !latestRedeploymentConfirmed;
}

if (redeployLastUpload && redeployLatest) {
this.log('redeploy-last-upload and redeploy-latest flags are not supported together.', 'error');
this.exit(1);
}

let uploadUid;
if (redeployLatest) {
const signedUploadUrlData = await this.createSignedUploadUrl();
uploadUid = signedUploadUrlData.uploadUid;
const { zipName, zipPath } = await this.archive();
await this.uploadFile(zipName, zipPath, signedUploadUrlData);
}

await this.createNewDeployment(true, uploadUid);
}

private async confirmRedeployment(): Promise<void> {
const redeploy = await cliux.inquire({
type: 'confirm',
name: 'deployLatestCommit',
message: 'Do you want to redeploy this existing Launch project?',
});
if (!redeploy) {
this.log('Project redeployment aborted.', 'info');
this.exit(1);
}
}

private async confirmLatestRedeployment(): Promise<boolean | void> {
const choices = [
...map(config.supportedFileUploadMethods, (fileUploadMethod) => ({
value: fileUploadMethod,
name: `Redeploy with ${fileUploadMethod}`,
}))
];

const selectedFileUploadMethod: FileUploadMethod = await cliux.inquire({
choices: choices,
type: 'search-list',
name: 'fileUploadMethod',
message: 'Choose a redeploy method to proceed',
});
if (selectedFileUploadMethod === FileUploadMethod.LastFileUpload) {
return false;
}
return true;
}

private async handleNewProject(): Promise<void> {
const uploadUid = await this.prepareAndUploadNewProjectFile();
await this.createNewProject(uploadUid);
}

/**
* @method createNewProject - Create new launch project
*
* @return {*} {Promise<void>}
* @memberof FileUpload
*/
async createNewProject(): Promise<void> {
async createNewProject(uploadUid: string): Promise<void> {
const { framework, projectName, buildCommand, outputDirectory, environmentName, serverCommand } = this.config;
await this.apolloClient
.mutate({
Expand All @@ -71,7 +118,7 @@ export default class FileUpload extends BaseClass {
project: {
projectType: 'FILEUPLOAD',
name: projectName,
fileUpload: { uploadUid: this.signedUploadUrlData.uploadUid },
fileUpload: { uploadUid },
environment: {
frameworkPreset: framework,
outputDirectory: outputDirectory,
Expand All @@ -95,7 +142,7 @@ export default class FileUpload extends BaseClass {
const canRetry = await this.handleNewProjectCreationError(error);

if (canRetry) {
return this.createNewProject();
return this.createNewProject(uploadUid);
}
});
}
Expand All @@ -106,7 +153,7 @@ export default class FileUpload extends BaseClass {
* @return {*} {Promise<void>}
* @memberof FileUpload
*/
async prepareForNewProjectCreation(): Promise<void> {
async prepareAndUploadNewProjectFile(): Promise<string> {
const {
name,
framework,
Expand All @@ -123,9 +170,9 @@ export default class FileUpload extends BaseClass {
this.config.deliveryToken = token;
// this.fileValidation();
await this.selectOrg();
await this.createSignedUploadUrl();
const signedUploadUrlData = await this.createSignedUploadUrl();
const { zipName, zipPath, projectName } = await this.archive();
await this.uploadFile(zipName, zipPath);
await this.uploadFile(zipName, zipPath, signedUploadUrlData);
this.config.projectName =
name ||
(await cliux.inquire({
Expand Down Expand Up @@ -186,6 +233,7 @@ export default class FileUpload extends BaseClass {
this.config.variableType = variableType as unknown as string;
this.config.envVariables = envVariables;
await this.handleEnvImportFlow();
return signedUploadUrlData.uploadUid;
}

/**
Expand Down Expand Up @@ -251,19 +299,23 @@ export default class FileUpload extends BaseClass {
/**
* @method createSignedUploadUrl - create pre signed url for file upload
*
* @return {*} {Promise<void>}
* @return {*} {Promise<SignedUploadUrlData>}
* @memberof FileUpload
*/
async createSignedUploadUrl(): Promise<void> {
this.signedUploadUrlData = await this.apolloClient
.mutate({ mutation: createSignedUploadUrlMutation })
.then(({ data: { signedUploadUrl } }) => signedUploadUrl)
.catch((error) => {
this.log('Something went wrong. Please try again.', 'warn');
this.log(error, 'error');
this.exit(1);
});
this.config.uploadUid = this.signedUploadUrlData.uploadUid;
async createSignedUploadUrl(): Promise<SignedUploadUrlData> {
try {
const result = await this.apolloClient.mutate({ mutation: createSignedUploadUrlMutation });
const signedUploadUrlData = result.data.signedUploadUrl;
this.config.uploadUid = signedUploadUrlData.uploadUid;
return signedUploadUrlData;
} catch (error) {
this.log('Something went wrong. Please try again.', 'warn');
if (error instanceof Error) {
this.log(error.message, 'error');
}
this.exit(1);
return {} as SignedUploadUrlData;
}
}

/**
Expand All @@ -274,8 +326,8 @@ export default class FileUpload extends BaseClass {
* @return {*} {Promise<void>}
* @memberof FileUpload
*/
async uploadFile(fileName: string, filePath: PathLike): Promise<void> {
const { uploadUrl, fields, headers, method } = this.signedUploadUrlData;
async uploadFile(fileName: string, filePath: PathLike, signedUploadUrlData: SignedUploadUrlData): Promise<void> {
const { uploadUrl, fields, headers, method } = signedUploadUrlData;
const formData = new FormData();

if (!isEmpty(fields)) {
Expand Down
Loading