Skip to content

Commit a4e58dc

Browse files
committed
feat(#105): implement scanOptionsFactory for bare and expo plugins
1 parent 84fc168 commit a4e58dc

11 files changed

Lines changed: 193 additions & 19 deletions

File tree

docs/docs/docs/react-native.mdx

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ If you are using Expo, you need to add the config plugin to your `app.json` or `
2727
```diff
2828
{
2929
"expo": {
30-
+ "plugins": ["react-native-legal"]
30+
+ "plugins": ["react-native-legal"]
3131
}
3232
}
3333
```
@@ -42,6 +42,42 @@ or
4242

4343
This will ensure the required native dependencies are included.
4444

45+
#### Expo Plugin Options
46+
47+
```diff
48+
{
49+
"expo": {
50+
+ "plugins": [
51+
+ [
52+
+ "react-native-legal",
53+
+ {
54+
+ devDepsMode: 'root-only',
55+
+ includeOptionalDeps: true,
56+
+ transitiveDepsMode: 'all'
57+
+ }
58+
+ ]
59+
+ ]
60+
}
61+
}
62+
```
63+
64+
```ts
65+
interface PluginOptions {
66+
// `'root-only'` (only direct devDependencies from the scanned project's root package.json)
67+
// `'none'` (no devDependencies included)
68+
// @default `'none'`
69+
devDepsMode?: 'root-only' | 'none';
70+
// @default `true`
71+
includeOptionalDeps?: boolean;
72+
// `'all'` (all transitive dependencies of direct dependencies)
73+
// `'from-external-only'` (only transitive dependencies of direct dependencies specified by non-workspace:... specifiers)
74+
// `'from-workspace-only'` (only direct dependencies of direct dependencies specified by `workspace:` specifier)
75+
// `'none'` (no transitive dependencies of direct dependencies)
76+
// @default `'all'`
77+
transitiveDepsMode?: 'all' | 'from-external-only' | 'from-workspace-only' | 'none';
78+
}
79+
```
80+
4581
:::warning
4682
This library **cannot be used** in [Expo Go](https://expo.dev/go) because it requires custom native code.
4783
:::
@@ -54,6 +90,14 @@ For bare React Native projects, you need to run the CLI plugin to generate and i
5490

5591
This will extract and prepare license metadata for use in your app.
5692

93+
#### Bare React Native Plugin Options
94+
95+
| Flag / Option | Description | Default |
96+
| ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
97+
| `--tm, --transitive-deps-mode [mode]` | Controls, which transitive dependencies are included: <ul><li>`'all'`</li> <li>`'from-external-only'` (only transitive dependencies of direct dependencies specified by non-workspace:... specifiers)</li> <li>`'from-workspace-only'` (only direct dependencies of direct dependencies specified by `workspace:` specifier)</li> <li>`'none'`</li></ul> | `'all'` |
98+
| `--dm, --dev-deps-mode [mode]` | <ul><li>`'root-only'` (only direct devDependencies from the scanned project's root package.json)</li> <li>`'none'`</li></ul> | `'none'` |
99+
| `--od, --include-optional-deps [include]` | Whether to include optionalDependencies in the scan; other flags apply | `true` |
100+
57101
## Usage
58102

59103
### Builtin list screen

packages/react-native-legal/bare-plugin/src/android/androidCommand.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import path from 'node:path';
22

3-
import { scanDependencies, writeAboutLibrariesNPMOutput } from '@callstack/licenses';
3+
import { type Types as SharedTypes, scanDependencies, writeAboutLibrariesNPMOutput } from '@callstack/licenses';
44

55
import { addListActivity } from './addListActivity';
66
import { applyAndConfigureAboutLibrariesPlugin } from './applyAndConfigureAboutLibrariesPlugin';
@@ -12,8 +12,11 @@ import { declareAboutLibrariesPlugin } from './declareAboutLibrariesPlugin';
1212
* It scans the NPM dependencies, generates AboutLibraries-compatible metadata for them,
1313
* installs & configures AboutLibraries Gradle plugin and adds Android Activity with a list of dependencies and their licenses
1414
*/
15-
export function androidCommand(androidProjectPath: string) {
16-
const licenses = scanDependencies(path.join(path.resolve(androidProjectPath, '..'), 'package.json'));
15+
export function androidCommand(androidProjectPath: string, scanOptionsFactory: SharedTypes.ScanPackageOptionsFactory) {
16+
const licenses = scanDependencies(
17+
path.join(path.resolve(androidProjectPath, '..'), 'package.json'),
18+
scanOptionsFactory,
19+
);
1720

1821
writeAboutLibrariesNPMOutput(licenses, androidProjectPath);
1922

packages/react-native-legal/bare-plugin/src/index.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
1+
import { createPluginScanOptionsFactory } from '../../plugin-utils/build/common';
2+
import type { PluginScanOptions } from '../../plugin-utils/build/types';
3+
14
import { androidCommand } from './android/androidCommand';
25
import { iosCommand } from './ios/iosCommand';
36

4-
function generateLegal(androidProjectPath: string | undefined, iosProjectPath: string | undefined) {
7+
function generateLegal(
8+
androidProjectPath: string | undefined,
9+
iosProjectPath: string | undefined,
10+
pluginScanOptions: PluginScanOptions,
11+
) {
12+
const scanOptionsFactory = createPluginScanOptionsFactory(pluginScanOptions);
13+
514
if (androidProjectPath) {
6-
androidCommand(androidProjectPath);
15+
androidCommand(androidProjectPath, scanOptionsFactory);
716
}
817

918
if (iosProjectPath) {
10-
iosCommand(iosProjectPath);
19+
iosCommand(iosProjectPath, scanOptionsFactory);
1120
}
1221
}
1322

packages/react-native-legal/bare-plugin/src/ios/iosCommand.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import path from 'node:path';
22

3-
import { scanDependencies, writeLicensePlistNPMOutput } from '@callstack/licenses';
3+
import { type Types as SharedTypes, scanDependencies, writeLicensePlistNPMOutput } from '@callstack/licenses';
44

55
import { addSettingsBundle } from './addSettingsBundle';
66
import { registerLicensePlistBuildPhase } from './registerLicensePlistBuildPhase';
@@ -11,8 +11,8 @@ import { registerLicensePlistBuildPhase } from './registerLicensePlistBuildPhase
1111
* It scans the NPM dependencies, generates LicensePlist-compatible metadata for them,
1212
* configures Settings.bundle and registers a shell script generating LicensePlist metadata for iOS dependencies
1313
*/
14-
export function iosCommand(iosProjectPath: string) {
15-
const licenses = scanDependencies(path.join(path.resolve(iosProjectPath, '..'), 'package.json'));
14+
export function iosCommand(iosProjectPath: string, scanOptionsFactory: SharedTypes.ScanPackageOptionsFactory) {
15+
const licenses = scanDependencies(path.join(path.resolve(iosProjectPath, '..'), 'package.json'), scanOptionsFactory);
1616

1717
writeLicensePlistNPMOutput(licenses, iosProjectPath);
1818

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import type { Types as SharedTypes } from '@callstack/licenses';
2+
3+
import type { PluginScanOptions } from './types';
4+
5+
export function createPluginScanOptionsFactory(
6+
pluginScanOptions: PluginScanOptions,
7+
): SharedTypes.ScanPackageOptionsFactory {
8+
return function ({ isRoot, isWorkspacePackage }) {
9+
let includeDevDependencies = false;
10+
11+
switch (pluginScanOptions.devDepsMode) {
12+
case 'root-only':
13+
includeDevDependencies = isRoot;
14+
break;
15+
16+
case 'none':
17+
includeDevDependencies = false;
18+
break;
19+
}
20+
21+
let includeTransitiveDependencies = true;
22+
23+
switch (pluginScanOptions.transitiveDepsMode) {
24+
case 'all':
25+
includeTransitiveDependencies = true;
26+
break;
27+
28+
case 'from-external-only':
29+
includeTransitiveDependencies = !isWorkspacePackage;
30+
break;
31+
32+
case 'from-workspace-only':
33+
includeTransitiveDependencies = isWorkspacePackage;
34+
break;
35+
36+
case 'none':
37+
includeTransitiveDependencies = false;
38+
break;
39+
}
40+
41+
const includeOptionalDependencies = pluginScanOptions.includeOptionalDeps;
42+
43+
return {
44+
includeDevDependencies,
45+
includeTransitiveDependencies,
46+
includeOptionalDependencies,
47+
};
48+
};
49+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface PluginScanOptions {
2+
devDepsMode: 'root-only' | 'none';
3+
includeOptionalDeps: boolean;
4+
transitiveDepsMode: 'all' | 'from-external-only' | 'from-workspace-only' | 'none';
5+
}

packages/react-native-legal/plugin/src/android/withAndroidLegal.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import path from 'node:path';
33
import { scanDependencies, writeAboutLibrariesNPMOutput } from '@callstack/licenses';
44
import { type ConfigPlugin, withAndroidManifest } from 'expo/config-plugins';
55

6+
import type { PlatformPluginOptions } from '../types';
7+
68
import { addListActivity } from './addListActivity';
79
import { applyAndConfigureAboutLibrariesPlugin } from './applyAndConfigureAboutLibrariesPlugin';
810
import { declareAboutLibrariesPlugin } from './declareAboutLibrariesPlugin';
@@ -13,9 +15,12 @@ import { declareAboutLibrariesPlugin } from './declareAboutLibrariesPlugin';
1315
* It scans the NPM dependencies, generates AboutLibraries-compatible metadata,
1416
* installs & configures AboutLibraries Gradle plugin and adds Android Activity with a list of dependencies and their licenses
1517
*/
16-
export const withAndroidLegal: ConfigPlugin = (config) => {
18+
export const withAndroidLegal: ConfigPlugin<PlatformPluginOptions> = (config, { scanOptionsFactory }) => {
1719
withAndroidManifest(config, async (exportedConfig) => {
18-
const licenses = scanDependencies(path.join(exportedConfig.modRequest.projectRoot, 'package.json'));
20+
const licenses = scanDependencies(
21+
path.join(exportedConfig.modRequest.projectRoot, 'package.json'),
22+
scanOptionsFactory,
23+
);
1924

2025
writeAboutLibrariesNPMOutput(licenses, exportedConfig.modRequest.platformProjectRoot);
2126
return exportedConfig;
Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
import type { ConfigPlugin } from 'expo/config-plugins';
2-
import { createRunOncePlugin, withPlugins } from 'expo/config-plugins';
2+
import { createRunOncePlugin } from 'expo/config-plugins';
3+
4+
import { createPluginScanOptionsFactory } from '../../plugin-utils/build/common';
5+
import type { PluginScanOptions } from '../../plugin-utils/build/types';
36

47
import { withAndroidLegal } from './android/withAndroidLegal';
58
import { withIosLegal } from './ios/withIosLegal';
9+
import type { PluginOptions } from './types';
610

711
// eslint-disable-next-line import/no-extraneous-dependencies
812
const pak = require('react-native-legal/package.json');
913

10-
const withReactNativeLegal: ConfigPlugin = (config) => {
11-
return withPlugins(config, [withAndroidLegal, withIosLegal]);
14+
const withReactNativeLegal: ConfigPlugin<PluginOptions> = (config, options) => {
15+
const { devDepsMode = 'none', includeOptionalDeps = true, transitiveDepsMode = 'all' } = options ?? {};
16+
const pluginScanOptions: PluginScanOptions = { devDepsMode, includeOptionalDeps, transitiveDepsMode };
17+
const scanOptionsFactory = createPluginScanOptionsFactory(pluginScanOptions);
18+
19+
config = withAndroidLegal(config, { scanOptionsFactory });
20+
config = withIosLegal(config, { scanOptionsFactory });
21+
22+
return config;
1223
};
1324

1425
export default createRunOncePlugin(withReactNativeLegal, pak.name, pak.version);

packages/react-native-legal/plugin/src/ios/withIosLegal.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import path from 'node:path';
33
import { scanDependencies, writeLicensePlistNPMOutput } from '@callstack/licenses';
44
import { type ConfigPlugin, withXcodeProject } from 'expo/config-plugins';
55

6+
import type { PlatformPluginOptions } from '../types';
7+
68
import { addSettingsBundle } from './addSettingsBundle';
79
import { registerLicensePlistBuildPhase } from './registerLicensePlistBuildPhase';
810

@@ -12,9 +14,12 @@ import { registerLicensePlistBuildPhase } from './registerLicensePlistBuildPhase
1214
* It scans the NPM dependencies, generates LicensePlist-compatible metadata,
1315
* configures Settings.bundle and registers a shell script generating LicensePlist metadata for iOS dependencies
1416
*/
15-
export const withIosLegal: ConfigPlugin = (config) => {
17+
export const withIosLegal: ConfigPlugin<PlatformPluginOptions> = (config, { scanOptionsFactory }) => {
1618
withXcodeProject(config, async (exportedConfig) => {
17-
const licenses = scanDependencies(path.join(exportedConfig.modRequest.projectRoot, 'package.json'));
19+
const licenses = scanDependencies(
20+
path.join(exportedConfig.modRequest.projectRoot, 'package.json'),
21+
scanOptionsFactory,
22+
);
1823

1924
writeLicensePlistNPMOutput(licenses, exportedConfig.modRequest.platformProjectRoot);
2025
return exportedConfig;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { Types as SharedTypes } from '@callstack/licenses';
2+
3+
import type { PluginScanOptions } from '../../plugin-utils/build/types';
4+
5+
export type PluginOptions = PluginScanOptions;
6+
7+
export type PlatformPluginOptions = {
8+
scanOptionsFactory: SharedTypes.ScanPackageOptionsFactory;
9+
};

0 commit comments

Comments
 (0)