Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/brown-sides-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'react-native-legal': 'minor'
---

Add support for "scan options" in bare and Expo plugin to make parity with `license-kit`
46 changes: 45 additions & 1 deletion docs/docs/docs/react-native.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ If you are using Expo, you need to add the config plugin to your `app.json` or `
```diff
{
"expo": {
+ "plugins": ["react-native-legal"]
+ "plugins": ["react-native-legal"]
}
}
```
Expand All @@ -42,6 +42,42 @@ or

This will ensure the required native dependencies are included.

#### Expo Plugin Options

```diff
{
"expo": {
+ "plugins": [
+ [
+ "react-native-legal",
+ {
+ "devDepsMode": "root-only",
+ "includeOptionalDeps": true,
+ "transitiveDepsMode": "all"
+ }
+ ]
+ ]
}
}
```

```ts
interface PluginOptions {
// `'root-only'` (only direct devDependencies from the scanned project's root package.json)
// `'none'` (no devDependencies included)
// @default `'none'`
devDepsMode?: 'root-only' | 'none';
// @default `true`
includeOptionalDeps?: boolean;
// `'all'` (all transitive dependencies of direct dependencies)
// `'from-external-only'` (only transitive dependencies of direct dependencies specified by non-workspace:... specifiers)
// `'from-workspace-only'` (only direct dependencies of direct dependencies specified by `workspace:` specifier)
// `'none'` (no transitive dependencies of direct dependencies)
// @default `'all'`
transitiveDepsMode?: 'all' | 'from-external-only' | 'from-workspace-only' | 'none';
}
```

:::warning
This library **cannot be used** in [Expo Go](https://expo.dev/go) because it requires custom native code.
:::
Expand All @@ -54,6 +90,14 @@ For bare React Native projects, you need to run the CLI plugin to generate and i

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

#### Bare React Native Plugin Options

| Flag / Option | Description | Default |
| ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
| `--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'` |
| `--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'` |
| `--od, --include-optional-deps [include]` | Whether to include optionalDependencies in the scan; other flags apply | `true` |
Comment thread
mateusz1913 marked this conversation as resolved.

## Usage

### Builtin list screen
Expand Down
13 changes: 7 additions & 6 deletions examples/bare-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@
"version": "0.0.1",
"private": true,
"scripts": {
"android": "react-native legal-generate && react-native run-android --no-packager",
"android:release": "react-native legal-generate && react-native run-android --no-packager --mode \"Release\" --appId com.reactnativelegalbareexample",
"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",
"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",
"android": "yarn legal-generate && react-native run-android --no-packager",
"android:release": "yarn legal-generate && react-native run-android --no-packager --mode \"Release\" --appId com.reactnativelegalbareexample",
"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",
"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",
"e2e:android": "yarn mke2e_results && maestro test --config=e2e/config.yaml --exclude-tags=ios --format junit --output=e2e_results/report.xml e2e",
"e2e:ios": "yarn mke2e_results && maestro test --config=e2e/config.yaml --exclude-tags=android --format junit --output=e2e_results/report.xml e2e",
"gem:install": "bundle install",
"ios": "react-native legal-generate && react-native run-ios --no-packager",
"ios:release": "react-native legal-generate && react-native run-ios --no-packager --mode \"Release\"",
"ios": "yarn legal-generate && react-native run-ios --no-packager",
"ios:release": "yarn legal-generate && react-native run-ios --no-packager --mode \"Release\"",
"legal-generate": "react-native legal-generate --dev-deps-mode=none --include-optional-deps --transitive-deps-mode=all",
"lint": "eslint .",
"mkdist": "node -e \"require('node:fs').mkdirSync('dist', { recursive: true, mode: 0o755 })\"",
"mke2e_results": "node -e \"require('node:fs').mkdirSync('e2e_results', { recursive: true, mode: 0o755 })\"",
Expand Down
8 changes: 7 additions & 1 deletion examples/expo-example/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,13 @@
}
}
],
"react-native-legal"
[
"react-native-legal", {
"devDepsMode": "none",
"includeOptionalDeps": true,
"transitiveDepsMode": "all"
}
]
]
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import fs from 'node:fs';
import path from 'node:path';

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

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

const aboutLibrariesConfigDirPath = path.join(androidProjectPath, 'config');

// Cleanup metadata in case `scanOptionsFactory` changed
fs.rmSync(aboutLibrariesConfigDirPath, { recursive: true, force: true });

writeAboutLibrariesNPMOutput(licenses, androidProjectPath);

Expand Down
15 changes: 12 additions & 3 deletions packages/react-native-legal/bare-plugin/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import { createPluginScanOptionsFactory } from '../../plugin-utils/build/common';
import type { PluginScanOptions } from '../../plugin-utils/build/types';

import { androidCommand } from './android/androidCommand';
import { iosCommand } from './ios/iosCommand';

function generateLegal(androidProjectPath: string | undefined, iosProjectPath: string | undefined) {
function generateLegal(
androidProjectPath: string | undefined,
iosProjectPath: string | undefined,
pluginScanOptions: PluginScanOptions,
) {
const scanOptionsFactory = createPluginScanOptionsFactory(pluginScanOptions);

if (androidProjectPath) {
androidCommand(androidProjectPath);
androidCommand(androidProjectPath, scanOptionsFactory);
}

if (iosProjectPath) {
iosCommand(iosProjectPath);
iosCommand(iosProjectPath, scanOptionsFactory);
}
}

Expand Down
6 changes: 3 additions & 3 deletions packages/react-native-legal/bare-plugin/src/ios/iosCommand.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from 'node:path';

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

import { addSettingsBundle } from './addSettingsBundle';
import { registerLicensePlistBuildPhase } from './registerLicensePlistBuildPhase';
Expand All @@ -11,8 +11,8 @@ import { registerLicensePlistBuildPhase } from './registerLicensePlistBuildPhase
* It scans the NPM dependencies, generates LicensePlist-compatible metadata for them,
* configures Settings.bundle and registers a shell script generating LicensePlist metadata for iOS dependencies
*/
export function iosCommand(iosProjectPath: string) {
const licenses = scanDependencies(path.join(path.resolve(iosProjectPath, '..'), 'package.json'));
export function iosCommand(iosProjectPath: string, scanOptionsFactory: SharedTypes.ScanPackageOptionsFactory) {
const licenses = scanDependencies(path.join(path.resolve(iosProjectPath, '..'), 'package.json'), scanOptionsFactory);
Comment thread
mateusz1913 marked this conversation as resolved.

writeLicensePlistNPMOutput(licenses, iosProjectPath);

Expand Down
2 changes: 1 addition & 1 deletion packages/react-native-legal/plugin-utils/src/android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export function applyAndConfigureAboutLibrariesPluginUtil(androidAppBuildGradleC
const pluginConfigRegex = /aboutLibraries {/;

if (!androidAppBuildGradleContent.match(pluginConfigRegex)?.length) {
androidAppBuildGradleContent += '\n\naboutLibraries {\n configPath = "config"\n prettyPrint = true\n}';
androidAppBuildGradleContent += '\n\naboutLibraries {\n configPath = "config"\n prettyPrint = true\n}\n';
console.log('About Libraries Gradle Plugin - CONFIGURED');
} else {
console.log('About Libraries Gradle Plugin already configured - SKIP');
Expand Down
49 changes: 49 additions & 0 deletions packages/react-native-legal/plugin-utils/src/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type { Types as SharedTypes } from '@callstack/licenses';

import type { PluginScanOptions } from './types';

export function createPluginScanOptionsFactory(
pluginScanOptions: PluginScanOptions,
): SharedTypes.ScanPackageOptionsFactory {
return function ({ isRoot, isWorkspacePackage }) {
let includeDevDependencies = false;

switch (pluginScanOptions.devDepsMode) {
case 'root-only':
includeDevDependencies = isRoot;
break;

case 'none':
includeDevDependencies = false;
break;
}

let includeTransitiveDependencies = true;

switch (pluginScanOptions.transitiveDepsMode) {
case 'all':
includeTransitiveDependencies = true;
break;

case 'from-external-only':
includeTransitiveDependencies = !isWorkspacePackage;
break;

case 'from-workspace-only':
includeTransitiveDependencies = isWorkspacePackage;
break;

case 'none':
includeTransitiveDependencies = false;
break;
}

const includeOptionalDependencies = pluginScanOptions.includeOptionalDeps;

return {
includeDevDependencies,
includeTransitiveDependencies,
includeOptionalDependencies,
};
};
}
5 changes: 5 additions & 0 deletions packages/react-native-legal/plugin-utils/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface PluginScanOptions {
devDepsMode: 'root-only' | 'none';
includeOptionalDeps: boolean;
transitiveDepsMode: 'all' | 'from-external-only' | 'from-workspace-only' | 'none';
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import path from 'node:path';
import { scanDependencies, writeAboutLibrariesNPMOutput } from '@callstack/licenses';
import { type ConfigPlugin, withAndroidManifest } from 'expo/config-plugins';

import type { PlatformPluginOptions } from '../types';

import { addListActivity } from './addListActivity';
import { applyAndConfigureAboutLibrariesPlugin } from './applyAndConfigureAboutLibrariesPlugin';
import { declareAboutLibrariesPlugin } from './declareAboutLibrariesPlugin';
Expand All @@ -13,9 +15,12 @@ import { declareAboutLibrariesPlugin } from './declareAboutLibrariesPlugin';
* It scans the NPM dependencies, generates AboutLibraries-compatible metadata,
* installs & configures AboutLibraries Gradle plugin and adds Android Activity with a list of dependencies and their licenses
*/
export const withAndroidLegal: ConfigPlugin = (config) => {
export const withAndroidLegal: ConfigPlugin<PlatformPluginOptions> = (config, { scanOptionsFactory }) => {
withAndroidManifest(config, async (exportedConfig) => {
const licenses = scanDependencies(path.join(exportedConfig.modRequest.projectRoot, 'package.json'));
const licenses = scanDependencies(
path.join(exportedConfig.modRequest.projectRoot, 'package.json'),
scanOptionsFactory,
);

writeAboutLibrariesNPMOutput(licenses, exportedConfig.modRequest.platformProjectRoot);
return exportedConfig;
Expand Down
17 changes: 14 additions & 3 deletions packages/react-native-legal/plugin/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
import type { ConfigPlugin } from 'expo/config-plugins';
import { createRunOncePlugin, withPlugins } from 'expo/config-plugins';
import { createRunOncePlugin } from 'expo/config-plugins';

import { createPluginScanOptionsFactory } from '../../plugin-utils/build/common';
import type { PluginScanOptions } from '../../plugin-utils/build/types';

import { withAndroidLegal } from './android/withAndroidLegal';
import { withIosLegal } from './ios/withIosLegal';
import type { PluginOptions } from './types';

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

const withReactNativeLegal: ConfigPlugin = (config) => {
return withPlugins(config, [withAndroidLegal, withIosLegal]);
const withReactNativeLegal: ConfigPlugin<PluginOptions> = (config, options) => {
const { devDepsMode = 'none', includeOptionalDeps = true, transitiveDepsMode = 'all' } = options ?? {};
const pluginScanOptions: PluginScanOptions = { devDepsMode, includeOptionalDeps, transitiveDepsMode };
const scanOptionsFactory = createPluginScanOptionsFactory(pluginScanOptions);

config = withAndroidLegal(config, { scanOptionsFactory });
config = withIosLegal(config, { scanOptionsFactory });

return config;
};

export default createRunOncePlugin(withReactNativeLegal, pak.name, pak.version);
9 changes: 7 additions & 2 deletions packages/react-native-legal/plugin/src/ios/withIosLegal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import path from 'node:path';
import { scanDependencies, writeLicensePlistNPMOutput } from '@callstack/licenses';
import { type ConfigPlugin, withXcodeProject } from 'expo/config-plugins';

import type { PlatformPluginOptions } from '../types';

import { addSettingsBundle } from './addSettingsBundle';
import { registerLicensePlistBuildPhase } from './registerLicensePlistBuildPhase';

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

writeLicensePlistNPMOutput(licenses, exportedConfig.modRequest.platformProjectRoot);
return exportedConfig;
Expand Down
9 changes: 9 additions & 0 deletions packages/react-native-legal/plugin/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { Types as SharedTypes } from '@callstack/licenses';

import type { PluginScanOptions } from '../../plugin-utils/build/types';

export type PluginOptions = PluginScanOptions;

export type PlatformPluginOptions = {
scanOptionsFactory: SharedTypes.ScanPackageOptionsFactory;
};
40 changes: 38 additions & 2 deletions packages/react-native-legal/react-native.config.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,48 @@
/** @type {import('@react-native-community/cli-types').Config} */
module.exports = {
commands: [
{
name: 'legal-generate',
description: 'Set up all native boilerplate for OSS licenses notice',
func: ([], { project: { android, ios } }, {}) => {
options: [
{
name: '--dm, --dev-deps-mode <string>',
description: 'Whether to include devDependencies in the scan',
parse: (val) => {
if (val === 'root-only') {
return val;
}

return 'none';
},
default: 'none',
},
{
name: '--od, --include-optional-deps [boolean]',
description:
'Whether to include optionalDependencies in the scan; includeTransitiveDependencies option applies',
parse: (val) => val !== 'false',
default: true,
},
{
name: '--tm, --transitive-deps-mode <string>',
description: 'Whether transitive dependencies should be scanned',
parse: (val) => {
if (val === 'all' || val === 'from-external-only' || val === 'from-workspace-only' || val === 'none') {
return val;
}

return 'all';
},
default: 'all',
},
],
func: ([], { project: { android, ios } }, args) => {
const generateLegal = require('./bare-plugin/build').default;
/** @type {import('./plugin-utils/build/types').PluginScanOptions} */
const { devDepsMode, includeOptionalDeps, transitiveDepsMode } = args;

generateLegal(android?.sourceDir, ios?.sourceDir);
generateLegal(android?.sourceDir, ios?.sourceDir, { devDepsMode, includeOptionalDeps, transitiveDepsMode });
},
},
],
Expand Down
Loading