Skip to content

Commit a71b968

Browse files
committed
fix: Do not use config file path to override the data directory
1 parent 6753080 commit a71b968

File tree

2 files changed

+284
-8
lines changed

2 files changed

+284
-8
lines changed

src/base-command.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import keys from 'lodash/keys';
2-
import { existsSync } from 'fs';
2+
import { existsSync, statSync } from 'fs';
33
import EventEmitter from 'events';
4-
import { dirname, resolve } from 'path';
4+
import { resolve } from 'path';
55
import includes from 'lodash/includes';
66
import { ApolloClient } from '@apollo/client/core';
77
import { Command } from '@contentstack/cli-command';
@@ -18,7 +18,8 @@ import {
1818
} from '@contentstack/cli-utilities';
1919

2020
import config from './config';
21-
import { getLaunchHubUrl, GraphqlApiClient, Logger } from './util';
21+
import { GraphqlApiClient, Logger } from './util';
22+
import { getLaunchHubUrl } from './util/common-utility';
2223
import { ConfigType, LogFn, Providers } from './types';
2324

2425
export type Flags<T extends typeof Command> = Interfaces.InferredFlags<(typeof BaseCommand)['baseFlags'] & T['flags']>;
@@ -101,22 +102,29 @@ export abstract class BaseCommand<T extends typeof Command> extends Command {
101102
* @memberof BaseCommand
102103
*/
103104
async prepareConfig(): Promise<void> {
104-
let configPath =
105-
this.flags['data-dir'] || this.flags.config
106-
? this.flags.config || resolve(this.flags['data-dir'], config.configName)
107-
: resolve(process.cwd(), config.configName);
105+
const currentWorkingDirectory = process.cwd();
106+
107+
const projectBasePath = this.flags['data-dir'] || currentWorkingDirectory;
108+
if (!existsSync(projectBasePath) || !statSync(projectBasePath).isDirectory()) {
109+
ux.print(`Invalid directory: ${projectBasePath}`, { color: 'red' });
110+
this.exit(1);
111+
}
112+
113+
const configPath = this.flags.config || resolve(currentWorkingDirectory, config.configName);
114+
108115
let baseUrl = config.launchBaseUrl || this.launchHubUrl;
109116
if (!baseUrl) {
110117
baseUrl = getLaunchHubUrl();
111118
}
119+
112120
this.sharedConfig = {
113121
...require('./config').default,
114122
currentConfig: {},
115123
...this.flags,
116124
flags: this.flags,
117125
host: this.cmaHost,
118126
config: configPath,
119-
projectBasePath: dirname(configPath),
127+
projectBasePath: projectBasePath,
120128
authtoken: configHandler.get('authtoken'),
121129
authType: configHandler.get('authorisationType'),
122130
authorization: configHandler.get('oauthAccessToken'),

test/unit/base-command.test.ts

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
//@ts-nocheck
2+
// TODO: Allow ts with any and remove ts-nocheck
3+
import { expect } from 'chai';
4+
import { stub, createSandbox } from 'sinon';
5+
import { cliux as ux, configHandler } from '@contentstack/cli-utilities';
6+
import fs from 'fs';
7+
import path from 'path';
8+
import { BaseCommand } from '../../src/base-command';
9+
import config from '../../src/config';
10+
import * as commonUtils from '../../src/util/common-utility';
11+
12+
describe('BaseCommand', () => {
13+
let sandbox;
14+
let baseCommandInstance;
15+
let flags;
16+
17+
describe('prepareConfig', () => {
18+
let statSyncResultObj;
19+
let statSyncStub;
20+
let existsSyncStub;
21+
let processCwdStub;
22+
let getLaunchHubUrlStub;
23+
let configHandlerGetStub;
24+
25+
beforeEach(() => {
26+
sandbox = createSandbox();
27+
28+
baseCommandInstance = new (class extends BaseCommand<typeof BaseCommand> {
29+
async run() {}
30+
})([], {} as any);
31+
32+
baseCommandInstance.flags = {};
33+
34+
baseCommandInstance.exit = sandbox.stub().callsFake((code) => {
35+
throw new Error(code);
36+
});
37+
38+
statSyncResultObj = {
39+
isDirectory: sandbox.stub().returns(true),
40+
};
41+
statSyncStub = sandbox.stub(fs, 'statSync').returns(statSyncResultObj);
42+
43+
existsSyncStub = sandbox.stub(fs, 'existsSync').returns(true);
44+
existsSyncStub.withArgs('/root/.cs-launch.json').returns(false);
45+
46+
processCwdStub = sandbox.stub(process, 'cwd').returns('/root/');
47+
48+
getLaunchHubUrlStub = sandbox
49+
.stub(commonUtils, 'getLaunchHubUrl')
50+
.returns('https://dev11-app.csnonprod.com/launch-api');
51+
52+
sandbox.stub(BaseCommand.prototype, 'cmaHost').value('host.contentstack.io');
53+
54+
configHandlerGetStub = sandbox.stub(configHandler, 'get').returns('testValue');
55+
configHandlerGetStub.withArgs('authtoken').returns('testauthtoken');
56+
configHandlerGetStub.withArgs('authorisationType').returns('testauthorisationType');
57+
configHandlerGetStub.withArgs('oauthAccessToken').returns('testoauthAccessToken');
58+
});
59+
60+
afterEach(() => {
61+
sandbox.restore();
62+
});
63+
64+
it('should initialize sharedConfig with default values if no flags passed', async () => {
65+
await baseCommandInstance.prepareConfig();
66+
67+
expect(configHandlerGetStub.args[1][0]).to.equal('authtoken');
68+
expect(configHandlerGetStub.args[2][0]).to.equal('authorisationType');
69+
expect(configHandlerGetStub.args[3][0]).to.equal('oauthAccessToken');
70+
expect(baseCommandInstance.sharedConfig).to.deep.equal({
71+
...require('../../src/config').default,
72+
currentConfig: {},
73+
flags: {},
74+
host: 'host.contentstack.io',
75+
projectBasePath: '/root/',
76+
authtoken: 'testauthtoken',
77+
authType: 'testauthorisationType',
78+
authorization: 'testoauthAccessToken',
79+
config: '/root/.cs-launch.json',
80+
logsApiBaseUrl: 'https://dev11-app.csnonprod.com/launch-api/logs/graphql',
81+
manageApiBaseUrl: 'https://dev11-app.csnonprod.com/launch-api/manage/graphql',
82+
});
83+
});
84+
85+
it('should successfully initialize sharedConfig.manageApiBaseUrl and sharedConfig.logsApiBaseUrl if config.launchBaseUrl is set', async () => {
86+
sandbox.stub(config, 'launchBaseUrl').value('https://configlaunch-baseurl.csnonprod.com/launch-api');
87+
88+
await baseCommandInstance.prepareConfig();
89+
90+
expect(existsSyncStub.args[0][0]).to.equal('/root/');
91+
expect(statSyncStub.args[0][0]).to.equal('/root/');
92+
expect(statSyncResultObj.isDirectory.calledOnce).to.be.true;
93+
expect(baseCommandInstance.sharedConfig).to.deep.equal({
94+
...require('../../src/config').default,
95+
currentConfig: {},
96+
flags: {},
97+
host: 'host.contentstack.io',
98+
projectBasePath: '/root/',
99+
authtoken: 'testauthtoken',
100+
authType: 'testauthorisationType',
101+
authorization: 'testoauthAccessToken',
102+
config: '/root/.cs-launch.json',
103+
logsApiBaseUrl: 'https://configlaunch-baseurl.csnonprod.com/launch-api/logs/graphql',
104+
manageApiBaseUrl: 'https://configlaunch-baseurl.csnonprod.com/launch-api/manage/graphql',
105+
});
106+
});
107+
108+
it('should successfully initialize sharedConfig.manageApiBaseUrl and sharedConfig.logsApiBaseUrl if launchHubUrl is set', async () => {
109+
sandbox.stub(BaseCommand.prototype, 'launchHubUrl').value('https://this-launchHubUrl.csnonprod.com/launch-api');
110+
111+
await baseCommandInstance.prepareConfig();
112+
113+
expect(existsSyncStub.args[0][0]).to.equal('/root/');
114+
expect(statSyncStub.args[0][0]).to.equal('/root/');
115+
expect(statSyncResultObj.isDirectory.calledOnce).to.be.true;
116+
expect(baseCommandInstance.sharedConfig).to.deep.equal({
117+
...require('../../src/config').default,
118+
currentConfig: {},
119+
flags: {},
120+
host: 'host.contentstack.io',
121+
projectBasePath: '/root/',
122+
authtoken: 'testauthtoken',
123+
authType: 'testauthorisationType',
124+
authorization: 'testoauthAccessToken',
125+
config: '/root/.cs-launch.json',
126+
logsApiBaseUrl: 'https://this-launchHubUrl.csnonprod.com/launch-api/logs/graphql',
127+
manageApiBaseUrl: 'https://this-launchHubUrl.csnonprod.com/launch-api/manage/graphql',
128+
});
129+
});
130+
131+
it('should successfully initialize sharedConfig.projectBasePath if "data-dir" flag is passed', async () => {
132+
const flags = {
133+
'data-dir': '/root/subdirectory/project1',
134+
};
135+
baseCommandInstance.flags = flags;
136+
137+
await baseCommandInstance.prepareConfig();
138+
139+
expect(existsSyncStub.args[0][0]).to.equal('/root/subdirectory/project1');
140+
expect(statSyncStub.args[0][0]).to.equal('/root/subdirectory/project1');
141+
expect(statSyncResultObj.isDirectory.calledOnce).to.be.true;
142+
expect(baseCommandInstance.sharedConfig).to.deep.equal({
143+
...require('../../src/config').default,
144+
currentConfig: {},
145+
flags,
146+
host: 'host.contentstack.io',
147+
'data-dir': '/root/subdirectory/project1',
148+
projectBasePath: '/root/subdirectory/project1',
149+
config: '/root/.cs-launch.json',
150+
authtoken: 'testauthtoken',
151+
authType: 'testauthorisationType',
152+
authorization: 'testoauthAccessToken',
153+
logsApiBaseUrl: 'https://dev11-app.csnonprod.com/launch-api/logs/graphql',
154+
manageApiBaseUrl: 'https://dev11-app.csnonprod.com/launch-api/manage/graphql',
155+
});
156+
});
157+
158+
it('should initialize sharedConfig.provider if "type" flag is passed', async () => {
159+
const flags = {
160+
'type': 'FILEUPLOAD',
161+
};
162+
baseCommandInstance.flags = flags;
163+
164+
await baseCommandInstance.prepareConfig();
165+
166+
expect(configHandlerGetStub.args[1][0]).to.equal('authtoken');
167+
expect(configHandlerGetStub.args[2][0]).to.equal('authorisationType');
168+
expect(configHandlerGetStub.args[3][0]).to.equal('oauthAccessToken');
169+
expect(baseCommandInstance.sharedConfig).to.deep.equal({
170+
...require('../../src/config').default,
171+
currentConfig: {},
172+
flags,
173+
type: 'FILEUPLOAD',
174+
provider: 'FILEUPLOAD',
175+
host: 'host.contentstack.io',
176+
projectBasePath: '/root/',
177+
authtoken: 'testauthtoken',
178+
authType: 'testauthorisationType',
179+
authorization: 'testoauthAccessToken',
180+
config: '/root/.cs-launch.json',
181+
logsApiBaseUrl: 'https://dev11-app.csnonprod.com/launch-api/logs/graphql',
182+
manageApiBaseUrl: 'https://dev11-app.csnonprod.com/launch-api/manage/graphql',
183+
});
184+
});
185+
186+
it('should exit with error if "data-dir" flag specified but path does not exist', async () => {
187+
existsSyncStub.returns(false);
188+
baseCommandInstance.flags = {
189+
'data-dir': '/root/subdirectory/project1',
190+
};
191+
let exitStatusCode;
192+
193+
try {
194+
await baseCommandInstance.prepareConfig();
195+
} catch (err) {
196+
exitStatusCode = err.message;
197+
}
198+
199+
expect(existsSyncStub.args[0][0]).to.equal('/root/subdirectory/project1');
200+
expect(exitStatusCode).to.equal('1');
201+
});
202+
203+
it('should exit with error if "data-dir" flag specified with a non-directory path', async () => {
204+
statSyncResultObj.isDirectory.returns(false);
205+
baseCommandInstance.flags = {
206+
'data-dir': '/root/subdirectory/project1/file.txt',
207+
};
208+
let exitStatusCode;
209+
210+
try {
211+
await baseCommandInstance.prepareConfig();
212+
} catch (err) {
213+
exitStatusCode = err.message;
214+
}
215+
216+
expect(existsSyncStub.args[0][0]).to.equal('/root/subdirectory/project1/file.txt');
217+
expect(statSyncStub.args[0][0]).to.equal('/root/subdirectory/project1/file.txt');
218+
expect(statSyncResultObj.isDirectory.calledOnce).to.be.true;
219+
expect(exitStatusCode).to.equal('1');
220+
});
221+
222+
it('should initialize sharedConfig.config if "config" flag if passed', async () => {
223+
const flags = {
224+
config: '/root/subdirectory/configs/dev.json',
225+
};
226+
baseCommandInstance.flags = flags;
227+
existsSyncStub.withArgs('/root/subdirectory/configs/dev.json').returns(false);
228+
229+
await baseCommandInstance.prepareConfig();
230+
231+
expect(baseCommandInstance.sharedConfig).to.deep.equal({
232+
...require('../../src/config').default,
233+
currentConfig: {},
234+
config: '/root/subdirectory/configs/dev.json',
235+
flags,
236+
host: 'host.contentstack.io',
237+
projectBasePath: '/root/',
238+
authtoken: 'testauthtoken',
239+
authType: 'testauthorisationType',
240+
authorization: 'testoauthAccessToken',
241+
config: '/root/subdirectory/configs/dev.json',
242+
logsApiBaseUrl: 'https://dev11-app.csnonprod.com/launch-api/logs/graphql',
243+
manageApiBaseUrl: 'https://dev11-app.csnonprod.com/launch-api/manage/graphql',
244+
});
245+
});
246+
247+
it('should initialize sharedConfig.isExistingProject if config file exists', async () => {
248+
existsSyncStub.withArgs('/root/.cs-launch.json').returns(true);
249+
250+
await baseCommandInstance.prepareConfig();
251+
252+
expect(baseCommandInstance.sharedConfig).to.deep.equal({
253+
...require('../../src/config').default,
254+
currentConfig: {},
255+
flags: {},
256+
host: 'host.contentstack.io',
257+
projectBasePath: '/root/',
258+
authtoken: 'testauthtoken',
259+
authType: 'testauthorisationType',
260+
authorization: 'testoauthAccessToken',
261+
config: '/root/.cs-launch.json',
262+
isExistingProject: true,
263+
logsApiBaseUrl: 'https://dev11-app.csnonprod.com/launch-api/logs/graphql',
264+
manageApiBaseUrl: 'https://dev11-app.csnonprod.com/launch-api/manage/graphql',
265+
});
266+
});
267+
});
268+
});

0 commit comments

Comments
 (0)