@contentsquare/react-native-cli 1.0.0
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/LICENSE +48 -0
- package/README.md +63 -0
- package/dist/auth.js +26 -0
- package/dist/config.js +55 -0
- package/dist/index.js +81 -0
- package/dist/logger.js +52 -0
- package/dist/sourcemap.js +54 -0
- package/dist/upload.js +57 -0
- package/dist/utils.js +24 -0
- package/package.json +42 -0
- package/scripts/contentsquare-xcode.sh +35 -0
- package/scripts/contentsquare.gradle +33 -0
- package/scripts/ios-config.sh +29 -0
- package/scripts/source-maps.gradle +223 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
The Content Square React Native CLI License 1.0
|
|
2
|
+
(c) Content Square SAS 2025
|
|
3
|
+
|
|
4
|
+
By downloading, installing, implementing or otherwise using the Content Square
|
|
5
|
+
SAS React Native CLI (“the Contentsquare React Native CLI”) you agree to the
|
|
6
|
+
terms and conditions of this License Agreement.
|
|
7
|
+
|
|
8
|
+
Contentsquare grants you a limited, royalty-free, non-exclusive,
|
|
9
|
+
non-transferrable, non-sublicensable, revocable copyright license to use and
|
|
10
|
+
reproduce, without modification, the Contentsquare React Native CLI solely to enable the
|
|
11
|
+
use of Contentsquare with or in your React Native platform applications, subject to the
|
|
12
|
+
following conditions:
|
|
13
|
+
|
|
14
|
+
Other than as permitted in this agreement, you may not distribute, display,
|
|
15
|
+
copy, execute publicly, make available to the public, reduce to human readable
|
|
16
|
+
form, lease, resale, disassemble, decompile, adapt, sublicense, host as a
|
|
17
|
+
service, make other commercial use of, sell, rent, lend, process, compile,
|
|
18
|
+
reverse engineer, combine with other software, translate, modify, or create
|
|
19
|
+
derivative works of the Contentsquare React Native CLI.
|
|
20
|
+
|
|
21
|
+
Contentsquare has no obligation to enable you or any of your applications to
|
|
22
|
+
access, interact with, or retrieve or publish content to any Contentsquare
|
|
23
|
+
platform or service. However, Contentsquare may provide you with such platform
|
|
24
|
+
services on additional terms.
|
|
25
|
+
|
|
26
|
+
Contentsquare further has no obligation to provide support, maintenance,
|
|
27
|
+
upgrades, modifications, or new releases of the Contentsquare React Native CLI, unless
|
|
28
|
+
otherwise agreed in writing.
|
|
29
|
+
|
|
30
|
+
All other rights are reserved.
|
|
31
|
+
|
|
32
|
+
TO THE FULLEST EXTENT PERMITTED BY LAW, CONTENTSQUARE MAKES NO, AND HEREBY
|
|
33
|
+
DISCLAIMS ANY AND ALL, REPRESENTATIONS WARRANTIES, CONDITIONS AND ALL OTHER
|
|
34
|
+
TERMS OF ANY KIND WHATSOEVER WITH RESPECT TO THE CONTENTSQUARE REACT NATIVE CLI AND
|
|
35
|
+
RELATED DOCUMENTATION, OR ADDITIONAL SERVICES, WHETHER EXPRESS OR IMPLIED, ORAL
|
|
36
|
+
OR WRITTEN, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OF ACCURACY, QUALITY,
|
|
37
|
+
PERFORMANCE, MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A PARTICULAR
|
|
38
|
+
PURPOSE. FOR THE AVOIDANCE OF DOUBT, CONTENTSQUARE DOES NOT WARRANT THAT THE
|
|
39
|
+
CONTENTSQUARE REACT NATIVE CLI SHALL MEET YOUR NEEDS OR BE ERROR FREE. IN NO EVENT SHALL
|
|
40
|
+
CONTENTSQUARE BE LIABLE FOR ANY TYPE OF DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
41
|
+
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
42
|
+
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
|
43
|
+
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
44
|
+
ARISING IN ANY WAY OUT OF THE USE OF THE CONTENTSQUARE REACT NATIVE CLI, EVEN IF ADVISED
|
|
45
|
+
OF THE POSSIBILITY OF SUCH DAMAGE. YOU ASSUME SOLE RESPONSIBILITY FOR RESULTS
|
|
46
|
+
OBTAINED FROM THE USE OF THE CONTENTSQUARE REACT NATIVE CLI BY YOU. THE CONTENTSQUARE
|
|
47
|
+
REACT NATIVE CLI, SCRIPTS, OR ADDITIONAL SERVICES ARE PROVIDED TO YOU ON AN “AS IS”
|
|
48
|
+
BASIS.
|
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# @contentsquare/react-native-cli
|
|
2
|
+
|
|
3
|
+
A Command Line Interface (CLI) tool for Contentsquare used to upload mapping files for React Native apps.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
yarn add @contentsquare/react-native-cli --dev
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Configuration
|
|
12
|
+
|
|
13
|
+
The upload command requires the following information:
|
|
14
|
+
|
|
15
|
+
- **The targeted platform**
|
|
16
|
+
|
|
17
|
+
`ios` or `android`
|
|
18
|
+
|
|
19
|
+
- **The project ID**
|
|
20
|
+
|
|
21
|
+
1. Login to the Contentsquare platform on https://app.contentsquare.com
|
|
22
|
+
2. Make sure to be on the right project
|
|
23
|
+
3. The project ID can be found in the URL query parameter `project`:<br/>`https://app.contentsquare.com/#/{MODULE_NAME}?project={PROJECT_ID}&hash={HASH}]`
|
|
24
|
+
|
|
25
|
+
- **Valid API credentials**
|
|
26
|
+
|
|
27
|
+
Follow the dedicated documentation from the Help Center to get the client ID and client Secret: [How to create API credentials](https://support.contentsquare.com/hc/en-gb/articles/9292880837660-How-to-create-API-credentials).
|
|
28
|
+
|
|
29
|
+
### Using JSON file
|
|
30
|
+
|
|
31
|
+
You can provide this information by adding a `contentsquare-cli.json` file at the root of your project.
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"android": {
|
|
36
|
+
"clientId": "[ANDROID CLIENT ID]",
|
|
37
|
+
"clientSecret": "[ANDROID CLIENT SECRET]",
|
|
38
|
+
"projectId": "[ANDROID PROJECT ID]",
|
|
39
|
+
"sourcemapPath": "[PATH TO SOURCEMAP IN ANDROID PROJECT]"
|
|
40
|
+
},
|
|
41
|
+
"ios": {
|
|
42
|
+
"clientId": "[IOS CLIENT ID]",
|
|
43
|
+
"clientSecret": "[IOS CLIENT SECRET]",
|
|
44
|
+
"projectId": "[IOS PROJECT ID]",
|
|
45
|
+
"sourcemapPath": "[PATH TO SOURCEMAP IN IOS PROJECT]"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Using command line arguments
|
|
51
|
+
|
|
52
|
+
Alternatively, this information can be added as arguments directly on the command line
|
|
53
|
+
|
|
54
|
+
| Option | Description |
|
|
55
|
+
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
56
|
+
| `--client-id` | The client ID of [API credentials](https://support.contentsquare.com/hc/en-gb/articles/9292880837660-How-to-create-API-credentials) |
|
|
57
|
+
| `--client-secret` | The client secret of [API credentials](https://support.contentsquare.com/hc/en-gb/articles/9292880837660-How-to-create-API-credentials) |
|
|
58
|
+
| `--config` | The path to the configuration file relative to project root |
|
|
59
|
+
| `--help` | Display the help message |
|
|
60
|
+
| `--platform` | The platform to target: `ios` or `android` |
|
|
61
|
+
| `--project-id` | The project ID |
|
|
62
|
+
| `--sourcemap-path` | The path to the sourcemap file |
|
|
63
|
+
| `--upload-url` | The URL of the Contentsquare platform, default value: https://api.csq.io |
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.authenticate = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const logger_1 = require("./logger");
|
|
9
|
+
const authenticate = async (clientId, clientSecret, projectId, baseUrl) => {
|
|
10
|
+
try {
|
|
11
|
+
const endpoint = `${baseUrl}/v1/oauth/token`;
|
|
12
|
+
logger_1.log.verbose(`Authentication details\nendpoint: ${endpoint},\nclientId: ${clientId},\nprojectId: ${projectId}`);
|
|
13
|
+
const response = await axios_1.default.post(endpoint, {
|
|
14
|
+
client_id: clientId,
|
|
15
|
+
client_secret: clientSecret,
|
|
16
|
+
project_id: projectId,
|
|
17
|
+
grant_type: 'client_credentials',
|
|
18
|
+
scope: 'symbolicator',
|
|
19
|
+
});
|
|
20
|
+
return response.data;
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
throw new Error(`Authentication failed: ${error}`);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
exports.authenticate = authenticate;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createConfig = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const logger_1 = require("./logger");
|
|
10
|
+
/**
|
|
11
|
+
* Creates a configuration object based on the provided options and config file.
|
|
12
|
+
* @param {Options} options - The options object.
|
|
13
|
+
* @param {string} configPath - The path to the config file.
|
|
14
|
+
* @returns {Config} - The configuration object.
|
|
15
|
+
*/
|
|
16
|
+
const createConfig = (options, configPath) => {
|
|
17
|
+
const configFile = configPath
|
|
18
|
+
? path_1.default.resolve(configPath)
|
|
19
|
+
: path_1.default.resolve('./contentsquare-cli.json');
|
|
20
|
+
let configFileValues = { android: {}, ios: {} };
|
|
21
|
+
if (fs_1.default.existsSync(configFile)) {
|
|
22
|
+
logger_1.log.verbose(`Loading config file from ${configFile}`);
|
|
23
|
+
configFileValues = JSON.parse(fs_1.default.readFileSync(configFile).toString());
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
logger_1.log.warn(`No config file found at: ${configFile}`);
|
|
27
|
+
}
|
|
28
|
+
const defaultUploadUrl = 'https://authentication.contentsquare.com';
|
|
29
|
+
const getConfigValue = (platform, key) => {
|
|
30
|
+
var _a, _b, _c;
|
|
31
|
+
return options.platform === platform
|
|
32
|
+
? ((_a = options[key]) === null || _a === void 0 ? void 0 : _a.toString()) || ((_b = configFileValues[platform]) === null || _b === void 0 ? void 0 : _b[key])
|
|
33
|
+
: (_c = configFileValues[platform]) === null || _c === void 0 ? void 0 : _c[key];
|
|
34
|
+
};
|
|
35
|
+
const config = {
|
|
36
|
+
android: {
|
|
37
|
+
clientId: getConfigValue('android', 'clientId'),
|
|
38
|
+
clientSecret: getConfigValue('android', 'clientSecret'),
|
|
39
|
+
projectId: getConfigValue('android', 'projectId'),
|
|
40
|
+
sourcemapPath: getConfigValue('android', 'sourcemapPath'),
|
|
41
|
+
uploadUrl: getConfigValue('android', 'uploadUrl') || defaultUploadUrl,
|
|
42
|
+
},
|
|
43
|
+
ios: {
|
|
44
|
+
clientId: getConfigValue('ios', 'clientId'),
|
|
45
|
+
clientSecret: getConfigValue('ios', 'clientSecret'),
|
|
46
|
+
projectId: getConfigValue('ios', 'projectId'),
|
|
47
|
+
sourcemapPath: getConfigValue('ios', 'sourcemapPath'),
|
|
48
|
+
uploadUrl: getConfigValue('ios', 'uploadUrl') || defaultUploadUrl,
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
// Log the final configuration for debug purposes
|
|
52
|
+
logger_1.log.verbose('Configuration:\n', JSON.stringify(config, null, 2));
|
|
53
|
+
return config;
|
|
54
|
+
};
|
|
55
|
+
exports.createConfig = createConfig;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const auth_1 = require("./auth");
|
|
6
|
+
const config_1 = require("./config");
|
|
7
|
+
const logger_1 = require("./logger");
|
|
8
|
+
const sourcemap_1 = require("./sourcemap");
|
|
9
|
+
const upload_1 = require("./upload");
|
|
10
|
+
const utils_1 = require("./utils");
|
|
11
|
+
const packageJson = require('../package.json');
|
|
12
|
+
const { version } = packageJson;
|
|
13
|
+
const program = new commander_1.Command();
|
|
14
|
+
program
|
|
15
|
+
.version(version)
|
|
16
|
+
.description('CLI tool from Contentsquare to upload source maps for React Native apps');
|
|
17
|
+
program
|
|
18
|
+
.command('update-sourcemap-id')
|
|
19
|
+
.description('Update source map ID')
|
|
20
|
+
.option('--platform <platform>', 'Platform (ios or android)')
|
|
21
|
+
.option('--verbose', 'Enable verbose logging')
|
|
22
|
+
.option('--working-dir <workingDir>', 'Working directory, defaults to cwd')
|
|
23
|
+
.action(async (options) => {
|
|
24
|
+
if (options.verbose) {
|
|
25
|
+
(0, logger_1.setLogLevel)('verbose');
|
|
26
|
+
}
|
|
27
|
+
if (options.workingDir) {
|
|
28
|
+
process.chdir(options.workingDir);
|
|
29
|
+
logger_1.log.verbose(`Changed working directory to ${process.cwd()}`);
|
|
30
|
+
}
|
|
31
|
+
const platforms = (0, utils_1.getPlatformSelection)(options.platform);
|
|
32
|
+
(0, sourcemap_1.updateSourcemapId)(platforms);
|
|
33
|
+
});
|
|
34
|
+
program
|
|
35
|
+
.command('upload-sourcemaps')
|
|
36
|
+
.description('Upload source maps for React Native app')
|
|
37
|
+
.option('--client-id <clientId>', 'Client ID of API credentials')
|
|
38
|
+
.option('--client-secret <clientSecret>', 'Client secret API credentials')
|
|
39
|
+
.option('--config <config>', 'Path to the config file relative to the root of the project')
|
|
40
|
+
.option('--platform <platform>', 'Platform (ios or android)')
|
|
41
|
+
.option('--project-id <projectId>', 'Project ID')
|
|
42
|
+
.option('--sourcemap-path <sourcemapPath>', 'Path to the source map file')
|
|
43
|
+
.option('--upload-url <uploadUrl>', 'URL for API upload endpoint')
|
|
44
|
+
.option('--verbose', 'Enable verbose logging')
|
|
45
|
+
.option('--working-dir <workingDir>', 'Working directory, defaults to cwd')
|
|
46
|
+
.action(async (options) => {
|
|
47
|
+
if (options.verbose) {
|
|
48
|
+
(0, logger_1.setLogLevel)('verbose');
|
|
49
|
+
}
|
|
50
|
+
if (options.workingDir) {
|
|
51
|
+
process.chdir(options.workingDir);
|
|
52
|
+
logger_1.log.verbose(`Changed working directory to ${process.cwd()}`);
|
|
53
|
+
}
|
|
54
|
+
const config = (0, config_1.createConfig)(options, options.config);
|
|
55
|
+
const platforms = (0, utils_1.getPlatformSelection)(options.platform);
|
|
56
|
+
for (const platform of platforms) {
|
|
57
|
+
const platformConfig = config[platform];
|
|
58
|
+
if (platformConfig.clientId &&
|
|
59
|
+
platformConfig.clientSecret &&
|
|
60
|
+
platformConfig.projectId &&
|
|
61
|
+
platformConfig.sourcemapPath &&
|
|
62
|
+
platformConfig.uploadUrl) {
|
|
63
|
+
try {
|
|
64
|
+
logger_1.log.verbose(`Authenticating for ${platform} ...`);
|
|
65
|
+
const { access_token: token, endpoint, token_type: tokenType, } = await (0, auth_1.authenticate)(platformConfig.clientId, platformConfig.clientSecret, platformConfig.projectId, (0, utils_1.ensureHttps)(platformConfig.uploadUrl));
|
|
66
|
+
logger_1.log.verbose(`Authentication successful for ${platform}`);
|
|
67
|
+
const sourcemapId = (0, sourcemap_1.readSourcemapId)(platform);
|
|
68
|
+
logger_1.log.verbose(`Uploading source map for ${platform} ...`);
|
|
69
|
+
await (0, upload_1.uploadSourcemap)(platformConfig.sourcemapPath, platform, sourcemapId, tokenType, token, (0, utils_1.ensureHttps)(endpoint));
|
|
70
|
+
logger_1.log.success(`Source map with ID: ${sourcemapId} uploaded successfully for ${platform}`);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
logger_1.log.error(`Failed to upload source map for ${platform}, project ID: ${platformConfig.projectId}:`, error);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
logger_1.log.warn(`Missing required parameters for ${platform}, skipping upload.`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
program.parse(process.argv);
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.log = exports.setLogLevel = void 0;
|
|
7
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
let logLevel = 'info';
|
|
10
|
+
const setLogLevel = (level) => {
|
|
11
|
+
logLevel = level;
|
|
12
|
+
};
|
|
13
|
+
exports.setLogLevel = setLogLevel;
|
|
14
|
+
const logLevels = {
|
|
15
|
+
verbose: 0,
|
|
16
|
+
info: 1,
|
|
17
|
+
success: 2,
|
|
18
|
+
warn: 3,
|
|
19
|
+
error: 4,
|
|
20
|
+
};
|
|
21
|
+
const shouldLog = (level) => {
|
|
22
|
+
return logLevels[level] >= logLevels[logLevel];
|
|
23
|
+
};
|
|
24
|
+
const logMessage = (level, ...messages) => {
|
|
25
|
+
if (shouldLog(level)) {
|
|
26
|
+
const color = getColor(level);
|
|
27
|
+
console.log(color(`${level}`), ...messages);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const getColor = (level) => {
|
|
31
|
+
switch (level) {
|
|
32
|
+
case 'verbose':
|
|
33
|
+
return chalk_1.default.dim;
|
|
34
|
+
case 'info':
|
|
35
|
+
return chalk_1.default.blue;
|
|
36
|
+
case 'success':
|
|
37
|
+
return chalk_1.default.green;
|
|
38
|
+
case 'warn':
|
|
39
|
+
return chalk_1.default.yellow;
|
|
40
|
+
case 'error':
|
|
41
|
+
return chalk_1.default.red;
|
|
42
|
+
default:
|
|
43
|
+
return chalk_1.default.white;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
exports.log = {
|
|
47
|
+
verbose: (...messages) => logMessage('verbose', ...messages),
|
|
48
|
+
info: (...messages) => logMessage('info', ...messages),
|
|
49
|
+
success: (...messages) => logMessage('success', ...messages),
|
|
50
|
+
warn: (...messages) => logMessage('warn', ...messages),
|
|
51
|
+
error: (...messages) => logMessage('error', ...messages),
|
|
52
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.readSourcemapId = exports.saveSourcemapId = exports.updateSourcemapId = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const uuid_1 = require("uuid");
|
|
10
|
+
const logger_1 = require("./logger");
|
|
11
|
+
const SOURCEMAP_INFO_FILE = 'contentsquare-sourcemap-info.json';
|
|
12
|
+
const updateSourcemapId = (platforms) => {
|
|
13
|
+
for (const platform of platforms) {
|
|
14
|
+
const sourcemapId = (0, uuid_1.v4)();
|
|
15
|
+
try {
|
|
16
|
+
(0, exports.saveSourcemapId)(platform, sourcemapId);
|
|
17
|
+
logger_1.log.success(`Updated source map ID for platform ${platform}: ${sourcemapId}`);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
logger_1.log.error(error);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
exports.updateSourcemapId = updateSourcemapId;
|
|
25
|
+
const saveSourcemapId = (platform, id) => {
|
|
26
|
+
const sourcemapInfoPath = path_1.default.join(process.cwd(), SOURCEMAP_INFO_FILE);
|
|
27
|
+
try {
|
|
28
|
+
let config = {};
|
|
29
|
+
if (fs_1.default.existsSync(sourcemapInfoPath)) {
|
|
30
|
+
config = JSON.parse(fs_1.default.readFileSync(sourcemapInfoPath).toString());
|
|
31
|
+
}
|
|
32
|
+
config[platform] = { id };
|
|
33
|
+
fs_1.default.writeFileSync(sourcemapInfoPath, JSON.stringify(config, null, 2));
|
|
34
|
+
logger_1.log.verbose(`Source map ID: ${id}\nsaved successfully for platform: ${platform}\nin path:${sourcemapInfoPath}`);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
throw new Error(`Failed to save source map ID for ${platform} in ${sourcemapInfoPath}: ${error}`);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
exports.saveSourcemapId = saveSourcemapId;
|
|
41
|
+
const readSourcemapId = (platform) => {
|
|
42
|
+
var _a;
|
|
43
|
+
const sourcemapInfoPath = path_1.default.join(process.cwd(), SOURCEMAP_INFO_FILE);
|
|
44
|
+
if (!fs_1.default.existsSync(sourcemapInfoPath)) {
|
|
45
|
+
throw new Error(`Source map info file not found at path: ${sourcemapInfoPath}.\nPlease run 'update-sourcemap-id' command before bundling.`);
|
|
46
|
+
}
|
|
47
|
+
const config = JSON.parse(fs_1.default.readFileSync(sourcemapInfoPath).toString());
|
|
48
|
+
const sourcemapId = (_a = config[platform]) === null || _a === void 0 ? void 0 : _a.id;
|
|
49
|
+
if (!sourcemapId) {
|
|
50
|
+
throw new Error(`Source map ID for platform '${platform}' not found in the source map info file.`);
|
|
51
|
+
}
|
|
52
|
+
return sourcemapId;
|
|
53
|
+
};
|
|
54
|
+
exports.readSourcemapId = readSourcemapId;
|
package/dist/upload.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.uploadSourcemap = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const node_zlib_1 = require("node:zlib");
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const logger_1 = require("./logger");
|
|
12
|
+
const uploadSourcemap = async (filePath, platform, sourcemapId, tokenType, authToken, baseUrl) => {
|
|
13
|
+
const endpoint = `${baseUrl}/v1/react-symbols/${sourcemapId}?platform=${platform}`;
|
|
14
|
+
const fileName = path_1.default.basename(filePath);
|
|
15
|
+
if (!fs_1.default.existsSync(filePath)) {
|
|
16
|
+
logger_1.log.error(`File ${filePath} not found`);
|
|
17
|
+
throw new Error(`File not found: ${filePath}`);
|
|
18
|
+
}
|
|
19
|
+
const gzippedFileName = `${fileName}.gz`;
|
|
20
|
+
logger_1.log.verbose(`Compressing file ${fileName} to ${gzippedFileName} ...`);
|
|
21
|
+
// Compress the file using gzip
|
|
22
|
+
const gzip = (0, node_zlib_1.createGzip)();
|
|
23
|
+
const source = fs_1.default.createReadStream(filePath);
|
|
24
|
+
const destination = fs_1.default.createWriteStream(gzippedFileName);
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
source
|
|
27
|
+
.pipe(gzip)
|
|
28
|
+
.pipe(destination)
|
|
29
|
+
.on('finish', async () => {
|
|
30
|
+
try {
|
|
31
|
+
logger_1.log.verbose(`Uploading compressed file ${gzippedFileName} to ${endpoint} ...`);
|
|
32
|
+
const gzippedFileStream = fs_1.default.createReadStream(gzippedFileName);
|
|
33
|
+
await axios_1.default.post(endpoint, gzippedFileStream, {
|
|
34
|
+
headers: {
|
|
35
|
+
'Content-Encoding': 'gzip',
|
|
36
|
+
'Content-Type': 'application/octet-stream',
|
|
37
|
+
Authorization: `${tokenType[0].toUpperCase() + tokenType.slice(1)} ${authToken}`,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
// Clean up the gzipped file
|
|
41
|
+
fs_1.default.unlinkSync(gzippedFileName);
|
|
42
|
+
logger_1.log.verbose(`File ${gzippedFileName} uploaded and removed locally.`);
|
|
43
|
+
resolve();
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
fs_1.default.unlinkSync(gzippedFileName);
|
|
47
|
+
logger_1.log.error(`Error uploading file ${gzippedFileName} to ${endpoint}`);
|
|
48
|
+
reject(new Error(`Error uploading file: ${error}`));
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
.on('error', (error) => {
|
|
52
|
+
logger_1.log.error(`Error compressing file ${fileName}: ${error.message}`);
|
|
53
|
+
reject(new Error(`Error compressing file: ${error.message}`));
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
exports.uploadSourcemap = uploadSourcemap;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getPlatformSelection = exports.ensureHttps = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Adds https prefix to a URL if not already present.
|
|
6
|
+
* @param {string} url - The URL to check and modify.
|
|
7
|
+
* @returns {string} - The URL with https prefix.
|
|
8
|
+
*/
|
|
9
|
+
const ensureHttps = (url) => {
|
|
10
|
+
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
|
11
|
+
return `https://${url}`;
|
|
12
|
+
}
|
|
13
|
+
return url;
|
|
14
|
+
};
|
|
15
|
+
exports.ensureHttps = ensureHttps;
|
|
16
|
+
/**
|
|
17
|
+
* Returns the exclusive platform selection based on the input, defaults to both platforms.
|
|
18
|
+
* @param {string} platform - The platform to check.
|
|
19
|
+
* @returns {Array<'ios' | 'android'>} - The platform selection.
|
|
20
|
+
*/
|
|
21
|
+
const getPlatformSelection = (platform) => {
|
|
22
|
+
return platform ? [platform] : ['ios', 'android'];
|
|
23
|
+
};
|
|
24
|
+
exports.getPlatformSelection = getPlatformSelection;
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@contentsquare/react-native-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI tool from Contentsquare to upload source maps for React Native apps",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"contentsquare-cli": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"/README.md",
|
|
12
|
+
"/LICENSE",
|
|
13
|
+
"/dist/",
|
|
14
|
+
"/bin/",
|
|
15
|
+
"/scripts/"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"start": "node dist/index.js",
|
|
20
|
+
"pack": "yarn pack --out %s-v%v.tgz",
|
|
21
|
+
"test": "jest",
|
|
22
|
+
"publish": "yarn npm publish --access public",
|
|
23
|
+
"publish:beta": "yarn npm publish --access public --tag beta",
|
|
24
|
+
"prepack": "echo 'TODO: Make it call 'yarn test' before packing'",
|
|
25
|
+
"postinstall": "chmod +x ./scripts/contentsquare-xcode.sh && chmod +x ./scripts/ios-config.sh"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"axios": "^1.15.0",
|
|
29
|
+
"commander": "^7.2.0",
|
|
30
|
+
"fs": "0.0.2",
|
|
31
|
+
"path": "0.12.7",
|
|
32
|
+
"uuid": "10.0.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/jest": "^29.5.13",
|
|
36
|
+
"@types/node": "20.14.2",
|
|
37
|
+
"@types/uuid": "^9.0.8",
|
|
38
|
+
"jest": "^29.7.0",
|
|
39
|
+
"ts-jest": "^29.2.5",
|
|
40
|
+
"typescript": "^4.0.0"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# This script is supposed to be invoked as part of Xcode build process
|
|
3
|
+
# and relies on environment variables (including PWD) set by Xcode
|
|
4
|
+
|
|
5
|
+
# Print commands before executing them (useful for troubleshooting)
|
|
6
|
+
set -x -e
|
|
7
|
+
|
|
8
|
+
# Path to contentsquare-cli folder inside node_modules
|
|
9
|
+
CONTENTSQUARE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
10
|
+
|
|
11
|
+
# Path to react-native folder inside node_modules
|
|
12
|
+
REACT_NATIVE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../react-native" && pwd)"
|
|
13
|
+
|
|
14
|
+
# Read the path to Contentsquare cli config file, default is ./contentsquare-cli.json
|
|
15
|
+
CONTENTSQUARE_CONFIG_PATH=${CONTENTSQUARE_CONFIG_PATH:-"./contentsquare-cli.json"}
|
|
16
|
+
|
|
17
|
+
# check and assign NODE_BINARY env
|
|
18
|
+
source "$REACT_NATIVE_DIR/scripts/node-binary.sh"
|
|
19
|
+
|
|
20
|
+
# Define the path to react-native-xcode.sh
|
|
21
|
+
REACT_NATIVE_XCODE="$REACT_NATIVE_DIR/scripts/react-native-xcode.sh"
|
|
22
|
+
|
|
23
|
+
# Change directory to React Native project root directory, so contentsquare-cli can be called from proper location
|
|
24
|
+
cd ..
|
|
25
|
+
echo "Changed directory to: $(pwd)"
|
|
26
|
+
|
|
27
|
+
# Pre-build step: Any specific tasks you need to run before react-native-xcode.sh
|
|
28
|
+
"$NODE_BINARY" ./node_modules/.bin/contentsquare-cli update-sourcemap-id --platform ios
|
|
29
|
+
|
|
30
|
+
/bin/sh -c "$REACT_NATIVE_XCODE"
|
|
31
|
+
|
|
32
|
+
# Post-build step: Any tasks you want to execute after react-native-xcode.sh
|
|
33
|
+
"$NODE_BINARY" ./node_modules/.bin/contentsquare-cli upload-sourcemaps --config "$CONTENTSQUARE_CONFIG_PATH" --platform ios
|
|
34
|
+
|
|
35
|
+
/bin/sh -c "$CONTENTSQUARE_DIR/scripts/ios-config.sh"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import groovy.json.JsonSlurper
|
|
2
|
+
|
|
3
|
+
// Apply the local source-map.gradle file as a script
|
|
4
|
+
apply from: "$rootDir/../node_modules/@contentsquare/react-native-cli/scripts/source-maps.gradle"
|
|
5
|
+
println "Source maps gradle script applied."
|
|
6
|
+
|
|
7
|
+
android.applicationVariants.all { variant ->
|
|
8
|
+
def flavorName = variant.flavorName?.capitalize() ?: "NoFlavor"
|
|
9
|
+
def buildTypeName = variant.buildType.name.capitalize()
|
|
10
|
+
def buildVariant = "${flavorName}${buildTypeName}"
|
|
11
|
+
|
|
12
|
+
// Determine contentsquareConfigPath, log both custom and default paths
|
|
13
|
+
def contentsquareConfigPath = project.hasProperty("contentsquareConfigPath${buildVariant}") ?
|
|
14
|
+
project["contentsquareConfigPath${buildVariant}"] :
|
|
15
|
+
project.contentsquareConfigPath
|
|
16
|
+
|
|
17
|
+
def contentsquareConfigPathAdjusted = "../${contentsquareConfigPath}"
|
|
18
|
+
println "Using Contentsquare config path for ${buildVariant}: ${contentsquareConfigPathAdjusted}"
|
|
19
|
+
|
|
20
|
+
// Check if contentsquareConfigPathAdjusted is defined
|
|
21
|
+
if (contentsquareConfigPathAdjusted == null) {
|
|
22
|
+
println "ERROR: Contentsquare config path is not defined for ${buildVariant}."
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
def buildType = variant.buildType
|
|
27
|
+
def productFlavor = variant.productFlavors.isEmpty() ? null : variant.productFlavors[0]
|
|
28
|
+
|
|
29
|
+
def crashExtension = productFlavor?.extensions?.findByName("errorAnalysisCrash")
|
|
30
|
+
?: buildType?.extensions?.findByName("errorAnalysisCrash")
|
|
31
|
+
|
|
32
|
+
crashExtension?.reactNativeCredentialsFile { file(contentsquareConfigPathAdjusted) }
|
|
33
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Print commands before executing them (useful for troubleshooting)
|
|
4
|
+
set -x -e
|
|
5
|
+
|
|
6
|
+
# Read the path to Contentsquare CLI config file, default is ./contentsquare-cli.json
|
|
7
|
+
CONTENTSQUARE_CONFIG_PATH=${CONTENTSQUARE_CONFIG_PATH:-"./contentsquare-cli.json"}
|
|
8
|
+
|
|
9
|
+
# TODO: Have to use this workaround that depends on env var, upload-symbols script does not support --url argument
|
|
10
|
+
CONTENTSQUARE_STAGING=${CONTENTSQUARE_STAGING:-"false"}
|
|
11
|
+
|
|
12
|
+
extract_ios_json_value() {
|
|
13
|
+
local key=$1
|
|
14
|
+
local file=$2
|
|
15
|
+
grep -A 10 '"ios": {' "$file" | grep -Eo "\"$key\": \".*\"|\"$key\": [0-9]+" | sed -E "s/\"$key\": \"(.*)\"/\1/" | sed -E "s/\"$key\": ([0-9]+)/\1/"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
# Extract values from the "ios" object in the JSON file
|
|
19
|
+
CLIENT_ID=$(extract_ios_json_value "clientId" "$CONTENTSQUARE_CONFIG_PATH")
|
|
20
|
+
CLIENT_SECRET=$(extract_ios_json_value "clientSecret" "$CONTENTSQUARE_CONFIG_PATH")
|
|
21
|
+
PROJECT_ID=$(extract_ios_json_value "projectId" "$CONTENTSQUARE_CONFIG_PATH")
|
|
22
|
+
|
|
23
|
+
# Contentsquare script for iOS symbol upload
|
|
24
|
+
# --staging requires you to be connected to the VPN
|
|
25
|
+
${SRCROOT}/upload-symbols ${DWARF_DSYM_FOLDER_PATH} \
|
|
26
|
+
--project-id $PROJECT_ID \
|
|
27
|
+
--client-id $CLIENT_ID \
|
|
28
|
+
--client-secret $CLIENT_SECRET \
|
|
29
|
+
$( [ "$CONTENTSQUARE_STAGING" = "true" ] && echo "--staging" )
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import org.apache.tools.ant.taskdefs.condition.Os
|
|
2
|
+
|
|
3
|
+
afterEvaluate {
|
|
4
|
+
def androidExtension = extensions.findByName("android")
|
|
5
|
+
|
|
6
|
+
if (androidExtension == null || androidExtension.applicationVariants == null) {
|
|
7
|
+
throw new GradleException("Android extension is missing or not running in the application module. Ensure 'contentsquare.gradle' is applied in the correct module, typically in 'android/app/build.gradle'.")
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
androidExtension.applicationVariants.all { variant ->
|
|
11
|
+
def flavorName = variant.flavorName.capitalize()
|
|
12
|
+
def buildTypeName = variant.buildType.name.capitalize()
|
|
13
|
+
def buildVariant = "${flavorName}${buildTypeName}"
|
|
14
|
+
|
|
15
|
+
println "Processing Build Variant: ${buildVariant}"
|
|
16
|
+
|
|
17
|
+
def reactConfig = getReactConfig(buildDir, variant)
|
|
18
|
+
def targetName = reactConfig["targetName"]
|
|
19
|
+
def targetPath = reactConfig["targetPath"]
|
|
20
|
+
def reactRoot = file(reactConfig.root)
|
|
21
|
+
|
|
22
|
+
def bundleTask = tasks.findByName(reactConfig.bundleTaskName)
|
|
23
|
+
|
|
24
|
+
if (bundleTask == null) {
|
|
25
|
+
logger.info("No JS bundle task found for variant=${targetName}.")
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
if (!bundleTask.enabled) {
|
|
29
|
+
logger.info("JS bundle task for variant=${targetName} is disabled.")
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
def serviceName = getServiceName(variant)
|
|
34
|
+
logger.info("Using service name=${serviceName} and version=${variant.versionName} for upload.")
|
|
35
|
+
|
|
36
|
+
def bundleAssetName = reactConfig.bundleAssetName
|
|
37
|
+
def sourceMapsDirectory = file("$buildDir/generated/sourcemaps/react/${targetPath}")
|
|
38
|
+
def sourceMapFile = file("$sourceMapsDirectory/${bundleAssetName}.map")
|
|
39
|
+
|
|
40
|
+
def updateTask = tasks.register("update${targetName}SourceMapsID", Exec) {
|
|
41
|
+
group = "contentsquare"
|
|
42
|
+
description = "Updates the source maps ID for the build."
|
|
43
|
+
|
|
44
|
+
workingDir = file(reactConfig.root)
|
|
45
|
+
commandLine "${getContentsquareCLIExecPath(reactConfig)}",
|
|
46
|
+
"update-sourcemap-id",
|
|
47
|
+
"--platform",
|
|
48
|
+
"android",
|
|
49
|
+
"--verbose",
|
|
50
|
+
"--working-dir",
|
|
51
|
+
reactConfig.root
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
def uploadTask = tasks.register("upload${targetName}SourceMaps") {
|
|
55
|
+
group = "contentsquare"
|
|
56
|
+
description = "Uploads source maps to Contentsquare."
|
|
57
|
+
|
|
58
|
+
// Declare inputs so Gradle knows this task depends on these files
|
|
59
|
+
inputs.file(sourceMapFile)
|
|
60
|
+
inputs.files(reactConfig.bundleFileResolver)
|
|
61
|
+
|
|
62
|
+
doLast {
|
|
63
|
+
def defaultConfigPath = project.ext.contentsquareConfigPath
|
|
64
|
+
|
|
65
|
+
def specificConfigKey = "contentsquareConfigPath${buildVariant}"
|
|
66
|
+
def specificConfigPath = project.ext.has(specificConfigKey) ? project.ext[specificConfigKey] : null
|
|
67
|
+
|
|
68
|
+
def resolvedConfigPath = specificConfigPath ? "$rootDir/${specificConfigPath}" : "$rootDir/${defaultConfigPath}"
|
|
69
|
+
println "Using config path for ${buildVariant}: ${resolvedConfigPath}"
|
|
70
|
+
|
|
71
|
+
def jsBundleFile = reactConfig.bundleFileResolver()
|
|
72
|
+
if (jsBundleFile == null || !sourceMapFile.exists()) {
|
|
73
|
+
throw new GradleException("Source maps or JS bundle file is missing; upload aborted.")
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
def cliPath = getContentsquareCLIExecPath(reactConfig).toString()
|
|
77
|
+
def configPath = resolvedConfigPath.toString()
|
|
78
|
+
def rootPath = reactConfig.root.toString()
|
|
79
|
+
|
|
80
|
+
def command = new ArrayList<String>()
|
|
81
|
+
command.add(cliPath)
|
|
82
|
+
command.add("upload-sourcemaps")
|
|
83
|
+
command.add("--config")
|
|
84
|
+
command.add(configPath)
|
|
85
|
+
command.add("--platform")
|
|
86
|
+
command.add("android")
|
|
87
|
+
command.add("--verbose")
|
|
88
|
+
command.add("--working-dir")
|
|
89
|
+
command.add(rootPath)
|
|
90
|
+
|
|
91
|
+
def processBuilder = new ProcessBuilder(command)
|
|
92
|
+
processBuilder.directory(reactRoot)
|
|
93
|
+
processBuilder.redirectErrorStream(true)
|
|
94
|
+
|
|
95
|
+
def process = processBuilder.start()
|
|
96
|
+
process.inputStream.eachLine { line -> println line }
|
|
97
|
+
|
|
98
|
+
def exitCode = process.waitFor()
|
|
99
|
+
if (exitCode != 0) {
|
|
100
|
+
throw new GradleException("Upload failed with exit code ${exitCode}")
|
|
101
|
+
} else {
|
|
102
|
+
println "Source maps uploaded successfully for variant=${buildVariant}."
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
bundleTask.dependsOn updateTask
|
|
108
|
+
bundleTask.finalizedBy uploadTask
|
|
109
|
+
|
|
110
|
+
uploadTask.configure {
|
|
111
|
+
mustRunAfter bundleTask
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private def getContentsquareCLIExecPath(reactConfig) {
|
|
117
|
+
def defaultPath = "${reactConfig.root}/node_modules/.bin/contentsquare-cli"
|
|
118
|
+
|
|
119
|
+
def envPath = System.getenv('CONTENTSQUARE_CLI_EXEC')
|
|
120
|
+
if (envPath && isExecutable(envPath)) {
|
|
121
|
+
return envPath
|
|
122
|
+
} else if (envPath) {
|
|
123
|
+
logger.warn("Invalid CONTENTSQUARE_CLI_EXEC path. Falling back to default.", true)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
def nodeExecutable = Os.isFamily(Os.FAMILY_WINDOWS) ? 'node.exe' : 'node'
|
|
127
|
+
def nodeScript = '''
|
|
128
|
+
const path = require('path');
|
|
129
|
+
const modulePath = require.resolve('@contentsquare/react-native-cli/package.json');
|
|
130
|
+
const cliPath = path.resolve(path.dirname(modulePath), '../../.bin/contentsquare-cli');
|
|
131
|
+
console.log(cliPath);
|
|
132
|
+
'''
|
|
133
|
+
|
|
134
|
+
return executeNodeScript(nodeExecutable, nodeScript) ?: defaultPath
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private def executeNodeScript(String executable, String script) {
|
|
138
|
+
try {
|
|
139
|
+
def process = new ProcessBuilder(executable)
|
|
140
|
+
.redirectErrorStream(true)
|
|
141
|
+
.start()
|
|
142
|
+
|
|
143
|
+
process.outputStream.withWriter { it.write(script) }
|
|
144
|
+
|
|
145
|
+
def output = process.inputStream.text.trim()
|
|
146
|
+
process.waitFor()
|
|
147
|
+
return output
|
|
148
|
+
} catch (Exception e) {
|
|
149
|
+
logger.error("Failed to execute Node.js script: ${e.message}")
|
|
150
|
+
return null
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private def isExecutable(String path) {
|
|
155
|
+
def file = new File(path)
|
|
156
|
+
return file.exists() && file.canExecute()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private def getReactConfig(File buildDir, variant) {
|
|
160
|
+
def config = [:]
|
|
161
|
+
|
|
162
|
+
if (project.extensions.findByName("react")) {
|
|
163
|
+
def bundleAssetName = project.extensions.react.bundleAssetName.get() ?: "index.android.bundle"
|
|
164
|
+
def targetName = variant.name.capitalize()
|
|
165
|
+
config.bundleTaskName = "createBundle${targetName}JsAndAssets"
|
|
166
|
+
config.bundleFileResolver = resolveBundleFile([
|
|
167
|
+
"$buildDir/generated/assets/react/${variant.name}",
|
|
168
|
+
"$buildDir/ASSETS/createBundle${targetName}JsAndAssets",
|
|
169
|
+
"$buildDir/generated/assets/createBundle${targetName}JsAndAssets"
|
|
170
|
+
], bundleAssetName)
|
|
171
|
+
config.bundleAssetName = bundleAssetName
|
|
172
|
+
config.targetPath = variant.name
|
|
173
|
+
config.targetName = targetName
|
|
174
|
+
config.root = project.extensions.react.root.get() ?: "../../"
|
|
175
|
+
} else {
|
|
176
|
+
def targetName = variant.name.capitalize()
|
|
177
|
+
def targetPath = variant.dirName
|
|
178
|
+
config.bundleTaskName = "bundle${targetName}JsAndAssets"
|
|
179
|
+
config.bundleFileResolver = resolveBundleFile(["$buildDir/generated/assets/react/${targetPath}"], "index.android.bundle")
|
|
180
|
+
config.targetPath = targetPath
|
|
181
|
+
config.targetName = targetName
|
|
182
|
+
config.root = "../../"
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return config
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private def resolveBundleFile(List<String> directories, String fileName) {
|
|
189
|
+
return {
|
|
190
|
+
directories.collect { file("$it/$fileName") }.find { it.exists() }
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private def runShellCommand(List<String> command, File workingDir) {
|
|
195
|
+
def outputStream = new ByteArrayOutputStream()
|
|
196
|
+
def errorStream = new ByteArrayOutputStream()
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
def result = project.exec {
|
|
200
|
+
workingDir = workingDir
|
|
201
|
+
standardOutput = outputStream
|
|
202
|
+
errorOutput = errorStream
|
|
203
|
+
commandLine command
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (result.exitValue != 0) {
|
|
207
|
+
logger.error("Error: ${errorStream.toString('UTF-8')}")
|
|
208
|
+
result.rethrowFailure()
|
|
209
|
+
} else {
|
|
210
|
+
logger.lifecycle("Success: ${outputStream.toString('UTF-8')}")
|
|
211
|
+
}
|
|
212
|
+
} catch (Exception e) {
|
|
213
|
+
logger.error("Command execution failed: ${e.message}")
|
|
214
|
+
throw e
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private def getServiceName(variant) {
|
|
219
|
+
if (project.hasProperty("contentsquare") && project.contentsquare.serviceName) {
|
|
220
|
+
return project.contentsquare.serviceName
|
|
221
|
+
}
|
|
222
|
+
return variant.applicationId
|
|
223
|
+
}
|