Skip to content

Commit 351d501

Browse files
committed
Removed creation of branches.json and added warning if the given data-dir / content dir contains any file/folder
1 parent 9e49666 commit 351d501

File tree

6 files changed

+101
-54
lines changed

6 files changed

+101
-54
lines changed

packages/contentstack-export/src/utils/export-config-handler.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import merge from 'merge';
22
import * as path from 'path';
33
import { configHandler, isAuthenticated, cliux, sanitizePath, log } from '@contentstack/cli-utilities';
44
import defaultConfig from '../config';
5-
import { readFile } from './file-helper';
5+
import { readFile, isDirectoryNonEmpty } from './file-helper';
66
import { askExportDir, askAPIKey } from './interactive';
77
import login from './basic-login';
88
import { filter, includes } from 'lodash';
@@ -42,6 +42,15 @@ const setupConfig = async (exportCmdFlags: any): Promise<ExportConfig> => {
4242
config.exportDir = config.exportDir.replace(/['"]/g, '');
4343
config.exportDir = path.resolve(config.exportDir);
4444

45+
if (exportCmdFlags['data-dir'] || exportCmdFlags['data']) {
46+
if (isDirectoryNonEmpty(config.exportDir)) {
47+
cliux.print(
48+
'\nThe export directory is not empty. Existing files in this folder may be overwritten.',
49+
{ color: 'yellow' },
50+
);
51+
}
52+
}
53+
4554
const managementTokenAlias = exportCmdFlags['management-token-alias'] || exportCmdFlags['alias'];
4655

4756
if (managementTokenAlias) {

packages/contentstack-export/src/utils/file-helper.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,32 @@ export const readdir = function (dirPath: string): any {
109109
}
110110
};
111111

112+
/**
113+
* Returns true if the path exists, is a directory, and contains at least one entry.
114+
*/
115+
export function isDirectoryNonEmpty(absolutePath: string): boolean {
116+
if (!absolutePath || !fs.existsSync(absolutePath)) {
117+
return false;
118+
}
119+
120+
let stat: fs.Stats;
121+
try {
122+
stat = fs.statSync(absolutePath);
123+
} catch {
124+
return false;
125+
}
126+
127+
if (!stat.isDirectory()) {
128+
return false;
129+
}
130+
131+
try {
132+
return fs.readdirSync(absolutePath).length > 0;
133+
} catch {
134+
return false;
135+
}
136+
}
137+
112138
exports.fileExistsSync = function (path: string) {
113139
return fs.existsSync(path);
114140
};

packages/contentstack-export/src/utils/setup-branches.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
import * as path from 'path';
2-
import { sanitizePath } from '@contentstack/cli-utilities';
3-
41
import { ExportConfig } from '../types';
5-
import { writeFileSync, makeDirectory } from './file-helper';
2+
import { makeDirectory } from './file-helper';
63

74
const setupBranches = async (config: ExportConfig, stackAPIClient: any) => {
85
if (typeof config !== 'object') {
@@ -40,9 +37,6 @@ const setupBranches = async (config: ExportConfig, stackAPIClient: any) => {
4037
}
4138

4239
makeDirectory(config.exportDir);
43-
// create branch info file
44-
writeFileSync(path.join(sanitizePath(config.exportDir), 'branches.json'), branches);
45-
// add branches list in the
4640
config.branches = branches;
4741
};
4842

packages/contentstack-export/test/unit/utils/export-config-handler.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,24 @@ describe('Export Config Handler', () => {
6363
expect(askExportDirStub.called).to.be.false;
6464
});
6565

66+
it('should print yellow warning when data-dir is set and export directory is not empty', async () => {
67+
configHandlerGetStub.withArgs('authorisationType').returns('OAUTH');
68+
const isNonEmptyStub = sandbox.stub(fileHelper, 'isDirectoryNonEmpty').returns(true);
69+
const cliuxPrint = utilities.cliux.print as sinon.SinonStub;
70+
cliuxPrint.resetHistory();
71+
72+
const flags = { 'data-dir': '/some/export' };
73+
await setupConfig(flags);
74+
75+
expect(isNonEmptyStub.calledWith(path.resolve('/some/export'))).to.be.true;
76+
expect(
77+
cliuxPrint.calledWith(
78+
'\nThe export directory is not empty. Existing files in this folder may be overwritten.',
79+
{ color: 'yellow' },
80+
),
81+
).to.be.true;
82+
});
83+
6684
it('should ask for export directory when not provided', async () => {
6785
// Set authenticated: isAuthenticated() checks configHandler.get('authorisationType')
6886
// Returns 'OAUTH' or 'AUTH' for authenticated, undefined for not authenticated
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { expect } from 'chai';
2+
import * as fs from 'fs';
3+
import * as os from 'os';
4+
import * as path from 'path';
5+
import { isDirectoryNonEmpty } from '../../../src/utils/file-helper';
6+
7+
describe('isDirectoryNonEmpty', () => {
8+
it('returns false for empty string', () => {
9+
expect(isDirectoryNonEmpty('')).to.be.false;
10+
});
11+
12+
it('returns false when path does not exist', () => {
13+
expect(isDirectoryNonEmpty(path.join(os.tmpdir(), `missing-${Date.now()}-${Math.random()}`))).to.be.false;
14+
});
15+
16+
it('returns false when directory is empty', () => {
17+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'empty-dir-'));
18+
try {
19+
expect(isDirectoryNonEmpty(dir)).to.be.false;
20+
} finally {
21+
fs.rmSync(dir, { recursive: true });
22+
}
23+
});
24+
25+
it('returns true when directory has an entry', () => {
26+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'nonempty-dir-'));
27+
fs.writeFileSync(path.join(dir, 'a.txt'), 'x');
28+
try {
29+
expect(isDirectoryNonEmpty(dir)).to.be.true;
30+
} finally {
31+
fs.rmSync(dir, { recursive: true });
32+
}
33+
});
34+
35+
it('returns false when path is a file', () => {
36+
const f = path.join(os.tmpdir(), `file-only-${Date.now()}.txt`);
37+
fs.writeFileSync(f, 'x');
38+
try {
39+
expect(isDirectoryNonEmpty(f)).to.be.false;
40+
} finally {
41+
fs.unlinkSync(f);
42+
}
43+
});
44+
});

packages/contentstack-export/test/unit/utils/setup-branches.test.ts

Lines changed: 2 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
import { expect } from 'chai';
22
import sinon from 'sinon';
3-
import * as path from 'node:path';
43
import setupBranches from '../../../src/utils/setup-branches';
54
import * as fileHelper from '../../../src/utils/file-helper';
6-
import * as utilities from '@contentstack/cli-utilities';
75
import { ExportConfig } from '../../../src/types';
86

97
describe('Setup Branches', () => {
108
let sandbox: sinon.SinonSandbox;
119
let mockStackAPIClient: any;
1210
let mockConfig: ExportConfig;
13-
let writeFileSyncStub: sinon.SinonStub;
1411
let makeDirectoryStub: sinon.SinonStub;
1512

1613
beforeEach(() => {
@@ -28,8 +25,6 @@ describe('Setup Branches', () => {
2825
branches: []
2926
} as Partial<ExportConfig> as ExportConfig;
3027

31-
// Stub file-helper functions
32-
writeFileSyncStub = sandbox.stub(fileHelper, 'writeFileSync');
3328
makeDirectoryStub = sandbox.stub(fileHelper, 'makeDirectory');
3429

3530
});
@@ -80,12 +75,7 @@ describe('Setup Branches', () => {
8075
expect(mockStackAPIClient.branch.calledWith(branchName)).to.be.true;
8176
expect(mockBranchClient.fetch.called).to.be.true;
8277
expect(makeDirectoryStub.calledWith(mockConfig.exportDir)).to.be.true;
83-
expect(writeFileSyncStub.called).to.be.true;
8478
expect(mockConfig.branches).to.deep.equal([mockBranch]);
85-
expect(writeFileSyncStub.firstCall.args[0]).to.equal(
86-
path.join(mockConfig.exportDir, 'branches.json')
87-
);
88-
expect(writeFileSyncStub.firstCall.args[1]).to.deep.equal([mockBranch]);
8979
});
9080

9181
it('should throw error when branch name is provided but branch does not exist', async () => {
@@ -105,7 +95,6 @@ describe('Setup Branches', () => {
10595
}
10696

10797
expect(makeDirectoryStub.called).to.be.false;
108-
expect(writeFileSyncStub.called).to.be.false;
10998
});
11099

111100
it('should throw error when branch fetch returns invalid result', async () => {
@@ -167,7 +156,6 @@ describe('Setup Branches', () => {
167156
expect(mockBranchClient.query.called).to.be.true;
168157
expect(mockQuery.find.called).to.be.true;
169158
expect(makeDirectoryStub.calledWith(mockConfig.exportDir)).to.be.true;
170-
expect(writeFileSyncStub.called).to.be.true;
171159
expect(mockConfig.branches).to.deep.equal(mockBranches);
172160
});
173161

@@ -186,7 +174,6 @@ describe('Setup Branches', () => {
186174

187175
expect(result).to.be.undefined;
188176
expect(makeDirectoryStub.called).to.be.false;
189-
expect(writeFileSyncStub.called).to.be.false;
190177
});
191178

192179
it('should return early when result has no items', async () => {
@@ -204,7 +191,6 @@ describe('Setup Branches', () => {
204191

205192
expect(result).to.be.undefined;
206193
expect(makeDirectoryStub.called).to.be.false;
207-
expect(writeFileSyncStub.called).to.be.false;
208194
});
209195

210196
it('should return early when items is not an array', async () => {
@@ -222,7 +208,6 @@ describe('Setup Branches', () => {
222208

223209
expect(result).to.be.undefined;
224210
expect(makeDirectoryStub.called).to.be.false;
225-
expect(writeFileSyncStub.called).to.be.false;
226211
});
227212

228213
it('should handle query errors gracefully and return early', async () => {
@@ -240,7 +225,6 @@ describe('Setup Branches', () => {
240225

241226
expect(result).to.be.undefined;
242227
expect(makeDirectoryStub.called).to.be.false;
243-
expect(writeFileSyncStub.called).to.be.false;
244228
});
245229

246230
it('should handle query catch rejection and return early', async () => {
@@ -258,35 +242,11 @@ describe('Setup Branches', () => {
258242

259243
expect(result).to.be.undefined;
260244
expect(makeDirectoryStub.called).to.be.false;
261-
expect(writeFileSyncStub.called).to.be.false;
262245
});
263246
});
264247

265248
describe('File Operations', () => {
266-
it('should create directory and write branches.json file', async () => {
267-
const mockBranch = { uid: 'branch-123', name: 'test-branch' };
268-
mockConfig.branchName = 'test-branch';
269-
mockConfig.exportDir = '/test/export';
270-
271-
const mockBranchClient = {
272-
fetch: sandbox.stub().resolves(mockBranch)
273-
};
274-
mockStackAPIClient.branch.returns(mockBranchClient);
275-
276-
await setupBranches(mockConfig, mockStackAPIClient);
277-
278-
expect(makeDirectoryStub.calledWith(mockConfig.exportDir)).to.be.true;
279-
expect(writeFileSyncStub.calledOnce).to.be.true;
280-
// sanitizePath is called internally, we verify the result instead
281-
282-
const filePath = writeFileSyncStub.firstCall.args[0];
283-
const fileData = writeFileSyncStub.firstCall.args[1];
284-
285-
expect(filePath).to.equal(path.join(mockConfig.exportDir, 'branches.json'));
286-
expect(fileData).to.deep.equal([mockBranch]);
287-
});
288-
289-
it('should use sanitized export directory path', async () => {
249+
it('should create export directory when branches are resolved', async () => {
290250
const mockBranch = { uid: 'branch-123', name: 'test-branch' };
291251
mockConfig.branchName = 'test-branch';
292252
mockConfig.exportDir = '/test/export/../export';
@@ -298,11 +258,7 @@ describe('Setup Branches', () => {
298258

299259
await setupBranches(mockConfig, mockStackAPIClient);
300260

301-
// sanitizePath will be called internally by the real implementation
302-
expect(writeFileSyncStub.called).to.be.true;
303-
// Verify the file path contains the directory and branches.json
304-
expect(writeFileSyncStub.firstCall.args[0]).to.include('branches.json');
305-
expect(writeFileSyncStub.firstCall.args[0]).to.include('/test/export');
261+
expect(makeDirectoryStub.calledWith(mockConfig.exportDir)).to.be.true;
306262
});
307263
});
308264

0 commit comments

Comments
 (0)