nextdesk 0.1.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.
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.build = build;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const ora_1 = __importDefault(require("ora"));
9
+ const execa_1 = __importDefault(require("execa"));
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const nextjs_1 = require("../utils/nextjs");
13
+ async function build(options) {
14
+ const cwd = process.cwd();
15
+ if (!(0, nextjs_1.isNextJsProject)(cwd)) {
16
+ console.error(chalk_1.default.red('✗ Not a Next.js project. Make sure next.config.js exists.'));
17
+ process.exit(1);
18
+ }
19
+ const appName = (0, nextjs_1.getAppName)(cwd);
20
+ console.log(chalk_1.default.bold(`\n📦 Building ${appName} for desktop...\n`));
21
+ const nextSpinner = (0, ora_1.default)('Building Next.js app...').start();
22
+ try {
23
+ await (0, execa_1.default)('npm', ['run', 'build'], { cwd, stdio: 'pipe' });
24
+ nextSpinner.succeed('Next.js app built');
25
+ }
26
+ catch (err) {
27
+ nextSpinner.fail(`Next.js build failed: ${err.message}`);
28
+ process.exit(1);
29
+ }
30
+ const bundleSpinner = (0, ora_1.default)('Bundling desktop runtime...').start();
31
+ try {
32
+ const outputDir = path_1.default.join(cwd, 'dist-desktop');
33
+ fs_1.default.mkdirSync(outputDir, { recursive: true });
34
+ const runtime = (0, nextjs_1.getRuntimeBinary)();
35
+ const outputBinary = path_1.default.join(outputDir, appName);
36
+ fs_1.default.copyFileSync(runtime, outputBinary);
37
+ fs_1.default.chmodSync(outputBinary, 0o755);
38
+ const nextOut = path_1.default.join(cwd, '.next');
39
+ fs_1.default.cpSync(nextOut, path_1.default.join(outputDir, '.next'), { recursive: true });
40
+ fs_1.default.writeFileSync(path_1.default.join(outputDir, 'run.sh'), `#!/bin/bash
41
+ PORT=3000
42
+ npm run start &
43
+ sleep 2
44
+ ./${appName} http://localhost:$PORT "${appName}"
45
+ `);
46
+ fs_1.default.chmodSync(path_1.default.join(outputDir, 'run.sh'), 0o755);
47
+ bundleSpinner.succeed('Runtime bundled');
48
+ console.log(chalk_1.default.bold('\n✨ Build complete!\n'));
49
+ console.log(chalk_1.default.gray(` Output: ${chalk_1.default.cyan(outputDir)}`));
50
+ console.log(chalk_1.default.gray(` Binary: ${chalk_1.default.cyan(outputBinary)}`));
51
+ console.log(chalk_1.default.gray(` Run: ${chalk_1.default.cyan('cd ' + outputDir + ' && ./run.sh')}\n`));
52
+ }
53
+ catch (err) {
54
+ bundleSpinner.fail(`Bundling failed: ${err.message}`);
55
+ process.exit(1);
56
+ }
57
+ }
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.dev = dev;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const ora_1 = __importDefault(require("ora"));
9
+ const execa_1 = __importDefault(require("execa"));
10
+ const nextjs_1 = require("../utils/nextjs");
11
+ async function dev(options) {
12
+ const port = options.port;
13
+ const url = `http://localhost:${port}`;
14
+ const cwd = process.cwd();
15
+ // Check if Next.js project
16
+ if (!(0, nextjs_1.isNextJsProject)(cwd)) {
17
+ console.error(chalk_1.default.red('✗ Not a Next.js project. Make sure next.config.js exists.'));
18
+ process.exit(1);
19
+ }
20
+ const appName = (0, nextjs_1.getAppName)(cwd);
21
+ console.log(chalk_1.default.bold(`\n🚀 MyDesk — ${appName}\n`));
22
+ // Start Next.js dev server
23
+ const nextSpinner = (0, ora_1.default)('Starting Next.js dev server...').start();
24
+ const nextProcess = (0, execa_1.default)('npm', ['run', 'dev'], {
25
+ cwd,
26
+ env: { ...process.env, PORT: port },
27
+ stdio: ['ignore', 'pipe', 'pipe'],
28
+ });
29
+ nextProcess.stdout?.on('data', (data) => {
30
+ const line = data.toString();
31
+ if (line.includes('Ready') || line.includes('ready')) {
32
+ nextSpinner.succeed(`Next.js ready on ${chalk_1.default.cyan(url)}`);
33
+ }
34
+ });
35
+ nextProcess.stderr?.on('data', (data) => {
36
+ const line = data.toString();
37
+ if (line.toLowerCase().includes('error')) {
38
+ console.error(chalk_1.default.red(line.trim()));
39
+ }
40
+ });
41
+ // Wait for server to be ready
42
+ try {
43
+ await (0, nextjs_1.waitForServer)(url);
44
+ }
45
+ catch (err) {
46
+ nextSpinner.fail('Next.js dev server failed to start');
47
+ nextProcess.kill();
48
+ process.exit(1);
49
+ }
50
+ // Launch desktop window
51
+ const windowSpinner = (0, ora_1.default)('Opening desktop window...').start();
52
+ try {
53
+ const runtime = (0, nextjs_1.getRuntimeBinary)();
54
+ const runtimeProcess = (0, execa_1.default)(runtime, [], {
55
+ env: {
56
+ ...process.env,
57
+ MYDESK_URL: url,
58
+ MYDESK_TITLE: appName,
59
+ GDK_BACKEND: 'x11',
60
+ WAYLAND_DISPLAY: '',
61
+ },
62
+ stdio: 'inherit',
63
+ });
64
+ windowSpinner.succeed(`${chalk_1.default.green('✨ Ready!')} Desktop window opened`);
65
+ console.log(chalk_1.default.gray(`\n App: ${chalk_1.default.cyan(url)}`));
66
+ console.log(chalk_1.default.gray(` Runtime: ${runtime}\n`));
67
+ // Clean shutdown
68
+ const cleanup = () => {
69
+ nextProcess.kill();
70
+ runtimeProcess.kill();
71
+ process.exit(0);
72
+ };
73
+ process.on('SIGINT', cleanup);
74
+ process.on('SIGTERM', cleanup);
75
+ await runtimeProcess;
76
+ }
77
+ catch (err) {
78
+ windowSpinner.fail(`Failed to open desktop window: ${err.message}`);
79
+ nextProcess.kill();
80
+ process.exit(1);
81
+ }
82
+ }
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.init = init;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const ora_1 = __importDefault(require("ora"));
9
+ const execa_1 = __importDefault(require("execa"));
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const path_1 = __importDefault(require("path"));
12
+ async function init(options) {
13
+ const cwd = process.cwd();
14
+ const appName = options.name || path_1.default.basename(cwd);
15
+ console.log(chalk_1.default.bold(`\n🚀 Initializing MyDesk project: ${appName}\n`));
16
+ const spinner = (0, ora_1.default)('Setting up project...').start();
17
+ try {
18
+ // Check if Next.js project exists
19
+ const hasNextConfig = fs_1.default.existsSync(path_1.default.join(cwd, 'next.config.js')) ||
20
+ fs_1.default.existsSync(path_1.default.join(cwd, 'next.config.mjs')) ||
21
+ fs_1.default.existsSync(path_1.default.join(cwd, 'next.config.ts'));
22
+ if (!hasNextConfig) {
23
+ spinner.info('No Next.js project found. Creating one...');
24
+ // Create a basic Next.js app
25
+ await (0, execa_1.default)('npx', ['create-next-app@latest', '.', '--typescript', '--tailwind', '--eslint', '--app', '--src-dir', '--no-import-alias'], {
26
+ cwd,
27
+ stdio: 'inherit'
28
+ });
29
+ }
30
+ // Create .mydesk directory
31
+ const mydeskDir = path_1.default.join(cwd, '.mydesk');
32
+ if (!fs_1.default.existsSync(mydeskDir)) {
33
+ fs_1.default.mkdirSync(mydeskDir, { recursive: true });
34
+ }
35
+ // Copy runtime binary
36
+ const runtimeSrc = path_1.default.join(__dirname, '../../target/release/mydesk-poc');
37
+ const runtimeDst = path_1.default.join(mydeskDir, 'mydesk-poc');
38
+ if (fs_1.default.existsSync(runtimeSrc)) {
39
+ fs_1.default.copyFileSync(runtimeSrc, runtimeDst);
40
+ fs_1.default.chmodSync(runtimeDst, 0o755);
41
+ }
42
+ // Create mydesk.config.js
43
+ const configContent = `export default {
44
+ name: "${appName}",
45
+ version: "1.0.0",
46
+ window: {
47
+ width: 900,
48
+ height: 640,
49
+ title: "${appName}",
50
+ },
51
+ permissions: [
52
+ "fs:read",
53
+ "fs:write",
54
+ "shell:open",
55
+ "clipboard:read",
56
+ "clipboard:write",
57
+ "notification:show",
58
+ ],
59
+ }
60
+ `;
61
+ fs_1.default.writeFileSync(path_1.default.join(cwd, 'mydesk.config.js'), configContent);
62
+ // Update package.json scripts
63
+ const packageJsonPath = path_1.default.join(cwd, 'package.json');
64
+ if (fs_1.default.existsSync(packageJsonPath)) {
65
+ const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
66
+ if (!packageJson.scripts) {
67
+ packageJson.scripts = {};
68
+ }
69
+ if (!packageJson.scripts['mydesk:dev']) {
70
+ packageJson.scripts['mydesk:dev'] = 'mydesk dev';
71
+ }
72
+ if (!packageJson.scripts['mydesk:build']) {
73
+ packageJson.scripts['mydesk:build'] = 'mydesk build';
74
+ }
75
+ fs_1.default.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
76
+ }
77
+ spinner.succeed('MyDesk initialized!');
78
+ console.log(chalk_1.default.bold('\n✨ All set!\n'));
79
+ console.log(chalk_1.default.gray(' Run these commands:'));
80
+ console.log(chalk_1.default.cyan(' npm run mydesk:dev # Start desktop app in dev mode'));
81
+ console.log(chalk_1.default.cyan(' npm run mydesk:build # Build desktop app for production\n'));
82
+ }
83
+ catch (err) {
84
+ spinner.fail(`Initialization failed: ${err.message}`);
85
+ process.exit(1);
86
+ }
87
+ }
package/dist/index.js ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const commander_1 = require("commander");
5
+ const dev_1 = require("./commands/dev");
6
+ const build_1 = require("./commands/build");
7
+ const init_1 = require("./commands/init");
8
+ commander_1.program
9
+ .name('mydesk')
10
+ .description('Turn your Next.js app into a desktop app in 30 seconds')
11
+ .version('0.1.0');
12
+ commander_1.program
13
+ .command('init')
14
+ .description('Initialize MyDesk in your Next.js project')
15
+ .option('-n, --name <name>', 'App name')
16
+ .action(init_1.init);
17
+ commander_1.program
18
+ .command('dev')
19
+ .description('Start your Next.js app as a desktop app')
20
+ .option('-p, --port <port>', 'Next.js dev server port', '3000')
21
+ .action(dev_1.dev);
22
+ commander_1.program
23
+ .command('build')
24
+ .description('Build your app for production')
25
+ .option('-t, --target <target>', 'Target platform (windows, macos, linux)', process.platform)
26
+ .action(build_1.build);
27
+ commander_1.program.parse();
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.isNextJsProject = isNextJsProject;
7
+ exports.getPackageJson = getPackageJson;
8
+ exports.getAppName = getAppName;
9
+ exports.waitForServer = waitForServer;
10
+ exports.getRuntimeBinary = getRuntimeBinary;
11
+ const fs_1 = __importDefault(require("fs"));
12
+ const path_1 = __importDefault(require("path"));
13
+ const http_1 = __importDefault(require("http"));
14
+ function isNextJsProject(cwd = process.cwd()) {
15
+ return (fs_1.default.existsSync(path_1.default.join(cwd, 'next.config.js')) ||
16
+ fs_1.default.existsSync(path_1.default.join(cwd, 'next.config.ts')) ||
17
+ fs_1.default.existsSync(path_1.default.join(cwd, 'next.config.mjs')));
18
+ }
19
+ function getPackageJson(cwd = process.cwd()) {
20
+ const pkgPath = path_1.default.join(cwd, 'package.json');
21
+ if (!fs_1.default.existsSync(pkgPath))
22
+ return null;
23
+ return JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf-8'));
24
+ }
25
+ function getAppName(cwd = process.cwd()) {
26
+ const pkg = getPackageJson(cwd);
27
+ return pkg?.name ?? path_1.default.basename(cwd);
28
+ }
29
+ function waitForServer(url, timeout = 60000) {
30
+ return new Promise((resolve, reject) => {
31
+ const start = Date.now();
32
+ const check = () => {
33
+ http_1.default.get(url, (res) => {
34
+ if (res.statusCode && res.statusCode < 500) {
35
+ resolve();
36
+ }
37
+ else {
38
+ retry();
39
+ }
40
+ }).on('error', retry);
41
+ };
42
+ const retry = () => {
43
+ if (Date.now() - start > timeout) {
44
+ reject(new Error(`Server at ${url} did not start within ${timeout}ms`));
45
+ return;
46
+ }
47
+ setTimeout(check, 500);
48
+ };
49
+ check();
50
+ });
51
+ }
52
+ function getRuntimeBinary() {
53
+ // In dev: use the compiled binary from the rust project
54
+ const devBinary = path_1.default.join(__dirname, '../../../target/debug/mydesk-poc');
55
+ if (fs_1.default.existsSync(devBinary))
56
+ return devBinary;
57
+ // In production: binary is bundled next to the CLI
58
+ const prodBinary = path_1.default.join(__dirname, '../bin/mydesk-runtime');
59
+ if (fs_1.default.existsSync(prodBinary))
60
+ return prodBinary;
61
+ throw new Error('MyDesk runtime binary not found. Run `cargo build` first.');
62
+ }
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "nextdesk",
3
+ "version": "0.1.0",
4
+ "description": "Turn your Next.js app into a 5MB desktop app in 30 seconds",
5
+ "bin": {
6
+ "nextdesk": "./dist/index.js"
7
+ },
8
+ "scripts": {
9
+ "build": "tsc && chmod +x dist/index.js",
10
+ "dev": "tsc --watch",
11
+ "start": "node dist/index.js"
12
+ },
13
+ "dependencies": {
14
+ "commander": "^12.0.0",
15
+ "chalk": "^4.1.2",
16
+ "ora": "^5.4.1",
17
+ "execa": "^5.1.1"
18
+ },
19
+ "devDependencies": {
20
+ "typescript": "^5.0.0",
21
+ "@types/node": "^20.0.0"
22
+ }
23
+ }
@@ -0,0 +1,64 @@
1
+ import chalk from 'chalk'
2
+ import ora from 'ora'
3
+ import execa from 'execa'
4
+ import fs from 'fs'
5
+ import path from 'path'
6
+ import { isNextJsProject, getAppName, getRuntimeBinary } from '../utils/nextjs'
7
+
8
+ interface BuildOptions {
9
+ target: string
10
+ }
11
+
12
+ export async function build(options: BuildOptions) {
13
+ const cwd = process.cwd()
14
+
15
+ if (!isNextJsProject(cwd)) {
16
+ console.error(chalk.red('✗ Not a Next.js project. Make sure next.config.js exists.'))
17
+ process.exit(1)
18
+ }
19
+
20
+ const appName = getAppName(cwd)
21
+ console.log(chalk.bold(`\n📦 Building ${appName} for desktop...\n`))
22
+
23
+ const nextSpinner = ora('Building Next.js app...').start()
24
+ try {
25
+ await execa('npm', ['run', 'build'], { cwd, stdio: 'pipe' })
26
+ nextSpinner.succeed('Next.js app built')
27
+ } catch (err: any) {
28
+ nextSpinner.fail(`Next.js build failed: ${err.message}`)
29
+ process.exit(1)
30
+ }
31
+
32
+ const bundleSpinner = ora('Bundling desktop runtime...').start()
33
+ try {
34
+ const outputDir = path.join(cwd, 'dist-desktop')
35
+ fs.mkdirSync(outputDir, { recursive: true })
36
+
37
+ const runtime = getRuntimeBinary()
38
+ const outputBinary = path.join(outputDir, appName)
39
+ fs.copyFileSync(runtime, outputBinary)
40
+ fs.chmodSync(outputBinary, 0o755)
41
+
42
+ const nextOut = path.join(cwd, '.next')
43
+ fs.cpSync(nextOut, path.join(outputDir, '.next'), { recursive: true })
44
+
45
+ fs.writeFileSync(path.join(outputDir, 'run.sh'), `#!/bin/bash
46
+ PORT=3000
47
+ npm run start &
48
+ sleep 2
49
+ ./${appName} http://localhost:$PORT "${appName}"
50
+ `)
51
+ fs.chmodSync(path.join(outputDir, 'run.sh'), 0o755)
52
+
53
+ bundleSpinner.succeed('Runtime bundled')
54
+
55
+ console.log(chalk.bold('\n✨ Build complete!\n'))
56
+ console.log(chalk.gray(` Output: ${chalk.cyan(outputDir)}`))
57
+ console.log(chalk.gray(` Binary: ${chalk.cyan(outputBinary)}`))
58
+ console.log(chalk.gray(` Run: ${chalk.cyan('cd ' + outputDir + ' && ./run.sh')}\n`))
59
+
60
+ } catch (err: any) {
61
+ bundleSpinner.fail(`Bundling failed: ${err.message}`)
62
+ process.exit(1)
63
+ }
64
+ }
@@ -0,0 +1,94 @@
1
+ import chalk from 'chalk'
2
+ import ora from 'ora'
3
+ import execa from 'execa'
4
+ import { isNextJsProject, waitForServer, getRuntimeBinary, getAppName } from '../utils/nextjs'
5
+
6
+ interface DevOptions {
7
+ port: string
8
+ }
9
+
10
+ export async function dev(options: DevOptions) {
11
+ const port = options.port
12
+ const url = `http://localhost:${port}`
13
+ const cwd = process.cwd()
14
+
15
+ // Check if Next.js project
16
+ if (!isNextJsProject(cwd)) {
17
+ console.error(chalk.red('✗ Not a Next.js project. Make sure next.config.js exists.'))
18
+ process.exit(1)
19
+ }
20
+
21
+ const appName = getAppName(cwd)
22
+ console.log(chalk.bold(`\n🚀 MyDesk — ${appName}\n`))
23
+
24
+ // Start Next.js dev server
25
+ const nextSpinner = ora('Starting Next.js dev server...').start()
26
+
27
+ const nextProcess = execa('npm', ['run', 'dev'], {
28
+ cwd,
29
+ env: { ...process.env, PORT: port },
30
+ stdio: ['ignore', 'pipe', 'pipe'],
31
+ })
32
+
33
+ nextProcess.stdout?.on('data', (data: Buffer) => {
34
+ const line = data.toString()
35
+ if (line.includes('Ready') || line.includes('ready')) {
36
+ nextSpinner.succeed(`Next.js ready on ${chalk.cyan(url)}`)
37
+ }
38
+ })
39
+
40
+ nextProcess.stderr?.on('data', (data: Buffer) => {
41
+ const line = data.toString()
42
+ if (line.toLowerCase().includes('error')) {
43
+ console.error(chalk.red(line.trim()))
44
+ }
45
+ })
46
+
47
+ // Wait for server to be ready
48
+ try {
49
+ await waitForServer(url)
50
+ } catch (err) {
51
+ nextSpinner.fail('Next.js dev server failed to start')
52
+ nextProcess.kill()
53
+ process.exit(1)
54
+ }
55
+
56
+ // Launch desktop window
57
+ const windowSpinner = ora('Opening desktop window...').start()
58
+
59
+ try {
60
+ const runtime = getRuntimeBinary()
61
+
62
+ const runtimeProcess = execa(runtime, [], {
63
+ env: {
64
+ ...process.env,
65
+ MYDESK_URL: url,
66
+ MYDESK_TITLE: appName,
67
+ GDK_BACKEND: 'x11',
68
+ WAYLAND_DISPLAY: '',
69
+ },
70
+ stdio: 'inherit',
71
+ })
72
+
73
+ windowSpinner.succeed(`${chalk.green('✨ Ready!')} Desktop window opened`)
74
+ console.log(chalk.gray(`\n App: ${chalk.cyan(url)}`))
75
+ console.log(chalk.gray(` Runtime: ${runtime}\n`))
76
+
77
+ // Clean shutdown
78
+ const cleanup = () => {
79
+ nextProcess.kill()
80
+ runtimeProcess.kill()
81
+ process.exit(0)
82
+ }
83
+
84
+ process.on('SIGINT', cleanup)
85
+ process.on('SIGTERM', cleanup)
86
+
87
+ await runtimeProcess
88
+
89
+ } catch (err: any) {
90
+ windowSpinner.fail(`Failed to open desktop window: ${err.message}`)
91
+ nextProcess.kill()
92
+ process.exit(1)
93
+ }
94
+ }
@@ -0,0 +1,101 @@
1
+ import chalk from 'chalk'
2
+ import ora from 'ora'
3
+ import execa from 'execa'
4
+ import fs from 'fs'
5
+ import path from 'path'
6
+
7
+ interface InitOptions {
8
+ name?: string
9
+ }
10
+
11
+ export async function init(options: InitOptions) {
12
+ const cwd = process.cwd()
13
+ const appName = options.name || path.basename(cwd)
14
+
15
+ console.log(chalk.bold(`\n🚀 Initializing MyDesk project: ${appName}\n`))
16
+
17
+ const spinner = ora('Setting up project...').start()
18
+
19
+ try {
20
+ // Check if Next.js project exists
21
+ const hasNextConfig = fs.existsSync(path.join(cwd, 'next.config.js')) ||
22
+ fs.existsSync(path.join(cwd, 'next.config.mjs')) ||
23
+ fs.existsSync(path.join(cwd, 'next.config.ts'))
24
+
25
+ if (!hasNextConfig) {
26
+ spinner.info('No Next.js project found. Creating one...')
27
+
28
+ // Create a basic Next.js app
29
+ await execa('npx', ['create-next-app@latest', '.', '--typescript', '--tailwind', '--eslint', '--app', '--src-dir', '--no-import-alias'], {
30
+ cwd,
31
+ stdio: 'inherit'
32
+ })
33
+ }
34
+
35
+ // Create .mydesk directory
36
+ const mydeskDir = path.join(cwd, '.mydesk')
37
+ if (!fs.existsSync(mydeskDir)) {
38
+ fs.mkdirSync(mydeskDir, { recursive: true })
39
+ }
40
+
41
+ // Copy runtime binary
42
+ const runtimeSrc = path.join(__dirname, '../../target/release/mydesk-poc')
43
+ const runtimeDst = path.join(mydeskDir, 'mydesk-poc')
44
+
45
+ if (fs.existsSync(runtimeSrc)) {
46
+ fs.copyFileSync(runtimeSrc, runtimeDst)
47
+ fs.chmodSync(runtimeDst, 0o755)
48
+ }
49
+
50
+ // Create mydesk.config.js
51
+ const configContent = `export default {
52
+ name: "${appName}",
53
+ version: "1.0.0",
54
+ window: {
55
+ width: 900,
56
+ height: 640,
57
+ title: "${appName}",
58
+ },
59
+ permissions: [
60
+ "fs:read",
61
+ "fs:write",
62
+ "shell:open",
63
+ "clipboard:read",
64
+ "clipboard:write",
65
+ "notification:show",
66
+ ],
67
+ }
68
+ `
69
+ fs.writeFileSync(path.join(cwd, 'mydesk.config.js'), configContent)
70
+
71
+ // Update package.json scripts
72
+ const packageJsonPath = path.join(cwd, 'package.json')
73
+ if (fs.existsSync(packageJsonPath)) {
74
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'))
75
+
76
+ if (!packageJson.scripts) {
77
+ packageJson.scripts = {}
78
+ }
79
+
80
+ if (!packageJson.scripts['mydesk:dev']) {
81
+ packageJson.scripts['mydesk:dev'] = 'mydesk dev'
82
+ }
83
+ if (!packageJson.scripts['mydesk:build']) {
84
+ packageJson.scripts['mydesk:build'] = 'mydesk build'
85
+ }
86
+
87
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n')
88
+ }
89
+
90
+ spinner.succeed('MyDesk initialized!')
91
+
92
+ console.log(chalk.bold('\n✨ All set!\n'))
93
+ console.log(chalk.gray(' Run these commands:'))
94
+ console.log(chalk.cyan(' npm run mydesk:dev # Start desktop app in dev mode'))
95
+ console.log(chalk.cyan(' npm run mydesk:build # Build desktop app for production\n'))
96
+
97
+ } catch (err: any) {
98
+ spinner.fail(`Initialization failed: ${err.message}`)
99
+ process.exit(1)
100
+ }
101
+ }
package/src/index.ts ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ import { program } from 'commander'
3
+ import { dev } from './commands/dev'
4
+ import { build } from './commands/build'
5
+ import { init } from './commands/init'
6
+
7
+ program
8
+ .name('mydesk')
9
+ .description('Turn your Next.js app into a desktop app in 30 seconds')
10
+ .version('0.1.0')
11
+
12
+ program
13
+ .command('init')
14
+ .description('Initialize MyDesk in your Next.js project')
15
+ .option('-n, --name <name>', 'App name')
16
+ .action(init)
17
+
18
+ program
19
+ .command('dev')
20
+ .description('Start your Next.js app as a desktop app')
21
+ .option('-p, --port <port>', 'Next.js dev server port', '3000')
22
+ .action(dev)
23
+
24
+ program
25
+ .command('build')
26
+ .description('Build your app for production')
27
+ .option('-t, --target <target>', 'Target platform (windows, macos, linux)', process.platform)
28
+ .action(build)
29
+
30
+ program.parse()
@@ -0,0 +1,60 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import http from 'http'
4
+
5
+ export function isNextJsProject(cwd: string = process.cwd()): boolean {
6
+ return (
7
+ fs.existsSync(path.join(cwd, 'next.config.js')) ||
8
+ fs.existsSync(path.join(cwd, 'next.config.ts')) ||
9
+ fs.existsSync(path.join(cwd, 'next.config.mjs'))
10
+ )
11
+ }
12
+
13
+ export function getPackageJson(cwd: string = process.cwd()): Record<string, any> | null {
14
+ const pkgPath = path.join(cwd, 'package.json')
15
+ if (!fs.existsSync(pkgPath)) return null
16
+ return JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
17
+ }
18
+
19
+ export function getAppName(cwd: string = process.cwd()): string {
20
+ const pkg = getPackageJson(cwd)
21
+ return pkg?.name ?? path.basename(cwd)
22
+ }
23
+
24
+ export function waitForServer(url: string, timeout = 60000): Promise<void> {
25
+ return new Promise((resolve, reject) => {
26
+ const start = Date.now()
27
+
28
+ const check = () => {
29
+ http.get(url, (res) => {
30
+ if (res.statusCode && res.statusCode < 500) {
31
+ resolve()
32
+ } else {
33
+ retry()
34
+ }
35
+ }).on('error', retry)
36
+ }
37
+
38
+ const retry = () => {
39
+ if (Date.now() - start > timeout) {
40
+ reject(new Error(`Server at ${url} did not start within ${timeout}ms`))
41
+ return
42
+ }
43
+ setTimeout(check, 500)
44
+ }
45
+
46
+ check()
47
+ })
48
+ }
49
+
50
+ export function getRuntimeBinary(): string {
51
+ // In dev: use the compiled binary from the rust project
52
+ const devBinary = path.join(__dirname, '../../../target/debug/mydesk-poc')
53
+ if (fs.existsSync(devBinary)) return devBinary
54
+
55
+ // In production: binary is bundled next to the CLI
56
+ const prodBinary = path.join(__dirname, '../bin/mydesk-runtime')
57
+ if (fs.existsSync(prodBinary)) return prodBinary
58
+
59
+ throw new Error('MyDesk runtime binary not found. Run `cargo build` first.')
60
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "resolveJsonModule": true
12
+ },
13
+ "include": ["src/**/*"],
14
+ "exclude": ["node_modules", "dist"]
15
+ }