Skip to content

Commit a0e1966

Browse files
author
Murat Mehmet
committed
feat: support configuration using integrate.config.js
#130
1 parent 4fd4cbc commit a0e1966

File tree

12 files changed

+244
-2
lines changed

12 files changed

+244
-2
lines changed
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
plugins: ['test']
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
require('../../../../mocks/mockAll');
2+
import path from 'path';
3+
import { Constants } from '../../../../../constants';
4+
import { ImportGetter } from '../../../../../types/upgrade.types';
5+
import { getProjectPath } from '../../../../../utils/getProjectPath';
6+
import { importIntegrateConfig } from '../../../../../utils/upgrade/other/importIntegrateConfig';
7+
import { mockFs } from '../../../../mocks/mockFs';
8+
9+
describe('importIntegrateConfig', () => {
10+
it('should get integrate.config.js', async () => {
11+
const configFile = path.join(
12+
__dirname,
13+
'../../../../mocks',
14+
Constants.INTEGRATE_CONFIG_FILE_NAME
15+
);
16+
mockFs.writeFileSync(configFile, "module.exports = {plugins: ['test']}");
17+
18+
const importGetter = importIntegrateConfig(
19+
path.join(__dirname, '../../../../mocks')
20+
) as ImportGetter;
21+
expect(importGetter).toBeTruthy();
22+
expect(importGetter.value).toEqual('1 plugin configuration');
23+
24+
await importGetter.apply();
25+
26+
expect(
27+
mockFs.readFileSync(
28+
path.join(getProjectPath(), Constants.INTEGRATE_CONFIG_FILE_NAME)
29+
)
30+
).toContain("module.exports = {plugins: ['test']}");
31+
});
32+
it('should handle errors', () => {
33+
mockFs.setReadPermission(false);
34+
mockFs.writeFileSync('/oldProject/' + Constants.LOCK_FILE_NAME, '');
35+
36+
const importGetter = importIntegrateConfig('/oldProject') as ImportGetter;
37+
expect(importGetter).toBeNull();
38+
});
39+
it('should handle not finding integrate config', () => {
40+
const importGetter = importIntegrateConfig('/oldProject') as ImportGetter;
41+
expect(importGetter).toBeNull();
42+
});
43+
});

src/constants.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export const Constants = {
22
CURRENT_LOCK_VERSION: 1,
33
LOCK_FILE_NAME: 'integrate-lock.json',
4+
INTEGRATE_CONFIG_FILE_NAME: 'integrate.config.js',
45
UPGRADE_FOLDER_NAME: '.upgrade',
56
UPGRADE_CONFIG_FILE_NAME: 'upgrade.yml',
67
GIT_FOLDER_NAME: '.git',

src/integrate.ts

+13
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ import { IntegrationConfig, PackageWithConfig } from './types/mod.types';
1515
import { analyzePackages } from './utils/analyzePackages';
1616
import { checkCondition } from './utils/checkCondition';
1717
import { getErrMessage } from './utils/getErrMessage';
18+
import {
19+
getIntegrateConfig,
20+
getIntegratePackageConfig,
21+
} from './utils/getIntegrateConfig';
1822
import { getPackageConfig } from './utils/getPackageConfig';
1923
import { logInfoNote } from './utils/logInfoNote';
2024
import { parseConfig } from './utils/parseConfig';
@@ -239,6 +243,7 @@ export async function integrate(packageName?: string): Promise<void> {
239243
}
240244
if (packagesToIntegrate.length) {
241245
packagesToIntegrate = topologicalSort(packagesToIntegrate);
246+
const integrateConfig = getIntegrateConfig();
242247
for (let i = 0; i < packagesToIntegrate.length; i++) {
243248
const { packageName, version, configPath, config } =
244249
packagesToIntegrate[i];
@@ -254,6 +259,14 @@ export async function integrate(packageName?: string): Promise<void> {
254259
variables.set(name, transformTextInObject(value))
255260
);
256261
}
262+
if (integrateConfig) {
263+
const integratePackageconfig = getIntegratePackageConfig(
264+
integrateConfig,
265+
packageName
266+
);
267+
if (integratePackageconfig)
268+
variables.set('config', integratePackageconfig);
269+
}
257270

258271
let failedTaskCount = 0,
259272
completedTaskCount = 0;

src/types/integrator.types.ts

+4
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,7 @@ export interface PackageUpgradeConfig {
3434
inputs?: Record<string, any>;
3535
files?: Record<string, string>;
3636
}
37+
38+
export interface IntegrateConfig {
39+
plugins?: (string | [string, Record<string, any>])[];
40+
}

src/utils/getIntegrateConfig.ts

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import fs from 'fs';
2+
import path from 'path';
3+
import { Constants } from '../constants';
4+
import { IntegrateConfig } from '../types/integrator.types';
5+
import { getProjectPath } from './getProjectPath';
6+
7+
export function getIntegrateConfig(projectPath?: string) {
8+
try {
9+
const configFilePath = path.join(
10+
projectPath || getProjectPath(),
11+
Constants.INTEGRATE_CONFIG_FILE_NAME
12+
);
13+
if (!fs.existsSync(configFilePath)) return null;
14+
// eslint-disable-next-line @typescript-eslint/no-require-imports
15+
const config = require(configFilePath) as
16+
| IntegrateConfig
17+
| { default: IntegrateConfig };
18+
return 'default' in config ? config.default : config;
19+
} catch (error) {
20+
console.error('Error reading integrate.config.js:', error);
21+
process.abort();
22+
}
23+
}
24+
25+
export function getIntegratePackageConfig(
26+
integrateConfig: IntegrateConfig,
27+
packageName: string
28+
) {
29+
if (!integrateConfig) return null;
30+
31+
const plugin = integrateConfig.plugins?.find(
32+
plugin => (Array.isArray(plugin) ? plugin[0] : plugin) === packageName
33+
);
34+
if (!Array.isArray(plugin)) return null;
35+
36+
return plugin[1] ?? null;
37+
}

src/utils/upgrade/importFromOldProject.ts

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { importIosDisplayName } from './ios/importIosDisplayName';
2020
import { importIosMarketingVersion } from './ios/importIosMarketingVersion';
2121
import { importIosProjectVersion } from './ios/importIosProjectVersion';
2222
import { importGitFolder } from './other/importGitFolder';
23+
import { importIntegrateConfig } from './other/importIntegrateConfig';
2324
import { importIntegrateLockJson } from './other/importIntegrateLockJson';
2425
import { importPackageJson } from './other/importPackageJson';
2526
import { importUpgradeFolder } from './other/importUpgradeFolder';
@@ -30,6 +31,7 @@ export async function importFromOldProject(
3031
const importedData: ImportGetter[] = [
3132
importPackageJson(oldProjectPath),
3233
importIntegrateLockJson(oldProjectPath),
34+
importIntegrateConfig(oldProjectPath),
3335
importUpgradeFolder(oldProjectPath),
3436
importAndroidDisplayName(oldProjectPath),
3537
importAndroidAppId(oldProjectPath),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import fs from 'fs';
2+
import path from 'path';
3+
import color from 'picocolors';
4+
import { Constants } from '../../../constants';
5+
import { logMessage } from '../../../prompter';
6+
import { ImportGetter } from '../../../types/upgrade.types';
7+
import { getIntegrateConfig } from '../../getIntegrateConfig';
8+
import { getProjectPath } from '../../getProjectPath';
9+
10+
export function importIntegrateConfig(
11+
projectPath: string
12+
): ImportGetter | null {
13+
try {
14+
const configFilePath = path.join(
15+
projectPath,
16+
Constants.INTEGRATE_CONFIG_FILE_NAME
17+
);
18+
19+
const integrateConfig = getIntegrateConfig(projectPath);
20+
21+
if (!integrateConfig) return null;
22+
23+
return {
24+
id: 'integrateConfigJs',
25+
title: 'Integrate Config File',
26+
value: `${integrateConfig.plugins?.length || '0'} plugin configuration`,
27+
apply: () => setIntegrateConfig(configFilePath),
28+
};
29+
} catch (_e) {
30+
return null;
31+
}
32+
}
33+
34+
async function setIntegrateConfig(file: string) {
35+
const destination = path.join(
36+
getProjectPath(),
37+
Constants.INTEGRATE_CONFIG_FILE_NAME
38+
);
39+
40+
await new Promise((res, rej) => {
41+
fs.copyFile(file, destination, err => {
42+
if (err) rej(err);
43+
else res(null);
44+
});
45+
});
46+
47+
logMessage(`imported ${color.yellow('integrate.config.js')}`);
48+
return Promise.resolve();
49+
}

src/utils/upgrade/runUpgradeTasks.ts

+1
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export async function runUpgradeTasks(
8181
'node_modules/',
8282
'package.json',
8383
'integrate-lock.json',
84+
'integrate.config.js',
8485
];
8586
for (let i = 0; i < imports.length; i++) {
8687
updateSpinner(

website/docs/guides/configuration.md

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
---
2+
sidebar_position: 2
3+
---
4+
5+
# Configure Integration
6+
7+
React Native Integrate now supports a configuration file system that allows you to pre-define options for packages to use while implementing their changes.
8+
9+
Most of the time you won't need this because options can be entered through prompts.
10+
11+
However, in some cases complex inputs are needed that can be defined here.
12+
13+
## Configuration File
14+
15+
Create an `integrate.config.js` file in your project root to configure React Native Integrate. The configuration file should export an object that matches the `IntegrateConfig` interface.
16+
17+
```javascript
18+
// integrate.config.js
19+
module.exports = {
20+
plugins: [
21+
// Plugin configurations
22+
['plugin-with-config', {
23+
// Plugin specific configuration
24+
option1: 'value1',
25+
option2: 'value2'
26+
}]
27+
]
28+
};
29+
```
30+
31+
## Configuration Options
32+
33+
### Plugins
34+
35+
The `plugins` array allows you to specify which plugins to use and their configurations:
36+
37+
```javascript
38+
plugins: [
39+
['my-plugin', {
40+
// Plugin specific configuration options
41+
config1: 'value1'
42+
}]
43+
]
44+
```
45+
46+
## Using Configuration in Your Project
47+
48+
The configuration file is automatically loaded when running react-native-integrate commands. The system will:
49+
50+
1. Look for `integrate.config.js` in your project root
51+
2. Load and validate the configuration
52+
3. Apply plugin configurations as needed
53+
54+
## Plugin Development
55+
56+
If you're developing an integration using complex options, you can access the configuration in two ways:
57+
58+
1. In JavaScript/TypeScript integration scripts:
59+
```typescript
60+
// Access configuration in your integration script
61+
import { getConfig } from '@react-native-integrate/core';
62+
const pluginConfig = getConfig();
63+
```
64+
65+
2. In integration YAML files:
66+
```yaml
67+
# Access configuration in your integration.yml
68+
steps:
69+
- when:
70+
config:
71+
someOption: true
72+
task: someTask
73+
# This step runs only when someOption is true in the config
74+
```
75+
76+
## Error Handling
77+
78+
If there are issues with your configuration file:
79+
80+
1. Syntax errors will be reported with detailed error messages
81+
2. Invalid configuration will trigger appropriate warnings
82+
3. Missing configuration file is handled gracefully - the system will use default settings
83+
84+
## Best Practices
85+
86+
1. Keep your configuration file in the project root
87+
2. Document plugin-specific configuration options
88+
3. Version control your configuration file
89+
4. Use TypeScript for better type checking in your configuration file

website/docs/guides/states.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
sidebar_position: 3
2+
sidebar_position: 4
33
---
44
# Task States
55

website/docs/guides/when.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
sidebar_position: 2
2+
sidebar_position: 3
33
---
44
# Conditional Tasks
55

0 commit comments

Comments
 (0)