react-native-tapline 0.0.0-stage → 1.0.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.
- package/LICENSE +21 -0
- package/README.md +86 -2
- package/TaplineNative.podspec +14 -0
- package/dist/actions.d.ts +160 -0
- package/dist/actions.js +1883 -0
- package/dist/actions.js.map +1 -0
- package/dist/agent.d.ts +79 -0
- package/dist/agent.js +255 -0
- package/dist/agent.js.map +1 -0
- package/dist/alerts.d.ts +35 -0
- package/dist/alerts.js +437 -0
- package/dist/alerts.js.map +1 -0
- package/dist/awake.d.ts +2 -0
- package/dist/awake.js +53 -0
- package/dist/awake.js.map +1 -0
- package/dist/checks.d.ts +19 -0
- package/dist/checks.js +318 -0
- package/dist/checks.js.map +1 -0
- package/dist/client.d.ts +111 -0
- package/dist/client.js +446 -0
- package/dist/client.js.map +1 -0
- package/dist/commands.d.ts +24 -0
- package/dist/commands.js +111 -0
- package/dist/commands.js.map +1 -0
- package/dist/events.d.ts +5 -0
- package/dist/events.js +22 -0
- package/dist/events.js.map +1 -0
- package/dist/feed.d.ts +117 -0
- package/dist/feed.js +440 -0
- package/dist/feed.js.map +1 -0
- package/dist/fiber.d.ts +43 -0
- package/dist/fiber.js +100 -0
- package/dist/fiber.js.map +1 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +133 -0
- package/dist/index.js.map +1 -0
- package/dist/inspect.d.ts +245 -0
- package/dist/inspect.js +1343 -0
- package/dist/inspect.js.map +1 -0
- package/dist/keyboard.d.ts +10 -0
- package/dist/keyboard.js +63 -0
- package/dist/keyboard.js.map +1 -0
- package/dist/link.d.ts +21 -0
- package/dist/link.js +63 -0
- package/dist/link.js.map +1 -0
- package/dist/live.d.ts +30 -0
- package/dist/live.js +279 -0
- package/dist/live.js.map +1 -0
- package/dist/logs.d.ts +16 -0
- package/dist/logs.js +171 -0
- package/dist/logs.js.map +1 -0
- package/dist/methods.d.ts +3 -0
- package/dist/methods.js +47 -0
- package/dist/methods.js.map +1 -0
- package/dist/native.d.ts +34 -0
- package/dist/native.js +63 -0
- package/dist/native.js.map +1 -0
- package/dist/network.d.ts +27 -0
- package/dist/network.js +238 -0
- package/dist/network.js.map +1 -0
- package/dist/register.d.ts +24 -0
- package/dist/register.js +180 -0
- package/dist/register.js.map +1 -0
- package/dist/settle.d.ts +27 -0
- package/dist/settle.js +70 -0
- package/dist/settle.js.map +1 -0
- package/dist/storage.d.ts +10 -0
- package/dist/storage.js +45 -0
- package/dist/storage.js.map +1 -0
- package/dist/touches.d.ts +15 -0
- package/dist/touches.js +89 -0
- package/dist/touches.js.map +1 -0
- package/dist/types.d.ts +168 -0
- package/dist/types.js +32 -0
- package/dist/types.js.map +1 -0
- package/dist/wait.d.ts +8 -0
- package/dist/wait.js +99 -0
- package/dist/wait.js.map +1 -0
- package/expo-module.config.json +7 -0
- package/ios/TaplineNativeModule.swift +79 -0
- package/metro.d.ts +10 -0
- package/metro.js +40 -0
- package/package.json +100 -4
- package/register.d.ts +1 -0
- package/register.js +3 -0
package/dist/wait.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type IdleProvider = () => boolean;
|
|
2
|
+
/** Where "the network is quiet" comes from (the network tracker; tests). Null restores "always quiet". */
|
|
3
|
+
export declare function setNetworkIdleProvider(provider: IdleProvider | null): void;
|
|
4
|
+
export declare function waitFor(raw: unknown): Promise<{
|
|
5
|
+
met: true;
|
|
6
|
+
waitedMs: number;
|
|
7
|
+
}>;
|
|
8
|
+
export {};
|
package/dist/wait.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setNetworkIdleProvider = setNetworkIdleProvider;
|
|
4
|
+
exports.waitFor = waitFor;
|
|
5
|
+
const inspect_1 = require("./inspect");
|
|
6
|
+
const types_1 = require("./types");
|
|
7
|
+
/**
|
|
8
|
+
* `waitFor`: polls the screen until an element appears (or is gone) and/or the network is
|
|
9
|
+
* quiet, so an agent waits for loading to finish instead of guessing a delay.
|
|
10
|
+
*/
|
|
11
|
+
const POLL_MS = 250;
|
|
12
|
+
const DEFAULT_TIMEOUT_S = 10;
|
|
13
|
+
const MAX_TIMEOUT_S = 30;
|
|
14
|
+
let networkIdle = () => true;
|
|
15
|
+
/** Where "the network is quiet" comes from (the network tracker; tests). Null restores "always quiet". */
|
|
16
|
+
function setNetworkIdleProvider(provider) {
|
|
17
|
+
networkIdle = provider ?? (() => true);
|
|
18
|
+
}
|
|
19
|
+
function parse(raw) {
|
|
20
|
+
if (!(0, types_1.isRecord)(raw))
|
|
21
|
+
throw new types_1.TaplineError('bad_params', 'waitFor needs an object');
|
|
22
|
+
const pick = (key) => {
|
|
23
|
+
const value = raw[key];
|
|
24
|
+
return typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined;
|
|
25
|
+
};
|
|
26
|
+
const text = pick('text');
|
|
27
|
+
const testID = pick('testID');
|
|
28
|
+
const label = pick('label');
|
|
29
|
+
const wantsNetwork = raw.networkIdle === true;
|
|
30
|
+
if (text === undefined && testID === undefined && label === undefined && !wantsNetwork) {
|
|
31
|
+
throw new types_1.TaplineError('bad_params', 'waitFor needs text, testID, label or networkIdle');
|
|
32
|
+
}
|
|
33
|
+
const timeout = raw.timeout === undefined ? DEFAULT_TIMEOUT_S : raw.timeout;
|
|
34
|
+
if (typeof timeout !== 'number' || !(timeout > 0) || timeout > MAX_TIMEOUT_S) {
|
|
35
|
+
throw new types_1.TaplineError('bad_params', `timeout is in seconds, above 0 and at most ${MAX_TIMEOUT_S}`);
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
...(text !== undefined ? { text } : {}),
|
|
39
|
+
...(testID !== undefined ? { testID } : {}),
|
|
40
|
+
...(label !== undefined ? { label } : {}),
|
|
41
|
+
gone: raw.gone === true,
|
|
42
|
+
networkIdle: wantsNetwork,
|
|
43
|
+
timeoutMs: timeout * 1000,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function matches(entry, condition) {
|
|
47
|
+
const { info } = entry;
|
|
48
|
+
if (condition.testID !== undefined && info.testID !== condition.testID)
|
|
49
|
+
return false;
|
|
50
|
+
// As a `label` target matches: the element's own label, else its text.
|
|
51
|
+
if (condition.label !== undefined && (info.label ?? info.text).toLowerCase() !== condition.label.toLowerCase())
|
|
52
|
+
return false;
|
|
53
|
+
if (condition.text !== undefined && !info.text.toLowerCase().includes(condition.text.toLowerCase()))
|
|
54
|
+
return false;
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
function satisfied(condition) {
|
|
58
|
+
if (condition.text !== undefined || condition.testID !== undefined || condition.label !== undefined) {
|
|
59
|
+
const present = (0, inspect_1.takeSnapshot)().entries.some((entry) => entry.info.interesting && matches(entry, condition));
|
|
60
|
+
if (present === condition.gone)
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
if (condition.networkIdle) {
|
|
64
|
+
try {
|
|
65
|
+
if (!networkIdle())
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// an unknown network state doesn't block the wait
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
function describe(condition) {
|
|
75
|
+
const what = condition.text !== undefined
|
|
76
|
+
? `text "${condition.text}"`
|
|
77
|
+
: condition.testID !== undefined
|
|
78
|
+
? `id "${condition.testID}"`
|
|
79
|
+
: condition.label !== undefined
|
|
80
|
+
? `label "${condition.label}"`
|
|
81
|
+
: null;
|
|
82
|
+
// The words Tapwright uses too (AGENT-TOOLS §5): `id` as the agent writes it, "to disappear".
|
|
83
|
+
const element = what === null ? null : `${what} ${condition.gone ? 'to disappear' : 'to appear'}`;
|
|
84
|
+
return [element, condition.networkIdle ? 'the network to be quiet' : null].filter((part) => part !== null).join(' and ');
|
|
85
|
+
}
|
|
86
|
+
async function waitFor(raw) {
|
|
87
|
+
const condition = parse(raw);
|
|
88
|
+
const started = Date.now();
|
|
89
|
+
for (;;) {
|
|
90
|
+
if (satisfied(condition))
|
|
91
|
+
return { met: true, waitedMs: Date.now() - started };
|
|
92
|
+
const waited = Date.now() - started;
|
|
93
|
+
if (waited >= condition.timeoutMs) {
|
|
94
|
+
throw new types_1.TaplineError('timeout', `Waited ${Math.round(waited / 100) / 10} s for ${describe(condition)}; it didn't happen.`);
|
|
95
|
+
}
|
|
96
|
+
await new Promise((resolve) => setTimeout(() => resolve(), Math.min(POLL_MS, condition.timeoutMs - waited)));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=wait.js.map
|
package/dist/wait.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wait.js","sourceRoot":"","sources":["../src/wait.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
/// Tapline's native side (optional: the SDK works without it, through react-native-view-shot).
|
|
5
|
+
///
|
|
6
|
+
/// - `capture`: a JPEG of the app's window. With `fresh: false` it draws what is on screen now
|
|
7
|
+
/// (`afterScreenUpdates: false`), so iOS isn't made to render a new frame for it: the live
|
|
8
|
+
/// viewer can capture often without slowing the app.
|
|
9
|
+
/// - `cover`: what UIKit presents over the app that React Native doesn't draw: an alert or action
|
|
10
|
+
/// sheet (`UIAlertController`, also the one `Alert.alert` shows) and the share sheet
|
|
11
|
+
/// (`UIActivityViewController`), with what an agent needs to name it.
|
|
12
|
+
private enum TaplineWindows {
|
|
13
|
+
static func keyWindow() -> UIWindow? {
|
|
14
|
+
let scenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }
|
|
15
|
+
let scene = scenes.first { $0.activationState == .foregroundActive } ?? scenes.first
|
|
16
|
+
guard let windows = scene?.windows else { return nil }
|
|
17
|
+
return windows.first { $0.isKeyWindow } ?? windows.first
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// The view controllers presented over the root, front last.
|
|
21
|
+
static func presented(from root: UIViewController?) -> [UIViewController] {
|
|
22
|
+
var chain: [UIViewController] = []
|
|
23
|
+
var current = root?.presentedViewController
|
|
24
|
+
while let controller = current {
|
|
25
|
+
chain.append(controller)
|
|
26
|
+
current = controller.presentedViewController
|
|
27
|
+
}
|
|
28
|
+
return chain
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public final class TaplineNativeModule: Module {
|
|
33
|
+
public func definition() -> ModuleDefinition {
|
|
34
|
+
Name("TaplineNative")
|
|
35
|
+
|
|
36
|
+
/// Base64 JPEG of the key window at `pixelWidth` pixels wide (the height keeps the window's
|
|
37
|
+
/// shape), or an empty string when there is no window to draw.
|
|
38
|
+
/// Only the drawing holds the main thread; the JPEG is made on a background queue, so the app's
|
|
39
|
+
/// own frames wait for the draw alone.
|
|
40
|
+
AsyncFunction("capture") { (pixelWidth: Double, quality: Double, fresh: Bool, promise: Promise) in
|
|
41
|
+
guard let window = TaplineWindows.keyWindow(), window.bounds.width > 0, pixelWidth > 0 else {
|
|
42
|
+
promise.resolve("")
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
let bounds = window.bounds
|
|
46
|
+
let size = CGSize(width: pixelWidth.rounded(), height: (pixelWidth * bounds.height / bounds.width).rounded())
|
|
47
|
+
let format = UIGraphicsImageRendererFormat()
|
|
48
|
+
format.scale = 1
|
|
49
|
+
format.opaque = true
|
|
50
|
+
let image = UIGraphicsImageRenderer(size: size, format: format).image { _ in
|
|
51
|
+
window.drawHierarchy(in: CGRect(origin: .zero, size: size), afterScreenUpdates: fresh)
|
|
52
|
+
}
|
|
53
|
+
let clamped = CGFloat(min(max(quality, 0.1), 1))
|
|
54
|
+
DispatchQueue.global(qos: .userInitiated).async {
|
|
55
|
+
promise.resolve(image.jpegData(compressionQuality: clamped)?.base64EncodedString() ?? "")
|
|
56
|
+
}
|
|
57
|
+
}.runOnQueue(.main)
|
|
58
|
+
|
|
59
|
+
/// What UIKit shows in front of the app: `kind` is "alert", "actionSheet", "share" or "" (nothing).
|
|
60
|
+
AsyncFunction("cover") { () -> [String: Any] in
|
|
61
|
+
let chain = TaplineWindows.presented(from: TaplineWindows.keyWindow()?.rootViewController)
|
|
62
|
+
for controller in chain.reversed() {
|
|
63
|
+
if let alert = controller as? UIAlertController {
|
|
64
|
+
return [
|
|
65
|
+
"kind": alert.preferredStyle == .actionSheet ? "actionSheet" : "alert",
|
|
66
|
+
"title": alert.title ?? "",
|
|
67
|
+
"message": alert.message ?? "",
|
|
68
|
+
"buttons": alert.actions.map { $0.title ?? "" },
|
|
69
|
+
"destructive": alert.actions.map { $0.style == .destructive },
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
if controller is UIActivityViewController {
|
|
73
|
+
return ["kind": "share", "title": "", "message": "", "buttons": [String](), "destructive": [Bool]()]
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return ["kind": "", "title": "", "message": "", "buttons": [String](), "destructive": [Bool]()]
|
|
77
|
+
}.runOnQueue(.main)
|
|
78
|
+
}
|
|
79
|
+
}
|
package/metro.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface TaplineMetroConfig {
|
|
2
|
+
serializer?: { getModulesRunBeforeMainModule?: (entryFilePath: string) => string[] } & Record<string, unknown>;
|
|
3
|
+
[key: string]: unknown;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Runs Tapline's `register` before the app's main module (only when Tapline is on:
|
|
8
|
+
* EXPO_PUBLIC_TAPLINE_ENABLED=1, or `enabled`), so no custom entry file is needed.
|
|
9
|
+
*/
|
|
10
|
+
export function withTapline<C extends TaplineMetroConfig>(config: C, options?: { enabled?: boolean }): C;
|
package/metro.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Metro config helper: runs Tapline's `register` before the app's main module, so the app
|
|
8
|
+
* doesn't need its own entry file just to import it first. Only when Tapline is on
|
|
9
|
+
* (EXPO_PUBLIC_TAPLINE_ENABLED=1 by default), so store builds stay untouched.
|
|
10
|
+
*
|
|
11
|
+
* // metro.config.js
|
|
12
|
+
* const { getDefaultConfig } = require('expo/metro-config');
|
|
13
|
+
* const { withTapline } = require('react-native-tapline/metro');
|
|
14
|
+
* module.exports = withTapline(getDefaultConfig(__dirname));
|
|
15
|
+
*
|
|
16
|
+
* The app still calls startTapline() (for example at the top of app/_layout.tsx).
|
|
17
|
+
*
|
|
18
|
+
* @template {{ serializer?: { getModulesRunBeforeMainModule?: (entryFilePath: string) => string[] } & Record<string, unknown> } & Record<string, unknown>} C
|
|
19
|
+
* @param {C} config
|
|
20
|
+
* @param {{ enabled?: boolean }} [options]
|
|
21
|
+
* @returns {C}
|
|
22
|
+
*/
|
|
23
|
+
function withTapline(config, options = {}) {
|
|
24
|
+
const enabled = options.enabled ?? process.env.EXPO_PUBLIC_TAPLINE_ENABLED === '1';
|
|
25
|
+
if (!enabled) return config;
|
|
26
|
+
// The same file the package's own index imports, so Metro has it in the bundle.
|
|
27
|
+
const register = path.join(__dirname, 'dist', 'register.js');
|
|
28
|
+
const serializer = config.serializer ?? {};
|
|
29
|
+
const previous = serializer.getModulesRunBeforeMainModule;
|
|
30
|
+
return {
|
|
31
|
+
...config,
|
|
32
|
+
serializer: {
|
|
33
|
+
...serializer,
|
|
34
|
+
/** @param {string} entryFilePath */
|
|
35
|
+
getModulesRunBeforeMainModule: (entryFilePath) => [...(previous ? previous(entryFilePath) : []), register],
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = { withTapline };
|
package/package.json
CHANGED
|
@@ -1,6 +1,102 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-tapline",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Tapline in-app agent: lets AI agents see and drive your own React Native / Expo app on a test phone on the same Wi-Fi.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/bo5amis/tapline.git",
|
|
9
|
+
"directory": "packages/react-native"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/bo5amis/tapline/tree/main/packages/react-native#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/bo5amis/tapline/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"react-native",
|
|
17
|
+
"expo",
|
|
18
|
+
"testing",
|
|
19
|
+
"mcp",
|
|
20
|
+
"ai-agent",
|
|
21
|
+
"e2e",
|
|
22
|
+
"ios"
|
|
23
|
+
],
|
|
24
|
+
"main": "dist/index.js",
|
|
25
|
+
"types": "dist/index.d.ts",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"default": "./dist/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./register": {
|
|
32
|
+
"types": "./dist/register.d.ts",
|
|
33
|
+
"default": "./dist/register.js"
|
|
34
|
+
},
|
|
35
|
+
"./metro": {
|
|
36
|
+
"types": "./metro.d.ts",
|
|
37
|
+
"default": "./metro.js"
|
|
38
|
+
},
|
|
39
|
+
"./package.json": "./package.json"
|
|
40
|
+
},
|
|
41
|
+
"typesVersions": {
|
|
42
|
+
"*": {
|
|
43
|
+
"register": [
|
|
44
|
+
"dist/register.d.ts"
|
|
45
|
+
],
|
|
46
|
+
"metro": [
|
|
47
|
+
"metro.d.ts"
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"files": [
|
|
52
|
+
"dist",
|
|
53
|
+
"register.js",
|
|
54
|
+
"register.d.ts",
|
|
55
|
+
"metro.js",
|
|
56
|
+
"metro.d.ts",
|
|
57
|
+
"README.md",
|
|
58
|
+
"ios",
|
|
59
|
+
"expo-module.config.json",
|
|
60
|
+
"TaplineNative.podspec"
|
|
61
|
+
],
|
|
62
|
+
"sideEffects": [
|
|
63
|
+
"./dist/register.js",
|
|
64
|
+
"./register.js"
|
|
65
|
+
],
|
|
66
|
+
"scripts": {
|
|
67
|
+
"build": "tsc -p tsconfig.build.json",
|
|
68
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
69
|
+
"test": "jest"
|
|
70
|
+
},
|
|
71
|
+
"peerDependencies": {
|
|
72
|
+
"expo-keep-awake": ">=12",
|
|
73
|
+
"expo-secure-store": ">=12",
|
|
74
|
+
"react": ">=18",
|
|
75
|
+
"react-native": ">=0.72",
|
|
76
|
+
"react-native-view-shot": ">=3"
|
|
77
|
+
},
|
|
78
|
+
"peerDependenciesMeta": {
|
|
79
|
+
"expo-keep-awake": {
|
|
80
|
+
"optional": true
|
|
81
|
+
},
|
|
82
|
+
"expo-secure-store": {
|
|
83
|
+
"optional": true
|
|
84
|
+
},
|
|
85
|
+
"react-native-view-shot": {
|
|
86
|
+
"optional": true
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"devDependencies": {
|
|
90
|
+
"@react-native/babel-preset": "0.88.1",
|
|
91
|
+
"@react-native/jest-preset": "0.88.1",
|
|
92
|
+
"@types/jest": "^29.5.14",
|
|
93
|
+
"@types/node": "^22.20.5",
|
|
94
|
+
"@types/react": "^19.1.1",
|
|
95
|
+
"@types/react-test-renderer": "^19.1.0",
|
|
96
|
+
"jest": "^29.7.0",
|
|
97
|
+
"react": "19.3.1",
|
|
98
|
+
"react-native": "0.88.1",
|
|
99
|
+
"react-test-renderer": "19.3.1",
|
|
100
|
+
"typescript": "^5.9.0"
|
|
101
|
+
}
|
|
102
|
+
}
|
package/register.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './dist/register';
|
package/register.js
ADDED