@nativescript/cloudflare-cli 0.1.0-beta.2 → 0.1.0-beta.4

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
@@ -1,5 +1,15 @@
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
+ npx @nativescript/cloudflare-cli live --electron # an Electron app with native views, run the same way
7
+ ```
8
+
9
+ `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` or `--electron`, `--allow-eval` lets the agent run code on this Mac.
10
+
11
+ ## Mirror a local project
12
+
3
13
  Mirror a NativeScript project into an ns-edge project from a terminal or a Vite dev server.
4
14
 
5
15
  ```sh
package/dist/bin.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { watch } from 'node:fs';
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,13 @@ 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 hosts `live --mac` and `live --electron` run; bump them with each host release this CLI is tested against. */
13
+ const DESKTOP_HOSTS = {
14
+ macos: '@nativescript/cloudflare-desktop@0.1.0-alpha.3',
15
+ electron: '@nativescript/cloudflare-electron@0.1.0-alpha.0',
16
+ };
17
+ const DESKTOP_TEMPLATES = new Set(['macos', 'electron']);
10
18
  const [cmd = 'help', ...args] = process.argv.slice(2);
11
19
  /**
12
20
  * Editors and tools write temp, swap and backup files next to the real one
@@ -18,6 +26,7 @@ const flag = (name) => {
18
26
  const i = args.indexOf(`--${name}`);
19
27
  return i >= 0 ? args[i + 1] : undefined;
20
28
  };
29
+ const has = (name) => args.includes(`--${name}`);
21
30
  const root = process.cwd();
22
31
  async function loadRef() {
23
32
  let ref;
@@ -52,7 +61,76 @@ async function printPairing(ref) {
52
61
  console.log(` device ${device}`);
53
62
  console.log(` ide ${url}/#key=${ref.writeKey}`);
54
63
  }
64
+ /** This folder's project, if it has one the server still knows. */
65
+ async function savedProject() {
66
+ if (!existsSync(join(root, CONFIG)))
67
+ return null;
68
+ const ref = JSON.parse(await readFile(join(root, CONFIG), 'utf8'));
69
+ if (!ref.writeKey)
70
+ return null;
71
+ const res = await fetch(`${projectUrl(ref)}/files`).catch(() => null);
72
+ return res?.ok ? ref : null;
73
+ }
74
+ function openInBrowser(url) {
75
+ const [command, ...rest] = process.platform === 'darwin' ? ['open', url] : process.platform === 'win32' ? ['cmd', '/c', 'start', '""', url.replace(/&/g, '^&')] : ['xdg-open', url];
76
+ spawn(command, rest, { stdio: 'ignore', detached: true }).on('error', () => { }).unref();
77
+ }
78
+ /** Runs the Mac host for a desktop project until it exits; the host says what joining allows. */
79
+ function runDesktopHost(ref) {
80
+ if (process.platform !== 'darwin' || process.arch !== 'arm64') {
81
+ throw new Error('Mac projects run on an Apple Silicon Mac (macOS 14.4 or later). Run the same command there.');
82
+ }
83
+ const device = `${projectUrl(ref)}#${ref.deviceKey}`;
84
+ const extra = has('allow-eval') ? ['--allow-eval'] : [];
85
+ const host = DESKTOP_HOSTS[ref.template ?? 'macos'] ?? DESKTOP_HOSTS.macos;
86
+ const child = spawn('npx', ['-y', host, device, '--yes', ...extra], { stdio: 'inherit' });
87
+ for (const signal of ['SIGINT', 'SIGTERM'])
88
+ process.on(signal, () => child.kill(signal));
89
+ return new Promise((resolve, reject) => {
90
+ child.on('error', reject);
91
+ child.on('exit', (code) => (code ? reject(new Error(`The Mac host exited with code ${code}.`)) : resolve()));
92
+ });
93
+ }
55
94
  const commands = {
95
+ async live() {
96
+ for (const later of ['ios', 'android', 'all']) {
97
+ if (has(later))
98
+ throw new Error(`--${later} isn't available yet. Run \`live\` for a phone app or \`live --mac\` for a Mac app.`);
99
+ }
100
+ const template = flag('template') ?? (has('electron') ? 'electron' : has('mac') ? 'macos' : 'core');
101
+ const desktop = DESKTOP_TEMPLATES.has(template);
102
+ let ref = has('new') ? null : await savedProject();
103
+ if (ref && DESKTOP_TEMPLATES.has(ref.template ?? 'core') !== desktop) {
104
+ 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.`);
105
+ }
106
+ if (ref && desktop && ref.template !== template) {
107
+ throw new Error(`This folder's project (${CONFIG}) is a ${ref.template} project. Add --new to replace it, or run this in another folder.`);
108
+ }
109
+ if (ref) {
110
+ console.log(`Opening this folder's project ${ref.id} (${CONFIG})`);
111
+ }
112
+ else {
113
+ ref = await createProject(flag('server') ?? LIVE_SERVER, flag('token') ?? process.env.NS_EDGE_CREATE_TOKEN, template);
114
+ await writeFile(join(root, CONFIG), JSON.stringify(ref, null, 2) + '\n');
115
+ console.log(`Created ${template === 'electron' ? 'an Electron' : desktop ? 'a Mac' : 'a phone'} project, saved to ${CONFIG} (it holds the project's keys: keep it private and out of git).`);
116
+ if (ref.free) {
117
+ 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. ` +
118
+ 'For an unlimited agent, add your own API key in the agent panel or connect Claude Code from Connect agent.');
119
+ }
120
+ }
121
+ const ide = `${projectUrl(ref)}/#key=${ref.writeKey}`;
122
+ console.log(`\n IDE ${ide}\n`);
123
+ if (!has('no-open'))
124
+ openInBrowser(ide);
125
+ if (desktop) {
126
+ console.log(`Starting the app on this Mac${template === 'electron' ? ' (the first run downloads Electron)' : ''}. Its window streams to the IDE's Device pane. Press Ctrl+C to stop.\n`);
127
+ return runDesktopHost(ref);
128
+ }
129
+ const device = `${projectUrl(ref)}#${ref.deviceKey}`;
130
+ console.log(await QRCode.toString(`${ref.pairScheme ?? 'nsedge'}://pair?url=${encodeURIComponent(device)}`, { type: 'terminal', small: true }));
131
+ 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.`);
132
+ console.log('Run this command again in this folder to reopen the project.');
133
+ },
56
134
  async create() {
57
135
  const server = flag('server') ?? 'http://localhost:5173';
58
136
  const ref = await createProject(server, flag('token') ?? process.env.NS_EDGE_CREATE_TOKEN, flag('template'));
@@ -125,7 +203,10 @@ const commands = {
125
203
  async help() {
126
204
  console.log(`ns-edge <command>
127
205
 
128
- create [--server <url>] [--token <t>] [--template core|vue]
206
+ live [--mac|--electron] [--new] [--no-open] [--allow-eval]
207
+ create a project (or reopen this folder's), open its IDE, and print a QR code
208
+ for your phone; --mac or --electron runs it on this Mac instead
209
+ create [--server <url>] [--token <t>] [--template core|vue|react|solid|svelte|angular]
129
210
  create an edge project and save .ns-edge.json
130
211
  push upload app/ and server/ in one transaction
131
212
  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[];
package/dist/sync.js CHANGED
@@ -1,7 +1,7 @@
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|vue|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
  }
@@ -11,10 +11,12 @@ export async function createProject(server, token, template) {
11
11
  method: 'POST',
12
12
  headers: token ? { authorization: `Bearer ${token}` } : {},
13
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.`);
14
16
  if (!res.ok)
15
- throw new Error(`create failed: ${res.status} ${await res.text()}`);
16
- const { id, writeKey, deviceKey, pair } = (await res.json());
17
- 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 }) };
18
20
  }
19
21
  /** Sends a batch as one edge transaction, so devices see one consistent update. */
20
22
  export async function applyChanges(ref, changes) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nativescript/cloudflare-cli",
3
- "version": "0.1.0-beta.2",
3
+ "version": "0.1.0-beta.4",
4
4
  "description": "Mirror a NativeScript project into an ns-edge project from a terminal or a Vite dev server",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",