@lingxia/bridge 0.15.0 → 0.17.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/dist/bridge-runtime.es2020.js +1 -1
- package/dist/bridge-runtime.es5.js +1 -1
- package/dist/es2020/bridge.js +316 -66
- package/dist/es2020/protocol-v3.js +191 -0
- package/dist/es2020/runtime-env.js +9 -2
- package/dist/es2020/types.js +1 -0
- package/dist/es5/bridge.js +429 -143
- package/dist/es5/protocol-v3.js +215 -0
- package/dist/es5/runtime-env.js +9 -2
- package/dist/es5/types.js +1 -0
- package/dist/protocol-v3.d.ts +43 -0
- package/dist/runtime-env.d.ts +9 -2
- package/dist/types.d.ts +27 -0
- package/package.json +3 -1
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
var __assign = (this && this.__assign) || function () {
|
|
2
|
+
__assign = Object.assign || function(t) {
|
|
3
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
4
|
+
s = arguments[i];
|
|
5
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
6
|
+
t[p] = s[p];
|
|
7
|
+
}
|
|
8
|
+
return t;
|
|
9
|
+
};
|
|
10
|
+
return __assign.apply(this, arguments);
|
|
11
|
+
};
|
|
12
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
13
|
+
var t = {};
|
|
14
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
15
|
+
t[p] = s[p];
|
|
16
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
17
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
18
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
19
|
+
t[p[i]] = s[p[i]];
|
|
20
|
+
}
|
|
21
|
+
return t;
|
|
22
|
+
};
|
|
23
|
+
/** Document-side framing for host-attested RequiredV3 documents. */
|
|
24
|
+
export var V3_PROTOCOL = 3;
|
|
25
|
+
export var DEFAULT_MAX_V3_FRAME_BYTES = 64 * 1024;
|
|
26
|
+
var documentToNativeKinds = new Set([
|
|
27
|
+
"hello",
|
|
28
|
+
"req",
|
|
29
|
+
"res",
|
|
30
|
+
"notify",
|
|
31
|
+
"cancel",
|
|
32
|
+
"ch.open",
|
|
33
|
+
"ch.data",
|
|
34
|
+
"ch.close",
|
|
35
|
+
"state.ack",
|
|
36
|
+
"console",
|
|
37
|
+
]);
|
|
38
|
+
var nativeToDocumentKinds = new Set([
|
|
39
|
+
"helloAck",
|
|
40
|
+
"ready",
|
|
41
|
+
"req",
|
|
42
|
+
"res",
|
|
43
|
+
"event",
|
|
44
|
+
"state.snapshot",
|
|
45
|
+
"state.patch",
|
|
46
|
+
"ch.ack",
|
|
47
|
+
"ch.data",
|
|
48
|
+
"ch.close",
|
|
49
|
+
]);
|
|
50
|
+
function isRecord(value) {
|
|
51
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
52
|
+
}
|
|
53
|
+
function hasDuplicateTopLevelSecurityKey(frame) {
|
|
54
|
+
var _a;
|
|
55
|
+
var securityKeys = new Set(["v", "kind", "sessionId", "secret"]);
|
|
56
|
+
var seen = new Set();
|
|
57
|
+
var depth = 0;
|
|
58
|
+
for (var index = 0; index < frame.length; index++) {
|
|
59
|
+
var char = frame[index];
|
|
60
|
+
if (char === "{" || char === "[") {
|
|
61
|
+
depth++;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (char === "}" || char === "]") {
|
|
65
|
+
depth--;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (char !== '"')
|
|
69
|
+
continue;
|
|
70
|
+
var start = index;
|
|
71
|
+
index++;
|
|
72
|
+
while (index < frame.length) {
|
|
73
|
+
if (frame[index] === "\\") {
|
|
74
|
+
index += 2;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (frame[index] === '"')
|
|
78
|
+
break;
|
|
79
|
+
index++;
|
|
80
|
+
}
|
|
81
|
+
if (index >= frame.length)
|
|
82
|
+
return false;
|
|
83
|
+
if (depth !== 1)
|
|
84
|
+
continue;
|
|
85
|
+
var next = index + 1;
|
|
86
|
+
while (/\s/.test((_a = frame[next]) !== null && _a !== void 0 ? _a : ""))
|
|
87
|
+
next++;
|
|
88
|
+
if (frame[next] !== ":")
|
|
89
|
+
continue;
|
|
90
|
+
var key = void 0;
|
|
91
|
+
try {
|
|
92
|
+
key = JSON.parse(frame.slice(start, index + 1));
|
|
93
|
+
}
|
|
94
|
+
catch (_b) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
if (typeof key === "string" && securityKeys.has(key)) {
|
|
98
|
+
if (seen.has(key))
|
|
99
|
+
return true;
|
|
100
|
+
seen.add(key);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Captures the document secret in a closure. Neither the returned codec nor
|
|
107
|
+
* parsed native messages expose it.
|
|
108
|
+
*/
|
|
109
|
+
export function createV3DocumentCodec(binding) {
|
|
110
|
+
if (!isRecord(binding) ||
|
|
111
|
+
typeof binding.sessionId !== "string" ||
|
|
112
|
+
binding.sessionId.length === 0 ||
|
|
113
|
+
typeof binding.secret !== "string" ||
|
|
114
|
+
binding.secret.length === 0) {
|
|
115
|
+
return { ok: false, error: "INVALID_DOCUMENT_BINDING" };
|
|
116
|
+
}
|
|
117
|
+
var sessionId = binding.sessionId, secret = binding.secret;
|
|
118
|
+
return {
|
|
119
|
+
ok: true,
|
|
120
|
+
value: {
|
|
121
|
+
encode: function (kind, payload) {
|
|
122
|
+
if (!documentToNativeKinds.has(kind) || !isRecord(payload)) {
|
|
123
|
+
return { ok: false, error: "INVALID_DOCUMENT_PAYLOAD" };
|
|
124
|
+
}
|
|
125
|
+
if (["v", "kind", "sessionId", "secret"].some(function (field) { return field in payload; })) {
|
|
126
|
+
return { ok: false, error: "SECURITY_FIELD_IN_PAYLOAD" };
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
ok: true,
|
|
130
|
+
value: __assign(__assign({}, payload), { v: V3_PROTOCOL, kind: kind, sessionId: sessionId, secret: secret }),
|
|
131
|
+
};
|
|
132
|
+
},
|
|
133
|
+
parse: function (frame, maxFrameBytes) {
|
|
134
|
+
if (maxFrameBytes === void 0) { maxFrameBytes = DEFAULT_MAX_V3_FRAME_BYTES; }
|
|
135
|
+
// Envelope checks deliberately precede typed payload handling.
|
|
136
|
+
if (new TextEncoder().encode(frame).byteLength > maxFrameBytes) {
|
|
137
|
+
return { ok: false, error: "FRAME_TOO_LARGE" };
|
|
138
|
+
}
|
|
139
|
+
if (hasDuplicateTopLevelSecurityKey(frame)) {
|
|
140
|
+
return { ok: false, error: "MALFORMED_ENVELOPE" };
|
|
141
|
+
}
|
|
142
|
+
var candidate;
|
|
143
|
+
try {
|
|
144
|
+
candidate = JSON.parse(frame);
|
|
145
|
+
}
|
|
146
|
+
catch (_a) {
|
|
147
|
+
return { ok: false, error: "MALFORMED_ENVELOPE" };
|
|
148
|
+
}
|
|
149
|
+
if (!isRecord(candidate))
|
|
150
|
+
return { ok: false, error: "MALFORMED_ENVELOPE" };
|
|
151
|
+
if (candidate.v !== V3_PROTOCOL) {
|
|
152
|
+
return { ok: false, error: "UNSUPPORTED_VERSION" };
|
|
153
|
+
}
|
|
154
|
+
if (typeof candidate.kind !== "string" ||
|
|
155
|
+
!nativeToDocumentKinds.has(candidate.kind)) {
|
|
156
|
+
return { ok: false, error: "UNSUPPORTED_NATIVE_KIND" };
|
|
157
|
+
}
|
|
158
|
+
if (typeof candidate.sessionId !== "string" || candidate.sessionId.length === 0) {
|
|
159
|
+
return { ok: false, error: "MALFORMED_ENVELOPE" };
|
|
160
|
+
}
|
|
161
|
+
if (candidate.sessionId !== sessionId)
|
|
162
|
+
return { ok: false, error: "SESSION_MISMATCH" };
|
|
163
|
+
if (Object.prototype.hasOwnProperty.call(candidate, "secret")) {
|
|
164
|
+
return { ok: false, error: "UNEXPECTED_SECRET" };
|
|
165
|
+
}
|
|
166
|
+
var _v = candidate.v, _kind = candidate.kind, _sessionId = candidate.sessionId, payload = __rest(candidate, ["v", "kind", "sessionId"]);
|
|
167
|
+
return {
|
|
168
|
+
ok: true,
|
|
169
|
+
value: { kind: candidate.kind, payload: payload },
|
|
170
|
+
};
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Atomically takes the host-installed one-shot handoff. The secret remains in
|
|
177
|
+
* the returned codec closure and is never copied into bridge globals/config.
|
|
178
|
+
*/
|
|
179
|
+
export function consumeV3Bootstrap() {
|
|
180
|
+
if (typeof window === "undefined")
|
|
181
|
+
return { kind: "absent" };
|
|
182
|
+
var take = window.__LingXiaTakeControlBootstrap;
|
|
183
|
+
if (typeof take !== "function")
|
|
184
|
+
return { kind: "absent" };
|
|
185
|
+
try {
|
|
186
|
+
var bootstrap = take();
|
|
187
|
+
if (!bootstrap ||
|
|
188
|
+
bootstrap.requiredProtocol !== V3_PROTOCOL ||
|
|
189
|
+
typeof bootstrap.publicSessionId !== "string" ||
|
|
190
|
+
bootstrap.publicSessionId.length === 0 ||
|
|
191
|
+
typeof bootstrap.secret !== "string" ||
|
|
192
|
+
bootstrap.secret.length === 0) {
|
|
193
|
+
return { kind: "blocked" };
|
|
194
|
+
}
|
|
195
|
+
var codec = createV3DocumentCodec({
|
|
196
|
+
sessionId: bootstrap.publicSessionId,
|
|
197
|
+
secret: bootstrap.secret,
|
|
198
|
+
});
|
|
199
|
+
return codec.ok
|
|
200
|
+
? { kind: "required", codec: codec.value }
|
|
201
|
+
: { kind: "blocked" };
|
|
202
|
+
}
|
|
203
|
+
catch (_a) {
|
|
204
|
+
return { kind: "blocked" };
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
// A malformed replacement must not leave a callable handoff for later.
|
|
208
|
+
try {
|
|
209
|
+
delete window.__LingXiaTakeControlBootstrap;
|
|
210
|
+
}
|
|
211
|
+
catch (_b) {
|
|
212
|
+
// A hostile non-configurable property has no attested secret to retain.
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
package/dist/es5/runtime-env.js
CHANGED
|
@@ -83,7 +83,7 @@ function applyDisplayLanguage(next) {
|
|
|
83
83
|
try {
|
|
84
84
|
for (var _b = __values(__spreadArray([], __read(current.listeners), false)), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
85
85
|
var listener = _c.value;
|
|
86
|
-
listener();
|
|
86
|
+
listener(normalized);
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
@@ -104,7 +104,14 @@ if (typeof window !== 'undefined' && !window.__lingxiaApplyDisplayLanguage) {
|
|
|
104
104
|
export function getDisplayLanguage() {
|
|
105
105
|
return store().value;
|
|
106
106
|
}
|
|
107
|
-
/**
|
|
107
|
+
/**
|
|
108
|
+
* Subscribe to host display-language changes. Returns an unsubscribe.
|
|
109
|
+
*
|
|
110
|
+
* Change-only, so that this composes with `useSyncExternalStore`: the listener
|
|
111
|
+
* runs when the language changes, never on subscribe. Read the current value
|
|
112
|
+
* with `getDisplayLanguage()`. Logic's `lx.app.displayLanguage.watch` differs
|
|
113
|
+
* deliberately — it has no render loop to feed, so it delivers immediately.
|
|
114
|
+
*/
|
|
108
115
|
export function subscribeDisplayLanguage(listener) {
|
|
109
116
|
var listeners = store().listeners;
|
|
110
117
|
listeners.add(listener);
|
package/dist/es5/types.js
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** Document-side framing for host-attested RequiredV3 documents. */
|
|
2
|
+
export declare const V3_PROTOCOL: 3;
|
|
3
|
+
export declare const DEFAULT_MAX_V3_FRAME_BYTES: number;
|
|
4
|
+
export type V3DocumentToNativeKind = "hello" | "req" | "res" | "notify" | "cancel" | "ch.open" | "ch.data" | "ch.close" | "state.ack" | "console";
|
|
5
|
+
export type V3NativeToDocumentKind = "helloAck" | "ready" | "req" | "res" | "event" | "state.snapshot" | "state.patch" | "ch.ack" | "ch.data" | "ch.close";
|
|
6
|
+
export type V3CodecError = "FRAME_TOO_LARGE" | "MALFORMED_ENVELOPE" | "UNSUPPORTED_VERSION" | "UNSUPPORTED_NATIVE_KIND" | "INVALID_DOCUMENT_BINDING" | "INVALID_DOCUMENT_PAYLOAD" | "SECURITY_FIELD_IN_PAYLOAD" | "SESSION_MISMATCH" | "UNEXPECTED_SECRET";
|
|
7
|
+
export type V3CodecResult<T> = {
|
|
8
|
+
readonly ok: true;
|
|
9
|
+
readonly value: T;
|
|
10
|
+
} | {
|
|
11
|
+
readonly ok: false;
|
|
12
|
+
readonly error: V3CodecError;
|
|
13
|
+
};
|
|
14
|
+
export type V3DocumentBinding = {
|
|
15
|
+
readonly sessionId: string;
|
|
16
|
+
readonly secret: string;
|
|
17
|
+
};
|
|
18
|
+
export type V3NativeEnvelope = {
|
|
19
|
+
readonly kind: V3NativeToDocumentKind;
|
|
20
|
+
readonly payload: Readonly<Record<string, unknown>>;
|
|
21
|
+
};
|
|
22
|
+
export type V3DocumentCodec = {
|
|
23
|
+
encode(kind: V3DocumentToNativeKind, payload: Record<string, unknown>): V3CodecResult<Record<string, unknown>>;
|
|
24
|
+
parse(frame: string, maxFrameBytes?: number): V3CodecResult<V3NativeEnvelope>;
|
|
25
|
+
};
|
|
26
|
+
export type V3BootstrapActivation = {
|
|
27
|
+
readonly kind: "absent";
|
|
28
|
+
} | {
|
|
29
|
+
readonly kind: "required";
|
|
30
|
+
readonly codec: V3DocumentCodec;
|
|
31
|
+
} | {
|
|
32
|
+
readonly kind: "blocked";
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Captures the document secret in a closure. Neither the returned codec nor
|
|
36
|
+
* parsed native messages expose it.
|
|
37
|
+
*/
|
|
38
|
+
export declare function createV3DocumentCodec(binding: V3DocumentBinding): V3CodecResult<V3DocumentCodec>;
|
|
39
|
+
/**
|
|
40
|
+
* Atomically takes the host-installed one-shot handoff. The secret remains in
|
|
41
|
+
* the returned codec closure and is never copied into bridge globals/config.
|
|
42
|
+
*/
|
|
43
|
+
export declare function consumeV3Bootstrap(): V3BootstrapActivation;
|
package/dist/runtime-env.d.ts
CHANGED
|
@@ -3,8 +3,15 @@ export type CommunicationMethod = 'messageport' | 'jsinterface' | 'webmessage' |
|
|
|
3
3
|
export type PlatformOS = NonNullable<BridgeConfig['os']> | 'unknown';
|
|
4
4
|
export declare const BRIDGE_CONFIG: BridgeConfig;
|
|
5
5
|
export declare function getDisplayLanguage(): string;
|
|
6
|
-
/**
|
|
7
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Subscribe to host display-language changes. Returns an unsubscribe.
|
|
8
|
+
*
|
|
9
|
+
* Change-only, so that this composes with `useSyncExternalStore`: the listener
|
|
10
|
+
* runs when the language changes, never on subscribe. Read the current value
|
|
11
|
+
* with `getDisplayLanguage()`. Logic's `lx.app.displayLanguage.watch` differs
|
|
12
|
+
* deliberately — it has no render loop to feed, so it delivers immediately.
|
|
13
|
+
*/
|
|
14
|
+
export declare function subscribeDisplayLanguage(listener: (language: string) => void): () => void;
|
|
8
15
|
export declare function getPlatformOS(): PlatformOS;
|
|
9
16
|
export declare function isHarmony(): boolean;
|
|
10
17
|
export declare function isIOS(): boolean;
|
package/dist/types.d.ts
CHANGED
|
@@ -21,6 +21,18 @@ export interface ErrorInfo {
|
|
|
21
21
|
reason?: string;
|
|
22
22
|
code?: number;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Private, one-shot handoff installed only in host-attested internal HTML.
|
|
26
|
+
*
|
|
27
|
+
* This is intentionally not part of the public bridge API and V2 never reads
|
|
28
|
+
* it. A future V3 bootstrap consumes the handoff before protocol activation.
|
|
29
|
+
*/
|
|
30
|
+
interface ControlBootstrap {
|
|
31
|
+
readonly requiredProtocol: 3;
|
|
32
|
+
readonly publicSessionId: string;
|
|
33
|
+
readonly secret: string;
|
|
34
|
+
}
|
|
35
|
+
type TakeControlBootstrap = () => ControlBootstrap | undefined;
|
|
24
36
|
export declare const BRIDGE_ERROR: {
|
|
25
37
|
readonly NOT_READY: "BRIDGE_NOT_READY";
|
|
26
38
|
readonly TIMEOUT: "BRIDGE_TIMEOUT";
|
|
@@ -35,6 +47,7 @@ export declare const BRIDGE_ERROR: {
|
|
|
35
47
|
readonly OUTBOX_FULL: "BRIDGE_OUTBOX_FULL";
|
|
36
48
|
readonly STREAM_OVERFLOW: "BRIDGE_STREAM_OVERFLOW";
|
|
37
49
|
readonly STREAM_CLOSED: "BRIDGE_STREAM_CLOSED";
|
|
50
|
+
readonly MESSAGE_TOO_LARGE: "BRIDGE_MESSAGE_TOO_LARGE";
|
|
38
51
|
};
|
|
39
52
|
export type BridgeErrorCode = (typeof BRIDGE_ERROR)[keyof typeof BRIDGE_ERROR];
|
|
40
53
|
export interface LxBridgeError {
|
|
@@ -132,16 +145,24 @@ declare global {
|
|
|
132
145
|
};
|
|
133
146
|
__LX_BRIDGE_INIT_STATE?: 'initializing' | 'initialized';
|
|
134
147
|
__LX_RUNTIME_CONFIG?: RuntimeConfig;
|
|
148
|
+
/**
|
|
149
|
+
* Host-attested, one-shot V3 bootstrap handoff. It is absent for legacy
|
|
150
|
+
* scheme loads, assets, and all untrusted documents.
|
|
151
|
+
*/
|
|
152
|
+
__LingXiaTakeControlBootstrap?: TakeControlBootstrap;
|
|
135
153
|
__pageBridge?: {
|
|
136
154
|
__names: string[];
|
|
137
155
|
[key: string]: unknown;
|
|
138
156
|
};
|
|
139
157
|
__LingXiaRecvMessage?: (message: string) => void;
|
|
158
|
+
/** WebView2 inject script queues inbound payloads here until the bridge binds `__LingXiaRecvMessage`. */
|
|
159
|
+
__LingXiaEarlyNativeMessages?: string[];
|
|
140
160
|
LingXiaBridge?: LingXiaBridgeInterface;
|
|
141
161
|
LingXiaProxy?: {
|
|
142
162
|
supportsMessagePort: () => boolean;
|
|
143
163
|
getPort: (name: string) => string;
|
|
144
164
|
postMessage: (message: string) => void;
|
|
165
|
+
nativeComponentUpdate?: ((payload: string) => void) | ((componentId: string, payload: string) => void);
|
|
145
166
|
};
|
|
146
167
|
NativeComponentBridge?: {
|
|
147
168
|
postMessage: (message: string) => void;
|
|
@@ -171,6 +192,11 @@ export interface LingXiaBridgeInterface {
|
|
|
171
192
|
proto: boolean;
|
|
172
193
|
all: boolean;
|
|
173
194
|
};
|
|
195
|
+
displayLanguage: {
|
|
196
|
+
get(): string;
|
|
197
|
+
/** Change-only; read the current value with `get()`. */
|
|
198
|
+
subscribe(listener: (language: string) => void): () => void;
|
|
199
|
+
};
|
|
174
200
|
platform: {
|
|
175
201
|
isHarmony(): boolean;
|
|
176
202
|
isIOS(): boolean;
|
|
@@ -211,3 +237,4 @@ export interface StreamOptions extends StreamCallOptions {
|
|
|
211
237
|
}
|
|
212
238
|
export interface ChannelOptions extends ChannelOpenOptions {
|
|
213
239
|
}
|
|
240
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lingxia/bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "LingXia Web Runtime - Bridge, boot, and error handling for WebView",
|
|
6
6
|
"repository": {
|
|
@@ -39,6 +39,8 @@
|
|
|
39
39
|
"build:modules:es2020": "tsc -p tsconfig.modules.json",
|
|
40
40
|
"build:modules:es5": "tsc -p tsconfig.modules.legacy.json",
|
|
41
41
|
"build:types": "tsc -p tsconfig.json --emitDeclarationOnly",
|
|
42
|
+
"build:v3-test": "tsc -p tsconfig.v3-test.json",
|
|
43
|
+
"test": "npm run build:es2020 && npm run build:es5 && npm run build:v3-test && node ./test-support/test-protocol-v3.mjs && node ./test-support/test-bridge-required-v3.mjs && node ./test-support/test-control-bootstrap.mjs",
|
|
42
44
|
"dev": "rolldown -c -w",
|
|
43
45
|
"clean": "node -e \"fs.rmSync('dist',{recursive:true,force:true})\"",
|
|
44
46
|
"prepublishOnly": "npm run build"
|