@nativescript/cloudflare-cli 0.1.0-beta.1 → 0.1.0-beta.3
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 +10 -1
- package/dist/bin.js +77 -3
- package/dist/sync.d.ts +7 -1
- package/dist/sync.js +9 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
# @nativescript/cloudflare-cli
|
|
2
2
|
|
|
3
|
+
```sh
|
|
4
|
+
npx @nativescript/cloudflare-cli live # a phone app: opens the IDE and prints a QR code for NativeScript Preview
|
|
5
|
+
npx @nativescript/cloudflare-cli live --mac # a Mac app: opens the IDE and runs the app on this Mac, streamed into the IDE
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
`live` creates a project on preview.nativescript.org (`--server` or `NS_EDGE_SERVER` for another edge), saves it to `.ns-edge.json` and opens its IDE. Run it again in the same folder to reopen that project, or add `--new` for another. Without a create token the project is free: a few hosted agent turns a day, no backend, deleted after 30 days unused. `--no-open` skips the browser; with `--mac`, `--allow-eval` lets the agent run code on this Mac.
|
|
9
|
+
|
|
10
|
+
## Mirror a local project
|
|
11
|
+
|
|
3
12
|
Mirror a NativeScript project into an ns-edge project from a terminal or a Vite dev server.
|
|
4
13
|
|
|
5
14
|
```sh
|
|
6
15
|
npm i -D @nativescript/cloudflare-cli@beta
|
|
7
|
-
npx ns-edge create --server https://preview.nativescript.org --token <create token>
|
|
16
|
+
npx ns-edge create --server https://preview.nativescript.org --token <create token> # --template vue for nativescript-vue
|
|
8
17
|
npx ns-edge watch # push, then every save
|
|
9
18
|
npx ns-edge qr # print the pairing QR code again
|
|
10
19
|
```
|
package/dist/bin.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { existsSync, watch } from 'node:fs';
|
|
3
4
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
4
5
|
import { join, sep } from 'node:path';
|
|
5
6
|
import { createInterface } from 'node:readline/promises';
|
|
@@ -7,6 +8,10 @@ import QRCode from 'qrcode';
|
|
|
7
8
|
import { applyChanges, collect, createProject, projectUrl, report, SYNCED } from './sync.js';
|
|
8
9
|
const CONFIG = '.ns-edge.json';
|
|
9
10
|
const DEBOUNCE_MS = 120;
|
|
11
|
+
const LIVE_SERVER = process.env.NS_EDGE_SERVER ?? 'https://preview.nativescript.org';
|
|
12
|
+
/** The desktop host `live --mac` runs; bump it with each host release this CLI is tested against. */
|
|
13
|
+
const DESKTOP_HOST = '@nativescript/cloudflare-desktop@0.1.0-alpha.2';
|
|
14
|
+
const DESKTOP_TEMPLATES = new Set(['macos', 'electron']);
|
|
10
15
|
const [cmd = 'help', ...args] = process.argv.slice(2);
|
|
11
16
|
/**
|
|
12
17
|
* Editors and tools write temp, swap and backup files next to the real one
|
|
@@ -18,6 +23,7 @@ const flag = (name) => {
|
|
|
18
23
|
const i = args.indexOf(`--${name}`);
|
|
19
24
|
return i >= 0 ? args[i + 1] : undefined;
|
|
20
25
|
};
|
|
26
|
+
const has = (name) => args.includes(`--${name}`);
|
|
21
27
|
const root = process.cwd();
|
|
22
28
|
async function loadRef() {
|
|
23
29
|
let ref;
|
|
@@ -52,10 +58,75 @@ async function printPairing(ref) {
|
|
|
52
58
|
console.log(` device ${device}`);
|
|
53
59
|
console.log(` ide ${url}/#key=${ref.writeKey}`);
|
|
54
60
|
}
|
|
61
|
+
/** This folder's project, if it has one the server still knows. */
|
|
62
|
+
async function savedProject() {
|
|
63
|
+
if (!existsSync(join(root, CONFIG)))
|
|
64
|
+
return null;
|
|
65
|
+
const ref = JSON.parse(await readFile(join(root, CONFIG), 'utf8'));
|
|
66
|
+
if (!ref.writeKey)
|
|
67
|
+
return null;
|
|
68
|
+
const res = await fetch(`${projectUrl(ref)}/files`).catch(() => null);
|
|
69
|
+
return res?.ok ? ref : null;
|
|
70
|
+
}
|
|
71
|
+
function openInBrowser(url) {
|
|
72
|
+
const [command, ...rest] = process.platform === 'darwin' ? ['open', url] : process.platform === 'win32' ? ['cmd', '/c', 'start', '""', url.replace(/&/g, '^&')] : ['xdg-open', url];
|
|
73
|
+
spawn(command, rest, { stdio: 'ignore', detached: true }).on('error', () => { }).unref();
|
|
74
|
+
}
|
|
75
|
+
/** Runs the Mac host for a desktop project until it exits; the host prints what joining allows. */
|
|
76
|
+
function runDesktopHost(ref) {
|
|
77
|
+
if (process.platform !== 'darwin' || process.arch !== 'arm64') {
|
|
78
|
+
throw new Error('Mac projects run on an Apple Silicon Mac (macOS 14.4 or later). Run the same command there.');
|
|
79
|
+
}
|
|
80
|
+
const device = `${projectUrl(ref)}#${ref.deviceKey}`;
|
|
81
|
+
const extra = has('allow-eval') ? ['--allow-eval'] : [];
|
|
82
|
+
const child = spawn('npx', ['-y', DESKTOP_HOST, device, '--yes', ...extra], { stdio: 'inherit' });
|
|
83
|
+
for (const signal of ['SIGINT', 'SIGTERM'])
|
|
84
|
+
process.on(signal, () => child.kill(signal));
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
child.on('error', reject);
|
|
87
|
+
child.on('exit', (code) => (code ? reject(new Error(`The Mac host exited with code ${code}.`)) : resolve()));
|
|
88
|
+
});
|
|
89
|
+
}
|
|
55
90
|
const commands = {
|
|
91
|
+
async live() {
|
|
92
|
+
for (const later of ['ios', 'android', 'all']) {
|
|
93
|
+
if (has(later))
|
|
94
|
+
throw new Error(`--${later} isn't available yet. Run \`live\` for a phone app or \`live --mac\` for a Mac app.`);
|
|
95
|
+
}
|
|
96
|
+
const template = flag('template') ?? (has('mac') ? 'macos' : 'core');
|
|
97
|
+
const desktop = DESKTOP_TEMPLATES.has(template);
|
|
98
|
+
let ref = has('new') ? null : await savedProject();
|
|
99
|
+
if (ref && DESKTOP_TEMPLATES.has(ref.template ?? 'core') !== desktop) {
|
|
100
|
+
throw new Error(`This folder's project (${CONFIG}) is a ${desktop ? 'phone' : 'Mac'} project. Add --new to replace it, or run this in another folder.`);
|
|
101
|
+
}
|
|
102
|
+
if (ref) {
|
|
103
|
+
console.log(`Opening this folder's project ${ref.id} (${CONFIG})`);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
ref = await createProject(flag('server') ?? LIVE_SERVER, flag('token') ?? process.env.NS_EDGE_CREATE_TOKEN, template);
|
|
107
|
+
await writeFile(join(root, CONFIG), JSON.stringify(ref, null, 2) + '\n');
|
|
108
|
+
console.log(`Created a ${desktop ? 'Mac' : 'phone'} project, saved to ${CONFIG} (it holds the project's keys: keep it private and out of git).`);
|
|
109
|
+
if (ref.free) {
|
|
110
|
+
console.log(`It's a free project: ${ref.free.agentTurnsPerDay} hosted agent turn${ref.free.agentTurnsPerDay === 1 ? '' : 's'} a day, deleted after ${ref.free.idleDays} days unused. ` +
|
|
111
|
+
'For an unlimited agent, add your own API key in the agent panel or connect Claude Code from Connect agent.');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const ide = `${projectUrl(ref)}/#key=${ref.writeKey}`;
|
|
115
|
+
console.log(`\n IDE ${ide}\n`);
|
|
116
|
+
if (!has('no-open'))
|
|
117
|
+
openInBrowser(ide);
|
|
118
|
+
if (desktop) {
|
|
119
|
+
console.log('Starting the app on this Mac. Its window streams to the IDE\'s Device pane. Press Ctrl+C to stop.\n');
|
|
120
|
+
return runDesktopHost(ref);
|
|
121
|
+
}
|
|
122
|
+
const device = `${projectUrl(ref)}#${ref.deviceKey}`;
|
|
123
|
+
console.log(await QRCode.toString(`${ref.pairScheme ?? 'nsedge'}://pair?url=${encodeURIComponent(device)}`, { type: 'terminal', small: true }));
|
|
124
|
+
console.log(`Scan with your phone's camera to open it in ${ref.pairScheme === 'nspreview' ? 'NativeScript Preview' : 'the edge shell'}, then edit in the IDE.`);
|
|
125
|
+
console.log('Run this command again in this folder to reopen the project.');
|
|
126
|
+
},
|
|
56
127
|
async create() {
|
|
57
128
|
const server = flag('server') ?? 'http://localhost:5173';
|
|
58
|
-
const ref = await createProject(server, flag('token') ?? process.env.NS_EDGE_CREATE_TOKEN);
|
|
129
|
+
const ref = await createProject(server, flag('token') ?? process.env.NS_EDGE_CREATE_TOKEN, flag('template'));
|
|
59
130
|
await writeFile(join(root, CONFIG), JSON.stringify(ref, null, 2) + '\n');
|
|
60
131
|
console.log(`Created project ${ref.id} (saved to ${CONFIG}; it holds the project keys, keep it out of git)`);
|
|
61
132
|
await printPairing(ref);
|
|
@@ -125,7 +196,10 @@ const commands = {
|
|
|
125
196
|
async help() {
|
|
126
197
|
console.log(`ns-edge <command>
|
|
127
198
|
|
|
128
|
-
|
|
199
|
+
live [--mac] [--new] [--no-open] [--allow-eval]
|
|
200
|
+
create a project (or reopen this folder's), open its IDE, and print a QR code
|
|
201
|
+
for your phone; --mac runs it on this Mac instead
|
|
202
|
+
create [--server <url>] [--token <t>] [--template core|vue|react|solid|svelte|angular]
|
|
129
203
|
create an edge project and save .ns-edge.json
|
|
130
204
|
push upload app/ and server/ in one transaction
|
|
131
205
|
watch push, then stream every save to the edge
|
package/dist/sync.d.ts
CHANGED
|
@@ -5,6 +5,12 @@ export interface ProjectRef {
|
|
|
5
5
|
deviceKey: string;
|
|
6
6
|
/** URL scheme of the app the server pairs with, from its create response. */
|
|
7
7
|
pairScheme?: string;
|
|
8
|
+
template?: string;
|
|
9
|
+
/** Set on free projects: their daily agent allowance and how long they last unused. */
|
|
10
|
+
free?: {
|
|
11
|
+
agentTurnsPerDay: number;
|
|
12
|
+
idleDays: number;
|
|
13
|
+
};
|
|
8
14
|
}
|
|
9
15
|
export interface ApplyResult {
|
|
10
16
|
changed: string[];
|
|
@@ -17,7 +23,7 @@ export interface ApplyResult {
|
|
|
17
23
|
/** Project-relative paths that are mirrored to the edge. */
|
|
18
24
|
export declare const SYNCED: RegExp;
|
|
19
25
|
export declare function projectUrl(ref: ProjectRef): string;
|
|
20
|
-
export declare function createProject(server: string, token?: string): Promise<ProjectRef>;
|
|
26
|
+
export declare function createProject(server: string, token?: string, template?: string): Promise<ProjectRef>;
|
|
21
27
|
/** Sends a batch as one edge transaction, so devices see one consistent update. */
|
|
22
28
|
export declare function applyChanges(ref: ProjectRef, changes: {
|
|
23
29
|
path: string;
|
package/dist/sync.js
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import { readFile, readdir, stat } from 'node:fs/promises';
|
|
2
2
|
import { join, relative, sep } from 'node:path';
|
|
3
3
|
/** Project-relative paths that are mirrored to the edge. */
|
|
4
|
-
export const SYNCED = /^(app|server)\/.+\.(ts|mts|js|mjs|xml|css|json)$/;
|
|
4
|
+
export const SYNCED = /^(app|server)\/.+\.(ts|tsx|mts|js|jsx|mjs|vue|svelte|xml|html|css|json)$/;
|
|
5
5
|
export function projectUrl(ref) {
|
|
6
6
|
return `${ref.server.replace(/\/$/, '')}/p/${ref.id}`;
|
|
7
7
|
}
|
|
8
|
-
export async function createProject(server, token) {
|
|
9
|
-
const
|
|
8
|
+
export async function createProject(server, token, template) {
|
|
9
|
+
const query = template ? `?template=${encodeURIComponent(template)}` : '';
|
|
10
|
+
const res = await fetch(`${server.replace(/\/$/, '')}/api/projects${query}`, {
|
|
10
11
|
method: 'POST',
|
|
11
12
|
headers: token ? { authorization: `Bearer ${token}` } : {},
|
|
12
13
|
});
|
|
14
|
+
if (res.status === 401)
|
|
15
|
+
throw new Error(`${server} creates projects only with a create token: pass --token or set NS_EDGE_CREATE_TOKEN.`);
|
|
13
16
|
if (!res.ok)
|
|
14
|
-
throw new Error(`create
|
|
15
|
-
const { id, writeKey, deviceKey, pair } = (await res.json());
|
|
16
|
-
return { server, id, writeKey, deviceKey, pairScheme: pair?.split('://')[0] };
|
|
17
|
+
throw new Error(`Couldn't create a project (${res.status}): ${await res.text()}`);
|
|
18
|
+
const { id, writeKey, deviceKey, pair, free } = (await res.json());
|
|
19
|
+
return { server, id, writeKey, deviceKey, pairScheme: pair?.split('://')[0], template: template ?? 'core', ...(free && { free }) };
|
|
17
20
|
}
|
|
18
21
|
/** Sends a batch as one edge transaction, so devices see one consistent update. */
|
|
19
22
|
export async function applyChanges(ref, changes) {
|
package/package.json
CHANGED