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
6 changes: 3 additions & 3 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 packages/contentstack-launch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ $ npm install -g @contentstack/cli-launch
$ csdx COMMAND
running command...
$ csdx (--version|-v)
@contentstack/cli-launch/1.1.0 darwin-arm64 node-v22.2.0
@contentstack/cli-launch/1.2.0 darwin-arm64 node-v22.2.0
$ csdx --help [COMMAND]
USAGE
$ csdx COMMAND
Expand Down
2 changes: 1 addition & 1 deletion packages/contentstack-launch/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@contentstack/cli-launch",
"version": "1.1.0",
"version": "1.2.0",
"description": "Launch related operations",
"author": "Contentstack CLI",
"bin": {
Expand Down
87 changes: 70 additions & 17 deletions packages/contentstack-launch/src/adapters/file-upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import find from 'lodash/find';
import FormData from 'form-data';
import filter from 'lodash/filter';
import includes from 'lodash/includes';
import isEmpty from 'lodash/isEmpty';
import { basename, resolve } from 'path';
import { cliux, configHandler, ux } from '@contentstack/cli-utilities';
import { cliux, configHandler, HttpClient, ux } from '@contentstack/cli-utilities';
import { createReadStream, existsSync, PathLike, statSync } from 'fs';

import { print } from '../util';
Expand Down Expand Up @@ -263,28 +264,80 @@ export default class FileUpload extends BaseClass {
* @memberof FileUpload
*/
async uploadFile(fileName: string, filePath: PathLike): Promise<void> {
const { uploadUrl, fields } = this.signedUploadUrlData;
const { uploadUrl, fields, headers } = this.signedUploadUrlData;
const formData = new FormData();

for (const { formFieldKey, formFieldValue } of fields) {
formData.append(formFieldKey, formFieldValue);
if (!isEmpty(fields)) {
for (const { formFieldKey, formFieldValue } of fields) {
formData.append(formFieldKey, formFieldValue);
}

formData.append('file', createReadStream(filePath), fileName);
await this.submitFormData(formData, uploadUrl);
} else if (!isEmpty(headers)) {
await this.uploadWithHttpClient(filePath, uploadUrl, headers, fileName);
}
}

formData.append('file', createReadStream(filePath) as any, fileName);
private async submitFormData(formData: FormData, uploadUrl: string): Promise<void> {
ux.action.start('Starting file upload...');
try {
await new Promise<void>((resolve, reject) => {
formData.submit(uploadUrl, (error, res) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});

await new Promise<void>((resolve) => {
ux.action.start('Starting file upload...');
formData.submit(uploadUrl, (error, res) => {
if (error) {
ux.action.stop('File upload failed!');
this.log('File upload failed. Please try again.', 'error');
this.log(error, 'error');
this.exit(1);
}
ux.action.stop();
} catch (error) {
ux.action.stop('File upload failed!');
this.log('File upload failed. Please try again.', 'error');
if (error instanceof Error) {
this.log(error.message, 'error');
}
this.exit(1);
}
}

private async uploadWithHttpClient(
filePath: PathLike,
uploadUrl: string,
headers: Array<{ key: string; value: string }>,
fileName: string,
): Promise<void> {
ux.action.start('Starting file upload...');
const httpClient = new HttpClient();
const form = new FormData();
form.append('file', createReadStream(filePath), fileName);

resolve();
// Convert headers array to a headers object
const headerObject = headers.reduce((acc, { key, value }) => {
acc[key] = value;
return acc;
}, {} as Record<string, string>);

try {
const response = await httpClient.headers(headerObject).put(uploadUrl, form);
const { status } = response;

if (status >= 200 && status < 300) {
ux.action.stop();
});
});
} else {
ux.action.stop('File upload failed!');
this.log('File upload failed. Please try again.', 'error');
this.exit(1);
}
} catch (error) {
ux.action.stop('File upload failed!');
this.log('File upload failed. Please try again.', 'error');
if (error instanceof Error) {
this.log(`Error: ${error.message}`, 'error');
}
this.exit(1);
}
}
}
4 changes: 2 additions & 2 deletions packages/contentstack-launch/src/base-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
} from '@contentstack/cli-utilities';

import config from './config';
import { GraphqlApiClient, Logger } from './util';
import { getLaunchHubUrl, GraphqlApiClient, Logger } from './util';
import { ConfigType, LogFn, Providers } from './types';

export type Flags<T extends typeof Command> = Interfaces.InferredFlags<(typeof BaseCommand)['baseFlags'] & T['flags']>;
Expand Down Expand Up @@ -105,7 +105,7 @@ export abstract class BaseCommand<T extends typeof Command> extends Command {
this.flags['data-dir'] || this.flags.config
? this.flags.config || resolve(this.flags['data-dir'], config.configName)
: resolve(process.cwd(), config.configName);
const baseUrl = config.launchBaseUrl || (config.launchHubUrls as Record<string, string>)[this.cmaAPIUrl];
const baseUrl = config.launchBaseUrl || getLaunchHubUrl();
this.sharedConfig = {
...require('./config').default,
currentConfig: {},
Expand Down
10 changes: 2 additions & 8 deletions packages/contentstack-launch/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,7 @@ const config = {
GITPROVIDER: 'GitHub',
FILEUPLOAD: 'FileUpload',
},
launchHubUrls: {
// NOTE CMA url used as launch url mapper to avoid conflict if user used any custom name
'https://api.contentstack.io': 'https://launch-api.contentstack.com',
'https://eu-api.contentstack.com': '',
'https://azure-na-api.contentstack.com': '',
'https://azure-eu-api.contentstack.com': '',
},
launchHubUrls: '',
launchBaseUrl: '',
supportedAdapters: ['GitHub'],
deploymentStatus: ['LIVE', 'FAILED', 'SKIPPED', 'DEPLOYED'],
Expand All @@ -45,7 +39,7 @@ const config = {
'Manually add custom variables to the list',
'Import variables from the local env file',
],
variableType: ''
variableType: '',
};

export default config;
4 changes: 4 additions & 0 deletions packages/contentstack-launch/src/graphql/mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ const createSignedUploadUrlMutation: DocumentNode = gql`
formFieldKey
formFieldValue
}
headers{
key
value
}
}
}
`;
Expand Down
20 changes: 19 additions & 1 deletion packages/contentstack-launch/src/util/common-utility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,22 @@ async function selectProject(options: {
}
}

export { getOrganizations, selectOrg, selectProject };
function getLaunchHubUrl(): string {
const { cma } = configHandler.get('region') || {};
if (!cma) {
throw new Error('Region not configured. Please set the region with command $ csdx config:set:region');
}

let launchHubBaseUrl = cma.replace('api', 'launch-api');

if (launchHubBaseUrl.startsWith('http')) {
launchHubBaseUrl = launchHubBaseUrl.split('//')[1];
}

launchHubBaseUrl = launchHubBaseUrl.startsWith('dev11') ? launchHubBaseUrl.replace('dev11', 'dev') : launchHubBaseUrl;
launchHubBaseUrl = launchHubBaseUrl.endsWith('io') ? launchHubBaseUrl.replace('io', 'com') : launchHubBaseUrl;

return `https://${launchHubBaseUrl}`;
}

export { getOrganizations, selectOrg, selectProject, getLaunchHubUrl };
73 changes: 33 additions & 40 deletions packages/contentstack-launch/src/util/logs-polling-utilities.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import EventEmitter from "events";
import { ux } from "@contentstack/cli-utilities";
import { ApolloClient, ObservableQuery } from "@apollo/client/core";
import EventEmitter from 'events';
import { ux } from '@contentstack/cli-utilities';
import { ApolloClient, ObservableQuery } from '@apollo/client/core';

import { LogPollingInput, ConfigType } from "../types";
import {
deploymentQuery,
deploymentLogsQuery,
serverlessLogsQuery,
} from "../graphql";
import { LogPollingInput, ConfigType } from '../types';
import { deploymentQuery, deploymentLogsQuery, serverlessLogsQuery } from '../graphql';

export default class LogPolling {
private config: ConfigType;
Expand Down Expand Up @@ -69,15 +65,15 @@ export default class LogPolling {
let statusWatchQuery = this.getDeploymentStatus();
statusWatchQuery.subscribe(({ data, errors, error }) => {
if (error) {
this.$event.emit("deployment-logs", {
this.$event.emit('deployment-logs', {
message: error?.message,
msgType: "error",
msgType: 'error',
});
}
if (errors?.length && data === null) {
this.$event.emit("deployment-logs", {
this.$event.emit('deployment-logs', {
message: errors,
msgType: "error",
msgType: 'error',
});
statusWatchQuery.stopPolling();
}
Expand Down Expand Up @@ -110,46 +106,45 @@ export default class LogPolling {
{
deploymentUid: string | undefined;
}
>
>,
): void {
let timestamp: number = 0;
logsWatchQuery.subscribe(({ data, errors, error }) => {
ux.action.start("Loading deployment logs...");
ux.action.start('Loading deployment logs...');

if (error) {
ux.action.stop();
this.$event.emit("deployment-logs", {
this.$event.emit('deployment-logs', {
message: error?.message,
msgType: "error",
msgType: 'error',
});
this.$event.emit("deployment-logs", {
message: "DONE",
msgType: "debug",
this.$event.emit('deployment-logs', {
message: 'DONE',
msgType: 'debug',
});
logsWatchQuery.stopPolling();
}
if (errors?.length && data === null) {
ux.action.stop();
this.$event.emit("deployment-logs", {
this.$event.emit('deployment-logs', {
message: errors,
msgType: "error",
msgType: 'error',
});
this.$event.emit("deployment-logs", {
message: "DONE",
msgType: "debug",
this.$event.emit('deployment-logs', {
message: 'DONE',
msgType: 'debug',
});
logsWatchQuery.stopPolling();
}
if (this.deploymentStatus) {
let logsData = data?.getLogs;
if (logsData?.length) {
ux.action.stop();
this.$event.emit("deployment-logs", {
this.$event.emit('deployment-logs', {
message: logsData,
msgType: "info",
msgType: 'info',
});
timestamp =
new Date(logsData[logsData.length - 1].timestamp).getTime() + 1;
timestamp = new Date(logsData[logsData.length - 1].timestamp).getTime() + 1;
logsWatchQuery.setVariables({
deploymentUid: this.config.deployment,
timestamp: new Date(timestamp).toISOString(),
Expand All @@ -158,9 +153,9 @@ export default class LogPolling {

if (this.config.deploymentStatus.includes(this.deploymentStatus)) {
logsWatchQuery.stopPolling();
this.$event.emit("deployment-logs", {
message: "DONE",
msgType: "debug",
this.$event.emit('deployment-logs', {
message: 'DONE',
msgType: 'debug',
});
}
}
Expand Down Expand Up @@ -208,27 +203,26 @@ export default class LogPolling {
endTime: number;
};
}
>
>,
): void {
serverLogsWatchQuery.subscribe(({ data, errors, error }) => {
ux.action.start("Loading server logs...");
ux.action.start('Loading server logs...');
if (error) {
ux.action.stop();
this.$event.emit("server-logs", { message: error?.message, msgType: "error" });
this.$event.emit('server-logs', { message: error?.message, msgType: 'error' });
}
if (errors?.length && data === null) {
ux.action.stop();
this.$event.emit("server-logs", { message: errors, msgType: "error" });
this.$event.emit('server-logs', { message: errors, msgType: 'error' });
serverLogsWatchQuery.stopPolling();
}

let logsData = data?.getServerlessLogs?.logs;
let logsLength = logsData?.length;
if (logsLength > 0) {
ux.action.stop();
this.$event.emit("server-logs", { message: logsData, msgType: "info" });
this.startTime =
new Date(logsData[logsLength - 1].timestamp).getTime() + 1;
this.$event.emit('server-logs', { message: logsData, msgType: 'info' });
this.startTime = new Date(logsData[logsLength - 1].timestamp).getTime() + 1;
this.endTime = this.startTime + 10 * 1000;
} else if (logsLength === 0) {
this.endTime = this.endTime + 10 * 1000;
Expand All @@ -243,4 +237,3 @@ export default class LogPolling {
});
}
}

2 changes: 1 addition & 1 deletion packages/contentstack/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ $ npm install -g @contentstack/cli
$ csdx COMMAND
running command...
$ csdx (--version|-v)
@contentstack/cli/1.22.0 darwin-arm64 node-v22.2.0
@contentstack/cli/1.23.0 darwin-arm64 node-v22.2.0
$ csdx --help [COMMAND]
USAGE
$ csdx COMMAND
Expand Down
Loading