diff --git a/.eslintrc.json b/.eslintrc.json index fc1dd4bf..80845b2e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,6 +1,6 @@ { "root": true, - "extends": ["@react-native", "prettier"], + "extends": ["prettier"], "plugins": ["prettier"], "ignorePatterns": ["node_modules/", "lib/"] } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c06fb21d..16039741 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,8 +30,12 @@ jobs: - name: Typecheck files run: yarn typecheck - tester-android: - name: Integrated tester Android App + - name: Test Brownfield CLI + run: | + yarn workspace @callstack/react-native-brownfield brownfield --version + + android: + name: 'Android: integrated tester & integration workflow with CLI' runs-on: ubuntu-latest needs: build-lint @@ -81,10 +85,39 @@ jobs: restore-keys: | ${{ runner.os }}-tester-integrated-android-gradle- + # == IntegratedTester == + + - name: Generate Brownie stores + run: yarn run brownie:codegen + - name: Build integrated Android tester app run: yarn run build:tester-integrated:android - tester-ios: + # == RNApp == + + - name: Package AAR with the Brownfield CLI + run: | + cd apps/RNApp + yarn run brownie:package:android + + - name: Publish AAR artifact to Maven Local + run: | + cd apps/RNApp + yarn run brownie:publish:android + + - name: Verify debug AAR exists in Maven Local + run: stat ~/.m2/repository/com/rnapp/brownfieldlib/0.0.1-local/brownfieldlib-0.0.1-local-debug.aar + + - name: Verify release AAR exists in Maven Local + run: stat ~/.m2/repository/com/rnapp/brownfieldlib/0.0.1-local/brownfieldlib-0.0.1-local-release.aar + + # == AndroidApp == + + - name: Build native Android Brownfield app + run: | + yarn run build:example:android-consumer + + ios: name: Integrated tester iOS App runs-on: macos-latest needs: build-lint @@ -130,6 +163,26 @@ jobs: cd apps/TesterIntegrated/swift pod install + # == IntegratedTester == + - name: Build integrated iOS tester app run: | yarn run build:tester-integrated:ios + + # == RNApp == + + - name: Install pods (RNApp) + run: | + cd apps/RNApp/ios + pod install + + - name: Package iOS framework with the Brownfield CLI + run: | + cd apps/RNApp + yarn run brownie:package:ios + + # == AppleApp == + + - name: Build Brownfield iOS native app + run: | + yarn run build:example:ios-consumer diff --git a/.gitignore b/.gitignore index bb3b4031..51764612 100644 --- a/.gitignore +++ b/.gitignore @@ -79,3 +79,6 @@ lib/ # Gradle secring.gpg + +# Typescript +**/*.tsbuildinfo diff --git a/.prettierignore b/.prettierignore index 491fc359..d2939734 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,3 @@ node_modules lib +dist diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f8f17037..949d40b3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,12 +8,21 @@ Depending on your needs, you may need to install CocoaPods in a subset of the be - example React Native iOS app: `cd apps/RNApp/ios && pod install` - integrated iOS tester app: `cd apps/TesterIntegrated/swift && pod install` +## Contributing changes + +After contributing your changes, please make sure to add a [changeset](https://github.com/changesets/changesets) describing your changes. This will help us in publishing new versions. + +## Publishing to npm + +We use [changesets](https://github.com/changesets/changesets) to make it easier to publish new versions. It handles common tasks like bumping version based on semver, creating tags and releases etc. + ## Scripts - `lint` - runs linting on all JS/TS source files in the monorepo *[Turbo]* - `gradle-plugin:lint` - runs linting on the Brownfield Gradle plugin source code - `typecheck` - runs TypeScript type checking on all TS source files in the monorepo *[Turbo]* - `build` - runs all `build*` tasks in the Turbo repo - see below for more details *[Turbo]* +- `build:watch` - runs all `build:watch` tasks in all workspaces - `release` - releases a new version of React Native Brownfield package using `release-it` - `brownfield:plugin:publish:local` - publishes the Brownfield Gradle plugin to your local Maven repository for testing purposes - `build:brownfield` - builds the React Native Brownfield package (`packages/react-native-brownfield`) *[Turbo]* diff --git a/apps/RNApp/.eslintrc.json b/apps/RNApp/.eslintrc.json new file mode 100644 index 00000000..8a2697f7 --- /dev/null +++ b/apps/RNApp/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "extends": ["@react-native", "prettier"], + "plugins": ["prettier"], + "ignorePatterns": ["node_modules/", "lib/"] +} diff --git a/apps/RNApp/ios/.gitignore b/apps/RNApp/ios/.gitignore index 1fcb1529..e6c30136 100644 --- a/apps/RNApp/ios/.gitignore +++ b/apps/RNApp/ios/.gitignore @@ -1 +1 @@ -out +.brownie diff --git a/apps/RNApp/ios/Podfile.lock b/apps/RNApp/ios/Podfile.lock index 43d64498..da1c16be 100644 --- a/apps/RNApp/ios/Podfile.lock +++ b/apps/RNApp/ios/Podfile.lock @@ -2438,7 +2438,7 @@ PODS: - React-perflogger (= 0.82.1) - React-utils (= 0.82.1) - SocketRocket - - RNScreens (4.18.0): + - RNScreens (4.19.0): - boost - DoubleConversion - fast_float @@ -2465,10 +2465,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNScreens/common (= 4.18.0) + - RNScreens/common (= 4.19.0) - SocketRocket - Yoga - - RNScreens/common (4.18.0): + - RNScreens/common (4.19.0): - boost - DoubleConversion - fast_float @@ -2814,10 +2814,10 @@ SPEC CHECKSUMS: ReactBrownfield: 1609c411abb926b1fd3c8557ef67d4138fb56a02 ReactCodegen: 0bce2d209e2e802589f4c5ff76d21618200e74cb ReactCommon: 801eff8cb9c940c04d3a89ce399c343ee3eff654 - RNScreens: 98771ad898d1c0528fc8139606bbacf5a2e9d237 + RNScreens: d6413aeb1878cdafd3c721e2c5218faf5d5d3b13 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: 46ff53afcbeda2bae19c85b65e17487c3e3984dd -PODFILE CHECKSUM: df18b5948daeaea0547157dc0e289b1adc4ed10c +PODFILE CHECKSUM: 7c116a16dd0744063c8c6293dbfc638c9d447c19 COCOAPODS: 1.15.2 diff --git a/apps/RNApp/package.json b/apps/RNApp/package.json index 2aaeb40b..ade867dc 100644 --- a/apps/RNApp/package.json +++ b/apps/RNApp/package.json @@ -7,6 +7,9 @@ "ios": "react-native run-ios", "build:example:android-rn": "react-native build-android", "build:example:ios-rn": "react-native build-ios", + "brownie:package:android": "brownie package:android --module-name :BrownfieldLib --variant release", + "brownie:publish:android": "brownie publish:android --module-name :BrownfieldLib", + "brownie:package:ios": "brownie package:ios --scheme BrownfieldLib --configuration Release", "lint": "eslint .", "start": "react-native start", "test": "jest" @@ -25,6 +28,7 @@ "@babel/core": "^7.25.2", "@babel/preset-env": "^7.25.3", "@babel/runtime": "^7.25.0", + "@callstack/brownie-cli": "workspace:^", "@react-native-community/cli": "20.0.0", "@react-native-community/cli-platform-android": "20.0.0", "@react-native-community/cli-platform-ios": "20.0.0", diff --git a/apps/TesterIntegrated/package.json b/apps/TesterIntegrated/package.json index e976a95e..74f59e83 100644 --- a/apps/TesterIntegrated/package.json +++ b/apps/TesterIntegrated/package.json @@ -6,7 +6,7 @@ "start": "react-native start", "build:tester-integrated:android": "cd kotlin && ./gradlew assembleDebug", "build:tester-integrated:ios": "cd swift && xcodebuild -workspace SwiftExample.xcworkspace -scheme SwiftExample -configuration Release -sdk iphonesimulator build CODE_SIGNING_ALLOWED=NO -derivedDataPath ./build", - "codegen": "brownie codegen" + "brownie:codegen": "brownie codegen" }, "dependencies": { "@callstack/brownie": "*", @@ -22,6 +22,7 @@ "@babel/core": "^7.25.2", "@babel/preset-env": "^7.25.3", "@babel/runtime": "^7.25.0", + "@callstack/brownie-cli": "workspace:^", "@react-native-community/cli": "20.0.0", "@react-native-community/cli-platform-android": "20.0.0", "@react-native-community/cli-platform-ios": "20.0.0", diff --git a/apps/TesterIntegrated/swift/Podfile.lock b/apps/TesterIntegrated/swift/Podfile.lock index 53f26d0a..b391b600 100644 --- a/apps/TesterIntegrated/swift/Podfile.lock +++ b/apps/TesterIntegrated/swift/Podfile.lock @@ -2848,7 +2848,7 @@ SPEC CHECKSUMS: ReactCommon: 804dc80944fa90b86800b43c871742ec005ca424 RNScreens: ffbb0296608eb3560de641a711bbdb663ed1f6b4 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 - Yoga: 689c8e04277f3ad631e60fe2a08e41d411daf8eb + Yoga: 8e01cef9947ca77f0477a098f0b32848a8e448c6 PODFILE CHECKSUM: cc9179225629f8397761aa16248efe751042af40 diff --git a/docs/docs/docs/_meta.json b/docs/docs/docs/_meta.json index 79143fbd..7b73727d 100644 --- a/docs/docs/docs/_meta.json +++ b/docs/docs/docs/_meta.json @@ -4,6 +4,11 @@ "name": "getting-started", "label": "Getting Started" }, + { + "type": "dir", + "name": "cli", + "label": "CLI" + }, { "type": "dir", "name": "api-reference", diff --git a/docs/docs/docs/cli/_meta.json b/docs/docs/docs/cli/_meta.json new file mode 100644 index 00000000..708ac0cd --- /dev/null +++ b/docs/docs/docs/cli/_meta.json @@ -0,0 +1 @@ +["introduction", "brownfield", "brownie"] diff --git a/docs/docs/docs/cli/brownfield.mdx b/docs/docs/docs/cli/brownfield.mdx new file mode 100644 index 00000000..2a5bacbf --- /dev/null +++ b/docs/docs/docs/cli/brownfield.mdx @@ -0,0 +1,66 @@ +# Usage with Brownfield package + +The `brownie` CLI provides utilities for building & packaging artifacts for brownfield projects that use the `@callstack/react-native-brownfield` library. + +## Usage + +```bash +brownie package:android --module-name :BrownfieldLib --variant release # Package AAR with BrownfieldLib module in release variant +brownie publish:android --module-name :BrownfieldLib # Publish all build variants of BrownfieldLib module to Maven local +brownie package:ios --scheme BrownfieldLib --configuration Release # Package XCFramework for BrownfieldLib scheme in Release configuration +brownie codegen --help # Show help +brownie --version # Show version +``` + +## iOS + +### Build for iOS + +Simply run `npx brownie package:ios` to create an XCFramework that you can later integrate into your native iOS app according to other instruction sections below. + +Available arguments: + +| Argument | Description | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| --verbose | Enable verbose logging | +| --configuration | Explicitly set the scheme configuration to use. This option is case sensitive. | +| --scheme | Explicitly set Xcode scheme to use | +| --target | Explicitly set Xcode target to use | +| --extra-params | Custom params that will be passed to xcodebuild command | +| --export-extra-params | Custom params that will be passed to xcodebuild export archive command. Example: `--export-extra-params "-allowProvisioningUpdates"` | +| --export-options-plist | Name of the export options file for archiving. Defaults to: `ExportOptions.plist` | +| --build-folder | Location for iOS build artifacts. Corresponds to Xcode's "-derivedDataPath". By default, the '\/build' path will be used. | +| --destination | Define destination(s) for the build. You can pass multiple destinations as separate values or repeated use of the flag. Values: "simulator", "device", or xcodebuild destinations | +| --archive | Create an Xcode archive (IPA) of the build, required for uploading to App Store Connect or distributing to TestFlight | +| --no-install-pods | Skip automatic CocoaPods installation | +| --no-new-arch | Run React Native in legacy async architecture | +| --local | Force local build with xcodebuild | +| --verbose | Enable verbose logging | + +## Android + +For Android, building happens in two steps: first, you build (`brownie package:android`) the AAR artifact(s) with your module, in the appropriate build variant(s), and then you `brownie publish:android` them to Maven local. +From there, native applications can consume your library from the local Maven repository. + +### Build for Android + +To build the artifact for Android without publishing, run `npx brownie package:android --module-name app`. + +Available arguments: + +| Argument | Description | +| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +| --variant | Specify your app's build variant, which is constructed from build type and product flavor, e.g. 'debug' or 'freeRelease'. (default: "debug") | +| --module-name | AAR module name | +| --verbose | Enable verbose logging | + +### Publish locally for Android + +To publish the `.aar`(s) built beforehand with `npx brownie publish:android` to Maven local, which will allow Gradle to be able to load it from Maven local repository, run: + +`npx brownie publish:android --module-name app` + +| Argument | Description | +| ------------- | ---------------------- | +| --module-name | AAR module name | +| --verbose | Enable verbose logging | diff --git a/docs/docs/docs/cli/brownie.mdx b/docs/docs/docs/cli/brownie.mdx new file mode 100644 index 00000000..b01ab897 --- /dev/null +++ b/docs/docs/docs/cli/brownie.mdx @@ -0,0 +1,133 @@ +# Usage with Brownie package + +The `brownie codegen` CLI command generates Brownie state management library native store types from TypeScript schema. + +## Usage + +```bash +brownie codegen # Generate for all configured platforms +brownie codegen -p swift # Generate Swift only +brownie codegen --platform kotlin # Generate Kotlin only +brownie codegen --help # Show help for Brownie state management codegen +brownie --version # Show version +``` + +## Configuration + +Add to your app's `package.json`: + +```json +{ + "brownie": { + "swift": "./ios/Generated/", + "kotlin": "./android/app/src/main/java/com/example/", + "kotlinPackageName": "com.example" + } +} +``` + +| Field | Required | Description | +| ------------------- | -------- | ----------------------------------------- | +| `swift` | No\* | Output directory for Swift files | +| `kotlin` | No\* | Output directory for Kotlin files | +| `kotlinPackageName` | No | Kotlin package name (extracted from path) | + +\*At least one of `swift` or `kotlin` is required. + +## Store Definition + +Stores are auto-discovered from `*.brownie.ts` files. Define your store shape using module augmentation: + +```ts +// BrownfieldStore.brownie.ts +import type { BrownieStore } from '@callstack/brownie'; + +interface BrownfieldStore extends BrownieStore { + counter: number; + user: string; + isLoading: boolean; +} + +declare module '@callstack/brownie' { + interface BrownieStores { + BrownfieldStore: BrownfieldStore; + } +} +``` + +Multiple stores in same file: + +```ts +// Stores.brownie.ts +import type { BrownieStore } from '@callstack/brownie'; + +interface UserStore extends BrownieStore { + name: string; + email: string; +} + +interface SettingsStore extends BrownieStore { + theme: 'light' | 'dark'; + notificationsEnabled: boolean; +} + +declare module '@callstack/brownie' { + interface BrownieStores { + UserStore: UserStore; + SettingsStore: SettingsStore; + } +} +``` + +## Generated Output + +**Swift** (`Codable` struct with mutable properties): + +```swift +struct BrownfieldStore: Codable { + var counter: Double + var isLoading: Bool + var user: String +} +``` + +**Kotlin** (data class): + +```kotlin +package com.example + +data class BrownfieldStore ( + val counter: Double, + val isLoading: Boolean, + val user: String +) +``` + +### Store Discovery + +1. Recursively finds `*.brownie.ts` files (skips node_modules) +2. Parses `declare module '@callstack/brownie'` blocks using ts-morph +3. Extracts store names from `BrownieStores` interface properties +4. Validates no duplicate store names exist + +### Codegen Pipeline + +``` +*.brownie.ts files (auto-discovered) + │ + ▼ +ts-morph (parse BrownieStores interface) + │ + ▼ +quicktype-typescript-input (schemaForTypeScriptSources) + │ + ▼ +JSON Schema + │ + ▼ +quicktype-core + │ + ├──▶ Swift (lang: 'swift', mutable-properties: true) + │ + └──▶ Kotlin (lang: 'kotlin', framework: 'just-types') +``` diff --git a/docs/docs/docs/cli/introduction.mdx b/docs/docs/docs/cli/introduction.mdx new file mode 100644 index 00000000..07368fd5 --- /dev/null +++ b/docs/docs/docs/cli/introduction.mdx @@ -0,0 +1,15 @@ +# Unified brownfield CLI + +React Native Brownfield comes with a batteries-included, all-in-one CLI tool called `brownie` that helps you build and publish your React Native app as a framework artifact for iOS (XCFramework) and Android (AAR). +Additionally, it contains CLI utilities for other tools like the `@callstack/brownie` state management tool. + +> ![NOTE] +> The `brownie` CLI is a unified tool serving both the needs of `@callstack/react-native-brownfield` and `@callstack/brownie` packages. The name `brownie` does not mean it is only meant for `@callstack/brownie` state management tool; it is the main CLI for React Native Brownfield as well. +> Technically, the CLI itself is encapsulated in the `@callstack/brownie-cli` package, which is a dependency of both `@callstack/react-native-brownfield` and `@callstack/brownie`. Therefore, installing either of the packages will bring the `brownie` CLI tool to your project. + +## Usage + +Please refer to the appropriate pages for detailed usage instructions: + +- [`@callstack/react-native-brownfield` - Brownfield library commands](./brownfield) +- [`@callstack/brownie` - Brownie state management library commands](./brownie) diff --git a/docs/docs/docs/getting-started/quick-start.mdx b/docs/docs/docs/getting-started/quick-start.mdx index edd39140..117dab41 100644 --- a/docs/docs/docs/getting-started/quick-start.mdx +++ b/docs/docs/docs/getting-started/quick-start.mdx @@ -32,6 +32,12 @@ The library will be automatically linked via autolinking. First, we need to package our React Native app as an XCFramework or Fat-AAR. +#### With the built-in CLI + +For best experience, you can use the built-in `brownie` CLI that comes with this library to package your React Native app for iOS and Android. + +Follow [the CLI documentation here](/docs/getting-started/cli) to see how to use it. + #### With Rock It is very likely you will find Rock the easiest way to use brownfield in your React Native project. Rock provides for instance a clean template with `@callstack/react-native-brownfield` already integrated so you can easily bootstrap a new, clean brownfield project with zero manual configuration required. Below you will find instructions both for bootstrapping a new brownfield project and integrating your existing React Native app with Rock. diff --git a/package.json b/package.json index 8f07d2c9..0444d14f 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "lint": "turbo run lint", "typecheck": "turbo run typecheck", "build": "turbo run build", + "build:watch": "yarn workspaces foreach -Api run build:watch", "release": "release-it --config packages/react-native-brownfield/.release-it.json", "brownfield:plugin:publish:local": "bash ./gradle-plugins/publish-to-maven-local.sh", "build:brownfield": "turbo run build:brownfield", diff --git a/packages/brownie/.eslintrc.json b/packages/brownie/.eslintrc.json new file mode 100644 index 00000000..14be03db --- /dev/null +++ b/packages/brownie/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "extends": ["@react-native", "prettier"], + "plugins": ["prettier"], + "ignorePatterns": ["dist/"] +} diff --git a/packages/brownie/ArchitectureOverview.md b/packages/brownie/ArchitectureOverview.md index b763d690..2204f915 100644 --- a/packages/brownie/ArchitectureOverview.md +++ b/packages/brownie/ArchitectureOverview.md @@ -27,9 +27,9 @@ Shared state management between React Native and Native apps (iOS). └───────────────────┘ └───────────────────────┘ ``` -## CLI +## CLI command -The `brownie` CLI generates native store types from TypeScript schema. +The `brownie codegen` CLI command generates native store types from TypeScript schema. ### Usage @@ -37,7 +37,7 @@ The `brownie` CLI generates native store types from TypeScript schema. brownie codegen # Generate for all configured platforms brownie codegen -p swift # Generate Swift only brownie codegen --platform kotlin # Generate Kotlin only -brownie --help # Show help +brownie codegen --help # Show help for Brownie state management codegen brownie --version # Show version ``` @@ -132,61 +132,6 @@ data class BrownfieldStore ( ) ``` -## CLI Implementation - -### File Structure - -``` -packages/brownie/scripts/ -├── cli.ts # Entry point, parseArgs, command routing -├── config.ts # Config loading from package.json -├── store-discovery.ts # Auto-discover *.brownie.ts files -├── commands/ -│ └── codegen.ts # Codegen command with --platform flag -└── generators/ - ├── swift.ts # Swift Codable generation - └── kotlin.ts # Kotlin data class generation -``` - -### Store Discovery - -1. Recursively finds `*.brownie.ts` files (skips node_modules) -2. Parses `declare module '@callstack/brownie'` blocks using ts-morph -3. Extracts store names from `BrownieStores` interface properties -4. Validates no duplicate store names exist - -### Codegen Pipeline - -``` -*.brownie.ts files (auto-discovered) - │ - ▼ -ts-morph (parse BrownieStores interface) - │ - ▼ -quicktype-typescript-input (schemaForTypeScriptSources) - │ - ▼ -JSON Schema - │ - ▼ -quicktype-core - │ - ├──▶ Swift (lang: 'swift', mutable-properties: true) - │ - └──▶ Kotlin (lang: 'kotlin', framework: 'just-types') -``` - -### Build - -Scripts are compiled with TypeScript during package build: - -```bash -yarn build # runs bob build && tsc -p scripts/tsconfig.json -``` - -Output goes to `lib/scripts/` and is exposed via `bin` field in `package.json`. - ## iOS Implementation ### Architecture diff --git a/packages/brownie/package.json b/packages/brownie/package.json index 794b01b8..9b3b7d8f 100644 --- a/packages/brownie/package.json +++ b/packages/brownie/package.json @@ -30,10 +30,10 @@ "scripts": { "lint": "eslint \"**/*.{js,ts,tsx}\"", "typecheck": "tsc --noEmit", - "build": "bob build && tsc -p scripts/tsconfig.json", + "build": "bob build", + "build:watch": "nodemon --watch src --ext js,ts,json --exec \"bob build\"", "test": "jest" }, - "bin": "./lib/scripts/cli.js", "keywords": [ "brownie", "react-native-brownfield", @@ -75,7 +75,9 @@ "@types/jest": "^29.5.14", "@types/node": "^22.0.0", "@types/react": "^19.1.1", + "eslint": "^8.57.1", "jest": "^29.7.0", + "nodemon": "^3.1.11", "react": "19.1.1", "react-native": "0.82.1", "react-native-builder-bob": "^0.40.14", diff --git a/packages/brownie/scripts/__tests__/tsconfig.json b/packages/brownie/scripts/__tests__/tsconfig.json deleted file mode 100644 index 25aea166..00000000 --- a/packages/brownie/scripts/__tests__/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "commonjs", - "moduleResolution": "node", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "types": ["node", "jest"] - }, - "include": [ - "**/*.ts", - "../__fixtures__/**/*.ts", - "../*.ts", - "../commands/*.ts", - "../generators/*.ts" - ] -} diff --git a/packages/brownie/scripts/cli.ts b/packages/brownie/scripts/cli.ts deleted file mode 100644 index 35aafa18..00000000 --- a/packages/brownie/scripts/cli.ts +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env node - -import { parseArgs, styleText } from 'node:util'; -import fs from 'fs'; -import path from 'path'; - -const packageJsonPath = path.resolve(__dirname, '../../package.json'); -const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); - -const HELP_TEXT = ` -${styleText('bold', 'brownie')} - Shared state management CLI for React Native Brownfield - -${styleText('yellow', 'Usage:')} - brownie [options] - -${styleText('yellow', 'Startup:')} - ${styleText('cyan', '-h, --help')} Show help - ${styleText('cyan', '-v, --version')} Show version - -${styleText('yellow', 'Commands:')} - ${styleText('cyan', 'codegen')} Generate native store types from TypeScript schema - -${styleText('yellow', 'Examples:')} - ${styleText('dim', 'brownie codegen')} Generate for all platforms - ${styleText('dim', 'brownie codegen -p swift')} Generate Swift only - ${styleText('dim', 'brownie codegen --platform kotlin')} Generate Kotlin only -`; - -const { values, positionals } = parseArgs({ - allowPositionals: true, - strict: false, - options: { - help: { type: 'boolean', short: 'h' }, - version: { type: 'boolean', short: 'v' }, - }, -}); - -if (values.version) { - console.log(packageJson.version); - process.exit(0); -} - -if (values.help && positionals.length === 0) { - console.log(HELP_TEXT); - process.exit(0); -} - -if (positionals.length === 0) { - console.log(HELP_TEXT); - process.exit(0); -} - -const [command] = positionals; - -async function main() { - switch (command) { - case 'codegen': { - const codegen = await import('./commands/codegen'); - await codegen.runCodegen(process.argv.slice(3), packageJson.version); - break; - } - default: - console.error(styleText('red', `Unknown command: ${command}`)); - console.log(HELP_TEXT); - process.exit(1); - } -} - -main().catch((error) => { - console.error( - styleText('red', error instanceof Error ? error.message : String(error)) - ); - process.exit(1); -}); diff --git a/packages/brownie/scripts/commands/codegen.ts b/packages/brownie/scripts/commands/codegen.ts deleted file mode 100644 index 320b77f9..00000000 --- a/packages/brownie/scripts/commands/codegen.ts +++ /dev/null @@ -1,146 +0,0 @@ -import path from 'node:path'; -import { parseArgs, styleText } from 'node:util'; -import { loadConfig, type BrownieConfig } from '../config'; -import { generateSwift } from '../generators/swift'; -import { generateKotlin } from '../generators/kotlin'; -import { discoverStores, type DiscoveredStore } from '../store-discovery'; - -type Platform = 'swift' | 'kotlin'; - -function getOutputPath(dir: string, name: string, ext: string): string { - return path.join(dir, `${name}.${ext}`); -} - -async function generateForStore( - store: DiscoveredStore, - config: BrownieConfig, - platforms: Platform[], - showLabel: boolean -): Promise { - const { name, schemaPath } = store; - const storeLabel = showLabel ? ` [${name}]` : ''; - - for (const p of platforms) { - const outputDir = config[p]; - if (!outputDir) { - continue; - } - - const ext = p === 'swift' ? 'swift' : 'kt'; - const outputPath = getOutputPath(outputDir, name, ext); - - try { - if (p === 'swift') { - await generateSwift({ - name, - schemaPath, - typeName: name, - outputPath, - }); - } else { - await generateKotlin({ - name, - schemaPath, - typeName: name, - outputPath, - packageName: config.kotlinPackageName, - }); - } - console.log(styleText('green', ` ✓ ${outputPath}${storeLabel}`)); - } catch (error) { - console.error( - styleText( - 'red', - `Error generating ${p}${storeLabel}: ${error instanceof Error ? error.message : error}` - ) - ); - process.exit(1); - } - } -} - -const HELP_TEXT = ` -${styleText('bold', 'brownie codegen')} - Generate native store types from TypeScript schema - -${styleText('yellow', 'Usage:')} - brownie codegen [options] - -${styleText('yellow', 'Startup:')} - ${styleText('cyan', '-h, --help')} Show help - ${styleText('cyan', '-v, --version')} Show version - -${styleText('yellow', 'Options:')} - ${styleText('cyan', '-p, --platform ')} Generate for specific platform (swift, kotlin) - -${styleText('yellow', 'Examples:')} - ${styleText('dim', 'brownie codegen')} Generate for all configured platforms - ${styleText('dim', 'brownie codegen -p swift')} Generate Swift only - ${styleText('dim', 'brownie codegen --platform kotlin')} Generate Kotlin only -`; - -/** - * Runs the codegen command with the given arguments. - */ -export async function runCodegen( - args: string[], - version: string -): Promise { - const { values } = parseArgs({ - args, - options: { - platform: { type: 'string', short: 'p' }, - help: { type: 'boolean', short: 'h' }, - version: { type: 'boolean', short: 'v' }, - }, - }); - - if (values.version) { - console.log(version); - return; - } - - if (values.help) { - console.log(HELP_TEXT); - return; - } - - const config = loadConfig(); - const platform = values.platform as Platform | undefined; - - if (platform && !['swift', 'kotlin'].includes(platform)) { - console.error( - styleText( - 'red', - `Invalid platform: ${platform}. Must be 'swift' or 'kotlin'` - ) - ); - process.exit(1); - } - - const stores = discoverStores(); - const isMultipleStores = stores.length > 1; - const schemaList = stores.map((s) => path.basename(s.schemaPath)).join(', '); - console.log( - styleText('cyan', `Generating store types from ${schemaList}...`) - ); - - for (const store of stores) { - const platforms: Platform[] = platform - ? [platform] - : (['swift', 'kotlin'] as Platform[]).filter((p) => config[p]); - - if (platforms.length === 0) { - console.warn( - styleText( - 'yellow', - `No output paths configured for store ${store.name}` - ) - ); - continue; - } - - await generateForStore(store, config, platforms, isMultipleStores); - } - - console.log(styleText('green', '\nDone!')); -} diff --git a/packages/brownie/scripts/tsconfig.json b/packages/brownie/scripts/tsconfig.json deleted file mode 100644 index 3fbd9e1f..00000000 --- a/packages/brownie/scripts/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "commonjs", - "moduleResolution": "node", - "outDir": "../lib/scripts", - "rootDir": ".", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "declaration": false, - "types": ["node", "jest"] - }, - "include": ["**/*.ts"], - "exclude": ["__tests__", "__fixtures__"] -} diff --git a/packages/cli/.eslintrc.json b/packages/cli/.eslintrc.json new file mode 100644 index 00000000..14be03db --- /dev/null +++ b/packages/cli/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "extends": ["@react-native", "prettier"], + "plugins": ["prettier"], + "ignorePatterns": ["dist/"] +} diff --git a/packages/cli/.gitignore b/packages/cli/.gitignore new file mode 100644 index 00000000..1521c8b7 --- /dev/null +++ b/packages/cli/.gitignore @@ -0,0 +1 @@ +dist diff --git a/packages/cli/.release-it.json b/packages/cli/.release-it.json new file mode 100644 index 00000000..14ff340f --- /dev/null +++ b/packages/cli/.release-it.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://unpkg.com/release-it@19/schema/release-it.json", + "git": { + "commitMessage": "chore: release ${version}", + "tagName": "v${version}" + }, + "npm": { + "publish": true + }, + "github": { + "release": true + }, + "plugins": { + "@release-it/conventional-changelog": { + "preset": "angular" + } + } +} diff --git a/packages/cli/package.json b/packages/cli/package.json new file mode 100644 index 00000000..582c6e3c --- /dev/null +++ b/packages/cli/package.json @@ -0,0 +1,80 @@ +{ + "name": "@callstack/brownie-cli", + "version": "1.0.0", + "license": "MIT", + "author": "Michal Chudziak ", + "bin": { + "brownie": "./dist/index.js" + }, + "type": "module", + "contributors": [ + "Artur Morys-Magiera ", + "Oskar Kwasniewski " + ], + "homepage": "https://github.com/callstack/react-native-brownfield", + "description": "Brownfield CLI for React Native, gathering all packages of the RN brownfield ecosystem", + "exports": { + ".": { + "source": "./src/index.ts" + }, + "./package.json": "./package.json" + }, + "scripts": { + "lint": "eslint \"**/*.{js,ts,tsx}\"", + "typecheck": "tsc --noEmit", + "build": "tsc -p tsconfig.json", + "build:watch": "tsc -p tsconfig.json --watch", + "test": "jest" + }, + "keywords": [ + "react-native-brownfield", + "react native brownfield", + "native", + "react native integration", + "cli", + "brownie", + "tooling" + ], + "files": [ + "src", + "dist", + "!**/__tests__", + "!**/__fixtures__", + "!**/__mocks__", + "!**/.*" + ], + "publishConfig": { + "access": "public" + }, + "peerDependencies": { + "@react-native-community/cli-config": "*", + "@react-native-community/cli-config-android": "*" + }, + "dependencies": { + "@react-native-community/cli-config": "^20.0.0", + "@react-native-community/cli-config-android": "^20.0.0", + "@rock-js/platform-android": "^0.12.5", + "@rock-js/platform-apple-helpers": "^0.12.5", + "@rock-js/plugin-brownfield-android": "^0.12.5", + "@rock-js/plugin-brownfield-ios": "^0.12.5", + "@rock-js/tools": "^0.12.5", + "commander": "^14.0.2", + "lodash.clonedeep": "^4.5.0", + "ts-morph": "^25.0.0" + }, + "devDependencies": { + "@react-native-community/cli-types": "^20.0.0", + "@react-native/babel-preset": "0.82.1", + "@react-native/eslint-config": "0.82.1", + "@types/jest": "^30.0.0", + "@types/lodash.clonedeep": "^4.5.9", + "@types/node": "^25.0.3", + "eslint": "^8.57.1", + "jest": "^29.7.0", + "nodemon": "^3.1.11", + "typescript": "5.8.3" + }, + "engines": { + "node": ">=20" + } +} diff --git a/packages/cli/src/brownfield/commands/index.ts b/packages/cli/src/brownfield/commands/index.ts new file mode 100644 index 00000000..bd66344e --- /dev/null +++ b/packages/cli/src/brownfield/commands/index.ts @@ -0,0 +1,3 @@ +export * from './packageAndroid.js'; +export * from './packageIos.js'; +export * from './publishAndroid.js'; diff --git a/packages/cli/src/brownfield/commands/packageAndroid.ts b/packages/cli/src/brownfield/commands/packageAndroid.ts new file mode 100644 index 00000000..068db393 --- /dev/null +++ b/packages/cli/src/brownfield/commands/packageAndroid.ts @@ -0,0 +1,39 @@ +import { packageAarAction } from '@rock-js/plugin-brownfield-android'; +import { + packageAarOptions, + type PackageAarFlags, +} from '@rock-js/platform-android'; + +import { Command } from 'commander'; + +import { getProjectInfo } from '../utils/index.js'; +import { + actionRunner, + curryOptions, + ExampleUsage, +} from '../../shared/index.js'; + +export const packageAndroidCommand = curryOptions( + new Command('package:android').description('Build Android AAR'), + packageAarOptions.map((option) => + option.name.startsWith('--variant') + ? { ...option, default: 'debug' } + : option + ) +).action( + actionRunner(async (options: PackageAarFlags) => { + const { projectRoot, platformConfig } = getProjectInfo('android'); + + await packageAarAction({ + projectRoot, + pluginConfig: platformConfig, + moduleName: options.moduleName, + variant: options.variant, + }); + }) +); + +export const packageAndroidExample = new ExampleUsage( + 'package:android --module-name :BrownfieldLib --variant release', + "Build Android AAR for 'BrownfieldLib' module in release variant" +); diff --git a/packages/cli/src/brownfield/commands/packageIos.ts b/packages/cli/src/brownfield/commands/packageIos.ts new file mode 100644 index 00000000..62d8513d --- /dev/null +++ b/packages/cli/src/brownfield/commands/packageIos.ts @@ -0,0 +1,71 @@ +import path from 'node:path'; + +import { + getBuildOptions, + type BuildFlags as AppleBuildFlags, +} from '@rock-js/platform-apple-helpers'; +import { packageIosAction } from '@rock-js/plugin-brownfield-ios'; +import { getReactNativeVersion } from '@rock-js/tools'; + +import { Command } from 'commander'; + +import { getProjectInfo } from '../utils/index.js'; +import { + actionRunner, + curryOptions, + ExampleUsage, +} from '../../shared/index.js'; + +export const packageIosCommand = curryOptions( + new Command('package:ios').description('Build iOS XCFramework'), + getBuildOptions({ platformName: 'ios' }).map((option) => + option.name.startsWith('--build-folder') + ? { + ...option, + description: + option.description + + " By default, the '/build' path will be used.", + } + : option + ) +).action( + actionRunner(async (options: AppleBuildFlags) => { + const { projectRoot, platformConfig, userConfig } = getProjectInfo('ios'); + + if (!userConfig.project.ios) { + throw new Error('iOS project not found.'); + } + + if (!userConfig.project.ios.xcodeProject) { + throw new Error('iOS Xcode project not found in the configuration.'); + } + + const brownieCacheDir = path.join( + userConfig.project.ios.sourceDir, + '.brownie' + ); + + options.buildFolder ??= path.join(brownieCacheDir, 'build'); + + packageIosAction( + options, + { + projectRoot, + reactNativePath: userConfig.reactNativePath, + // below: the userConfig.reactNativeVersion may be a non-semver-format string, + // e.g. '0.82' (note the missing patch component), + // therefore we resolve it manually from RN's package.json using Rock's utils + reactNativeVersion: getReactNativeVersion(projectRoot), + usePrebuiltRNCore: 0, // for brownfield, it is required to build RN from source + packageDir: path.join(brownieCacheDir, 'package'), // the output directory for artifacts + skipCache: true, // cache is dependent on existence of Rock config file + }, + platformConfig + ); + }) +); + +export const packageIosExample = new ExampleUsage( + 'package:ios --scheme BrownfieldLib --configuration Release', + "Build iOS XCFramework for 'BrownfieldLib' scheme in Release configuration" +); diff --git a/packages/cli/src/brownfield/commands/publishAndroid.ts b/packages/cli/src/brownfield/commands/publishAndroid.ts new file mode 100644 index 00000000..00657b51 --- /dev/null +++ b/packages/cli/src/brownfield/commands/publishAndroid.ts @@ -0,0 +1,36 @@ +import { publishLocalAarAction } from '@rock-js/plugin-brownfield-android'; +import { + publishLocalAarOptions, + type PublishLocalAarFlags, +} from '@rock-js/platform-android'; + +import { Command } from 'commander'; + +import { getProjectInfo } from '../utils/index.js'; +import { + actionRunner, + curryOptions, + ExampleUsage, +} from '../../shared/index.js'; + +export const publishAndroidCommand = curryOptions( + new Command('publish:android').description( + 'Publish Android package to Maven local' + ), + publishLocalAarOptions +).action( + actionRunner(async (options: PublishLocalAarFlags) => { + const { projectRoot, platformConfig } = getProjectInfo('android'); + + await publishLocalAarAction({ + projectRoot, + pluginConfig: platformConfig, + moduleName: options.moduleName, + }); + }) +); + +export const publishAndroidExample = new ExampleUsage( + 'publish:android --module-name :BrownfieldLib', + "Publish all built variants for 'BrownfieldLib' module to Maven local" +); diff --git a/packages/cli/src/brownfield/index.ts b/packages/cli/src/brownfield/index.ts new file mode 100644 index 00000000..3bcad5c5 --- /dev/null +++ b/packages/cli/src/brownfield/index.ts @@ -0,0 +1,10 @@ +import { styleText } from 'node:util'; + +import * as Commands from './commands/index.js'; + +export * from './utils/index.js'; + +export const groupName = `${styleText(['bold', 'blueBright'], '@callstack/react-native-brownfield')}${styleText('whiteBright', ' - utilities for React Native Brownfield projects')}`; + +export { Commands }; +export default Commands; diff --git a/packages/cli/src/brownfield/utils/index.ts b/packages/cli/src/brownfield/utils/index.ts new file mode 100644 index 00000000..4eaf901a --- /dev/null +++ b/packages/cli/src/brownfield/utils/index.ts @@ -0,0 +1,2 @@ +export * from './paths.js'; +export * from './rn-cli.js'; diff --git a/packages/cli/src/brownfield/utils/paths.ts b/packages/cli/src/brownfield/utils/paths.ts new file mode 100644 index 00000000..ede38c96 --- /dev/null +++ b/packages/cli/src/brownfield/utils/paths.ts @@ -0,0 +1,40 @@ +import * as path from 'node:path'; +import * as fs from 'node:fs'; + +import type { + AndroidProjectConfig, + IOSProjectConfig, +} from '@react-native-community/cli-types'; +import cloneDeep from 'lodash.clonedeep'; + +export function makeRelativeProjectConfigPaths< + UserConfig extends AndroidProjectConfig | IOSProjectConfig | undefined, +>(projectRoot: string, userConfig: UserConfig): UserConfig { + const relativeConfig = cloneDeep(userConfig); + + if (userConfig?.sourceDir) { + relativeConfig!.sourceDir = path.relative( + projectRoot, + userConfig.sourceDir + ); + } + + return relativeConfig; +} + +/** + * Helper function to find RN project root by recursively looking for a package.json in the parent directories + * @returns The path to the project root directory + */ +export function findProjectRoot(): string { + let currentDir = process.cwd(); + + while (currentDir !== '/') { + if (fs.existsSync(path.join(currentDir, 'package.json'))) { + return currentDir; + } + currentDir = path.dirname(currentDir); + } + + throw new Error('Could not find project root (no package.json found)'); +} diff --git a/packages/cli/src/brownfield/utils/rn-cli.ts b/packages/cli/src/brownfield/utils/rn-cli.ts new file mode 100644 index 00000000..9e900a9f --- /dev/null +++ b/packages/cli/src/brownfield/utils/rn-cli.ts @@ -0,0 +1,52 @@ +import type { PackageAarFlags } from '@rock-js/platform-android'; +import type { + AndroidProjectConfig, + Config as UserConfig, + ProjectConfig, +} from '@react-native-community/cli-types'; +import cliConfig from '@react-native-community/cli-config'; + +import { findProjectRoot, makeRelativeProjectConfigPaths } from './paths.js'; + +/** + * Gets the project info for the given platform from the current working directory + * @param platform the platform for which to get project info + * @returns project root and android project config + */ +export function getProjectInfo( + platform: Platform +): { + projectRoot: string; + userConfig: UserConfig; + platformConfig: ProjectConfig[Platform]; +} { + const projectRoot = findProjectRoot(); + + const userConfig = cliConfig.default({ + projectRoot, + selectedPlatform: platform, + }); + + // below: relative sourceDir path is required by RN CLI's API + const platformConfig = makeRelativeProjectConfigPaths( + projectRoot, + userConfig.project[platform] + ); + + if (!platformConfig) { + throw new Error(`${platform} project not found.`); + } + + return { projectRoot, userConfig, platformConfig }; +} + +export const getAarConfig = ( + args: PackageAarFlags, + androidConfig: AndroidProjectConfig +) => { + const config = { + sourceDir: androidConfig.sourceDir, + moduleName: args.moduleName ?? '', + }; + return config; +}; diff --git a/packages/brownie/scripts/__fixtures__/SecondStore.brownie.ts b/packages/cli/src/brownie/__fixtures__/SecondStore.brownie.ts similarity index 75% rename from packages/brownie/scripts/__fixtures__/SecondStore.brownie.ts rename to packages/cli/src/brownie/__fixtures__/SecondStore.brownie.ts index 2d5a6162..b241bff9 100644 --- a/packages/brownie/scripts/__fixtures__/SecondStore.brownie.ts +++ b/packages/cli/src/brownie/__fixtures__/SecondStore.brownie.ts @@ -3,6 +3,7 @@ type SecondStore = { enabled: boolean; }; +// @ts-expect-error - inexistent module augmentation declare module '@callstack/brownie' { interface BrownieStores { SecondStore: SecondStore; diff --git a/packages/brownie/scripts/__fixtures__/TestStore.brownie.ts b/packages/cli/src/brownie/__fixtures__/TestStore.brownie.ts similarity index 76% rename from packages/brownie/scripts/__fixtures__/TestStore.brownie.ts rename to packages/cli/src/brownie/__fixtures__/TestStore.brownie.ts index 06e40f1f..20d21bd8 100644 --- a/packages/brownie/scripts/__fixtures__/TestStore.brownie.ts +++ b/packages/cli/src/brownie/__fixtures__/TestStore.brownie.ts @@ -4,6 +4,7 @@ type TestStore = { isActive: boolean; }; +// @ts-expect-error - inexistent module augmentation declare module '@callstack/brownie' { interface BrownieStores { TestStore: TestStore; diff --git a/packages/brownie/src/__tests__/__mocks__/NativeBrownieModule.ts b/packages/cli/src/brownie/__tests__/__mocks__/NativeBrownieModule.ts similarity index 100% rename from packages/brownie/src/__tests__/__mocks__/NativeBrownieModule.ts rename to packages/cli/src/brownie/__tests__/__mocks__/NativeBrownieModule.ts diff --git a/packages/brownie/scripts/__tests__/commands/codegen.test.ts b/packages/cli/src/brownie/__tests__/commands/codegen.test.ts similarity index 81% rename from packages/brownie/scripts/__tests__/commands/codegen.test.ts rename to packages/cli/src/brownie/__tests__/commands/codegen.test.ts index e6e5addf..5de1c009 100644 --- a/packages/brownie/scripts/__tests__/commands/codegen.test.ts +++ b/packages/cli/src/brownie/__tests__/commands/codegen.test.ts @@ -1,9 +1,9 @@ import fs from 'fs'; import path from 'path'; -import { runCodegen } from '../../commands/codegen'; -import * as swiftGenerator from '../../generators/swift'; -import * as kotlinGenerator from '../../generators/kotlin'; -import * as storeDiscovery from '../../store-discovery'; +import { runCodegen } from '../../commands/codegen.js'; +import * as swiftGenerator from '../../generators/swift.js'; +import * as kotlinGenerator from '../../generators/kotlin.js'; +import * as storeDiscovery from '../../store-discovery.js'; const FIXTURES_DIR = path.join(__dirname, '../../__fixtures__'); @@ -15,7 +15,6 @@ const mockGenerateSwift = swiftGenerator.generateSwift as jest.Mock; const mockGenerateKotlin = kotlinGenerator.generateKotlin as jest.Mock; const mockDiscoverStores = storeDiscovery.discoverStores as jest.Mock; const mockCwd = jest.spyOn(process, 'cwd'); -const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(); const mockConsoleError = jest.spyOn(console, 'error').mockImplementation(); jest.spyOn(console, 'warn').mockImplementation(); jest.spyOn(process, 'exit').mockImplementation((code) => { @@ -52,18 +51,6 @@ describe('runCodegen', () => { jest.clearAllMocks(); }); - it('prints version when -v flag passed', async () => { - await runCodegen(['-v'], '1.0.0'); - expect(mockConsoleLog).toHaveBeenCalledWith('1.0.0'); - }); - - it('prints help when -h flag passed', async () => { - await runCodegen(['-h'], '1.0.0'); - expect(mockConsoleLog).toHaveBeenCalled(); - const output = mockConsoleLog.mock.calls[0]![0]; - expect(output).toContain('brownie codegen'); - }); - it('generates swift files for discovered store', async () => { tempDir = createTempPackageJson({ brownie: { @@ -72,7 +59,7 @@ describe('runCodegen', () => { }); mockCwd.mockReturnValue(tempDir); - await runCodegen([], '1.0.0'); + await runCodegen({}); expect(mockGenerateSwift).toHaveBeenCalledWith({ name: 'TestStore', @@ -92,7 +79,7 @@ describe('runCodegen', () => { }); mockCwd.mockReturnValue(tempDir); - await runCodegen([], '1.0.0'); + await runCodegen({}); expect(mockGenerateKotlin).toHaveBeenCalledWith({ name: 'TestStore', @@ -113,13 +100,13 @@ describe('runCodegen', () => { }); mockCwd.mockReturnValue(tempDir); - await runCodegen([], '1.0.0'); + await runCodegen({}); expect(mockGenerateSwift).toHaveBeenCalled(); expect(mockGenerateKotlin).toHaveBeenCalled(); }); - it('generates only specified platform with -p flag', async () => { + it('generates only specified platform', async () => { tempDir = createTempPackageJson({ brownie: { swift: './Generated', @@ -128,7 +115,7 @@ describe('runCodegen', () => { }); mockCwd.mockReturnValue(tempDir); - await runCodegen(['-p', 'swift'], '1.0.0'); + await runCodegen({ platform: 'swift' }); expect(mockGenerateSwift).toHaveBeenCalled(); expect(mockGenerateKotlin).not.toHaveBeenCalled(); @@ -150,7 +137,7 @@ describe('runCodegen', () => { }, ]); - await runCodegen([], '1.0.0'); + await runCodegen({}); expect(mockGenerateSwift).toHaveBeenCalledTimes(2); expect(mockGenerateSwift).toHaveBeenNthCalledWith(1, { @@ -175,7 +162,8 @@ describe('runCodegen', () => { }); mockCwd.mockReturnValue(tempDir); - await expect(runCodegen(['-p', 'invalid'], '1.0.0')).rejects.toThrow( + // @ts-expect-error - testing invalid input + await expect(runCodegen({ platform: 'invalid' })).rejects.toThrow( 'process.exit(1)' ); expect(mockConsoleError).toHaveBeenCalled(); @@ -190,7 +178,7 @@ describe('runCodegen', () => { mockCwd.mockReturnValue(tempDir); mockGenerateSwift.mockRejectedValue(new Error('Generation failed')); - await expect(runCodegen([], '1.0.0')).rejects.toThrow('process.exit(1)'); + await expect(runCodegen({})).rejects.toThrow('process.exit(1)'); expect(mockConsoleError).toHaveBeenCalled(); }); @@ -202,7 +190,7 @@ describe('runCodegen', () => { }); mockCwd.mockReturnValue(tempDir); - await runCodegen(['-p', 'kotlin'], '1.0.0'); + await runCodegen({ platform: 'kotlin' }); expect(mockGenerateKotlin).not.toHaveBeenCalled(); }); diff --git a/packages/brownie/scripts/__tests__/config.test.ts b/packages/cli/src/brownie/__tests__/config.test.ts similarity index 96% rename from packages/brownie/scripts/__tests__/config.test.ts rename to packages/cli/src/brownie/__tests__/config.test.ts index e6ad5c9a..d73ffbd0 100644 --- a/packages/brownie/scripts/__tests__/config.test.ts +++ b/packages/cli/src/brownie/__tests__/config.test.ts @@ -1,6 +1,7 @@ -import fs from 'fs'; -import path from 'path'; -import { loadConfig } from '../config'; +import fs from 'node:fs'; +import path from 'node:path'; + +import { loadConfig } from '../config.js'; const FIXTURES_DIR = path.join(__dirname, '../__fixtures__'); diff --git a/packages/brownie/scripts/__tests__/store-discovery.test.ts b/packages/cli/src/brownie/__tests__/store-discovery.test.ts similarity index 98% rename from packages/brownie/scripts/__tests__/store-discovery.test.ts rename to packages/cli/src/brownie/__tests__/store-discovery.test.ts index 804f25b0..1b17f6db 100644 --- a/packages/brownie/scripts/__tests__/store-discovery.test.ts +++ b/packages/cli/src/brownie/__tests__/store-discovery.test.ts @@ -1,6 +1,6 @@ import fs from 'fs'; import path from 'path'; -import { discoverStores } from '../store-discovery'; +import { discoverStores } from '../store-discovery.js'; const FIXTURES_DIR = path.join(__dirname, '../__fixtures__'); diff --git a/packages/brownie/src/__tests__/tsconfig.json b/packages/cli/src/brownie/__tests__/tsconfig.json similarity index 59% rename from packages/brownie/src/__tests__/tsconfig.json rename to packages/cli/src/brownie/__tests__/tsconfig.json index b8b1c359..dcc46c48 100644 --- a/packages/brownie/src/__tests__/tsconfig.json +++ b/packages/cli/src/brownie/__tests__/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../../tsconfig", + "extends": "../../../../../tsconfig.json", "compilerOptions": { "verbatimModuleSyntax": false } diff --git a/packages/cli/src/brownie/commands/codegen.ts b/packages/cli/src/brownie/commands/codegen.ts new file mode 100644 index 00000000..21ad3f8a --- /dev/null +++ b/packages/cli/src/brownie/commands/codegen.ts @@ -0,0 +1,130 @@ +import path from 'node:path'; +import { styleText } from 'node:util'; + +import { Command, Option } from 'commander'; + +import { actionRunner } from '../../shared/index.js'; +import { loadConfig, type BrownieConfig } from '../config.js'; +import { generateSwift } from '../generators/swift.js'; +import { generateKotlin } from '../generators/kotlin.js'; +import { discoverStores, type DiscoveredStore } from '../store-discovery.js'; +import { Platform } from '../types.js'; +import { intro, logger, outro } from '@rock-js/tools'; +import { QuickTypeError } from 'quicktype-core'; + +function getOutputPath(dir: string, name: string, ext: string): string { + return path.join(dir, `${name}.${ext}`); +} + +function formatQuickTypeError(error: QuickTypeError): string { + let message = error.errorMessage; + for (const [key, value] of Object.entries(error.properties)) { + message = message.replaceAll('${' + key + '}', value); + } + + return message; +} + +async function generateForStore( + store: DiscoveredStore, + config: BrownieConfig, + platforms: Platform[], + showLabel: boolean +): Promise { + const { name, schemaPath } = store; + const storeLabel = showLabel ? ` [${name}]` : ''; + + logger.info(`Generating types for store ${name}`); + + for (const p of platforms) { + const outputDir = config[p]; + if (!outputDir) { + continue; + } + + const ext = p === 'swift' ? 'swift' : 'kt'; + const outputPath = getOutputPath(outputDir, name, ext); + + try { + if (p === 'swift') { + await generateSwift({ + name, + schemaPath, + typeName: name, + outputPath, + }); + } else { + await generateKotlin({ + name, + schemaPath, + typeName: name, + outputPath, + packageName: config.kotlinPackageName, + }); + } + logger.success( + `Generated ${outputPath}${styleText('italic', storeLabel)}` + ); + } catch (error) { + logger.error( + `Error generating ${p}${storeLabel}: ${error instanceof QuickTypeError ? formatQuickTypeError(error) : error instanceof Error ? error.message : error}` + ); + process.exit(1); + } + } +} + +export type RunCodegenOptions = { platform?: Platform }; + +/** + * Runs the codegen command with the given arguments. + */ +export async function runCodegen({ platform }: RunCodegenOptions) { + intro( + `Running brownie codegen for ${platform ? `platform ${platform}` : 'all platforms'}` + ); + + const config = loadConfig(); + + if (platform && !['swift', 'kotlin'].includes(platform)) { + logger.error(`Invalid platform: ${platform}. Must be 'swift' or 'kotlin'`); + process.exit(1); + } + + const stores = discoverStores(); + const isMultipleStores = stores.length > 1; + const schemaList = stores.map((s) => path.basename(s.schemaPath)).join(', '); + + logger.info( + styleText('cyan', `Generating store types from ${schemaList}...`) + ); + + for (const store of stores) { + const platforms: Platform[] = platform + ? [platform] + : (['swift', 'kotlin'] as Platform[]).filter((p) => config[p]); + + if (platforms.length === 0) { + logger.warn(`No output paths configured for store ${store.name}`); + continue; + } + + await generateForStore(store, config, platforms, isMultipleStores); + } + + outro('Done!'); +} + +export const codegenCommand = new Command('codegen') + .description('Generate native store types from TypeScript schema') + .addOption( + new Option( + '-p, --platform ', + 'Generate for specific platform (swift, kotlin)' + ).choices(['swift', 'kotlin']) + ) + .action( + actionRunner(async (options: RunCodegenOptions) => { + await runCodegen(options); + }) + ); diff --git a/packages/cli/src/brownie/commands/index.ts b/packages/cli/src/brownie/commands/index.ts new file mode 100644 index 00000000..4ea54b84 --- /dev/null +++ b/packages/cli/src/brownie/commands/index.ts @@ -0,0 +1 @@ +export * from './codegen.js'; diff --git a/packages/brownie/scripts/config.ts b/packages/cli/src/brownie/config.ts similarity index 100% rename from packages/brownie/scripts/config.ts rename to packages/cli/src/brownie/config.ts diff --git a/packages/brownie/scripts/generators/kotlin.ts b/packages/cli/src/brownie/generators/kotlin.ts similarity index 100% rename from packages/brownie/scripts/generators/kotlin.ts rename to packages/cli/src/brownie/generators/kotlin.ts diff --git a/packages/brownie/scripts/generators/swift.ts b/packages/cli/src/brownie/generators/swift.ts similarity index 100% rename from packages/brownie/scripts/generators/swift.ts rename to packages/cli/src/brownie/generators/swift.ts diff --git a/packages/cli/src/brownie/index.ts b/packages/cli/src/brownie/index.ts new file mode 100644 index 00000000..fd4b48c5 --- /dev/null +++ b/packages/cli/src/brownie/index.ts @@ -0,0 +1,11 @@ +import { styleText } from 'node:util'; + +import * as Commands from './commands/index.js'; + +export type * from './types.js'; +export * from './store-discovery.js'; + +export const groupName = `${styleText(['bold', 'blueBright'], '@callstack/brownie')}${styleText('whiteBright', ' - Shared state management CLI for React Native Brownfield')}`; + +export { Commands }; +export default Commands; diff --git a/packages/brownie/scripts/store-discovery.ts b/packages/cli/src/brownie/store-discovery.ts similarity index 100% rename from packages/brownie/scripts/store-discovery.ts rename to packages/cli/src/brownie/store-discovery.ts diff --git a/packages/cli/src/brownie/types.ts b/packages/cli/src/brownie/types.ts new file mode 100644 index 00000000..7bafef24 --- /dev/null +++ b/packages/cli/src/brownie/types.ts @@ -0,0 +1 @@ +export type Platform = 'swift' | 'kotlin'; diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts new file mode 100644 index 00000000..74e89f27 --- /dev/null +++ b/packages/cli/src/index.ts @@ -0,0 +1,84 @@ +#!/usr/bin/env node + +import { styleText } from 'node:util'; + +import { logger } from '@rock-js/tools'; + +import { Command } from 'commander'; + +import { ExampleUsage } from './shared/index.js'; +import brownfieldCommands, { + groupName as brownfieldCommandsGroupName, +} from './brownfield/index.js'; +import brownieCommands, { + groupName as brownieCommandsGroupName, +} from './brownie/index.js'; + +const program = new Command(); + +program + .name(styleText('magenta', 'brownie')) + .usage(styleText('yellow', '[options] [command]')) + .description( + styleText('magentaBright', 'React Native Brownfield CLI - ') + + styleText(['magenta', 'bold', 'underline'], 'Brownie') + ) + .version(process.env.npm_package_version ?? '0.0.0'); + +program + .optionsGroup('Global options:') + .option('--verbose', 'enable verbose logging') + .hook('preAction', (_cmd) => { + const opts = program.opts(); + if (opts.verbose) { + logger.setVerbose(opts.verbose ?? false); + } + }); + +program.configureHelp({ + styleTitle: (str) => styleText('bold', str), + styleCommandText: (str) => styleText('cyan', str), + styleArgumentText: (str) => styleText('yellow', str), + styleSubcommandText: (str) => styleText('blue', str), +}); + +function registrationHelper( + commandsRegistration: Record, + groupName: string +) { + program.commandsGroup(groupName); + + const exampleUsageItems: ExampleUsage[] = []; + Object.values(commandsRegistration).forEach((commandOrExampleUsage) => { + if (commandOrExampleUsage instanceof Command) { + // command + program.addCommand(commandOrExampleUsage); + } else if (commandOrExampleUsage instanceof ExampleUsage) { + // piece of example usage for the command group + exampleUsageItems.push(commandOrExampleUsage); + } + }); + + if (exampleUsageItems.length) { + const longestUsageItemCommandLength = exampleUsageItems.reduce( + (max, item) => Math.max(max, item.command.length), + 0 + ); + + program.addHelpText( + 'after', + `\nExamples:\n${exampleUsageItems.map((item) => `\t ${styleText('dim', item.command.padEnd(longestUsageItemCommandLength))}\t${item.description}`).join('\n')}\n` + ); + } +} + +registrationHelper(brownfieldCommands, brownfieldCommandsGroupName); +registrationHelper(brownieCommands, brownieCommandsGroupName); + +program.commandsGroup('Utility commands').helpCommand('help [command]'); + +program.parse(process.argv); + +if (!process.argv.slice(2).length) { + program.outputHelp(); +} diff --git a/packages/cli/src/shared/classes/ExampleUsage.ts b/packages/cli/src/shared/classes/ExampleUsage.ts new file mode 100644 index 00000000..1a59cbf5 --- /dev/null +++ b/packages/cli/src/shared/classes/ExampleUsage.ts @@ -0,0 +1,6 @@ +export class ExampleUsage { + constructor( + public readonly command: string, + public readonly description: string + ) {} +} diff --git a/packages/cli/src/shared/classes/index.ts b/packages/cli/src/shared/classes/index.ts new file mode 100644 index 00000000..3f4b08a7 --- /dev/null +++ b/packages/cli/src/shared/classes/index.ts @@ -0,0 +1 @@ +export * from './ExampleUsage.js'; diff --git a/packages/cli/src/shared/index.ts b/packages/cli/src/shared/index.ts new file mode 100644 index 00000000..af46c9fc --- /dev/null +++ b/packages/cli/src/shared/index.ts @@ -0,0 +1,2 @@ +export * from './utils/index.js'; +export * from './classes/index.js'; diff --git a/packages/cli/src/shared/utils/cli.ts b/packages/cli/src/shared/utils/cli.ts new file mode 100644 index 00000000..91f7ed00 --- /dev/null +++ b/packages/cli/src/shared/utils/cli.ts @@ -0,0 +1,33 @@ +import { logger, type RockCLIOptions } from '@rock-js/tools'; + +import type { Command } from 'commander'; + +export function curryOptions(programCommand: Command, options: RockCLIOptions) { + options.forEach((option) => { + if (option.parse) { + programCommand = programCommand.option( + option.name, + option.description, + option.parse, + option.default + ); + } else { + programCommand = programCommand.option( + option.name, + option.description, + option.default + ); + } + }); + + return programCommand; +} + +function handleActionError(error: Error) { + logger.error(`Error running command: ${error.message}`); + process.exit(1); +} + +export function actionRunner(fn: (...args: T[]) => Promise) { + return (...args: T[]) => fn(...args).catch(handleActionError); +} diff --git a/packages/cli/src/shared/utils/index.ts b/packages/cli/src/shared/utils/index.ts new file mode 100644 index 00000000..c01c2dcf --- /dev/null +++ b/packages/cli/src/shared/utils/index.ts @@ -0,0 +1 @@ +export * from './cli.js'; diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json new file mode 100644 index 00000000..34a82911 --- /dev/null +++ b/packages/cli/tsconfig.json @@ -0,0 +1,114 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "libReplacement": true, /* Enable lib replacement. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "node16", /* Specify what module code is generated. */ + "rootDir": "./src", /* Specify the root folder within your source files. */ + "moduleResolution": "node16", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ + // "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */ + "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + }, + "exclude": ["__tests__", "__fixtures__", "dist"], +} diff --git a/packages/react-native-brownfield/.eslintrc.json b/packages/react-native-brownfield/.eslintrc.json new file mode 100644 index 00000000..8a2697f7 --- /dev/null +++ b/packages/react-native-brownfield/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "extends": ["@react-native", "prettier"], + "plugins": ["prettier"], + "ignorePatterns": ["node_modules/", "lib/"] +} diff --git a/packages/react-native-brownfield/ios/ReactNativeBrownfieldModule.mm b/packages/react-native-brownfield/ios/ReactNativeBrownfieldModule.mm index 3b916690..336f98bc 100644 --- a/packages/react-native-brownfield/ios/ReactNativeBrownfieldModule.mm +++ b/packages/react-native-brownfield/ios/ReactNativeBrownfieldModule.mm @@ -6,8 +6,6 @@ #import "ReactBrownfield-Swift.h" #endif -#import "RCTTurboModule.h" - @implementation ReactNativeBrownfieldModule RCT_EXPORT_MODULE(ReactNativeBrownfield); diff --git a/packages/react-native-brownfield/package.json b/packages/react-native-brownfield/package.json index e1ec2a16..36152784 100644 --- a/packages/react-native-brownfield/package.json +++ b/packages/react-native-brownfield/package.json @@ -3,6 +3,9 @@ "version": "2.0.1", "license": "MIT", "author": "Michal Chudziak ", + "bin": { + "brownfield": "./lib/commonjs/cli/index.js" + }, "contributors": [ "Piotr Drapich " ], @@ -29,6 +32,7 @@ "lint": "eslint \"**/*.{js,ts,tsx}\"", "typecheck": "tsc --noEmit", "build": "bob build", + "build:watch": "nodemon --watch src --ext js,ts,json --exec \"bob build\"", "build:brownfield": "yarn run build" }, "keywords": [ @@ -62,6 +66,9 @@ "react": "*", "react-native": "*" }, + "dependencies": { + "@callstack/brownie-cli": "workspace:^" + }, "devDependencies": { "@babel/core": "^7.25.2", "@babel/preset-env": "^7.25.3", @@ -70,6 +77,8 @@ "@react-native/eslint-config": "0.82.1", "@types/jest": "^30.0.0", "@types/react": "^19.1.1", + "eslint": "^8.57.1", + "nodemon": "^3.1.11", "react": "19.1.1", "react-native": "0.82.1", "react-native-builder-bob": "^0.40.14", diff --git a/packages/react-native-brownfield/tsconfig.json b/packages/react-native-brownfield/tsconfig.json index b0e41ab4..fd9e8623 100644 --- a/packages/react-native-brownfield/tsconfig.json +++ b/packages/react-native-brownfield/tsconfig.json @@ -2,6 +2,6 @@ "extends": "../../tsconfig", "compilerOptions": { "rootDir": ".", - "outDir": "./lib/typescript", + "outDir": "./lib/typescript" } -} \ No newline at end of file +} diff --git a/tsconfig.json b/tsconfig.json index 87269073..c1ce9caa 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,5 +22,10 @@ "verbatimModuleSyntax": true, "customConditions": ["source"] }, - "exclude": ["packages/*/lib"] + "references": [ + { "path": "packages/react-native-brownfield" }, + { "path": "packages/cli" }, + { "path": "packages/brownie" } + ], + "exclude": ["**/node_modules", "apps/**/Pods", "packages/*/lib"] } diff --git a/yarn.lock b/yarn.lock index 23ae96ff..ffae74c6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1542,6 +1542,7 @@ __metadata: "@babel/core": "npm:^7.25.2" "@babel/preset-env": "npm:^7.25.3" "@babel/runtime": "npm:^7.25.0" + "@callstack/brownie-cli": "workspace:^" "@callstack/react-native-brownfield": "npm:*" "@react-native-community/cli": "npm:20.0.0" "@react-native-community/cli-platform-android": "npm:20.0.0" @@ -1595,6 +1596,38 @@ __metadata: languageName: unknown linkType: soft +"@callstack/brownie-cli@workspace:^, @callstack/brownie-cli@workspace:packages/cli": + version: 0.0.0-use.local + resolution: "@callstack/brownie-cli@workspace:packages/cli" + dependencies: + "@react-native-community/cli-config": "npm:^20.0.0" + "@react-native-community/cli-config-android": "npm:^20.0.0" + "@react-native-community/cli-types": "npm:^20.0.0" + "@react-native/babel-preset": "npm:0.82.1" + "@react-native/eslint-config": "npm:0.82.1" + "@rock-js/platform-android": "npm:^0.12.5" + "@rock-js/platform-apple-helpers": "npm:^0.12.5" + "@rock-js/plugin-brownfield-android": "npm:^0.12.5" + "@rock-js/plugin-brownfield-ios": "npm:^0.12.5" + "@rock-js/tools": "npm:^0.12.5" + "@types/jest": "npm:^30.0.0" + "@types/lodash.clonedeep": "npm:^4.5.9" + "@types/node": "npm:^25.0.3" + commander: "npm:^14.0.2" + eslint: "npm:^8.57.1" + jest: "npm:^29.7.0" + lodash.clonedeep: "npm:^4.5.0" + nodemon: "npm:^3.1.11" + ts-morph: "npm:^25.0.0" + typescript: "npm:5.8.3" + peerDependencies: + "@react-native-community/cli-config": "*" + "@react-native-community/cli-config-android": "*" + bin: + brownie: ./dist/index.js + languageName: unknown + linkType: soft + "@callstack/brownie@npm:*, @callstack/brownie@workspace:packages/brownie": version: 0.0.0-use.local resolution: "@callstack/brownie@workspace:packages/brownie" @@ -1608,7 +1641,9 @@ __metadata: "@types/jest": "npm:^29.5.14" "@types/node": "npm:^22.0.0" "@types/react": "npm:^19.1.1" + eslint: "npm:^8.57.1" jest: "npm:^29.7.0" + nodemon: "npm:^3.1.11" quicktype-core: "npm:^23.0.170" quicktype-typescript-input: "npm:^23.0.170" react: "npm:19.1.1" @@ -1619,8 +1654,6 @@ __metadata: peerDependencies: react: "*" react-native: "*" - bin: - brownie: ./lib/scripts/cli.js languageName: unknown linkType: soft @@ -1632,6 +1665,7 @@ __metadata: "@babel/preset-env": "npm:^7.25.3" "@babel/runtime": "npm:^7.25.0" "@callstack/brownie": "npm:*" + "@callstack/brownie-cli": "workspace:^" "@callstack/react-native-brownfield": "npm:*" "@react-native-community/cli": "npm:20.0.0" "@react-native-community/cli-platform-android": "npm:20.0.0" @@ -1657,10 +1691,13 @@ __metadata: "@babel/core": "npm:^7.25.2" "@babel/preset-env": "npm:^7.25.3" "@babel/runtime": "npm:^7.25.0" + "@callstack/brownie-cli": "workspace:^" "@react-native/babel-preset": "npm:0.82.1" "@react-native/eslint-config": "npm:0.82.1" "@types/jest": "npm:^30.0.0" "@types/react": "npm:^19.1.1" + eslint: "npm:^8.57.1" + nodemon: "npm:^3.1.11" react: "npm:19.1.1" react-native: "npm:0.82.1" react-native-builder-bob: "npm:^0.40.14" @@ -1668,6 +1705,8 @@ __metadata: peerDependencies: react: "*" react-native: "*" + bin: + brownfield: ./lib/commonjs/cli/index.js languageName: unknown linkType: soft @@ -1697,6 +1736,27 @@ __metadata: languageName: node linkType: hard +"@clack/core@npm:0.5.0": + version: 0.5.0 + resolution: "@clack/core@npm:0.5.0" + dependencies: + picocolors: "npm:^1.0.0" + sisteransi: "npm:^1.0.5" + checksum: 10/6f351fcf813f581b75df8e3d49f8264152492e3be6211e7157ff62a6c145517ea6c32eb821e9e8af306198ac0179623c349c6b21b63b8f85a5219bf53a3e08fa + languageName: node + linkType: hard + +"@clack/prompts@npm:^0.11.0": + version: 0.11.0 + resolution: "@clack/prompts@npm:0.11.0" + dependencies: + "@clack/core": "npm:0.5.0" + picocolors: "npm:^1.0.0" + sisteransi: "npm:^1.0.5" + checksum: 10/3159002db77f029e47a1bde7aebb7b233bffed867c41ded3daf9986accfa4bda0b248ed5f17d483cd34446a480a37764f2c94df0a7960f5744cf30f426c14ed6 + languageName: node + linkType: hard + "@commitlint/cli@npm:^20.1.0": version: 20.3.0 resolution: "@commitlint/cli@npm:20.3.0" @@ -3084,6 +3144,18 @@ __metadata: languageName: node linkType: hard +"@react-native-community/cli-config-android@npm:^20.0.0": + version: 20.1.0 + resolution: "@react-native-community/cli-config-android@npm:20.1.0" + dependencies: + "@react-native-community/cli-tools": "npm:20.1.0" + fast-glob: "npm:^3.3.2" + fast-xml-parser: "npm:^4.4.1" + picocolors: "npm:^1.1.1" + checksum: 10/d14b6c5d172c54a9a4ff7fda8feb91d2f644ca50f2bd1d5109b77d0cfd51c808aca4e6fd957500bb6aaf01bdb7b916ea6028a4658b46b2f8f72ee54bea3dfdd2 + languageName: node + linkType: hard + "@react-native-community/cli-config-apple@npm:20.0.0": version: 20.0.0 resolution: "@react-native-community/cli-config-apple@npm:20.0.0" @@ -3096,6 +3168,18 @@ __metadata: languageName: node linkType: hard +"@react-native-community/cli-config-apple@npm:^20.0.0": + version: 20.1.0 + resolution: "@react-native-community/cli-config-apple@npm:20.1.0" + dependencies: + "@react-native-community/cli-tools": "npm:20.1.0" + execa: "npm:^5.0.0" + fast-glob: "npm:^3.3.2" + picocolors: "npm:^1.1.1" + checksum: 10/5097011bb1831b9eff8d7e1f2964fe265bab3bb7e97ad681985368dc41a853811c474d235ea793edf5e31ad49810744f16bbfff3623e7629ef757eb80858a832 + languageName: node + linkType: hard + "@react-native-community/cli-config@npm:20.0.0": version: 20.0.0 resolution: "@react-native-community/cli-config@npm:20.0.0" @@ -3110,6 +3194,20 @@ __metadata: languageName: node linkType: hard +"@react-native-community/cli-config@npm:^20.0.0": + version: 20.1.0 + resolution: "@react-native-community/cli-config@npm:20.1.0" + dependencies: + "@react-native-community/cli-tools": "npm:20.1.0" + cosmiconfig: "npm:^9.0.0" + deepmerge: "npm:^4.3.0" + fast-glob: "npm:^3.3.2" + joi: "npm:^17.2.1" + picocolors: "npm:^1.1.1" + checksum: 10/8d53c31641527a245be566992c6a13dc6711d8cfee4f65293ba169e0466c45c71af08a36e00dd550edfa74c1207f702293a8f07acd9a09a36320440e5922bb51 + languageName: node + linkType: hard + "@react-native-community/cli-doctor@npm:20.0.0": version: 20.0.0 resolution: "@react-native-community/cli-doctor@npm:20.0.0" @@ -3204,6 +3302,24 @@ __metadata: languageName: node linkType: hard +"@react-native-community/cli-tools@npm:20.1.0": + version: 20.1.0 + resolution: "@react-native-community/cli-tools@npm:20.1.0" + dependencies: + "@vscode/sudo-prompt": "npm:^9.0.0" + appdirsjs: "npm:^1.2.4" + execa: "npm:^5.0.0" + find-up: "npm:^5.0.0" + launch-editor: "npm:^2.9.1" + mime: "npm:^2.4.1" + ora: "npm:^5.4.1" + picocolors: "npm:^1.1.1" + prompts: "npm:^2.4.2" + semver: "npm:^7.5.2" + checksum: 10/f773a183a078fe2077a14405370840fb16a96b77ce2f0b3789b3383840d425657979f64793f8b38e6e9051106a9fa0cca7d062919479e24b1bd3df1692435578 + languageName: node + linkType: hard + "@react-native-community/cli-types@npm:20.0.0": version: 20.0.0 resolution: "@react-native-community/cli-types@npm:20.0.0" @@ -3213,6 +3329,15 @@ __metadata: languageName: node linkType: hard +"@react-native-community/cli-types@npm:^20.0.0": + version: 20.1.0 + resolution: "@react-native-community/cli-types@npm:20.1.0" + dependencies: + joi: "npm:^17.2.1" + checksum: 10/1ae86701a98ac21d0e67f2fcce3859e44c2315c55076aaf82cf80ebf19e91fe95f177208803fddd562f6578549f6ee816356406b5ce106fa0a4e7d30f5aafcdc + languageName: node + linkType: hard + "@react-native-community/cli@npm:20.0.0": version: 20.0.0 resolution: "@react-native-community/cli@npm:20.0.0" @@ -3607,6 +3732,74 @@ __metadata: languageName: node linkType: hard +"@rock-js/platform-android@npm:^0.12.5": + version: 0.12.5 + resolution: "@rock-js/platform-android@npm:0.12.5" + dependencies: + "@react-native-community/cli-config-android": "npm:^20.0.0" + "@rock-js/tools": "npm:^0.12.5" + tslib: "npm:^2.3.0" + checksum: 10/de54f29b9f6f31f9af09918d6890b823d9949c338cfdcd3abecdd517eabeab6a19a058e3f874ebb2479d48013c325752eba3e0baa783769c2f8c9afcdfcc9fb5 + languageName: node + linkType: hard + +"@rock-js/platform-apple-helpers@npm:^0.12.5": + version: 0.12.5 + resolution: "@rock-js/platform-apple-helpers@npm:0.12.5" + dependencies: + "@react-native-community/cli-config": "npm:^20.0.0" + "@react-native-community/cli-config-apple": "npm:^20.0.0" + "@rock-js/tools": "npm:^0.12.5" + adm-zip: "npm:^0.5.16" + fast-xml-parser: "npm:^4.5.0" + tslib: "npm:^2.3.0" + checksum: 10/48e78bc2d64e0eb72806c548cc67a14e6cf3629b6ac4128cef602c636a108ad636a8ccad352b02ca0941a64263f70c20326c617f86698981d653c7ec2e595806 + languageName: node + linkType: hard + +"@rock-js/plugin-brownfield-android@npm:^0.12.5": + version: 0.12.5 + resolution: "@rock-js/plugin-brownfield-android@npm:0.12.5" + dependencies: + "@react-native-community/cli-config-android": "npm:^20.0.0" + "@rock-js/platform-android": "npm:^0.12.5" + "@rock-js/tools": "npm:^0.12.5" + tslib: "npm:^2.3.0" + checksum: 10/1c63ef28a7acb5f3a1439d96208d76e57b064e77ea5175f7d21faa0a433d38a4bc6b06fabfa5fed9b5101a872ab8bbf9a4487ed7e62ba38d17f5c1afd768086d + languageName: node + linkType: hard + +"@rock-js/plugin-brownfield-ios@npm:^0.12.5": + version: 0.12.5 + resolution: "@rock-js/plugin-brownfield-ios@npm:0.12.5" + dependencies: + "@react-native-community/cli-config-apple": "npm:^20.0.0" + "@react-native-community/cli-types": "npm:^20.0.0" + "@rock-js/platform-apple-helpers": "npm:^0.12.5" + "@rock-js/tools": "npm:^0.12.5" + tslib: "npm:^2.3.0" + checksum: 10/3bb343f3d780b7dc809249302249a01114ff1f9ffbf1bb291d5d2f11c7c4ac683510d6bd7632d39d353c4ae8e7da78e7b37c37f8f3a315598b94bde97a79fc70 + languageName: node + linkType: hard + +"@rock-js/tools@npm:^0.12.5": + version: 0.12.5 + resolution: "@rock-js/tools@npm:0.12.5" + dependencies: + "@clack/prompts": "npm:^0.11.0" + adm-zip: "npm:^0.5.16" + appdirsjs: "npm:^1.2.7" + fs-fingerprint: "npm:^0.11.0" + is-unicode-supported: "npm:^2.1.0" + nano-spawn: "npm:^0.2.0" + picocolors: "npm:^1.1.1" + string-argv: "npm:^0.3.2" + tar: "npm:^7.5.1" + tslib: "npm:^2.3.0" + checksum: 10/9c54075f75eb33c0f1740828c5a4f24e3634ef039126f18d8a065e65d0579c5d32707455fc1dbc3a0f3edc7f77bad934ef8059972b5c6c8b267580c94ed22572 + languageName: node + linkType: hard + "@rsbuild/core@npm:~1.5.4": version: 1.5.17 resolution: "@rsbuild/core@npm:1.5.17" @@ -4364,6 +4557,22 @@ __metadata: languageName: node linkType: hard +"@types/lodash.clonedeep@npm:^4.5.9": + version: 4.5.9 + resolution: "@types/lodash.clonedeep@npm:4.5.9" + dependencies: + "@types/lodash": "npm:*" + checksum: 10/ef85512b7dce7a4f981a818ae44d11982907e1f26b5b26bedf0957c35e8591eb8e1d24fa31ca851d4b40e0a1ee88563853d762412691fe5f357e8335cead2325 + languageName: node + linkType: hard + +"@types/lodash@npm:*": + version: 4.17.21 + resolution: "@types/lodash@npm:4.17.21" + checksum: 10/34920830a3bc82ba619cda05e606fef00c148a69b4f19f770645d2587ccdb8e42ef3ddfc174b7884c0c709fc0a1aeb48f7326da969bad12a1464a03efbbe414c + languageName: node + linkType: hard + "@types/mdast@npm:^4.0.0": version: 4.0.4 resolution: "@types/mdast@npm:4.0.4" @@ -4736,6 +4945,13 @@ __metadata: languageName: node linkType: hard +"adm-zip@npm:^0.5.16": + version: 0.5.16 + resolution: "adm-zip@npm:0.5.16" + checksum: 10/e167d1b9e60cde37334efda828fa514680af9facbd4183952f36526390e5c7da9a90ca1e6880dfd3aba7b3517f1506c6178e0dc29cd630b26b98c795f97fc599 + languageName: node + linkType: hard + "agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": version: 7.1.4 resolution: "agent-base@npm:7.1.4" @@ -4867,7 +5083,7 @@ __metadata: languageName: node linkType: hard -"appdirsjs@npm:^1.2.4": +"appdirsjs@npm:^1.2.4, appdirsjs@npm:^1.2.7": version: 1.2.7 resolution: "appdirsjs@npm:1.2.7" checksum: 10/8f6cb9cc18de2b38e2f5efddf764c5f0331aba4168ee28cb7370b98e1dc69316352b9a936acf4d628b4dcc510d77b1645ed4b68ab2231e302f835d35e11348d3 @@ -5617,7 +5833,7 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:^3.6.0": +"chokidar@npm:^3.5.2, chokidar@npm:^3.6.0": version: 3.6.0 resolution: "chokidar@npm:3.6.0" dependencies: @@ -5907,6 +6123,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^14.0.2": + version: 14.0.2 + resolution: "commander@npm:14.0.2" + checksum: 10/2d202db5e5f9bb770112a3c1579b893d17ac6f6d932183077308bdd96d0f87f0bbe6a68b5b9ed2cf3b2514be6bb7de637480703c0e2db9741ee1b383237deb26 + languageName: node + linkType: hard + "commander@npm:^2.20.0": version: 2.20.3 resolution: "commander@npm:2.20.3" @@ -6305,7 +6528,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.4.0, debug@npm:^4.4.1": +"debug@npm:4, debug@npm:^4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.4.0, debug@npm:^4.4.1": version: 4.4.3 resolution: "debug@npm:4.4.3" dependencies: @@ -7541,7 +7764,7 @@ __metadata: languageName: node linkType: hard -"fast-xml-parser@npm:^4.4.1": +"fast-xml-parser@npm:^4.4.1, fast-xml-parser@npm:^4.5.0": version: 4.5.3 resolution: "fast-xml-parser@npm:4.5.3" dependencies: @@ -7769,6 +7992,16 @@ __metadata: languageName: node linkType: hard +"fs-fingerprint@npm:^0.11.0": + version: 0.11.0 + resolution: "fs-fingerprint@npm:0.11.0" + dependencies: + p-limit: "npm:^7.1.0" + tinyglobby: "npm:^0.2.15" + checksum: 10/ebfe8e8a257a475ca8b3da6aee7f356845a4e37e5a2fdeafd6f5a75539652408fad5decd1c552e73b43d01eb59b9ab9deb01a41220ea4d8303bf8203d0b5dbb5 + languageName: node + linkType: hard + "fs-minipass@npm:^3.0.0": version: 3.0.3 resolution: "fs-minipass@npm:3.0.3" @@ -8169,6 +8402,13 @@ __metadata: languageName: node linkType: hard +"has-flag@npm:^3.0.0": + version: 3.0.0 + resolution: "has-flag@npm:3.0.0" + checksum: 10/4a15638b454bf086c8148979aae044dd6e39d63904cd452d970374fa6a87623423da485dfb814e7be882e05c096a7ccf1ebd48e7e7501d0208d8384ff4dea73b + languageName: node + linkType: hard + "has-flag@npm:^4.0.0": version: 4.0.0 resolution: "has-flag@npm:4.0.0" @@ -8591,6 +8831,13 @@ __metadata: languageName: node linkType: hard +"ignore-by-default@npm:^1.0.1": + version: 1.0.1 + resolution: "ignore-by-default@npm:1.0.1" + checksum: 10/441509147b3615e0365e407a3c18e189f78c07af08564176c680be1fabc94b6c789cad1342ad887175d4ecd5225de86f73d376cec8e06b42fd9b429505ffcf8a + languageName: node + linkType: hard + "ignore@npm:^5.0.5, ignore@npm:^5.2.0": version: 5.3.2 resolution: "ignore@npm:5.3.2" @@ -10314,6 +10561,13 @@ __metadata: languageName: node linkType: hard +"lodash.clonedeep@npm:^4.5.0": + version: 4.5.0 + resolution: "lodash.clonedeep@npm:4.5.0" + checksum: 10/957ed243f84ba6791d4992d5c222ffffca339a3b79dbe81d2eaf0c90504160b500641c5a0f56e27630030b18b8e971ea10b44f928a977d5ced3c8948841b555f + languageName: node + linkType: hard + "lodash.debounce@npm:^4.0.8": version: 4.0.8 resolution: "lodash.debounce@npm:4.0.8" @@ -11743,6 +11997,13 @@ __metadata: languageName: node linkType: hard +"nano-spawn@npm:^0.2.0": + version: 0.2.1 + resolution: "nano-spawn@npm:0.2.1" + checksum: 10/8cd450e5a6626e4296cdde2d02f5bd1de4b188868abdd433ed18f7ce27689af3afa1c9560078bc49149ee5998c31ef8e0a62acf8a3d308611544404b4d6b2e84 + languageName: node + linkType: hard + "nanoid@npm:^3.3.11": version: 3.3.11 resolution: "nanoid@npm:3.3.11" @@ -11872,6 +12133,26 @@ __metadata: languageName: node linkType: hard +"nodemon@npm:^3.1.11": + version: 3.1.11 + resolution: "nodemon@npm:3.1.11" + dependencies: + chokidar: "npm:^3.5.2" + debug: "npm:^4" + ignore-by-default: "npm:^1.0.1" + minimatch: "npm:^3.1.2" + pstree.remy: "npm:^1.1.8" + semver: "npm:^7.5.3" + simple-update-notifier: "npm:^2.0.0" + supports-color: "npm:^5.5.0" + touch: "npm:^3.1.0" + undefsafe: "npm:^2.0.5" + bin: + nodemon: bin/nodemon.js + checksum: 10/0f43d2c70abe0764e26e438dbe0c78bc429746a558dabf5dd94b13f6f3a79b4bd7d5793347acafddaee5eab594a806c2ad43efc999d342e28d185661718da8dc + languageName: node + linkType: hard + "nopt@npm:^9.0.0": version: 9.0.0 resolution: "nopt@npm:9.0.0" @@ -12241,6 +12522,15 @@ __metadata: languageName: node linkType: hard +"p-limit@npm:^7.1.0": + version: 7.2.0 + resolution: "p-limit@npm:7.2.0" + dependencies: + yocto-queue: "npm:^1.2.1" + checksum: 10/ea1cae6e8bbf84a65cc832e4d6a802ec34902bfe0da2fba0c6fadb72f0c51d48db67b7fb2df8433dde5f4a970cf482b9c2cb4edbfa8cfadd7f4341c6fdb38a5d + languageName: node + linkType: hard + "p-locate@npm:^3.0.0": version: 3.0.0 resolution: "p-locate@npm:3.0.0" @@ -12539,7 +12829,7 @@ __metadata: languageName: node linkType: hard -"picocolors@npm:^1.1.1": +"picocolors@npm:^1.0.0, picocolors@npm:^1.1.1": version: 1.1.1 resolution: "picocolors@npm:1.1.1" checksum: 10/e1cf46bf84886c79055fdfa9dcb3e4711ad259949e3565154b004b260cd356c5d54b31a1437ce9782624bf766272fe6b0154f5f0c744fb7af5d454d2b60db045 @@ -12757,6 +13047,13 @@ __metadata: languageName: node linkType: hard +"pstree.remy@npm:^1.1.8": + version: 1.1.8 + resolution: "pstree.remy@npm:1.1.8" + checksum: 10/ef13b1b5896b35f67dbd4fb7ba54bb2a5da1a5c317276cbad4bcad4159bf8f7b5e1748dc244bf36865f3d560d2fc952521581280a91468c9c2df166cc760c8c1 + languageName: node + linkType: hard + "pump@npm:^3.0.0": version: 3.0.3 resolution: "pump@npm:3.0.3" @@ -13977,6 +14274,15 @@ __metadata: languageName: node linkType: hard +"simple-update-notifier@npm:^2.0.0": + version: 2.0.0 + resolution: "simple-update-notifier@npm:2.0.0" + dependencies: + semver: "npm:^7.5.3" + checksum: 10/40bd4f96aa89aedbf717ae9f4ab8fca70e8f7511e8b766feb15471cca3f6fe4fe673743309b08b4ba8abfe0965c9cd927e1de46550a757b819b70fc7430cc85d + languageName: node + linkType: hard + "sisteransi@npm:^1.0.5": version: 1.0.5 resolution: "sisteransi@npm:1.0.5" @@ -14205,6 +14511,13 @@ __metadata: languageName: node linkType: hard +"string-argv@npm:^0.3.2": + version: 0.3.2 + resolution: "string-argv@npm:0.3.2" + checksum: 10/f9d3addf887026b4b5f997a271149e93bf71efc8692e7dc0816e8807f960b18bcb9787b45beedf0f97ff459575ee389af3f189d8b649834cac602f2e857e75af + languageName: node + linkType: hard + "string-length@npm:^4.0.1": version: 4.0.2 resolution: "string-length@npm:4.0.2" @@ -14429,6 +14742,15 @@ __metadata: languageName: node linkType: hard +"supports-color@npm:^5.5.0": + version: 5.5.0 + resolution: "supports-color@npm:5.5.0" + dependencies: + has-flag: "npm:^3.0.0" + checksum: 10/5f505c6fa3c6e05873b43af096ddeb22159831597649881aeb8572d6fe3b81e798cc10840d0c9735e0026b250368851b7f77b65e84f4e4daa820a4f69947f55b + languageName: node + linkType: hard + "supports-color@npm:^7.1.0": version: 7.2.0 resolution: "supports-color@npm:7.2.0" @@ -14470,7 +14792,7 @@ __metadata: languageName: node linkType: hard -"tar@npm:^7.5.2": +"tar@npm:^7.5.1, tar@npm:^7.5.2": version: 7.5.2 resolution: "tar@npm:7.5.2" dependencies: @@ -14597,6 +14919,15 @@ __metadata: languageName: node linkType: hard +"touch@npm:^3.1.0": + version: 3.1.1 + resolution: "touch@npm:3.1.1" + bin: + nodetouch: bin/nodetouch.js + checksum: 10/853e763a1f4903302c5654ed353f84ad85baf757dac62c2d37ab67e0477cfd271e8c64771fcfad42310aff7c9d284ddb435ee5ca13ff36d0f3693fedd8e971d1 + languageName: node + linkType: hard + "tr46@npm:~0.0.3": version: 0.0.3 resolution: "tr46@npm:0.0.3" @@ -14675,7 +15006,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.1, tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.8.0": +"tslib@npm:^2.0.1, tslib@npm:^2.1.0, tslib@npm:^2.3.0, tslib@npm:^2.4.0, tslib@npm:^2.8.0": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7 @@ -14975,6 +15306,13 @@ __metadata: languageName: node linkType: hard +"undefsafe@npm:^2.0.5": + version: 2.0.5 + resolution: "undefsafe@npm:2.0.5" + checksum: 10/f42ab3b5770fedd4ada175fc1b2eb775b78f609156f7c389106aafd231bfc210813ee49f54483d7191d7b76e483bc7f537b5d92d19ded27156baf57592eb02cc + languageName: node + linkType: hard + "undici-types@npm:~6.21.0": version: 6.21.0 resolution: "undici-types@npm:6.21.0" @@ -15706,7 +16044,7 @@ __metadata: languageName: node linkType: hard -"yocto-queue@npm:^1.0.0": +"yocto-queue@npm:^1.0.0, yocto-queue@npm:^1.2.1": version: 1.2.2 resolution: "yocto-queue@npm:1.2.2" checksum: 10/92dd9880c324dbc94ff4b677b7d350ba8d835619062b7102f577add7a59ab4d87f40edc5a03d77d369dfa9d11175b1b2ec4a06a6f8a5d8ce5d1306713f66ee41