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
2 changes: 1 addition & 1 deletion .talismanrc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ fileignoreconfig:
- filename: test/unit/commands/rollback.test.ts
checksum: d1f931f2d9a397131409399ad6463653e28b5a2224e870b641d9ba57c4418f18
- filename: package-lock.json
checksum: c94ce0fbbbedcbad4dceef8168971831ed82aad85824db579f2497dcd49aecab
checksum: a713321dbdd291f8087846c9c45793588787eb8adc76faf4f74968964e502d3e
- filename: src/adapters/github.test.ts
checksum: b3d3d3a3c14adde103152d2ac4e1c4b7121a5f7533a87c3cc600f6d25fb59d1b
- filename: src/adapters/file-upload.test.ts
Expand Down
3,255 changes: 27 additions & 3,228 deletions package-lock.json

Large diffs are not rendered by default.

17 changes: 14 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@contentstack/cli-launch",
"version": "1.11.2",
"version": "1.11.3",
"description": "Launch related operations",
"author": "Contentstack CLI",
"bin": {
Expand Down Expand Up @@ -116,7 +116,18 @@
"ajv": "^6.12.6"
},
"qs": "^6.15.2",
"tmp": "^0.2.4"
"tmp": "^0.2.4",
"fast-uri": "^3.1.5",
"js-yaml": "^4.3.1",
"minimatch@10.2.5": {
"brace-expansion": "^5.0.9"
},
"minimatch@9.0.9": {
"brace-expansion": "^2.1.4"
},
"minimatch@5.1.9": {
"brace-expansion": "^2.1.4"
}
},
"engines": {
"node": ">=22.0.0"
Expand All @@ -141,4 +152,4 @@
"launch:deployments": "LNCH-DPLMNT"
}
}
}
}
24 changes: 24 additions & 0 deletions src/adapters/file-upload.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,30 @@ describe('FileUpload Adapter', () => {
expect(showSuggestionMock).not.toHaveBeenCalled();
});

it('should exit with code 1 when deployment status is CANCELLED for new project', async () => {
const fileUploadInstance = new FileUpload({
config: {
isExistingProject: false,
currentDeploymentStatus: DeploymentStatus.CANCELLED,
},
log: logMock,
exit: exitMock,
} as any);

try {
await fileUploadInstance.run();
} catch (error: any) {
expect(error.message).toBe('1');
}

expect(handleNewProjectMock).toHaveBeenCalled();
expect(prepareLaunchConfigMock).toHaveBeenCalled();
expect(showLogsMock).toHaveBeenCalled();
expect(exitMock).toHaveBeenCalledWith(1);
expect(showDeploymentUrlMock).not.toHaveBeenCalled();
expect(showSuggestionMock).not.toHaveBeenCalled();
});

it('should continue normally when deployment status is not FAILED', async () => {
const fileUploadInstance = new FileUpload({
config: {
Expand Down
5 changes: 4 additions & 1 deletion src/adapters/file-upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ export default class FileUpload extends BaseClass {

this.prepareLaunchConfig();
await this.showLogs();
if(this.config.currentDeploymentStatus === DeploymentStatus.FAILED) {
if (
this.config.currentDeploymentStatus === DeploymentStatus.FAILED ||
this.config.currentDeploymentStatus === DeploymentStatus.CANCELLED
) {
this.exit(1);
}
this.showDeploymentUrl();
Expand Down
24 changes: 24 additions & 0 deletions src/adapters/github.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,30 @@ describe('GitHub Adapter', () => {
expect(showSuggestionMock).not.toHaveBeenCalled();
});

it('should exit with code 1 when deployment status is CANCELLED for new project', async () => {
const githubInstance = new GitHub({
config: {
isExistingProject: false,
currentDeploymentStatus: DeploymentStatus.CANCELLED,
},
log: logMock,
exit: exitMock,
} as any);

try {
await githubInstance.run();
} catch (error: any) {
expect(error.message).toBe('1');
}

expect(handleNewProjectMock).toHaveBeenCalled();
expect(prepareLaunchConfigMock).toHaveBeenCalled();
expect(showLogsMock).toHaveBeenCalled();
expect(exitMock).toHaveBeenCalledWith(1);
expect(showDeploymentUrlMock).not.toHaveBeenCalled();
expect(showSuggestionMock).not.toHaveBeenCalled();
});

it('should continue normally when deployment status is not FAILED', async () => {
const githubInstance = new GitHub({
config: {
Expand Down
5 changes: 4 additions & 1 deletion src/adapters/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ export default class GitHub extends BaseClass {

this.prepareLaunchConfig();
await this.showLogs();
if (this.config.currentDeploymentStatus === DeploymentStatus.FAILED) {
if (
this.config.currentDeploymentStatus === DeploymentStatus.FAILED ||
this.config.currentDeploymentStatus === DeploymentStatus.CANCELLED
) {
this.exit(1);
}
this.showDeploymentUrl();
Expand Down
2 changes: 2 additions & 0 deletions src/commands/launch/rollback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ export default class Rollback extends BaseCommand<typeof Rollback> {
if (!this.flags.environment) {
await this.getConfig();
}

await this.prepareApiClients();
Comment thread
chhavi-mandowara-cstk marked this conversation as resolved.

if (!this.sharedConfig.currentConfig?.uid) {
await selectOrg({
Expand Down
2 changes: 1 addition & 1 deletion src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const config = {
launchHubUrls: '',
launchBaseUrl: '',
supportedAdapters: ['GitHub'],
deploymentStatus: ['LIVE', 'FAILED', 'SKIPPED', 'DEPLOYED'],
deploymentStatus: ['LIVE', 'FAILED', 'SKIPPED', 'DEPLOYED', 'CANCELLED'],
pollingInterval: 1000,
variablePreparationTypeOptions: [
VariablePreparationTypeOptions.IMPORT_FROM_STACK,
Expand Down
80 changes: 80 additions & 0 deletions src/util/logs-polling-utilities.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
import { EventEmitter } from 'events';
import { logPolling as cliUtilitiesJestMock } from '../test/mocks/cli-utilities';
import LogPolling from './logs-polling-utilities';
import defaultConfig from '../config';

type LogPollingCtor = typeof import('./logs-polling-utilities').default;

jest.mock('@contentstack/cli-utilities', () => cliUtilitiesJestMock);
jest.mock('timers/promises', () => ({ setTimeout: jest.fn().mockResolvedValue(undefined) }));

function makeWatchQuery() {
let subscriber: (result: any) => void = () => {};
return {
subscribe: jest.fn((cb: (result: any) => void) => {
subscriber = cb;
return { unsubscribe: jest.fn() };
}),
setVariables: jest.fn(),
stopPolling: jest.fn(),
emit: (result: any) => subscriber(result),
};
Comment thread
chhavi-mandowara-cstk marked this conversation as resolved.
}

const CONFIG = {
deployment: 'd1',
Expand Down Expand Up @@ -115,3 +132,66 @@ describe('LogPolling Apollo deprecation regression', () => {
expect(watchQuery).toHaveBeenCalledTimes(1);
});
});

describe('cancelled deployment stops log polling', () => {
function buildInstance(deploymentStatus: string[]) {
const statusWatchQuery = makeWatchQuery();
const logsWatchQuery = makeWatchQuery();
const config = {
deployment: 'd1',
environment: 'e1',
pollingInterval: 1000,
deploymentStatus,
};
const instance = new LogPolling({
apolloManageClient: { watchQuery: jest.fn().mockReturnValue(statusWatchQuery) } as any,
apolloLogsClient: { watchQuery: jest.fn().mockReturnValue(logsWatchQuery) } as any,
config: config as any,
$event: new EventEmitter(),
});
return { instance, statusWatchQuery, logsWatchQuery, config };
}

it('stops status polling once the deployment status is CANCELLED', async () => {
const { instance, statusWatchQuery } = buildInstance(['LIVE', 'FAILED', 'SKIPPED', 'DEPLOYED', 'CANCELLED']);

await instance.deploymentLogs();
statusWatchQuery.emit({ data: { Deployment: { status: 'CANCELLED' } } });

expect(instance.deploymentStatus).toBe('CANCELLED');
expect(statusWatchQuery.stopPolling).toHaveBeenCalledTimes(1);
});

it('stops deployment-logs polling and emits DONE once status is CANCELLED', async () => {
const { instance, statusWatchQuery, logsWatchQuery } = buildInstance([
'LIVE',
'FAILED',
'SKIPPED',
'DEPLOYED',
'CANCELLED',
]);
const events: string[] = [];
(instance as any).$event.on('deployment-logs', (e: any) => events.push(e.message));

await instance.deploymentLogs();
statusWatchQuery.emit({ data: { Deployment: { status: 'CANCELLED' } } });
await logsWatchQuery.emit({ data: { getLogs: [] } });

expect(logsWatchQuery.stopPolling).toHaveBeenCalledTimes(1);
expect(events).toContain('DONE');
});

it('regression guard: keeps polling forever if CANCELLED is missing from deploymentStatus', async () => {
const { instance, statusWatchQuery } = buildInstance(['LIVE', 'FAILED', 'SKIPPED', 'DEPLOYED']);

await instance.deploymentLogs();
statusWatchQuery.emit({ data: { Deployment: { status: 'CANCELLED' } } });

expect(instance.deploymentStatus).toBe('CANCELLED');
expect(statusWatchQuery.stopPolling).not.toHaveBeenCalled();
});

it('real app config (src/config) lists CANCELLED as a terminal deployment status', () => {
expect(defaultConfig.deploymentStatus).toContain('CANCELLED');
});
});
Loading