react-native-vision-camera-spoof-detector 1.0.28 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.md +5 -4
  2. package/android/build.gradle +3 -5
  3. package/android/src/main/java/com/faceantispoof/FaceAntiSpoofModule.kt +55 -0
  4. package/android/src/main/java/com/faceantispoof/FaceAntiSpoofPackage.kt +2 -24
  5. package/android/src/main/java/com/faceantispoof/FaceAntiSpoofingAdvanced.kt +58 -1
  6. package/index.d.ts +14 -1
  7. package/index.js +39 -51
  8. package/package.json +4 -7
  9. package/react-native-vision-camera-spoof-detector.podspec +4 -4
  10. package/.gitattributes +0 -2
  11. package/.npmignore +0 -99
  12. package/CONTRIBUTING.md +0 -280
  13. package/android/.gradle/8.9/checksums/checksums.lock +0 -0
  14. package/android/.gradle/8.9/checksums/sha1-checksums.bin +0 -0
  15. package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
  16. package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
  17. package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
  18. package/android/.gradle/8.9/gc.properties +0 -0
  19. package/android/.gradle/9.2.0/checksums/checksums.lock +0 -0
  20. package/android/.gradle/9.2.0/checksums/md5-checksums.bin +0 -0
  21. package/android/.gradle/9.2.0/checksums/sha1-checksums.bin +0 -0
  22. package/android/.gradle/9.2.0/fileChanges/last-build.bin +0 -0
  23. package/android/.gradle/9.2.0/fileHashes/fileHashes.bin +0 -0
  24. package/android/.gradle/9.2.0/fileHashes/fileHashes.lock +0 -0
  25. package/android/.gradle/9.2.0/gc.properties +0 -0
  26. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  27. package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
  28. package/android/.gradle/vcs-1/gc.properties +0 -0
  29. package/android/build/reports/problems/problems-report.html +0 -659
  30. package/android/src/main/java/com/faceantispoof/FaceAntiSpoofFrameProcessor.kt +0 -198
package/README.md CHANGED
@@ -22,7 +22,8 @@ High-performance face anti-spoofing and liveness detection module for React Nati
22
22
  - React Native >= 0.60.0
23
23
  - React Native Vision Camera `^4.6.4` or `^5.0.0`
24
24
  - `react-native-reanimated` `^3.0.0`
25
- - `react-native-worklets-core` `^1.0.0`
25
+ - `react-native-worklets` `^0.12.0`
26
+ - `react-native-vision-camera-worklets` `^5.0.0`
26
27
  - iOS 11.0 or later
27
28
  - react-native-vision-camera-face-detector (optional, for enhanced features)
28
29
 
@@ -39,9 +40,9 @@ yarn add react-native-vision-camera-spoof-detector
39
40
  ### Step 2: Install peer dependencies
40
41
 
41
42
  ```bash
42
- npm install react-native-vision-camera react-native-reanimated react-native-worklets-core
43
+ npm install react-native-vision-camera react-native-reanimated react-native-worklets react-native-vision-camera-worklets
43
44
  # or
44
- yarn add react-native-vision-camera react-native-reanimated react-native-worklets-core
45
+ yarn add react-native-vision-camera react-native-reanimated react-native-worklets react-native-vision-camera-worklets
45
46
  ```
46
47
 
47
48
  ### Step 3: Configure iOS
@@ -167,7 +168,7 @@ const styles = StyleSheet.create({
167
168
 
168
169
  ```javascript
169
170
  import { useCallback, useMemo, useEffect, useRef } from 'react';
170
- import { Worklets } from 'react-native-worklets-core';
171
+ import { scheduleOnRN } from 'react-native-worklets';
171
172
  import { useFrameProcessor } from 'react-native-vision-camera';
172
173
  import { useFaceDetector } from 'react-native-vision-camera-face-detector';
173
174
  import {
@@ -130,10 +130,8 @@ dependencies {
130
130
  // AndroidX - EXIF Support for image orientation
131
131
  implementation "androidx.exifinterface:exifinterface:1.3.6"
132
132
 
133
- // Vision Camera + Worklets
133
+ // VisionCamera 5 uses Nitro frame outputs backed by react-native-worklets.
134
134
  implementation project(':react-native-vision-camera')
135
- implementation project(':react-native-worklets-core')
136
-
137
- // Optional: Face Detector if needed
138
- implementation project(':react-native-vision-camera-face-detector')
135
+ implementation project(':react-native-vision-camera-worklets')
136
+ implementation project(':react-native-worklets')
139
137
  }
@@ -4,6 +4,8 @@ import com.facebook.react.bridge.ReactApplicationContext
4
4
  import com.facebook.react.bridge.ReactContextBaseJavaModule
5
5
  import com.facebook.react.bridge.ReactMethod
6
6
  import com.facebook.react.bridge.Promise
7
+ import com.facebook.react.bridge.Arguments
8
+ import com.facebook.react.bridge.ReadableArray
7
9
  import com.facebook.react.module.annotations.ReactModule
8
10
 
9
11
  @ReactModule(name = FaceAntiSpoofModule.NAME)
@@ -126,6 +128,59 @@ class FaceAntiSpoofModule(reactContext: ReactApplicationContext) : ReactContextB
126
128
  }
127
129
  }
128
130
 
131
+ @ReactMethod
132
+ fun processFrame(
133
+ frameData: ReadableArray,
134
+ width: Int,
135
+ height: Int,
136
+ rowStrides: ReadableArray,
137
+ pixelStrides: ReadableArray,
138
+ promise: Promise
139
+ ) {
140
+ try {
141
+ if (frameData.size() < 3 || rowStrides.size() < 3 || pixelStrides.size() < 3) {
142
+ promise.reject("INVALID_FRAME", "Expected Y, U and V camera planes")
143
+ return
144
+ }
145
+
146
+ if (!isModuleInitialized || faceAntiSpoofing == null) {
147
+ promise.reject("NOT_INITIALIZED", "Face anti-spoofing is not initialized")
148
+ return
149
+ }
150
+
151
+ val planes = Array(3) { planeIndex ->
152
+ val plane = frameData.getArray(planeIndex)
153
+ ?: throw IllegalArgumentException("Camera plane $planeIndex is null")
154
+ ByteArray(plane.size()) { index -> plane.getInt(index).toByte() }
155
+ }
156
+ val yuvRowStrides = IntArray(3) { rowStrides.getDouble(it).toInt() }
157
+ val yuvPixelStrides = IntArray(3) { pixelStrides.getDouble(it).toInt().coerceAtLeast(1) }
158
+ val result = faceAntiSpoofing!!.advancedAntiSpoofing(
159
+ planes,
160
+ width,
161
+ height,
162
+ yuvRowStrides,
163
+ yuvPixelStrides
164
+ )
165
+ val isLive = result.neuralNetworkScore < FaceAntiSpoofingAdvanced.BASE_THRESHOLD &&
166
+ result.laplacianScore > FaceAntiSpoofingAdvanced.LAPLACIAN_THRESHOLD
167
+ promise.resolve(
168
+ Arguments.makeNativeMap(
169
+ mapOf(
170
+ "isLive" to isLive,
171
+ "label" to if (isLive) "Live Face" else "Spoof Face",
172
+ "neuralNetworkScore" to result.neuralNetworkScore.toDouble(),
173
+ "laplacianScore" to result.laplacianScore,
174
+ "combinedScore" to result.combinedScore.toDouble(),
175
+ "confidence" to result.confidence.toDouble()
176
+ )
177
+ )
178
+ )
179
+ } catch (e: Exception) {
180
+ promise.reject("PROCESS_ERROR", "Failed to process camera frame", e)
181
+ }
182
+ }
183
+
129
184
  @ReactMethod
130
185
  fun isAvailable(promise: Promise) {
131
186
  try {
@@ -4,32 +4,10 @@ import com.facebook.react.ReactPackage
4
4
  import com.facebook.react.bridge.NativeModule
5
5
  import com.facebook.react.bridge.ReactApplicationContext
6
6
  import com.facebook.react.uimanager.ViewManager
7
- import com.mrousavy.camera.frameprocessors.FrameProcessorPluginRegistry
8
7
 
9
8
  class FaceAntiSpoofPackage : ReactPackage {
10
-
11
- companion object {
12
- init {
13
- try {
14
- FrameProcessorPluginRegistry.addFrameProcessorPlugin("faceAntiSpoof") { proxy, options ->
15
- val plugin = FaceAntiSpoofFrameProcessor()
16
- try {
17
- plugin.initialize(proxy.context.assets)
18
- } catch (e: Exception) {
19
- android.util.Log.e("FaceAntiSpoof", "Failed to initialize plugin with assets", e)
20
- }
21
- plugin
22
- }
23
- } catch (e: Exception) {
24
- android.util.Log.e("FaceAntiSpoof", "Plugin already registered or failed: ${e.message}", e)
25
- }
26
- }
27
- }
28
-
29
- override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
30
- // Only return the module — DO NOT register plugin here.
31
- return listOf(FaceAntiSpoofModule(reactContext))
32
- }
9
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
10
+ listOf(FaceAntiSpoofModule(reactContext))
33
11
 
34
12
  override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
35
13
  return emptyList()
@@ -24,7 +24,7 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
24
24
  const val BASE_THRESHOLD = 0.2f
25
25
  const val ROUTE_INDEX = 6
26
26
  const val LAPLACE_THRESHOLD = 50
27
- const val LAPLACIAN_THRESHOLD = 4000 // Increased from 1000 - real faces have ~3500+ laplacian score
27
+ const val LAPLACIAN_THRESHOLD = 1000
28
28
  const val MODEL_FILE = "FaceAntiSpoofing.tflite"
29
29
  private const val TAG = "FaceAntiSpoofing"
30
30
 
@@ -165,6 +165,25 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
165
165
  }
166
166
  }
167
167
 
168
+ fun advancedAntiSpoofing(
169
+ planes: Array<ByteArray>,
170
+ width: Int,
171
+ height: Int,
172
+ rowStrides: IntArray,
173
+ pixelStrides: IntArray
174
+ ): AntiSpoofingResult {
175
+ if (planes.size < 3) {
176
+ throw IllegalArgumentException("Expected Y, U and V planes")
177
+ }
178
+
179
+ val bitmap = yuv420ToBitmap(planes, width, height, rowStrides, pixelStrides)
180
+ return try {
181
+ runBlocking { advancedAntiSpoofing(bitmap).getOrThrow() }
182
+ } finally {
183
+ bitmap.recycle()
184
+ }
185
+ }
186
+
168
187
  // FROM WORKING VERSION: neuralNetworkAntiSpoofing with Bitmap
169
188
  private fun neuralNetworkAntiSpoofing(bitmap: Bitmap): Result<Float> {
170
189
  return try {
@@ -365,6 +384,44 @@ class FaceAntiSpoofingAdvanced(private val assetManager: AssetManager) {
365
384
  return bitmap
366
385
  }
367
386
 
387
+ private fun yuv420ToBitmap(
388
+ planes: Array<ByteArray>,
389
+ width: Int,
390
+ height: Int,
391
+ rowStrides: IntArray,
392
+ pixelStrides: IntArray
393
+ ): Bitmap {
394
+ val pixels = IntArray(width * height)
395
+ val yPlane = planes[0]
396
+ val uPlane = planes[1]
397
+ val vPlane = planes[2]
398
+
399
+ fun sample(plane: ByteArray, row: Int, column: Int, rowStride: Int, pixelStride: Int): Int {
400
+ val index = row * rowStride + column * pixelStride
401
+ return if (index in plane.indices) plane[index].toInt() and 0xFF else 128
402
+ }
403
+
404
+ for (y in 0 until height) {
405
+ for (x in 0 until width) {
406
+ val yValue = sample(yPlane, y, x, rowStrides[0], pixelStrides[0])
407
+ val chromaRow = y / 2
408
+ val chromaColumn = x / 2
409
+ val uValue = sample(uPlane, chromaRow, chromaColumn, rowStrides[1], pixelStrides[1]) - 128
410
+ val vValue = sample(vPlane, chromaRow, chromaColumn, rowStrides[2], pixelStrides[2]) - 128
411
+
412
+ val red = (yValue + 1.402f * vValue).coerceIn(0f, 255f).toInt()
413
+ val green = (yValue - 0.344f * uValue - 0.714f * vValue).coerceIn(0f, 255f).toInt()
414
+ val blue = (yValue + 1.772f * uValue).coerceIn(0f, 255f).toInt()
415
+ pixels[y * width + x] = (0xFF shl 24) or
416
+ (red shl 16) or
417
+ (green shl 8) or
418
+ blue
419
+ }
420
+ }
421
+
422
+ return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888)
423
+ }
424
+
368
425
  @Throws(IOException::class)
369
426
  private fun loadModelFile(assetManager: AssetManager, modelName: String): MappedByteBuffer {
370
427
  return try {
package/index.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * TypeScript type definitions
5
5
  */
6
6
 
7
- import type { Frame, FrameProcessor } from 'react-native-vision-camera';
7
+ import type { CameraFrameOutput, Frame, FrameOutputOptions, FrameProcessor } from 'react-native-vision-camera';
8
8
  import type { SharedValue } from 'react-native-reanimated';
9
9
 
10
10
  // ============================================================================
@@ -281,6 +281,19 @@ export interface UseFaceDetectionFrameProcessorReturn {
281
281
  antiSpoofState: AntiSpoofState;
282
282
  }
283
283
 
284
+ /**
285
+ * VisionCamera 5 frame-output options for spoof detection.
286
+ * The callback runs on the React Native runtime after native inference.
287
+ */
288
+ export interface UseFaceAntiSpoofFrameOutputOptions extends Partial<FrameOutputOptions> {
289
+ onResult?: (result: FaceAntiSpoofingResult) => void;
290
+ }
291
+
292
+ /** Create a VisionCamera 5 frame output backed by the native detector. */
293
+ export function useFaceAntiSpoofFrameOutput(
294
+ options?: UseFaceAntiSpoofFrameOutputOptions,
295
+ ): CameraFrameOutput;
296
+
284
297
  // ============================================================================
285
298
  // Module Functions
286
299
  // ============================================================================
package/index.js CHANGED
@@ -1,60 +1,50 @@
1
1
  import { NativeModules } from 'react-native';
2
- import { VisionCameraProxy } from 'react-native-vision-camera';
2
+ import { useFrameOutput } from 'react-native-vision-camera';
3
+ import { scheduleOnRN } from 'react-native-worklets';
3
4
 
4
5
  const { FaceAntiSpoof } = NativeModules;
5
6
 
6
- // Initialize the plugin before any worklet captures this module.
7
- let _faceAntiSpoofPlugin = null;
8
-
9
- try {
10
- _faceAntiSpoofPlugin = VisionCameraProxy.initFrameProcessorPlugin('faceAntiSpoof', {});
11
- } catch (e) {
12
- console.warn('[FaceAntiSpoof] Failed to init frame processor plugin:', e);
13
- }
7
+ const processFrameOnRN = (frameData, width, height, onResult) => {
8
+ FaceAntiSpoof.processFrame(frameData.planes, width, height, frameData.rowStrides, frameData.pixelStrides)
9
+ .then(onResult)
10
+ .catch((error) => console.error('[FaceAntiSpoof] Frame processing failed:', error));
11
+ };
14
12
 
15
- function ensurePluginInitialized() {
16
- if (!_faceAntiSpoofPlugin) {
13
+ export const useFaceAntiSpoofFrameOutput = ({ onResult, ...options } = {}) => {
14
+ const onFrame = (frame) => {
15
+ 'worklet';
17
16
  try {
18
- _faceAntiSpoofPlugin = VisionCameraProxy.initFrameProcessorPlugin('faceAntiSpoof', {});
19
- } catch (e) {
20
- console.warn('[FaceAntiSpoof] Failed to init frame processor plugin:', e);
21
- _faceAntiSpoofPlugin = null;
17
+ const planes = frame.getPlanes();
18
+ if (planes.length < 3) return;
19
+
20
+ const planeData = planes.map((plane) => Array.from(new Uint8Array(plane.getPixelBuffer())));
21
+ const rowStrides = planes.map((plane) => plane.bytesPerRow);
22
+ const pixelStrides = planes.map((plane) => plane.bytesPerRow / plane.width);
23
+ scheduleOnRN(
24
+ processFrameOnRN,
25
+ { planes: planeData, rowStrides, pixelStrides },
26
+ frame.width,
27
+ frame.height,
28
+ onResult,
29
+ );
30
+ } finally {
31
+ frame.dispose();
22
32
  }
23
- }
24
- return !!_faceAntiSpoofPlugin;
25
- }
33
+ };
26
34
 
27
- // Worklet called by VisionCamera
28
- export const faceAntiSpoofFrameProcessor = function (frame) {
35
+ return useFrameOutput({
36
+ ...options,
37
+ pixelFormat: 'yuv',
38
+ targetResolution: { width: 256, height: 256 },
39
+ onFrame,
40
+ });
41
+ };
42
+
43
+ // Kept as a named export for source compatibility. VisionCamera 5 applications
44
+ // must use useFaceAntiSpoofFrameOutput because the old plugin API was removed.
45
+ export const faceAntiSpoofFrameProcessor = function () {
29
46
  'worklet';
30
- try {
31
- if (!_faceAntiSpoofPlugin) {
32
- console.warn('[FaceAntiSpoof] Plugin not initialized');
33
- return null;
34
- }
35
-
36
- if (typeof _faceAntiSpoofPlugin.call !== 'function') {
37
- console.warn('[FaceAntiSpoof] Plugin call method not available');
38
- return null;
39
- }
40
-
41
- if (!frame) {
42
- console.warn('[FaceAntiSpoof] Frame is null or undefined');
43
- return null;
44
- }
45
-
46
- const result = _faceAntiSpoofPlugin.call(frame);
47
-
48
- // Validate result structure
49
- if (result && typeof result === 'object') {
50
- return result;
51
- }
52
-
53
- return null;
54
- } catch (err) {
55
- console.error('[FaceAntiSpoof] Error in frame processor:', err);
56
- return null;
57
- }
47
+ return null;
58
48
  };
59
49
 
60
50
  export default FaceAntiSpoof;
@@ -86,9 +76,7 @@ export const initializeFaceAntiSpoof = async () => {
86
76
  }
87
77
 
88
78
  const isSuccess = result && status.pluginAvailable;
89
- // If native initialization succeeded, register the frame processor plugin for worklets
90
- const pluginInitialized = isSuccess ? ensurePluginInitialized() : false;
91
- const initialized = isSuccess && pluginInitialized;
79
+ const initialized = isSuccess;
92
80
  console.log('[FaceAntiSpoof] Initialization result:', initialized);
93
81
  return initialized;
94
82
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-vision-camera-spoof-detector",
3
- "version": "1.0.28",
3
+ "version": "1.1.0",
4
4
  "description": "High-performance face anti-spoofing and liveness detection module for React Native Vision Camera. Uses TensorFlow Lite with GPU acceleration and optimized YUV processing.",
5
5
  "homepage": "https://github.com/dpraful/react-native-vision-camera-spoof-detector",
6
6
  "repository": {
@@ -10,7 +10,7 @@
10
10
  "bugs": {
11
11
  "url": "https://github.com/dpraful/react-native-vision-camera-spoof-detector/issues"
12
12
  },
13
- "license": "JSCON TECHNOLOGIES PVT LTD",
13
+ "license": "MIT",
14
14
  "author": {
15
15
  "name": "PRAFULDAS M M",
16
16
  "company": "JESCON TECHNOLOGIES PVT LTD",
@@ -29,11 +29,7 @@
29
29
  "files": [
30
30
  "android/",
31
31
  "ios/",
32
- ".gitattributes",
33
- ".gitignore",
34
- ".npmignore",
35
32
  "CHANGELOG.md",
36
- "CONTRIBUTING.md",
37
33
  "index.js",
38
34
  "index.d.ts",
39
35
  "README.md",
@@ -62,7 +58,8 @@
62
58
  "react-native": ">=0.60.0",
63
59
  "react-native-vision-camera": "^4.6.4 || ^5.0.0",
64
60
  "react-native-reanimated": "^3.0.0",
65
- "react-native-worklets-core": "^1.0.0"
61
+ "react-native-vision-camera-worklets": "^5.0.0",
62
+ "react-native-worklets": "^0.12.0"
66
63
  },
67
64
  "peerDependenciesMeta": {
68
65
  "react-native-vision-camera-face-detector": {
@@ -1,10 +1,10 @@
1
1
  Pod::Spec.new do |s|
2
2
  s.name = 'react-native-vision-camera-spoof-detector'
3
- s.version = '0.1.0'
3
+ s.version = '1.1.0'
4
4
  s.summary = 'Face anti-spoof detector for VisionCamera (iOS)'
5
- s.homepage = 'https://example.com'
6
- s.license = { :type => 'MIT' }
7
- s.authors = { 'You' => 'you@example.com' }
5
+ s.homepage = 'https://github.com/dpraful/react-native-vision-camera-spoof-detector'
6
+ s.license = { :type => 'MIT', :file => 'LICENSE' }
7
+ s.authors = { 'JESCON TECHNOLOGIES PVT LTD' => 'jescontechnologies@gmail.com' }
8
8
  s.platform = :ios, '11.0'
9
9
  s.source = { :path => '.' }
10
10
 
package/.gitattributes DELETED
@@ -1,2 +0,0 @@
1
- # Auto detect text files and perform LF normalization
2
- * text=auto
package/.npmignore DELETED
@@ -1,99 +0,0 @@
1
- # npm ignore patterns - files that should not be published to npm
2
-
3
- # Development files
4
- .git
5
- .gitignore
6
- .gitattributes
7
-
8
- # Development dependencies
9
- node_modules
10
- npm-debug.log*
11
- yarn-debug.log*
12
- yarn-error.log*
13
-
14
- # IDE / Editor files
15
- .vscode
16
- .idea
17
- .DS_Store
18
- *.swp
19
- *.swo
20
- *~
21
- .env
22
- .env.local
23
-
24
- # Build artifacts and temporary files
25
- build/
26
- dist/
27
- .rollup.cache/
28
- .eslintcache
29
- *.tsbuildinfo
30
-
31
- # Android build artifacts and caches
32
- android/build/
33
- android/.gradle/
34
- android/.kotlin/
35
- android/*.log
36
- *.apk
37
- *.aar
38
- *.so
39
-
40
- # Testing
41
- __tests__
42
- __mocks__
43
- *.test.ts
44
- *.test.tsx
45
- *.spec.ts
46
- *.spec.tsx
47
- coverage
48
- .jest
49
-
50
- # Documentation (optional - remove if you want to publish docs)
51
- docs/*.md
52
- .docs/
53
-
54
- # Example files (optional - remove if you want to include examples)
55
- example/
56
- examples/
57
- demo/
58
-
59
- # CI/CD
60
- .github/workflows
61
- .gitlab-ci.yml
62
- .travis.yml
63
- azure-pipelines.yml
64
-
65
- # Root level files
66
- .prettierrc
67
- .prettierignore
68
- .eslintrc
69
- .eslintignore
70
- .babelrc
71
- tsconfig.json
72
- jest.config.js
73
- babel.config.js
74
- webpack.config.js
75
- Makefile
76
-
77
- # Contribution files
78
- CONTRIBUTING.md
79
- CODE_OF_CONDUCT.md
80
- PULL_REQUEST_TEMPLATE.md
81
-
82
- # Misc
83
- *.map
84
- .npmrc
85
- .yarnrc
86
- .DS_Store
87
- Thumbs.db
88
- *.lock
89
- package-lock.json
90
- yarn.lock
91
-
92
- # Keep essential files
93
- !android/
94
- !index.js
95
- !index.d.ts
96
- !README.md
97
- !CHANGELOG.md
98
- !LICENSE
99
- !package.json