nexus-sync-cli 0.0.0-stage → 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Adityareal
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,293 @@
1
- # Temporary Holding Version
1
+ <div align="center">
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ # ✦ Nexus-Sync CLI
4
+
5
+ **Develop locally, run in the cloud.**
6
+
7
+ A Full-Stack-as-a-Service CLI that mirrors your local dev environment to a live cloud container — your "Shadow Environment" — in real time.
8
+
9
+ [![Node.js](https://img.shields.io/badge/Node.js-18%2B-339933?style=flat-square&logo=node.js&logoColor=white)](https://nodejs.org/)
10
+ [![License](https://img.shields.io/badge/License-MIT-blue?style=flat-square)](LICENSE)
11
+ [![npm](https://img.shields.io/badge/npm-nexus--sync--cli-cb3837?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/nexus-sync-cli)
12
+
13
+ </div>
14
+
15
+ ---
16
+
17
+ ## 🚀 What is Nexus-Sync?
18
+
19
+ Nexus-Sync eliminates the gap between local development and production. When you save a file, it's instantly reflected in your **Shadow Environment** — a live cloud container that mirrors your full stack (frontend, backend, database, edge functions).
20
+
21
+ ```
22
+ Local Machine Shadow Environment (Cloud)
23
+ ┌──────────────────┐ real-time ┌──────────────────────────┐
24
+ │ src/app/page.tsx │ ──── sync ────▶ │ https://my-app.nexus- │
25
+ │ api/users.ts │ (20-60ms) │ sync.example │
26
+ │ db/schema.sql │ │ │
27
+ └──────────────────┘ │ ✔ Live preview │
28
+ │ ✔ Shared with team │
29
+ │ ✔ Full-stack sandbox │
30
+ └──────────────────────────┘
31
+ ```
32
+
33
+ ### Why?
34
+
35
+ - **No more "works on my machine"** — your Shadow Environment IS the deployed version
36
+ - **Instant feedback** — every save is live in ~35ms
37
+ - **Team collaboration** — invite teammates to your live environment
38
+ - **Safe database testing** — spin up anonymized DB sandboxes in seconds
39
+ - **One-command deploy** — push to production when you're ready
40
+
41
+ ---
42
+
43
+ ## 📦 Installation
44
+
45
+ ### Global install (recommended)
46
+
47
+ ```bash
48
+ npm install -g nexus-sync-cli
49
+ ```
50
+
51
+ Then use `nexus-sync` from any project directory.
52
+
53
+ ### Local install
54
+
55
+ ```bash
56
+ npm install nexus-sync-cli
57
+ npx nexus-sync --help
58
+ ```
59
+
60
+ ### From source
61
+
62
+ ```bash
63
+ git clone https://github.com/Adityareal/nexus-sync-cli.git
64
+ cd nexus-sync-cli
65
+ npm install
66
+ node bin/nexus-sync.js --help
67
+ ```
68
+
69
+ > **Requirements:** Node.js 18 or higher
70
+
71
+ ---
72
+
73
+ ## ⚡ Quick Start
74
+
75
+ ### 1. Initialize your project
76
+
77
+ ```bash
78
+ nexus-sync init
79
+ ```
80
+
81
+ This walks you through an interactive setup:
82
+
83
+ ```
84
+ ✦ Nexus-Sync
85
+
86
+ ? Project name: my-app
87
+ ? Framework: Next.js
88
+ ? Preferred region: us-east-1
89
+
90
+ ✔ Project initialized!
91
+ Config written to .nexus/config.json
92
+ ```
93
+
94
+ A `.nexus/config.json` file is created with your preferences:
95
+
96
+ ```json
97
+ {
98
+ "projectName": "my-app",
99
+ "framework": "Next.js",
100
+ "region": "us-east-1",
101
+ "watchPaths": ["src/", "app/", "pages/", "api/"],
102
+ "createdAt": "2026-05-30T12:00:00.000Z"
103
+ }
104
+ ```
105
+
106
+ ### 2. Start syncing
107
+
108
+ ```bash
109
+ nexus-sync start
110
+ ```
111
+
112
+ ```
113
+ ✦ Nexus-Sync
114
+
115
+ Watching: src/, app/, pages/, api/
116
+ ✦ Shadow live at: https://my-app.nexus-sync.example
117
+
118
+ [12:04:31] ✔ Synced src/app/page.tsx → shadow (38ms)
119
+ [12:04:45] ✔ Synced api/users.ts → shadow (24ms)
120
+ [12:05:02] ✔ Added src/lib/utils.ts → shadow (31ms)
121
+ ```
122
+
123
+ The watcher monitors your source files in real time. Press `Ctrl+C` to stop.
124
+
125
+ ### 3. Check status
126
+
127
+ ```bash
128
+ nexus-sync status
129
+ ```
130
+
131
+ ```
132
+ ✦ Nexus-Sync
133
+
134
+ Shadow Environment
135
+ ─────────────────────────────────
136
+ Sync state ● active
137
+ Last synced src/app/page.tsx (2s ago)
138
+ Uptime 00:14:32
139
+ Live URL https://my-app.nexus-sync.example
140
+ Latency ⚡ 34ms
141
+ ```
142
+
143
+ ---
144
+
145
+ ## 📖 All Commands
146
+
147
+ | Command | Description |
148
+ |---------|-------------|
149
+ | `nexus-sync init` | Interactive project setup → creates `.nexus/config.json` |
150
+ | `nexus-sync start` | Start real-time file sync to your Shadow Environment |
151
+ | `nexus-sync stop` | Stop the file watcher gracefully |
152
+ | `nexus-sync status` | Show sync state, uptime, latency, and live URL |
153
+ | `nexus-sync db sandbox` | Create an isolated database sandbox with anonymized data |
154
+ | `nexus-sync collab invite <email>` | Invite a teammate to your live environment |
155
+ | `nexus-sync test` | Run the test suite against your Shadow Environment |
156
+ | `nexus-sync deploy` | Deploy to production |
157
+ | `nexus-sync --help` | Show all available commands |
158
+
159
+ ---
160
+
161
+ ### `nexus-sync db sandbox`
162
+
163
+ Spin up an isolated database sandbox cloned from production with PII automatically anonymized:
164
+
165
+ ```
166
+ ✦ Nexus-Sync
167
+
168
+ → Cloning production schema... ████████████████ 100%
169
+ → Anonymizing PII fields... ████████████████ 100%
170
+
171
+ ✔ Sandbox ready at: postgres://sandbox.nexus-sync.example:5432/my-app_sandbox
172
+ ```
173
+
174
+ ---
175
+
176
+ ### `nexus-sync collab invite <email>`
177
+
178
+ Share your Shadow Environment with teammates:
179
+
180
+ ```bash
181
+ nexus-sync collab invite teammate@company.com
182
+ ```
183
+
184
+ ```
185
+ ✦ Nexus-Sync
186
+
187
+ ✔ Invite sent to teammate@company.com
188
+ They can join at: https://my-app.nexus-sync.example?collab=a1b2c3d4e5f6
189
+ ```
190
+
191
+ ---
192
+
193
+ ### `nexus-sync test`
194
+
195
+ Run your test suite against the Shadow Environment:
196
+
197
+ ```
198
+ ✦ Nexus-Sync
199
+
200
+ ✔ API route /api/users returns 200 (12ms)
201
+ ✔ DB connection healthy (8ms)
202
+ ✔ Auth middleware blocks unauthenticated (5ms)
203
+ ✔ Edge function /api/geo responds correctly (19ms)
204
+ ✗ Email service timeout (3001ms)
205
+
206
+ ─────────────────────────────────
207
+ 4 passed, 1 failed
208
+ ```
209
+
210
+ ---
211
+
212
+ ### `nexus-sync deploy`
213
+
214
+ Ship to production with a single command:
215
+
216
+ ```
217
+ ✦ Nexus-Sync
218
+
219
+ ✔ ⚡ Building project...
220
+ ✔ ⚡ Running pre-deploy tests...
221
+ ✔ ⚡ Pushing to Shadow Environment...
222
+ ✔ ⚡ Warming edge nodes...
223
+
224
+ ✦ Production deploy complete: https://my-app.nexus-sync.example
225
+ ✦ Deploy time: 2.4s
226
+ ```
227
+
228
+ ---
229
+
230
+ ## 🗂️ Project Structure
231
+
232
+ ```
233
+ nexus-sync-cli/
234
+ ├── bin/
235
+ │ └── nexus-sync.js # CLI entry point
236
+ ├── src/
237
+ │ ├── commands/
238
+ │ │ ├── init.js # Interactive project setup
239
+ │ │ ├── start.js # File watcher + sync
240
+ │ │ ├── stop.js # Graceful shutdown
241
+ │ │ ├── status.js # Status dashboard
242
+ │ │ ├── db.js # Database sandbox
243
+ │ │ ├── collab.js # Collaboration invites
244
+ │ │ ├── test.js # Test runner
245
+ │ │ └── deploy.js # Production deployment
246
+ │ └── utils/
247
+ │ ├── logger.js # Shared chalk helpers
248
+ │ └── config.js # .nexus/config.json I/O
249
+ ├── package.json
250
+ ├── README.md
251
+ └── .gitignore
252
+ ```
253
+
254
+ ---
255
+
256
+ ## 🛠️ Tech Stack
257
+
258
+ | Package | Purpose |
259
+ |---------|---------|
260
+ | [commander](https://www.npmjs.com/package/commander) | Command parsing and help generation |
261
+ | [chalk](https://www.npmjs.com/package/chalk) | Terminal colors and styling |
262
+ | [ora](https://www.npmjs.com/package/ora) | Elegant terminal spinners |
263
+ | [cli-progress](https://www.npmjs.com/package/cli-progress) | Progress bars |
264
+ | [chokidar](https://www.npmjs.com/package/chokidar) | Cross-platform file watching |
265
+ | [inquirer](https://www.npmjs.com/package/inquirer) | Interactive command-line prompts |
266
+
267
+ ---
268
+
269
+ ## 🔧 Configuration
270
+
271
+ The `.nexus/config.json` file controls the CLI behavior:
272
+
273
+ | Field | Type | Description |
274
+ |-------|------|-------------|
275
+ | `projectName` | `string` | Your project's slug (used in URLs) |
276
+ | `framework` | `string` | `Next.js` \| `Remix` \| `SvelteKit` \| `Other` |
277
+ | `region` | `string` | Cloud region: `us-east-1` \| `eu-west-1` \| `ap-south-1` |
278
+ | `watchPaths` | `string[]` | Directories to watch for file changes |
279
+ | `createdAt` | `string` | ISO timestamp of initialization |
280
+
281
+ You can manually edit this file or re-run `nexus-sync init`.
282
+
283
+ ---
284
+
285
+ ## 📝 Note
286
+
287
+ This CLI is currently a **simulation/prototype**. All sync, deploy, and database operations produce realistic terminal output but don't connect to actual cloud infrastructure. It's designed to demonstrate the developer experience of a full-stack shadow environment platform.
288
+
289
+ ---
290
+
291
+ ## 📄 License
292
+
293
+ MIT © [Adityareal](https://github.com/Adityareal)
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync } from 'node:fs';
4
+ import { Command } from 'commander';
5
+ import chalk from 'chalk';
6
+ import { registerInit } from '../src/commands/init.js';
7
+ import { registerStart } from '../src/commands/start.js';
8
+ import { registerStop } from '../src/commands/stop.js';
9
+ import { registerStatus } from '../src/commands/status.js';
10
+ import { registerDb } from '../src/commands/db.js';
11
+ import { registerCollab } from '../src/commands/collab.js';
12
+ import { registerTest } from '../src/commands/test.js';
13
+ import { registerDeploy } from '../src/commands/deploy.js';
14
+
15
+ const pkg = JSON.parse(
16
+ readFileSync(new URL('../package.json', import.meta.url), 'utf-8')
17
+ );
18
+
19
+ const program = new Command();
20
+
21
+ program
22
+ .name('nexus-sync')
23
+ .version(pkg.version)
24
+ .description(
25
+ chalk.bold.cyan('✦ Nexus-Sync') +
26
+ ' — develop locally, run in the cloud.'
27
+ );
28
+
29
+ registerInit(program);
30
+ registerStart(program);
31
+ registerStop(program);
32
+ registerStatus(program);
33
+ registerDb(program);
34
+ registerCollab(program);
35
+ registerTest(program);
36
+ registerDeploy(program);
37
+
38
+ program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,43 @@
1
1
  {
2
2
  "name": "nexus-sync-cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Nexus-Sync CLI: develop locally, run in the cloud. A simulation/prototype that demos the shadow-environment workflow; it does not connect to real cloud infrastructure.",
5
+ "type": "module",
6
+ "bin": {
7
+ "nexus-sync": "./bin/nexus-sync.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "src",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "engines": {
16
+ "node": ">=18.0.0"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/Adityareal/nexus-sync-cli.git"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/Adityareal/nexus-sync-cli/issues"
24
+ },
25
+ "homepage": "https://github.com/Adityareal/nexus-sync-cli#readme",
26
+ "keywords": [
27
+ "cli",
28
+ "sync",
29
+ "cloud",
30
+ "shadow-environment",
31
+ "full-stack"
32
+ ],
33
+ "author": "Adityareal",
34
+ "license": "MIT",
35
+ "dependencies": {
36
+ "chalk": "^5.3.0",
37
+ "chokidar": "^3.6.0",
38
+ "cli-progress": "^3.12.0",
39
+ "commander": "^12.1.0",
40
+ "inquirer": "^9.3.7",
41
+ "ora": "^8.1.1"
42
+ }
43
+ }
@@ -0,0 +1,41 @@
1
+ import crypto from 'node:crypto';
2
+ import chalk from 'chalk';
3
+ import { readConfig, configExists } from '../utils/config.js';
4
+ import { header, error, success } from '../utils/logger.js';
5
+
6
+ /**
7
+ * Register the `collab` command with subcommand `invite`.
8
+ * @param {import('commander').Command} program
9
+ */
10
+ export function registerCollab(program) {
11
+ const collab = program
12
+ .command('collab')
13
+ .description('Collaboration commands');
14
+
15
+ collab
16
+ .command('invite <email>')
17
+ .description('Invite a collaborator by email')
18
+ .action((email) => {
19
+ header();
20
+
21
+ if (!configExists()) {
22
+ error('No config found. Run `nexus-sync init` first.');
23
+ process.exit(1);
24
+ }
25
+
26
+ const config = readConfig();
27
+ const projectName = config.projectName || 'my-app';
28
+ const token = crypto.randomBytes(8).toString('hex');
29
+ const collabUrl =
30
+ `https://${projectName}.nexus-sync.example` +
31
+ `?collab=${token}`;
32
+
33
+ console.log();
34
+ success(`Invite sent to ${chalk.bold(email)}`);
35
+ console.log(
36
+ chalk.gray(` They can join at: `) +
37
+ chalk.cyan.underline(collabUrl)
38
+ );
39
+ console.log();
40
+ });
41
+ }
@@ -0,0 +1,78 @@
1
+ import chalk from 'chalk';
2
+ import cliProgress from 'cli-progress';
3
+ import { readConfig, configExists } from '../utils/config.js';
4
+ import { header, error, success, sleep } from '../utils/logger.js';
5
+
6
+ /**
7
+ * Register the `db` command with subcommand `sandbox`.
8
+ * @param {import('commander').Command} program
9
+ */
10
+ export function registerDb(program) {
11
+ const db = program
12
+ .command('db')
13
+ .description('Database management commands');
14
+
15
+ db.command('sandbox')
16
+ .description('Create an isolated database sandbox')
17
+ .action(async () => {
18
+ header();
19
+
20
+ if (!configExists()) {
21
+ error('No config found. Run `nexus-sync init` first.');
22
+ process.exit(1);
23
+ }
24
+
25
+ const config = readConfig();
26
+ const projectName = config.projectName || 'my-app';
27
+
28
+ console.log();
29
+
30
+ // Step 1: Clone schema
31
+ process.stdout.write(
32
+ chalk.white(' → Cloning production schema... ')
33
+ );
34
+ const bar1 = new cliProgress.SingleBar(
35
+ {
36
+ format: `${chalk.cyan('{bar}')} {percentage}%`,
37
+ barCompleteChar: '█',
38
+ barIncompleteChar: '░',
39
+ hideCursor: true,
40
+ },
41
+ cliProgress.Presets.shades_classic
42
+ );
43
+ bar1.start(100, 0);
44
+ for (let i = 0; i <= 100; i += 5) {
45
+ bar1.update(i);
46
+ await sleep(60);
47
+ }
48
+ bar1.stop();
49
+
50
+ // Step 2: Anonymize PII
51
+ process.stdout.write(
52
+ chalk.white(' → Anonymizing PII fields... ')
53
+ );
54
+ const bar2 = new cliProgress.SingleBar(
55
+ {
56
+ format: `${chalk.cyan('{bar}')} {percentage}%`,
57
+ barCompleteChar: '█',
58
+ barIncompleteChar: '░',
59
+ hideCursor: true,
60
+ },
61
+ cliProgress.Presets.shades_classic
62
+ );
63
+ bar2.start(100, 0);
64
+ for (let i = 0; i <= 100; i += 4) {
65
+ bar2.update(Math.min(i, 100));
66
+ await sleep(50);
67
+ }
68
+ bar2.update(100);
69
+ bar2.stop();
70
+
71
+ console.log();
72
+ const sandboxUrl =
73
+ `postgres://sandbox.nexus-sync.example:5432` +
74
+ `/${projectName}_sandbox`;
75
+ success(`Sandbox ready at: ${chalk.underline(sandboxUrl)}`);
76
+ console.log();
77
+ });
78
+ }
@@ -0,0 +1,61 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { readConfig, configExists } from '../utils/config.js';
4
+ import { header, error, sleep } from '../utils/logger.js';
5
+
6
+ const DEPLOY_STEPS = [
7
+ { text: 'Building project...', duration: 600 },
8
+ { text: 'Running pre-deploy tests...', duration: 600 },
9
+ { text: 'Pushing to Shadow Environment...', duration: 600 },
10
+ { text: 'Warming edge nodes...', duration: 600 },
11
+ ];
12
+
13
+ /**
14
+ * Register the `deploy` command.
15
+ * @param {import('commander').Command} program
16
+ */
17
+ export function registerDeploy(program) {
18
+ program
19
+ .command('deploy')
20
+ .description('Deploy to production')
21
+ .action(async () => {
22
+ header();
23
+
24
+ if (!configExists()) {
25
+ error('No config found. Run `nexus-sync init` first.');
26
+ process.exit(1);
27
+ }
28
+
29
+ const config = readConfig();
30
+ const projectName = config.projectName || 'my-app';
31
+ const liveUrl = `https://${projectName}.nexus-sync.example`;
32
+
33
+ console.log();
34
+ const startTime = Date.now();
35
+
36
+ for (const step of DEPLOY_STEPS) {
37
+ const spinner = ora({
38
+ text: `⚡ ${step.text}`,
39
+ indent: 2,
40
+ }).start();
41
+ await sleep(step.duration);
42
+ spinner.succeed(`⚡ ${step.text}`);
43
+ }
44
+
45
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
46
+
47
+ console.log();
48
+ console.log(
49
+ chalk.bold.cyan(
50
+ ` ✦ Production deploy complete: ` +
51
+ chalk.underline(liveUrl)
52
+ )
53
+ );
54
+ console.log(
55
+ chalk.bold.cyan(
56
+ ` ✦ Deploy time: ${elapsed}s`
57
+ )
58
+ );
59
+ console.log();
60
+ });
61
+ }
@@ -0,0 +1,75 @@
1
+ import inquirer from 'inquirer';
2
+ import chalk from 'chalk';
3
+ import { writeConfig, configExists } from '../utils/config.js';
4
+ import { header, success, warn, info } from '../utils/logger.js';
5
+
6
+ /**
7
+ * Register the `init` command.
8
+ * @param {import('commander').Command} program
9
+ */
10
+ export function registerInit(program) {
11
+ program
12
+ .command('init')
13
+ .description('Scaffold a .nexus/ config directory')
14
+ .action(async () => {
15
+ header();
16
+
17
+ if (configExists()) {
18
+ warn('A .nexus/config.json already exists.');
19
+ const { overwrite } = await inquirer.prompt([
20
+ {
21
+ type: 'confirm',
22
+ name: 'overwrite',
23
+ message: 'Overwrite existing config?',
24
+ default: false,
25
+ },
26
+ ]);
27
+ if (!overwrite) {
28
+ info('Aborted.');
29
+ return;
30
+ }
31
+ }
32
+
33
+ const answers = await inquirer.prompt([
34
+ {
35
+ type: 'input',
36
+ name: 'projectName',
37
+ message: 'Project name:',
38
+ default: 'my-app',
39
+ validate: (v) =>
40
+ /^[a-z0-9-]+$/.test(v)
41
+ ? true
42
+ : 'Lowercase alphanumeric + hyphens only',
43
+ },
44
+ {
45
+ type: 'list',
46
+ name: 'framework',
47
+ message: 'Framework:',
48
+ choices: ['Next.js', 'Remix', 'SvelteKit', 'Other'],
49
+ },
50
+ {
51
+ type: 'list',
52
+ name: 'region',
53
+ message: 'Preferred region:',
54
+ choices: ['us-east-1', 'eu-west-1', 'ap-south-1'],
55
+ },
56
+ ]);
57
+
58
+ const config = {
59
+ projectName: answers.projectName,
60
+ framework: answers.framework,
61
+ region: answers.region,
62
+ watchPaths: ['src/', 'app/', 'pages/', 'api/'],
63
+ createdAt: new Date().toISOString(),
64
+ };
65
+
66
+ writeConfig(config);
67
+
68
+ console.log();
69
+ success('Project initialized!');
70
+ info(
71
+ `Config written to ${chalk.underline('.nexus/config.json')}`
72
+ );
73
+ console.log();
74
+ });
75
+ }
@@ -0,0 +1,100 @@
1
+ import chokidar from 'chokidar';
2
+ import chalk from 'chalk';
3
+ import { readConfig, configExists } from '../utils/config.js';
4
+ import {
5
+ header,
6
+ success,
7
+ error,
8
+ info,
9
+ timestamp,
10
+ } from '../utils/logger.js';
11
+
12
+ /**
13
+ * Register the `start` command.
14
+ * @param {import('commander').Command} program
15
+ */
16
+ export function registerStart(program) {
17
+ program
18
+ .command('start')
19
+ .description('Start real-time file sync to shadow env')
20
+ .action(() => {
21
+ header();
22
+
23
+ if (!configExists()) {
24
+ error('No config found. Run `nexus-sync init` first.');
25
+ process.exit(1);
26
+ }
27
+
28
+ const config = readConfig();
29
+ const projectName = config.projectName || 'my-app';
30
+ const watchPaths = config.watchPaths || [
31
+ 'src/',
32
+ 'app/',
33
+ 'pages/',
34
+ 'api/',
35
+ ];
36
+
37
+ const liveUrl = `https://${projectName}.nexus-sync.example`;
38
+
39
+ console.log();
40
+ info(`Watching: ${chalk.cyan(watchPaths.join(', '))}`);
41
+ console.log(
42
+ chalk.bold.cyan(` ✦ Shadow live at: ${chalk.underline(liveUrl)}`)
43
+ );
44
+ console.log();
45
+
46
+ const watcher = chokidar.watch(watchPaths, {
47
+ ignoreInitial: true,
48
+ ignored: [
49
+ '**/node_modules/**',
50
+ '**/.git/**',
51
+ '**/.nexus/**',
52
+ ],
53
+ });
54
+
55
+ watcher.on('change', (filePath) => {
56
+ const latency = Math.floor(Math.random() * 41) + 20; // 20-60ms
57
+ console.log(
58
+ `${timestamp()} ${chalk.green('✔')} Synced ` +
59
+ `${chalk.white(filePath)} → ` +
60
+ `${chalk.cyan('shadow')} ` +
61
+ `${chalk.gray(`(${latency}ms)`)}`
62
+ );
63
+ });
64
+
65
+ watcher.on('add', (filePath) => {
66
+ const latency = Math.floor(Math.random() * 41) + 20;
67
+ console.log(
68
+ `${timestamp()} ${chalk.green('✔')} Added ` +
69
+ `${chalk.white(filePath)} → ` +
70
+ `${chalk.cyan('shadow')} ` +
71
+ `${chalk.gray(`(${latency}ms)`)}`
72
+ );
73
+ });
74
+
75
+ watcher.on('unlink', (filePath) => {
76
+ console.log(
77
+ `${timestamp()} ${chalk.yellow('⚠')} Removed ` +
78
+ `${chalk.white(filePath)}`
79
+ );
80
+ });
81
+
82
+ watcher.on('error', (err) => {
83
+ error(`Watcher error: ${err.message}`);
84
+ });
85
+
86
+ // Graceful shutdown
87
+ const shutdown = () => {
88
+ console.log();
89
+ info('Stopping file watcher...');
90
+ watcher.close().then(() => {
91
+ success('Shadow sync stopped. Goodbye! 👋');
92
+ console.log();
93
+ process.exit(0);
94
+ });
95
+ };
96
+
97
+ process.on('SIGINT', shutdown);
98
+ process.on('SIGTERM', shutdown);
99
+ });
100
+ }
@@ -0,0 +1,44 @@
1
+ import chalk from 'chalk';
2
+ import { readConfig, configExists } from '../utils/config.js';
3
+ import { header, error, keyValue } from '../utils/logger.js';
4
+
5
+ /**
6
+ * Register the `status` command.
7
+ * @param {import('commander').Command} program
8
+ */
9
+ export function registerStatus(program) {
10
+ program
11
+ .command('status')
12
+ .description('Show current sync status')
13
+ .action(() => {
14
+ header();
15
+
16
+ if (!configExists()) {
17
+ error('No config found. Run `nexus-sync init` first.');
18
+ process.exit(1);
19
+ }
20
+
21
+ const config = readConfig();
22
+ const projectName = config.projectName || 'my-app';
23
+ const liveUrl = `https://${projectName}.nexus-sync.example`;
24
+ const latency = Math.floor(Math.random() * 41) + 20;
25
+
26
+ // Simulated uptime
27
+ const uptimeH = Math.floor(Math.random() * 2);
28
+ const uptimeM = Math.floor(Math.random() * 60);
29
+ const uptimeS = Math.floor(Math.random() * 60);
30
+ const hh = String(uptimeH).padStart(2, '0');
31
+ const mm = String(uptimeM).padStart(2, '0');
32
+ const ss = String(uptimeS).padStart(2, '0');
33
+
34
+ console.log();
35
+ console.log(chalk.bold.white(' Shadow Environment'));
36
+ console.log(chalk.gray(' ─────────────────────────────────'));
37
+ keyValue('Sync state ', chalk.green('● active'));
38
+ keyValue('Last synced', `src/app/page.tsx ${chalk.gray('(2s ago)')}`);
39
+ keyValue('Uptime ', `${hh}:${mm}:${ss}`);
40
+ keyValue('Live URL ', chalk.cyan.underline(liveUrl));
41
+ keyValue('Latency ', `⚡ ${chalk.green(`${latency}ms`)}`);
42
+ console.log();
43
+ });
44
+ }
@@ -0,0 +1,26 @@
1
+ import chalk from 'chalk';
2
+ import { header, success, info } from '../utils/logger.js';
3
+
4
+ /**
5
+ * Register the `stop` command.
6
+ * @param {import('commander').Command} program
7
+ */
8
+ export function registerStop(program) {
9
+ program
10
+ .command('stop')
11
+ .description('Stop the shadow sync watcher')
12
+ .action(() => {
13
+ header();
14
+ console.log();
15
+ info('Shutting down shadow environment...');
16
+ success('File watcher stopped.');
17
+ success('Shadow environment suspended.');
18
+ console.log();
19
+ console.log(
20
+ chalk.gray(
21
+ ' Run `nexus-sync start` to resume syncing.'
22
+ )
23
+ );
24
+ console.log();
25
+ });
26
+ }
@@ -0,0 +1,66 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { header, sleep } from '../utils/logger.js';
4
+
5
+ const TEST_CASES = [
6
+ { name: 'API route /api/users returns 200', pass: true, ms: 12 },
7
+ { name: 'DB connection healthy', pass: true, ms: 8 },
8
+ { name: 'Auth middleware blocks unauthenticated', pass: true, ms: 5 },
9
+ { name: 'Edge function /api/geo responds correctly', pass: true, ms: 19 },
10
+ { name: 'Email service timeout', pass: false, ms: 3001 },
11
+ ];
12
+
13
+ /**
14
+ * Register the `test` command.
15
+ * @param {import('commander').Command} program
16
+ */
17
+ export function registerTest(program) {
18
+ program
19
+ .command('test')
20
+ .description('Run simulated test suite')
21
+ .action(async () => {
22
+ header();
23
+ console.log();
24
+
25
+ const spinner = ora({
26
+ text: 'Running test suite...',
27
+ indent: 2,
28
+ }).start();
29
+
30
+ await sleep(1500);
31
+ spinner.stop();
32
+
33
+ let passed = 0;
34
+ let failed = 0;
35
+
36
+ for (const tc of TEST_CASES) {
37
+ await sleep(180);
38
+ const timeStr = chalk.gray(`(${tc.ms}ms)`);
39
+ if (tc.pass) {
40
+ passed++;
41
+ console.log(
42
+ ` ${chalk.green('✔')} ${chalk.white(tc.name)}` +
43
+ ` ${timeStr}`
44
+ );
45
+ } else {
46
+ failed++;
47
+ console.log(
48
+ ` ${chalk.red('✗')} ${chalk.white(tc.name)}` +
49
+ ` ${timeStr}`
50
+ );
51
+ }
52
+ }
53
+
54
+ console.log();
55
+ console.log(
56
+ chalk.gray(' ─────────────────────────────────')
57
+ );
58
+
59
+ const summary = [];
60
+ if (passed > 0) summary.push(chalk.green(`${passed} passed`));
61
+ if (failed > 0) summary.push(chalk.red(`${failed} failed`));
62
+
63
+ console.log(` ${summary.join(chalk.gray(', '))}`);
64
+ console.log();
65
+ });
66
+ }
@@ -0,0 +1,51 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+
4
+ const NEXUS_DIR = '.nexus';
5
+ const CONFIG_FILE = 'config.json';
6
+
7
+ /**
8
+ * Resolve the path to the .nexus directory in the cwd.
9
+ * @returns {string}
10
+ */
11
+ export function nexusDir() {
12
+ return join(process.cwd(), NEXUS_DIR);
13
+ }
14
+
15
+ /**
16
+ * Resolve the path to the config file.
17
+ * @returns {string}
18
+ */
19
+ export function configPath() {
20
+ return join(nexusDir(), CONFIG_FILE);
21
+ }
22
+
23
+ /**
24
+ * Check whether a .nexus/config.json exists.
25
+ * @returns {boolean}
26
+ */
27
+ export function configExists() {
28
+ return existsSync(configPath());
29
+ }
30
+
31
+ /**
32
+ * Read and parse the config file.
33
+ * @returns {object}
34
+ */
35
+ export function readConfig() {
36
+ const raw = readFileSync(configPath(), 'utf-8');
37
+ return JSON.parse(raw);
38
+ }
39
+
40
+ /**
41
+ * Write a config object to .nexus/config.json.
42
+ * Creates the .nexus/ directory if it doesn't exist.
43
+ * @param {object} config
44
+ */
45
+ export function writeConfig(config) {
46
+ const dir = nexusDir();
47
+ if (!existsSync(dir)) {
48
+ mkdirSync(dir, { recursive: true });
49
+ }
50
+ writeFileSync(configPath(), JSON.stringify(config, null, 2) + '\n');
51
+ }
@@ -0,0 +1,70 @@
1
+ import chalk from 'chalk';
2
+
3
+ /**
4
+ * Print the branded header line.
5
+ */
6
+ export function header() {
7
+ console.log(chalk.bold.cyan('\n✦ Nexus-Sync'));
8
+ }
9
+
10
+ /**
11
+ * Print a success line.
12
+ * @param {string} msg
13
+ */
14
+ export function success(msg) {
15
+ console.log(chalk.green(` ✔ ${msg}`));
16
+ }
17
+
18
+ /**
19
+ * Print a warning line.
20
+ * @param {string} msg
21
+ */
22
+ export function warn(msg) {
23
+ console.log(chalk.yellow(` ⚠ ${msg}`));
24
+ }
25
+
26
+ /**
27
+ * Print an error line.
28
+ * @param {string} msg
29
+ */
30
+ export function error(msg) {
31
+ console.log(chalk.red(` ✗ ${msg}`));
32
+ }
33
+
34
+ /**
35
+ * Print an info line.
36
+ * @param {string} msg
37
+ */
38
+ export function info(msg) {
39
+ console.log(chalk.white(` ${msg}`));
40
+ }
41
+
42
+ /**
43
+ * Print a highlighted URL or key value.
44
+ * @param {string} label
45
+ * @param {string} value
46
+ */
47
+ export function keyValue(label, value) {
48
+ console.log(` ${chalk.gray(label)} ${chalk.bold.white(value)}`);
49
+ }
50
+
51
+ /**
52
+ * Return a formatted timestamp string [HH:MM:SS].
53
+ * @returns {string}
54
+ */
55
+ export function timestamp() {
56
+ const now = new Date();
57
+ const hh = String(now.getHours()).padStart(2, '0');
58
+ const mm = String(now.getMinutes()).padStart(2, '0');
59
+ const ss = String(now.getSeconds()).padStart(2, '0');
60
+ return chalk.gray(`[${hh}:${mm}:${ss}]`);
61
+ }
62
+
63
+ /**
64
+ * Utility: sleep for ms milliseconds.
65
+ * @param {number} ms
66
+ * @returns {Promise<void>}
67
+ */
68
+ export function sleep(ms) {
69
+ return new Promise((resolve) => setTimeout(resolve, ms));
70
+ }