@nativescript/cloudflare-expo-desktop 0.0.0-stage → 0.1.0-alpha.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/NSEdgeExpo.app.tar.gz +0 -0
- package/README.md +15 -2
- package/bin.mjs +102 -0
- package/package.json +28 -4
|
Binary file
|
package/README.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @nativescript/cloudflare-expo-desktop
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Runs an ns-edge project made from the `expo` template on a Mac, in the ns-edge Expo Desktop shell: React Native for macOS on Hermes, with AppKit and Foundation through `@nativescript/react-native` worklets. The app's window streams to the project's browser IDE, where people click into it.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npx @nativescript/cloudflare-cli live --expo # create a project and run it here
|
|
7
|
+
npx @nativescript/cloudflare-expo-desktop '<device link>' [--yes] [--allow-eval] # run an existing one
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
The first run of each version unpacks the shell to `~/Library/Caches/ns-edge/expo-desktop/<version>/`. The shell asks before joining: the project's code runs in a sandboxed app that can reach the network, and anyone with the write key can watch and click its window. `--allow-eval` lets the agent's `runOnDevice` run code on the app's main thread.
|
|
11
|
+
|
|
12
|
+
Projects import `react`, `react-native` and `@nativescript/react-native`, which the shell links. The edge compiles each module to CommonJS for Hermes, including `"worklet"` functions, so the shell needs no bundler. If a new version throws while rendering, the shell keeps showing the last version that rendered.
|
|
13
|
+
|
|
14
|
+
The shell is `apps/expo-desktop-shell` in the ns-cloudflare repo; `npm pack` here packs its Release build (`NS_EXPO_APP` points at another build). `NS_EDGE_DEBUG=1` shows the app's own log output.
|
|
15
|
+
|
|
16
|
+
Apple Silicon, macOS 14.4 or later, Node 22.15 or later.
|
package/bin.mjs
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Starts the ns-edge Expo Desktop shell on a project.
|
|
3
|
+
// npx @nativescript/cloudflare-expo-desktop '<device link>' [--yes] [--allow-eval]
|
|
4
|
+
import { execFileSync, spawn } from 'node:child_process';
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync } from 'node:fs';
|
|
6
|
+
import { homedir, release } from 'node:os';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { createInterface } from 'node:readline/promises';
|
|
9
|
+
import { parseArgs } from 'node:util';
|
|
10
|
+
|
|
11
|
+
const USAGE = `Usage: ns-edge-expo-desktop <device link> [options]
|
|
12
|
+
|
|
13
|
+
Runs an ns-edge expo project on this Mac and streams its window to the project's IDE.
|
|
14
|
+
The device link is the project's https://<host>/p/<id>#<device key> link.
|
|
15
|
+
|
|
16
|
+
Options:
|
|
17
|
+
--yes Join without asking
|
|
18
|
+
--allow-eval Let the project's agent run arbitrary code in the app (runOnDevice)
|
|
19
|
+
|
|
20
|
+
NS_EDGE_DEBUG=1 shows the app's own log output too.
|
|
21
|
+
`;
|
|
22
|
+
const LINK = /^https?:\/\/[^/#]+\/p\/[0-9a-f]{32}\/?#[0-9a-f]{32}$/;
|
|
23
|
+
/** Lines kept from the app's own output, shown if it crashes. */
|
|
24
|
+
const TAIL_LINES = 30;
|
|
25
|
+
|
|
26
|
+
const out = (line) => process.stderr.write(`${line}\n`);
|
|
27
|
+
|
|
28
|
+
async function main() {
|
|
29
|
+
const { values, positionals } = parseArgs({
|
|
30
|
+
allowPositionals: true,
|
|
31
|
+
options: {
|
|
32
|
+
yes: { type: 'boolean', default: false },
|
|
33
|
+
'allow-eval': { type: 'boolean', default: false },
|
|
34
|
+
help: { type: 'boolean', short: 'h', default: false },
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
const link = positionals[0]?.trim();
|
|
38
|
+
if (values.help || !link) return out(USAGE);
|
|
39
|
+
if (!LINK.test(link)) throw new Error(`Not a device link: ${link}\nExpected https://<host>/p/<project id>#<device key>`);
|
|
40
|
+
|
|
41
|
+
const [major, minor] = release().split('.').map(Number);
|
|
42
|
+
if (process.platform !== 'darwin' || process.arch !== 'arm64' || major < 23 || (major === 23 && minor < 4)) {
|
|
43
|
+
throw new Error('The Expo Desktop shell runs on an Apple Silicon Mac with macOS 14.4 or later.');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
out(`Project: ${link.replace(/#.*$/, '')}`);
|
|
47
|
+
out("Its code will run in a sandboxed app that can reach the network, and anyone with the project's write key");
|
|
48
|
+
out(`can watch and click its window${values['allow-eval'] ? ' and run code in it' : ''}.`);
|
|
49
|
+
if (!values.yes) {
|
|
50
|
+
if (!process.stdin.isTTY) throw new Error('Not a terminal: pass --yes to join without asking.');
|
|
51
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
52
|
+
const answer = await rl.question('Join? [y/N] ');
|
|
53
|
+
rl.close();
|
|
54
|
+
if (!/^y(es)?$/i.test(answer.trim())) return out('Not joined.');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const app = extractedApp();
|
|
58
|
+
const child = spawn(join(app, 'Contents/MacOS/NSEdgeExpo'), [link, ...(values['allow-eval'] ? ['--allow-eval'] : [])], {
|
|
59
|
+
stdio: ['ignore', 'ignore', 'pipe'],
|
|
60
|
+
});
|
|
61
|
+
const tail = [];
|
|
62
|
+
createInterface({ input: child.stderr }).on('line', (line) => {
|
|
63
|
+
if (line.startsWith('[ns-edge]') || process.env.NS_EDGE_DEBUG) return out(line);
|
|
64
|
+
tail.push(line);
|
|
65
|
+
if (tail.length > TAIL_LINES) tail.shift();
|
|
66
|
+
});
|
|
67
|
+
for (const signal of ['SIGINT', 'SIGTERM']) process.on(signal, () => child.kill(signal));
|
|
68
|
+
child.on('exit', (code, signal) => {
|
|
69
|
+
if ((signal && signal !== 'SIGINT' && signal !== 'SIGTERM') || (code !== null && code !== 0)) {
|
|
70
|
+
out(`[ns-edge] the Expo Desktop shell stopped (${signal ?? `code ${code}`}). Its last output:\n${tail.join('\n')}`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
process.exit(0);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The app ships as a tarball (npm drops the symlinks an .app's frameworks
|
|
79
|
+
* need) and is unpacked once per package version, outside the npx cache so
|
|
80
|
+
* it survives cache cleanups.
|
|
81
|
+
*/
|
|
82
|
+
function extractedApp() {
|
|
83
|
+
const pkg = import.meta.dirname;
|
|
84
|
+
const { version } = JSON.parse(readFileSync(join(pkg, 'package.json'), 'utf8'));
|
|
85
|
+
const dir = join(homedir(), 'Library/Caches/ns-edge/expo-desktop', version);
|
|
86
|
+
const app = join(dir, 'NSEdgeExpo.app');
|
|
87
|
+
if (existsSync(app)) return app;
|
|
88
|
+
out('[ns-edge] unpacking the Expo Desktop shell (once per version)…');
|
|
89
|
+
const staging = `${dir}.partial-${process.pid}`;
|
|
90
|
+
rmSync(staging, { recursive: true, force: true });
|
|
91
|
+
mkdirSync(staging, { recursive: true });
|
|
92
|
+
execFileSync('tar', ['-xzf', join(pkg, 'NSEdgeExpo.app.tar.gz'), '-C', staging]);
|
|
93
|
+
mkdirSync(join(dir, '..'), { recursive: true });
|
|
94
|
+
rmSync(dir, { recursive: true, force: true });
|
|
95
|
+
renameSync(staging, dir);
|
|
96
|
+
return app;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
main().catch((e) => {
|
|
100
|
+
out(String(e instanceof Error ? e.message : e));
|
|
101
|
+
process.exit(1);
|
|
102
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nativescript/cloudflare-expo-desktop",
|
|
3
|
-
"version": "0.0.0
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0-alpha.0",
|
|
4
|
+
"description": "Runs an ns-edge expo project on a Mac: React Native for macOS on Hermes with NativeScript's AppKit access, its window streamed to the browser IDE",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"ns-edge-expo-desktop": "./bin.mjs"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=22.15"
|
|
12
|
+
},
|
|
13
|
+
"os": [
|
|
14
|
+
"darwin"
|
|
15
|
+
],
|
|
16
|
+
"cpu": [
|
|
17
|
+
"arm64"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"prepack": "node scripts/app.mjs"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"bin.mjs",
|
|
24
|
+
"NSEdgeExpo.app.tar.gz"
|
|
25
|
+
],
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public",
|
|
28
|
+
"tag": "alpha"
|
|
29
|
+
}
|
|
30
|
+
}
|