Skip to content

Commit f1d20b1

Browse files
authored
feat(#105): implement scanOptionsFactory for bare and expo plugins (#111)
1 parent 9dc5610 commit f1d20b1

15 files changed

Lines changed: 221 additions & 27 deletions

File tree

.changeset/brown-sides-flow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'react-native-legal': 'minor'
3+
---
4+
5+
Add support for "scan options" in bare and Expo plugin to make parity with `license-kit`

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

examples/bare-example/package.json

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@
33
"version": "0.0.1",
44
"private": true,
55
"scripts": {
6-
"android": "react-native legal-generate && react-native run-android --no-packager",
7-
"android:release": "react-native legal-generate && react-native run-android --no-packager --mode \"Release\" --appId com.reactnativelegalbareexample",
8-
"build:android": "yarn mkdist && react-native legal-generate && react-native bundle --entry-file index.js --platform android --dev true --bundle-output dist/main.android.jsbundle --assets-dest dist/res",
9-
"build:ios": "yarn mkdist && react-native legal-generate && react-native bundle --entry-file index.js --platform ios --dev false --bundle-output dist/main.ios.jsbundle --assets-dest dist",
6+
"android": "yarn legal-generate && react-native run-android --no-packager",
7+
"android:release": "yarn legal-generate && react-native run-android --no-packager --mode \"Release\" --appId com.reactnativelegalbareexample",
8+
"build:android": "yarn mkdist && yarn legal-generate && react-native bundle --entry-file index.js --platform android --dev true --bundle-output dist/main.android.jsbundle --assets-dest dist/res",
9+
"build:ios": "yarn mkdist && yarn legal-generate && react-native bundle --entry-file index.js --platform ios --dev false --bundle-output dist/main.ios.jsbundle --assets-dest dist",
1010
"e2e:android": "yarn mke2e_results && maestro test --config=e2e/config.yaml --exclude-tags=ios --format junit --output=e2e_results/report.xml e2e",
1111
"e2e:ios": "yarn mke2e_results && maestro test --config=e2e/config.yaml --exclude-tags=android --format junit --output=e2e_results/report.xml e2e",
1212
"gem:install": "bundle install",
13-
"ios": "react-native legal-generate && react-native run-ios --no-packager",
14-
"ios:release": "react-native legal-generate && react-native run-ios --no-packager --mode \"Release\"",
13+
"ios": "yarn legal-generate && react-native run-ios --no-packager",
14+
"ios:release": "yarn legal-generate && react-native run-ios --no-packager --mode \"Release\"",
15+
"legal-generate": "react-native legal-generate --dev-deps-mode=none --include-optional-deps --transitive-deps-mode=all",
1516
"lint": "eslint .",
1617
"mkdist": "node -e \"require('node:fs').mkdirSync('dist', { recursive: true, mode: 0o755 })\"",
1718
"mke2e_results": "node -e \"require('node:fs').mkdirSync('e2e_results', { recursive: true, mode: 0o755 })\"",

examples/expo-example/app.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,13 @@
4545
}
4646
}
4747
],
48-
"react-native-legal"
48+
[
49+
"react-native-legal", {
50+
"devDepsMode": "none",
51+
"includeOptionalDeps": true,
52+
"transitiveDepsMode": "all"
53+
}
54+
]
4955
]
5056
}
5157
}

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

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

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

56
import { addListActivity } from './addListActivity';
67
import { applyAndConfigureAboutLibrariesPlugin } from './applyAndConfigureAboutLibrariesPlugin';
@@ -12,8 +13,16 @@ import { declareAboutLibrariesPlugin } from './declareAboutLibrariesPlugin';
1213
* It scans the NPM dependencies, generates AboutLibraries-compatible metadata for them,
1314
* installs & configures AboutLibraries Gradle plugin and adds Android Activity with a list of dependencies and their licenses
1415
*/
15-
export function androidCommand(androidProjectPath: string) {
16-
const licenses = scanDependencies(path.join(path.resolve(androidProjectPath, '..'), 'package.json'));
16+
export function androidCommand(androidProjectPath: string, scanOptionsFactory: SharedTypes.ScanPackageOptionsFactory) {
17+
const licenses = scanDependencies(
18+
path.join(path.resolve(androidProjectPath, '..'), 'package.json'),
19+
scanOptionsFactory,
20+
);
21+
22+
const aboutLibrariesConfigDirPath = path.join(androidProjectPath, 'config');
23+
24+
// Cleanup metadata in case `scanOptionsFactory` changed
25+
fs.rmSync(aboutLibrariesConfigDirPath, { recursive: true, force: true });
1726

1827
writeAboutLibrariesNPMOutput(licenses, androidProjectPath);
1928

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

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export function applyAndConfigureAboutLibrariesPluginUtil(androidAppBuildGradleC
4747
const pluginConfigRegex = /aboutLibraries {/;
4848

4949
if (!androidAppBuildGradleContent.match(pluginConfigRegex)?.length) {
50-
androidAppBuildGradleContent += '\n\naboutLibraries {\n configPath = "config"\n prettyPrint = true\n}';
50+
androidAppBuildGradleContent += '\n\naboutLibraries {\n configPath = "config"\n prettyPrint = true\n}\n';
5151
console.log('About Libraries Gradle Plugin - CONFIGURED');
5252
} else {
5353
console.log('About Libraries Gradle Plugin already configured - SKIP');
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+
}

0 commit comments

Comments
 (0)