@tokamakdev/tok 0.1.1-beta.83 → 0.1.1-beta.84

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/README.md CHANGED
@@ -12,7 +12,7 @@ npx tok targets
12
12
  | Entry | Contents |
13
13
  | --- | --- |
14
14
  | `@tokamakdev/tok` | The `Config` type of the Vite plugin's options |
15
- | `@tokamakdev/tok/events` | The app's events: `onStart`, `onResume` and `onSuspend` |
15
+ | `@tokamakdev/tok/events` | The app's events, `onStart`, `onResume` and `onSuspend`, and `getLifecycleStage()` |
16
16
  | `@tokamakdev/tok/vite` | The Vite plugin, `tokamak()` |
17
17
  | `tok` | The CLI |
18
18
 
@@ -31,8 +31,9 @@ export default defineConfig({
31
31
 
32
32
  It does nothing unless `tok` runs the build or development command. Then it
33
33
  makes the entry Worker import `src/tokamak.ts` and export the entrypoint the
34
- runtime delivers events through, and in development it delivers the device's
35
- events to the development Worker through Cloudflare's dev registry.
34
+ runtime delivers events through. In development it delivers the device's
35
+ events to the development Worker through Cloudflare's dev registry, and passes
36
+ the development Worker's plugin calls to the device.
36
37
 
37
38
  The package installs the `tok` binary for the current machine through an
38
39
  optional dependency (`@tokamakdev/tok-darwin-arm64`, `-darwin-x64`,
@@ -1,10 +1,23 @@
1
+ /** The header that carries the device's session token, which `tok dev`'s relay forwards. */
2
+ export declare const SESSION_HEADER = "x-tokamak-session";
3
+ /** A plugin's result, as the page's native transport receives it. */
4
+ export interface PluginResult {
5
+ value?: unknown;
6
+ error?: {
7
+ name: string;
8
+ message: string;
9
+ };
10
+ done: boolean;
11
+ }
1
12
  /** The loopback server the device's dev WebSocket reaches through `tok dev`'s relay. */
2
13
  export interface DevSocket {
3
14
  readonly port: number;
4
15
  close(): Promise<void>;
5
16
  }
6
17
  /**
7
- * Listens on a loopback port for dev WebSockets that carry `token`, and delivers each event they
8
- * carry to `TokamakEvents` in the development Worker `workerName`, replying with its reply.
18
+ * Listens on a loopback port, with `token`, for the device's dev WebSocket and the development
19
+ * Worker's plugin calls. Delivers each event the device sends to `TokamakEvents` in the
20
+ * development Worker `workerName`, and passes each plugin call the Worker posts to `/call` or
21
+ * `/subscribe` to the device.
9
22
  */
10
23
  export declare function openDevSocket(workerName: string | undefined, token: string | undefined): Promise<DevSocket>;
@@ -2,8 +2,10 @@ import http from "node:http";
2
2
  import { createRequire } from "node:module";
3
3
  import { pathToFileURL } from "node:url";
4
4
  import { WebSocketServer } from "ws";
5
- /** The header `tok dev`'s relay forwards with the device's session token. */
6
- const SESSION_HEADER = "x-tokamak-session";
5
+ /** The header that carries the device's session token, which `tok dev`'s relay forwards. */
6
+ export const SESSION_HEADER = "x-tokamak-session";
7
+ /** How long a plugin call waits for the device to connect. */
8
+ const CONNECT_TIMEOUT = 10_000;
7
9
  /** Calls `TokamakEvents` in the Worker its `TOKAMAK` service binding names, through the dev registry. */
8
10
  const CALLER = `export default {
9
11
  async fetch(request, env) {
@@ -17,19 +19,32 @@ const CALLER = `export default {
17
19
  },
18
20
  };`;
19
21
  /**
20
- * Listens on a loopback port for dev WebSockets that carry `token`, and delivers each event they
21
- * carry to `TokamakEvents` in the development Worker `workerName`, replying with its reply.
22
+ * Listens on a loopback port, with `token`, for the device's dev WebSocket and the development
23
+ * Worker's plugin calls. Delivers each event the device sends to `TokamakEvents` in the
24
+ * development Worker `workerName`, and passes each plugin call the Worker posts to `/call` or
25
+ * `/subscribe` to the device.
22
26
  */
23
27
  export async function openDevSocket(workerName, token) {
24
28
  const worker = new DevWorker(workerName);
25
- const server = http.createServer((_request, response) => response.writeHead(426).end());
29
+ const device = new Device();
30
+ const server = http.createServer((request, response) => {
31
+ if (!token || request.headers[SESSION_HEADER] !== token) {
32
+ response.writeHead(401).end();
33
+ return;
34
+ }
35
+ if (request.method === "POST" && (request.url === "/call" || request.url === "/subscribe")) {
36
+ void forward(device, request, response);
37
+ return;
38
+ }
39
+ response.writeHead(request.method === "GET" ? 426 : 404).end();
40
+ });
26
41
  const sockets = new WebSocketServer({ noServer: true });
27
42
  server.on("upgrade", (request, socket, head) => {
28
43
  if (!token || request.headers[SESSION_HEADER] !== token) {
29
44
  socket.end("HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n");
30
45
  return;
31
46
  }
32
- sockets.handleUpgrade(request, socket, head, (websocket) => serve(websocket, worker));
47
+ sockets.handleUpgrade(request, socket, head, (websocket) => serve(websocket, worker, device));
33
48
  });
34
49
  await new Promise((resolve, reject) => {
35
50
  server.once("error", reject);
@@ -40,21 +55,65 @@ export async function openDevSocket(workerName, token) {
40
55
  async close() {
41
56
  for (const websocket of sockets.clients)
42
57
  websocket.terminate();
58
+ server.closeAllConnections();
43
59
  server.close();
44
60
  await worker.dispose();
45
61
  },
46
62
  };
47
63
  }
48
- function serve(websocket, worker) {
64
+ function serve(websocket, worker, device) {
65
+ device.connect(websocket);
49
66
  // The device opens another dev WebSocket after this one fails.
50
67
  websocket.on("error", () => websocket.terminate());
51
68
  websocket.on("message", (data) => {
52
69
  const message = parseMessage(String(data));
70
+ if (message?.type === "result") {
71
+ device.receive(message.id, message.result);
72
+ return;
73
+ }
53
74
  if (message?.type !== "event")
54
75
  return;
55
76
  worker.dispatch(message.name, message.event).then((reply) => websocket.send(JSON.stringify({ type: "reply", id: message.id, reply })), (error) => websocket.send(JSON.stringify({ type: "error", id: message.id, message: String(error) })));
56
77
  });
57
78
  }
79
+ /**
80
+ * Passes the plugin call `request` carries to the device. A call's response is its result; a
81
+ * subscription's streams each result as a line of JSON until one is done, and closing it ends the
82
+ * subscription.
83
+ */
84
+ async function forward(device, request, response) {
85
+ let call;
86
+ try {
87
+ call = JSON.parse(await readBody(request));
88
+ }
89
+ catch {
90
+ response.writeHead(400).end();
91
+ return;
92
+ }
93
+ response.writeHead(200, { "content-type": "application/json" });
94
+ if (request.url === "/call") {
95
+ response.end(JSON.stringify(await new Promise((resolve) => device.send("call", call, resolve))));
96
+ return;
97
+ }
98
+ response.flushHeaders();
99
+ const cancel = device.send("subscribe", call, (result) => {
100
+ response.write(`${JSON.stringify(result)}\n`);
101
+ if (result.done)
102
+ response.end();
103
+ });
104
+ response.once("close", () => {
105
+ if (!response.writableEnded)
106
+ cancel();
107
+ });
108
+ }
109
+ function readBody(request) {
110
+ return new Promise((resolve, reject) => {
111
+ const chunks = [];
112
+ request.on("data", (chunk) => chunks.push(chunk));
113
+ request.once("end", () => resolve(Buffer.concat(chunks).toString()));
114
+ request.once("error", reject);
115
+ });
116
+ }
58
117
  function parseMessage(text) {
59
118
  try {
60
119
  return JSON.parse(text);
@@ -63,6 +122,82 @@ function parseMessage(text) {
63
122
  return undefined;
64
123
  }
65
124
  }
125
+ /** A failed result, which ends a subscription. */
126
+ function failure(name, message) {
127
+ return { error: { name, message }, done: true };
128
+ }
129
+ /** The device, through its latest dev WebSocket, and the plugin calls waiting for its results. */
130
+ class Device {
131
+ #socket;
132
+ #connected = [];
133
+ #nextId = 1;
134
+ #pending = new Map();
135
+ connect(socket) {
136
+ this.#socket = socket;
137
+ for (const connected of this.#connected.splice(0))
138
+ connected();
139
+ socket.once("close", () => {
140
+ if (this.#socket === socket)
141
+ this.#socket = undefined;
142
+ for (const [id, pending] of this.#pending) {
143
+ if (pending.socket !== socket)
144
+ continue;
145
+ this.#pending.delete(id);
146
+ pending.receive(failure("NetworkError", "The device disconnected"));
147
+ }
148
+ });
149
+ }
150
+ receive(id, result) {
151
+ const pending = this.#pending.get(id);
152
+ if (!pending)
153
+ return;
154
+ if (result.done || pending.type === "call")
155
+ this.#pending.delete(id);
156
+ pending.receive(result);
157
+ }
158
+ /**
159
+ * Sends a call or subscription once the device is connected, waiting up to 10 seconds for it,
160
+ * and passes each of its results to `receive`. Returns the function that unsubscribes.
161
+ */
162
+ send(type, request, receive) {
163
+ const id = this.#nextId++;
164
+ let cancelled = false;
165
+ void this.#connection().then((socket) => {
166
+ if (cancelled)
167
+ return;
168
+ if (!socket) {
169
+ receive(failure("NetworkError", "The device is not connected"));
170
+ return;
171
+ }
172
+ this.#pending.set(id, { type, socket, receive });
173
+ socket.send(JSON.stringify({ type, id, ...request }));
174
+ });
175
+ return () => {
176
+ cancelled = true;
177
+ const pending = this.#pending.get(id);
178
+ if (!pending)
179
+ return;
180
+ this.#pending.delete(id);
181
+ pending.socket.send(JSON.stringify({ type: "unsubscribe", id }));
182
+ };
183
+ }
184
+ /** The open dev WebSocket, once the device connects, or undefined after 10 seconds without. */
185
+ #connection() {
186
+ if (this.#socket)
187
+ return Promise.resolve(this.#socket);
188
+ return new Promise((resolve) => {
189
+ const connected = () => {
190
+ clearTimeout(timer);
191
+ resolve(this.#socket);
192
+ };
193
+ const timer = setTimeout(() => {
194
+ this.#connected.splice(this.#connected.indexOf(connected), 1);
195
+ resolve(undefined);
196
+ }, CONNECT_TIMEOUT);
197
+ this.#connected.push(connected);
198
+ });
199
+ }
200
+ }
66
201
  /** The development Worker's `TokamakEvents`, called through a Miniflare session started on first use. */
67
202
  class DevWorker {
68
203
  name;
package/dist/events.d.mts CHANGED
@@ -6,3 +6,11 @@ export declare const onStart: (listener: import("@tokamakdev/plugin/events").Lis
6
6
  export declare const onResume: (listener: import("@tokamakdev/plugin/events").Listener<Readonly<Record<string, never>>, void>) => void;
7
7
  /** Runs each time the app moves out of the foreground. */
8
8
  export declare const onSuspend: (listener: import("@tokamakdev/plugin/events").Listener<Readonly<Record<string, never>>, void>) => void;
9
+ /** Whether the app is on screen. */
10
+ export type LifecycleStage = "foreground" | "background";
11
+ /**
12
+ * The app's stage now, which can differ from the last event delivered: an `onSuspend` listener
13
+ * that runs late reads `"foreground"` once the user has returned. Rejects with
14
+ * `NotSupportedError` outside a tokamak Worker.
15
+ */
16
+ export declare function getLifecycleStage(): Promise<LifecycleStage>;
package/dist/events.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { Plugin } from "@tokamakdev/plugin";
1
2
  import { defineEvent } from "@tokamakdev/plugin/events";
2
3
  /** Runs once per runtime start, including when the system starts the app in the background. */
3
4
  export const onStart = defineEvent("start");
@@ -5,3 +6,21 @@ export const onStart = defineEvent("start");
5
6
  export const onResume = defineEvent("resume");
6
7
  /** Runs each time the app moves out of the foreground. */
7
8
  export const onSuspend = defineEvent("suspend");
9
+ /** The functions the runtime answers itself, as the reserved plugin `tokamak`. */
10
+ class Tokamak extends Plugin {
11
+ constructor() {
12
+ super("tokamak");
13
+ }
14
+ lifecycleStage() {
15
+ return this.call("lifecycleStage");
16
+ }
17
+ }
18
+ const tokamak = new Tokamak();
19
+ /**
20
+ * The app's stage now, which can differ from the last event delivered: an `onSuspend` listener
21
+ * that runs late reads `"foreground"` once the user has returned. Rejects with
22
+ * `NotSupportedError` outside a tokamak Worker.
23
+ */
24
+ export function getLifecycleStage() {
25
+ return tokamak.lifecycleStage();
26
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Defines `__tokamakNativeCall` and `__tokamakNativeListen`, which post to the plugin's loopback
3
+ * `port` with the session `token`. `waitUntil` keeps each listener's invocation running, as
4
+ * `waitUntil` from `cloudflare:workers` does.
5
+ */
6
+ export declare function defineNativeCalls(port: number, token: string, waitUntil: (promise: Promise<unknown>) => void): void;
@@ -0,0 +1,73 @@
1
+ // Defines the development Worker's native calls, which reach the device through the tokamak Vite
2
+ // plugin. The plugin's entry Worker transform imports this module in development only.
3
+ /**
4
+ * Defines `__tokamakNativeCall` and `__tokamakNativeListen`, which post to the plugin's loopback
5
+ * `port` with the session `token`. `waitUntil` keeps each listener's invocation running, as
6
+ * `waitUntil` from `cloudflare:workers` does.
7
+ */
8
+ export function defineNativeCalls(port, token, waitUntil) {
9
+ const post = async (path, plugin, method, arguments_, signal) => {
10
+ const response = await fetch(`http://127.0.0.1:${String(port)}${path}`, {
11
+ method: "POST",
12
+ headers: { "x-tokamak-session": token },
13
+ body: JSON.stringify({ plugin, method, arguments: arguments_ }),
14
+ signal,
15
+ });
16
+ if (!response.ok)
17
+ throw new Error(`the tokamak Vite plugin answered HTTP ${String(response.status)}`);
18
+ return response;
19
+ };
20
+ globalThis.__tokamakNativeCall = async (plugin, method, arguments_ = null) => {
21
+ let result;
22
+ try {
23
+ result = (await (await post("/call", plugin, method, arguments_)).json());
24
+ }
25
+ catch (error) {
26
+ throw networkError(error);
27
+ }
28
+ if (result.error)
29
+ throw new DOMException(result.error.message, result.error.name);
30
+ return result.value;
31
+ };
32
+ globalThis.__tokamakNativeListen = (plugin, method, arguments_, next, error) => {
33
+ const listening = new AbortController();
34
+ const deliver = (result) => {
35
+ try {
36
+ if (result.error)
37
+ error(new DOMException(result.error.message, result.error.name));
38
+ else
39
+ next(result.value);
40
+ }
41
+ catch (thrown) {
42
+ reportError(thrown);
43
+ }
44
+ };
45
+ const listen = async () => {
46
+ const response = await post("/subscribe", plugin, method, arguments_ ?? null, listening.signal);
47
+ for await (const result of lines(response))
48
+ deliver(result);
49
+ };
50
+ waitUntil(listen().catch((failure) => {
51
+ if (!listening.signal.aborted)
52
+ deliver({ error: networkError(failure), done: true });
53
+ }));
54
+ return () => {
55
+ listening.abort();
56
+ };
57
+ };
58
+ }
59
+ /** Each line of `response`'s body, parsed as a result. */
60
+ async function* lines(response) {
61
+ const decoder = new TextDecoder();
62
+ let text = "";
63
+ for await (const chunk of response.body ?? []) {
64
+ text += decoder.decode(chunk, { stream: true });
65
+ const complete = text.split("\n");
66
+ text = complete.pop() ?? "";
67
+ for (const line of complete)
68
+ yield JSON.parse(line);
69
+ }
70
+ }
71
+ function networkError(error) {
72
+ return new DOMException(`The device could not be reached: ${String(error)}`, "NetworkError");
73
+ }
package/dist/vite.d.mts CHANGED
@@ -3,8 +3,10 @@ import type { Config } from "./index.mjs";
3
3
  /**
4
4
  * Reports the app's tokamak configuration, and the development server's
5
5
  * address, HTTPS certificate, Worker name and dev socket port, to `tok`, and
6
- * makes the entry Worker import `module` and export `TokamakEvents`. Without
7
- * `TOKAMAK_VITE_OUTPUT`, which `tok` sets, it adds no hooks.
6
+ * makes the entry Worker import `module` and export `TokamakEvents`. In
7
+ * development, the entry Worker also defines native calls that reach the
8
+ * device through the dev socket. Without `TOKAMAK_VITE_OUTPUT`, which `tok`
9
+ * sets, it adds no hooks.
8
10
  */
9
11
  export declare function tokamak({ module, ...config }?: Config & {
10
12
  /**
package/dist/vite.mjs CHANGED
@@ -9,11 +9,15 @@ const DEFAULT_MODULES = ["src/tokamak.ts", "src/tokamak.js"];
9
9
  /** Wrangler configuration files Cloudflare's plugin tries in order. */
10
10
  const WRANGLER_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
11
11
  const MIDDLEWARE_MODE_ERROR = "Vite is running in middleware mode, so tokamak cannot find the development server's address; tok dev needs Vite's own dev server";
12
+ /** The module that defines the development Worker's native calls. */
13
+ const NATIVE_CALLS = normalizePath(fileURLToPath(new URL("./native.mjs", import.meta.url)));
12
14
  /**
13
15
  * Reports the app's tokamak configuration, and the development server's
14
16
  * address, HTTPS certificate, Worker name and dev socket port, to `tok`, and
15
- * makes the entry Worker import `module` and export `TokamakEvents`. Without
16
- * `TOKAMAK_VITE_OUTPUT`, which `tok` sets, it adds no hooks.
17
+ * makes the entry Worker import `module` and export `TokamakEvents`. In
18
+ * development, the entry Worker also defines native calls that reach the
19
+ * device through the dev socket. Without `TOKAMAK_VITE_OUTPUT`, which `tok`
20
+ * sets, it adds no hooks.
17
21
  */
18
22
  export function tokamak({ module, ...config } = {}) {
19
23
  const output = process.env.TOKAMAK_VITE_OUTPUT;
@@ -30,6 +34,9 @@ export function tokamak({ module, ...config } = {}) {
30
34
  const entryIds = new Map();
31
35
  /** The entry environments whose input exports `TokamakEvents`. */
32
36
  const exporting = new Set();
37
+ /** The development server's dev socket, once open. */
38
+ let devSocket;
39
+ const token = process.env.TOKAMAK_SESSION_TOKEN;
33
40
  return [
34
41
  {
35
42
  name: "tokamak",
@@ -42,20 +49,25 @@ export function tokamak({ module, ...config } = {}) {
42
49
  file = findModule(root, module);
43
50
  writeJson(output, "config.json", { root, config });
44
51
  },
45
- async configureServer(server) {
46
- const httpServer = server.httpServer;
47
- // A middleware-mode server beside a reported one, such as the server
48
- // Astro syncs content with, leaves the report in place.
49
- if (!httpServer) {
50
- if (!fs.existsSync(path.join(output, "server.json"))) {
51
- writeJson(output, "server.json", { error: MIDDLEWARE_MODE_ERROR });
52
+ // Runs before plugins that load the entry Worker, whose native calls
53
+ // need the dev socket's port.
54
+ configureServer: {
55
+ order: "pre",
56
+ async handler(server) {
57
+ const httpServer = server.httpServer;
58
+ // A middleware-mode server beside a reported one, such as the server
59
+ // Astro syncs content with, leaves the report in place.
60
+ if (!httpServer) {
61
+ if (!fs.existsSync(path.join(output, "server.json"))) {
62
+ writeJson(output, "server.json", { error: MIDDLEWARE_MODE_ERROR });
63
+ }
64
+ return;
52
65
  }
53
- return;
54
- }
55
- const worker = await readWorker(server.config.root);
56
- httpServer.once("listening", () => {
57
- openDevSocket(worker?.name, process.env.TOKAMAK_SESSION_TOKEN).then((socket) => {
58
- httpServer.once("close", () => void socket.close());
66
+ const worker = await readWorker(server.config.root);
67
+ const socket = await openDevSocket(worker?.name, token);
68
+ devSocket = socket;
69
+ httpServer.once("close", () => void socket.close());
70
+ httpServer.once("listening", () => {
59
71
  const urls = server.resolvedUrls;
60
72
  writeJson(output, "server.json", {
61
73
  url: urls?.local[0] ?? urls?.network[0],
@@ -63,8 +75,8 @@ export function tokamak({ module, ...config } = {}) {
63
75
  certificates: endEntityCertificates(server.config.server.https),
64
76
  socketPort: socket.port,
65
77
  });
66
- }, (error) => server.config.logger.error(`tokamak could not open its dev socket: ${String(error)}`));
67
- });
78
+ });
79
+ },
68
80
  },
69
81
  async buildApp(builder) {
70
82
  const environments = Object.values(builder.environments);
@@ -88,7 +100,11 @@ export function tokamak({ module, ...config } = {}) {
88
100
  // In Cloudflare's Worker entry, which dev and build both start from,
89
101
  // this follows the imports of its polyfills and of the app's entry.
90
102
  const imports = file ? `import ${JSON.stringify(file)};\n` : "";
91
- return { code: `${code}\n${imports}export { TokamakEvents } from ${JSON.stringify(entrypoint)};\n`, map: null };
103
+ const native = environment.mode === "dev" && devSocket && token ? nativeCalls(devSocket.port, token) : "";
104
+ return {
105
+ code: `${code}\n${imports}export { TokamakEvents } from ${JSON.stringify(entrypoint)};\n${native}`,
106
+ map: null,
107
+ };
92
108
  },
93
109
  // A module that registers listeners adds them again each time it
94
110
  // evaluates, so when an update re-evaluates one, the registry and every
@@ -114,6 +130,17 @@ export function tokamak({ module, ...config } = {}) {
114
130
  },
115
131
  ];
116
132
  }
133
+ /**
134
+ * Code that defines the development Worker's native calls on the dev socket's
135
+ * `port`, with the session `token`, which only the entry Worker's environment
136
+ * sees.
137
+ */
138
+ function nativeCalls(port, token) {
139
+ return `import { waitUntil as __tokamakWaitUntil } from "cloudflare:workers";
140
+ import { defineNativeCalls as __tokamakDefineNativeCalls } from ${JSON.stringify(NATIVE_CALLS)};
141
+ __tokamakDefineNativeCalls(${String(port)}, ${JSON.stringify(token)}, __tokamakWaitUntil);
142
+ `;
143
+ }
117
144
  /** `module` and every module that imports it, directly or through others. */
118
145
  function withImporters(module) {
119
146
  const found = new Set();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokamakdev/tok",
3
- "version": "0.1.1-beta.83",
3
+ "version": "0.1.1-beta.84",
4
4
  "description": "The tokamak CLI, Vite plugin and app API: build native apps from a Cloudflare Workers codebase",
5
5
  "repository": {
6
6
  "type": "git",
@@ -39,7 +39,7 @@
39
39
  "node": ">=18"
40
40
  },
41
41
  "dependencies": {
42
- "@tokamakdev/plugin": "0.1.0-beta.83",
42
+ "@tokamakdev/plugin": "0.1.0-beta.84",
43
43
  "ws": "^8.22.0"
44
44
  },
45
45
  "peerDependencies": {
@@ -64,17 +64,17 @@
64
64
  "wrangler": "4.147.0"
65
65
  },
66
66
  "optionalDependencies": {
67
- "@tokamakdev/platform-android-arm64": "0.1.1-beta.83",
68
- "@tokamakdev/platform-ios-arm64": "0.1.1-beta.83",
69
- "@tokamakdev/platform-ios-simulator-arm64": "0.1.1-beta.83",
70
- "@tokamakdev/platform-ios-simulator-x64": "0.1.1-beta.83",
71
- "@tokamakdev/platform-macos-arm64": "0.1.1-beta.83",
72
- "@tokamakdev/platform-macos-x64": "0.1.1-beta.83",
73
- "@tokamakdev/platform-windows-x64": "0.1.1-beta.83",
74
- "@tokamakdev/tok-darwin-arm64": "0.1.1-beta.83",
75
- "@tokamakdev/tok-darwin-x64": "0.1.1-beta.83",
76
- "@tokamakdev/tok-linux-x64": "0.1.1-beta.83",
77
- "@tokamakdev/tok-win32-x64": "0.1.1-beta.83"
67
+ "@tokamakdev/platform-android-arm64": "0.1.1-beta.84",
68
+ "@tokamakdev/platform-ios-arm64": "0.1.1-beta.84",
69
+ "@tokamakdev/platform-ios-simulator-arm64": "0.1.1-beta.84",
70
+ "@tokamakdev/platform-ios-simulator-x64": "0.1.1-beta.84",
71
+ "@tokamakdev/platform-macos-arm64": "0.1.1-beta.84",
72
+ "@tokamakdev/platform-macos-x64": "0.1.1-beta.84",
73
+ "@tokamakdev/platform-windows-x64": "0.1.1-beta.84",
74
+ "@tokamakdev/tok-darwin-arm64": "0.1.1-beta.84",
75
+ "@tokamakdev/tok-darwin-x64": "0.1.1-beta.84",
76
+ "@tokamakdev/tok-linux-x64": "0.1.1-beta.84",
77
+ "@tokamakdev/tok-win32-x64": "0.1.1-beta.84"
78
78
  },
79
79
  "publishConfig": {
80
80
  "access": "public"