@contentstack/cli-cm-export-query 1.0.0-beta.1 → 1.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commands/cm/stacks/export-query.d.ts +0 -1
- package/lib/commands/cm/stacks/export-query.js +9 -2
- package/lib/core/module-exporter.js +0 -1
- package/lib/core/query-executor.js +86 -8
- package/lib/types/index.d.ts +3 -0
- package/lib/utils/branch-helper.d.ts +10 -0
- package/lib/utils/branch-helper.js +63 -0
- package/lib/utils/config-handler.js +10 -6
- package/lib/utils/index.d.ts +1 -0
- package/lib/utils/index.js +3 -1
- package/lib/utils/referenced-asset-handler.js +13 -2
- package/oclif.manifest.json +2 -4
- package/package.json +1 -1
|
@@ -16,8 +16,16 @@ class ExportQueryCommand extends cli_command_1.Command {
|
|
|
16
16
|
exportQueryConfig.developerHubBaseUrl = this.developerHubUrl;
|
|
17
17
|
}
|
|
18
18
|
this.exportDir = (0, cli_utilities_1.sanitizePath)(exportQueryConfig.exportDir);
|
|
19
|
-
// Initialize
|
|
19
|
+
// Initialize management API client
|
|
20
20
|
const managementAPIClient = await (0, cli_utilities_1.managementSDKClient)(exportQueryConfig);
|
|
21
|
+
// Setup and validate branch configuration
|
|
22
|
+
const stackAPIClient = managementAPIClient.stack({
|
|
23
|
+
api_key: exportQueryConfig.stackApiKey,
|
|
24
|
+
management_token: exportQueryConfig.managementToken,
|
|
25
|
+
});
|
|
26
|
+
// Setup branches (validate branch or set default to 'main')
|
|
27
|
+
await (0, utils_1.setupBranches)(exportQueryConfig, stackAPIClient);
|
|
28
|
+
// Initialize and run query export
|
|
21
29
|
const queryExporter = new query_executor_1.QueryExporter(managementAPIClient, exportQueryConfig);
|
|
22
30
|
await queryExporter.execute();
|
|
23
31
|
(0, utils_1.log)(exportQueryConfig, 'Query-based export completed successfully!', 'success');
|
|
@@ -76,4 +84,3 @@ ExportQueryCommand.flags = {
|
|
|
76
84
|
description: 'Skip confirmation prompts',
|
|
77
85
|
}),
|
|
78
86
|
};
|
|
79
|
-
ExportQueryCommand.aliases = ['cm:export-query'];
|
|
@@ -15,7 +15,6 @@ class ModuleExporter {
|
|
|
15
15
|
(0, logger_1.log)(this.exportQueryConfig, `Exporting module: ${moduleName}`, 'info');
|
|
16
16
|
// Build command arguments
|
|
17
17
|
const cmd = this.buildExportCommand(moduleName, options);
|
|
18
|
-
(0, logger_1.log)(this.exportQueryConfig, `Running export command: ${cmd.join(' ')}`, 'debug');
|
|
19
18
|
// Configurable delay
|
|
20
19
|
const delay = this.exportQueryConfig.exportDelayMs || 2000;
|
|
21
20
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
@@ -225,19 +225,97 @@ class QueryExporter {
|
|
|
225
225
|
async exportReferencedAssets() {
|
|
226
226
|
(0, logger_1.log)(this.exportQueryConfig, 'Starting export of referenced assets...', 'info');
|
|
227
227
|
try {
|
|
228
|
+
const assetsDir = path.join((0, cli_utilities_1.sanitizePath)(this.exportQueryConfig.exportDir), (0, cli_utilities_1.sanitizePath)(this.exportQueryConfig.branchName || ''), 'assets');
|
|
229
|
+
const metadataFilePath = path.join(assetsDir, 'metadata.json');
|
|
230
|
+
const assetFilePath = path.join(assetsDir, 'assets.json');
|
|
231
|
+
// Define temp file paths
|
|
232
|
+
const tempMetadataFilePath = path.join(assetsDir, 'metadata_temp.json');
|
|
233
|
+
const tempAssetFilePath = path.join(assetsDir, 'assets_temp.json');
|
|
228
234
|
const assetHandler = new utils_4.AssetReferenceHandler(this.exportQueryConfig);
|
|
229
235
|
// Extract referenced asset UIDs from all entries
|
|
230
236
|
const assetUIDs = assetHandler.extractReferencedAssets();
|
|
231
237
|
if (assetUIDs.length > 0) {
|
|
232
|
-
(0, logger_1.log)(this.exportQueryConfig, `
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
238
|
+
(0, logger_1.log)(this.exportQueryConfig, `Found ${assetUIDs.length} referenced assets to export`, 'info');
|
|
239
|
+
// Define batch size - can be configurable through exportQueryConfig
|
|
240
|
+
const batchSize = this.exportQueryConfig.assetBatchSize || 100;
|
|
241
|
+
if (assetUIDs.length <= batchSize) {
|
|
242
|
+
const query = {
|
|
243
|
+
modules: {
|
|
244
|
+
assets: {
|
|
245
|
+
uid: { $in: assetUIDs },
|
|
246
|
+
},
|
|
237
247
|
},
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
|
|
248
|
+
};
|
|
249
|
+
await this.moduleExporter.exportModule('assets', { query });
|
|
250
|
+
}
|
|
251
|
+
// if asset size is bigger than batch size, then we need to export in batches
|
|
252
|
+
// Calculate number of batches
|
|
253
|
+
const totalBatches = Math.ceil(assetUIDs.length / batchSize);
|
|
254
|
+
(0, logger_1.log)(this.exportQueryConfig, `Processing assets in ${totalBatches} batches of ${batchSize}`, 'info');
|
|
255
|
+
// Process assets in batches
|
|
256
|
+
for (let i = 0; i < totalBatches; i++) {
|
|
257
|
+
const start = i * batchSize;
|
|
258
|
+
const end = Math.min(start + batchSize, assetUIDs.length);
|
|
259
|
+
const batchAssetUIDs = assetUIDs.slice(start, end);
|
|
260
|
+
(0, logger_1.log)(this.exportQueryConfig, `Exporting batch ${i + 1}/${totalBatches} (${batchAssetUIDs.length} assets)...`, 'info');
|
|
261
|
+
const query = {
|
|
262
|
+
modules: {
|
|
263
|
+
assets: {
|
|
264
|
+
uid: { $in: batchAssetUIDs },
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
await this.moduleExporter.exportModule('assets', { query });
|
|
269
|
+
// Read the current batch's metadata.json and assets.json files
|
|
270
|
+
const currentMetadata = utils_2.fsUtil.readFile((0, cli_utilities_1.sanitizePath)(metadataFilePath));
|
|
271
|
+
const currentAssets = utils_2.fsUtil.readFile((0, cli_utilities_1.sanitizePath)(assetFilePath));
|
|
272
|
+
// Check if this is the first batch
|
|
273
|
+
if (i === 0) {
|
|
274
|
+
// For first batch, initialize temp files with current content
|
|
275
|
+
utils_2.fsUtil.writeFile((0, cli_utilities_1.sanitizePath)(tempMetadataFilePath), currentMetadata);
|
|
276
|
+
utils_2.fsUtil.writeFile((0, cli_utilities_1.sanitizePath)(tempAssetFilePath), currentAssets);
|
|
277
|
+
(0, logger_1.log)(this.exportQueryConfig, `Initialized temporary files with first batch data`, 'info');
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
// For subsequent batches, append to temp files with incremented keys
|
|
281
|
+
// Handle metadata (which contains arrays of asset info)
|
|
282
|
+
const tempMetadata = utils_2.fsUtil.readFile((0, cli_utilities_1.sanitizePath)(tempMetadataFilePath)) || {};
|
|
283
|
+
// Merge metadata by combining arrays
|
|
284
|
+
if (currentMetadata) {
|
|
285
|
+
Object.keys(currentMetadata).forEach((key) => {
|
|
286
|
+
if (!tempMetadata[key]) {
|
|
287
|
+
tempMetadata[key] = currentMetadata[key];
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
// Write updated metadata back to temp file
|
|
292
|
+
utils_2.fsUtil.writeFile((0, cli_utilities_1.sanitizePath)(tempMetadataFilePath), tempMetadata);
|
|
293
|
+
// Handle assets (which is an object with numeric keys)
|
|
294
|
+
const tempAssets = utils_2.fsUtil.readFile((0, cli_utilities_1.sanitizePath)(tempAssetFilePath)) || {};
|
|
295
|
+
let nextIndex = Object.keys(tempAssets).length + 1;
|
|
296
|
+
// Add current assets with incremented keys
|
|
297
|
+
Object.values(currentAssets).forEach((value) => {
|
|
298
|
+
tempAssets[nextIndex.toString()] = value;
|
|
299
|
+
nextIndex++;
|
|
300
|
+
});
|
|
301
|
+
utils_2.fsUtil.writeFile((0, cli_utilities_1.sanitizePath)(tempAssetFilePath), tempAssets);
|
|
302
|
+
(0, logger_1.log)(this.exportQueryConfig, `Updated temporary files with batch ${i + 1} data`, 'info');
|
|
303
|
+
}
|
|
304
|
+
// Optional: Add delay between batches to avoid rate limiting
|
|
305
|
+
if (i < totalBatches - 1 && this.exportQueryConfig.batchDelayMs) {
|
|
306
|
+
await new Promise((resolve) => setTimeout(resolve, this.exportQueryConfig.batchDelayMs));
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// After all batches are processed, copy temp files back to original files
|
|
310
|
+
const finalMetadata = utils_2.fsUtil.readFile((0, cli_utilities_1.sanitizePath)(tempMetadataFilePath));
|
|
311
|
+
const finalAssets = utils_2.fsUtil.readFile((0, cli_utilities_1.sanitizePath)(tempAssetFilePath));
|
|
312
|
+
utils_2.fsUtil.writeFile((0, cli_utilities_1.sanitizePath)(metadataFilePath), finalMetadata);
|
|
313
|
+
utils_2.fsUtil.writeFile((0, cli_utilities_1.sanitizePath)(assetFilePath), finalAssets);
|
|
314
|
+
(0, logger_1.log)(this.exportQueryConfig, `Final data written back to original files`, 'info');
|
|
315
|
+
// Clean up temp files
|
|
316
|
+
utils_2.fsUtil.removeFile((0, cli_utilities_1.sanitizePath)(tempMetadataFilePath));
|
|
317
|
+
utils_2.fsUtil.removeFile((0, cli_utilities_1.sanitizePath)(tempAssetFilePath));
|
|
318
|
+
(0, logger_1.log)(this.exportQueryConfig, `Temporary files cleaned up`, 'info');
|
|
241
319
|
(0, logger_1.log)(this.exportQueryConfig, 'Referenced assets exported successfully', 'success');
|
|
242
320
|
}
|
|
243
321
|
else {
|
package/lib/types/index.d.ts
CHANGED
|
@@ -140,6 +140,9 @@ export interface QueryExportConfig extends DefaultConfig {
|
|
|
140
140
|
logsPath: string;
|
|
141
141
|
dataPath: string;
|
|
142
142
|
exportDelayMs?: number;
|
|
143
|
+
batchDelayMs?: number;
|
|
144
|
+
assetBatchSize?: number;
|
|
145
|
+
assetBatchDelayMs?: number;
|
|
143
146
|
}
|
|
144
147
|
export interface QueryMetadata {
|
|
145
148
|
query: any;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { QueryExportConfig } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Validates and sets up branch configuration for the stack
|
|
4
|
+
*
|
|
5
|
+
* @param config The export configuration
|
|
6
|
+
* @param stackAPIClient The stack API client
|
|
7
|
+
* @returns Promise that resolves when branch setup is complete
|
|
8
|
+
*/
|
|
9
|
+
export declare const setupBranches: (config: QueryExportConfig, stackAPIClient: any) => Promise<void>;
|
|
10
|
+
export default setupBranches;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setupBranches = void 0;
|
|
4
|
+
const logger_1 = require("./logger");
|
|
5
|
+
/**
|
|
6
|
+
* Validates and sets up branch configuration for the stack
|
|
7
|
+
*
|
|
8
|
+
* @param config The export configuration
|
|
9
|
+
* @param stackAPIClient The stack API client
|
|
10
|
+
* @returns Promise that resolves when branch setup is complete
|
|
11
|
+
*/
|
|
12
|
+
const setupBranches = async (config, stackAPIClient) => {
|
|
13
|
+
if (typeof config !== 'object') {
|
|
14
|
+
throw new Error('Invalid config to setup the branch');
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
if (config.branchName) {
|
|
18
|
+
// Check if the specified branch exists
|
|
19
|
+
(0, logger_1.log)(config, `Validating branch: ${config.branchName}`, 'info');
|
|
20
|
+
const result = await stackAPIClient
|
|
21
|
+
.branch(config.branchName)
|
|
22
|
+
.fetch()
|
|
23
|
+
.catch((err) => {
|
|
24
|
+
(0, logger_1.log)(config, `Error fetching branch: ${err.message}`, 'error');
|
|
25
|
+
return null;
|
|
26
|
+
});
|
|
27
|
+
if (result && typeof result === 'object') {
|
|
28
|
+
(0, logger_1.log)(config, `Branch '${config.branchName}' found`, 'success');
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
throw new Error(`No branch found with the name '${config.branchName}'`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
// If no branch name provided, check if the stack has branches
|
|
36
|
+
(0, logger_1.log)(config, 'No branch specified, checking if stack has branches', 'info');
|
|
37
|
+
const result = await stackAPIClient
|
|
38
|
+
.branch()
|
|
39
|
+
.query()
|
|
40
|
+
.find()
|
|
41
|
+
.catch(() => {
|
|
42
|
+
(0, logger_1.log)(config, 'Stack does not have branches', 'info');
|
|
43
|
+
return null;
|
|
44
|
+
});
|
|
45
|
+
if (result && result.items && Array.isArray(result.items) && result.items.length > 0) {
|
|
46
|
+
// Set default branch to 'main' if it exists
|
|
47
|
+
config.branchName = 'main';
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
// Stack doesn't have branches
|
|
51
|
+
(0, logger_1.log)(config, 'Stack does not have branches', 'info');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
config.branchEnabled = true;
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
(0, logger_1.log)(config, `Error setting up branches: ${error.message}`, 'error');
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
exports.setupBranches = setupBranches;
|
|
63
|
+
exports.default = exports.setupBranches;
|
|
@@ -12,22 +12,26 @@ async function setupQueryExportConfig(flags) {
|
|
|
12
12
|
const exportQueryConfig = Object.assign(Object.assign({}, config_1.default), { exportDir, stackApiKey: flags['stack-api-key'] || '', managementToken: flags.alias ? (_a = cli_utilities_1.configHandler.get(`tokens.${flags.alias}`)) === null || _a === void 0 ? void 0 : _a.token : undefined, query: flags.query, skipReferences: flags['skip-references'] || false, skipDependencies: flags['skip-dependencies'] || false, branchName: flags.branch, securedAssets: flags['secured-assets'] || false, isQueryBasedExport: true, logsPath: exportDir, dataPath: exportDir,
|
|
13
13
|
// Todo: accept the path of the config file from the user
|
|
14
14
|
externalConfigPath: path.join(__dirname, '../config/export-config.json') });
|
|
15
|
+
// override the external config path if the user provides a config file
|
|
16
|
+
if (flags.config) {
|
|
17
|
+
exportQueryConfig.externalConfigPath = (0, cli_utilities_1.sanitizePath)(flags['config']);
|
|
18
|
+
}
|
|
15
19
|
// Handle authentication
|
|
16
20
|
if (flags.alias) {
|
|
17
21
|
const { token, apiKey } = cli_utilities_1.configHandler.get(`tokens.${flags.alias}`) || {};
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (!
|
|
22
|
+
exportQueryConfig.managementToken = token;
|
|
23
|
+
exportQueryConfig.stackApiKey = apiKey;
|
|
24
|
+
if (!exportQueryConfig.managementToken) {
|
|
21
25
|
throw new Error(`No management token found on given alias ${flags.alias}`);
|
|
22
26
|
}
|
|
23
27
|
}
|
|
24
|
-
if (!
|
|
28
|
+
if (!exportQueryConfig.managementToken) {
|
|
25
29
|
if (!(0, cli_utilities_1.isAuthenticated)()) {
|
|
26
30
|
throw new Error('Please login or provide an alias for the management token');
|
|
27
31
|
}
|
|
28
32
|
else {
|
|
29
|
-
|
|
30
|
-
if (typeof
|
|
33
|
+
exportQueryConfig.stackApiKey = flags['stack-api-key'] || (await (0, common_helper_1.askAPIKey)());
|
|
34
|
+
if (typeof exportQueryConfig.stackApiKey !== 'string') {
|
|
31
35
|
throw new Error('Invalid API key received');
|
|
32
36
|
}
|
|
33
37
|
}
|
package/lib/utils/index.d.ts
CHANGED
package/lib/utils/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.unlinkFileLogger = exports.log = exports.fsUtil = exports.fileHelper = void 0;
|
|
3
|
+
exports.setupBranches = exports.unlinkFileLogger = exports.log = exports.fsUtil = exports.fileHelper = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
exports.fileHelper = tslib_1.__importStar(require("./file-helper"));
|
|
6
6
|
var file_helper_1 = require("./file-helper");
|
|
@@ -13,3 +13,5 @@ tslib_1.__exportStar(require("./config-handler"), exports);
|
|
|
13
13
|
tslib_1.__exportStar(require("./content-type-helper"), exports);
|
|
14
14
|
tslib_1.__exportStar(require("./dependency-resolver"), exports);
|
|
15
15
|
tslib_1.__exportStar(require("./referenced-asset-handler"), exports);
|
|
16
|
+
var branch_helper_1 = require("./branch-helper");
|
|
17
|
+
Object.defineProperty(exports, "setupBranches", { enumerable: true, get: function () { return branch_helper_1.setupBranches; } });
|
|
@@ -83,10 +83,21 @@ class AssetReferenceHandler {
|
|
|
83
83
|
assetUIDs.add(match[1]);
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
|
+
let assetUrlRegex = '';
|
|
87
|
+
let assetUIDMatchIndex;
|
|
88
|
+
if (process.env.ENVIRONMENT === 'NON_PROD') {
|
|
89
|
+
assetUrlRegex = '(https://.*?/v3/assets/(.*?)/(.*?)/(.*?)/(.*?)(?="))';
|
|
90
|
+
assetUIDMatchIndex = 3;
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
assetUrlRegex =
|
|
94
|
+
'(https://(assets|(eu-|azure-na-|azure-eu-|gcp-na-|gcp-eu-)?images).contentstack.(io|com)/v3/assets/(.*?)/(.*?)/(.*?)/(.*?)(?="))';
|
|
95
|
+
assetUIDMatchIndex = 6;
|
|
96
|
+
}
|
|
86
97
|
// Pattern 2: Contentstack asset URLs
|
|
87
|
-
const urlRegex = new RegExp(
|
|
98
|
+
const urlRegex = new RegExp(assetUrlRegex, 'g');
|
|
88
99
|
while ((match = urlRegex.exec(content)) !== null) {
|
|
89
|
-
const assetUID = match[
|
|
100
|
+
const assetUID = match[assetUIDMatchIndex]; // The asset UID is in the 6th capture group
|
|
90
101
|
if (assetUID) {
|
|
91
102
|
assetUIDs.add(assetUID);
|
|
92
103
|
}
|
package/oclif.manifest.json
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"commands": {
|
|
3
3
|
"cm:stacks:export-query": {
|
|
4
|
-
"aliases": [
|
|
5
|
-
"cm:export-query"
|
|
6
|
-
],
|
|
4
|
+
"aliases": [],
|
|
7
5
|
"args": {},
|
|
8
6
|
"description": "Export content from a stack using query-based filtering",
|
|
9
7
|
"examples": [
|
|
@@ -104,5 +102,5 @@
|
|
|
104
102
|
]
|
|
105
103
|
}
|
|
106
104
|
},
|
|
107
|
-
"version": "1.0.0-beta.
|
|
105
|
+
"version": "1.0.0-beta.3"
|
|
108
106
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentstack/cli-cm-export-query",
|
|
3
3
|
"description": "Contentstack CLI plugin to export content from stack",
|
|
4
|
-
"version": "1.0.0-beta.
|
|
4
|
+
"version": "1.0.0-beta.3",
|
|
5
5
|
"author": "Contentstack",
|
|
6
6
|
"bugs": "https://github.com/contentstack/cli/issues",
|
|
7
7
|
"dependencies": {
|