moontraze 1.0.1 → 1.0.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/bin/moon.js ADDED
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * moon <platform> <command>
4
+ * Example: moon moontraze login
5
+ * moon moontraze deploy --prod
6
+ */
7
+ const chalk = require('chalk');
8
+ const path = require('path');
9
+
10
+ const PLATFORMS = {
11
+ moontraze: {
12
+ label: 'Moontraze',
13
+ runner: path.join(__dirname, 'moontraze.js'),
14
+ },
15
+ // future:
16
+ // otherapp: { label: 'Other', runner: path.join(__dirname, 'other.js') },
17
+ };
18
+
19
+ function printRootHelp() {
20
+ console.log(`
21
+ ${chalk.bold('moon')} — multi-platform CLI
22
+
23
+ Usage:
24
+ moon <platform> <command>
25
+
26
+ Platforms:
27
+ moontraze Moontraze hosting (moontraze.com)
28
+
29
+ Examples:
30
+ moon moontraze login
31
+ moon moontraze link my-site
32
+ moon moontraze deploy --prod
33
+ moon moontraze --prod
34
+
35
+ Same as:
36
+ npx moontraze login
37
+ npx moontraze deploy --prod
38
+ `);
39
+ }
40
+
41
+ const args = process.argv.slice(2);
42
+ const platform = (args[0] || '').toLowerCase();
43
+
44
+ if (!platform || platform === '-h' || platform === '--help' || platform === 'help') {
45
+ printRootHelp();
46
+ process.exit(0);
47
+ }
48
+
49
+ if (platform === '-v' || platform === '--version') {
50
+ try {
51
+ console.log(require('../package.json').version);
52
+ } catch {
53
+ console.log('0.0.0');
54
+ }
55
+ process.exit(0);
56
+ }
57
+
58
+ const meta = PLATFORMS[platform];
59
+ if (!meta) {
60
+ console.error(chalk.red(`Unknown platform: "${platform}"`));
61
+ console.error(chalk.gray('Known: ' + Object.keys(PLATFORMS).join(', ')));
62
+ console.error(chalk.gray('Example: moon moontraze login'));
63
+ process.exit(1);
64
+ }
65
+
66
+ // Forward remaining args to platform CLI
67
+ process.argv = [process.argv[0], meta.runner, ...args.slice(1)];
68
+ require(meta.runner);
package/bin/moontraze.js CHANGED
@@ -2,21 +2,21 @@
2
2
  const { Command } = require('commander');
3
3
  const chalk = require('chalk');
4
4
  const { login } = require('../lib/auth');
5
- const { getConfig, setConfig, getProject, setProject } = require('../lib/config');
5
+ const { getConfig, getProject, setProject } = require('../lib/config');
6
6
  const { deploy } = require('../lib/deploy');
7
7
 
8
8
  const program = new Command();
9
9
 
10
10
  program
11
11
  .name('moontraze')
12
- .description('Deploy sites to Moontraze hosting')
13
- .version('1.0.0');
12
+ .description('Deploy sites to Moontraze hosting (moontraze.com)')
13
+ .version('1.0.3');
14
14
 
15
- // npx moontraze login
15
+ // moon moontraze login | npx moontraze login
16
16
  program
17
17
  .command('login')
18
- .description('Save your API token (from app dashboard)')
19
- .option('--token <jwt>', 'JWT token (or prompt)')
18
+ .description('Log in with email/password or token')
19
+ .option('--token <jwt>', 'JWT token (advanced, skip interactive login)')
20
20
  .action(async (opts) => {
21
21
  try {
22
22
  await login(opts.token);
@@ -26,7 +26,7 @@ program
26
26
  }
27
27
  });
28
28
 
29
- // npx moontraze link [name]
29
+ // moon moontraze link [name]
30
30
  program
31
31
  .command('link [name]')
32
32
  .description('Link this folder to a hosting project name')
@@ -49,16 +49,17 @@ program
49
49
  if (!projectName) process.exit(1);
50
50
  projectName = projectName.toLowerCase().replace(/[^a-z0-9-]/g, '');
51
51
  setProject({ projectName });
52
- console.log(chalk.green(`Linked ${projectName}.${getConfig().domain || 'frelanceo.com'}`));
53
- console.log(chalk.gray('(saved .moontraze/project.json — same as .vercel)'));
52
+ const domain = getConfig().domain || 'moontraze.com';
53
+ console.log(chalk.green(`Linked ${projectName}.${domain}`));
54
+ console.log(
55
+ chalk.gray('(saved .moontraze/project.json — same as .vercel)')
56
+ );
54
57
  } catch (e) {
55
58
  console.error(chalk.red(e.message));
56
59
  process.exit(1);
57
60
  }
58
61
  });
59
62
 
60
- // npx moontraze deploy --prod
61
- // also: npx moontraze --prod (vercel style)
62
63
  async function runDeploy(opts) {
63
64
  try {
64
65
  await deploy({
@@ -76,16 +77,15 @@ program
76
77
  .description('Deploy current folder')
77
78
  .option('--prod', 'Production redeploy')
78
79
  .option('--production', 'Alias for --prod')
79
- .option('-p, --project <name>', 'Project name (or use moontraze link)')
80
+ .option('-p, --project <name>', 'Project name (or use link)')
80
81
  .action(runDeploy);
81
82
 
82
- // vercel-style: npx moontraze --prod
83
+ // moon moontraze --prod
83
84
  program
84
85
  .option('--prod', 'Deploy to production')
85
86
  .option('--production', 'Deploy to production')
86
87
  .option('-p, --project <name>', 'Project name')
87
88
  .action(async (opts, cmd) => {
88
- // only if no subcommand
89
89
  if (cmd.args?.length) return;
90
90
  if (opts.prod || opts.production || opts.project) {
91
91
  await runDeploy(opts);
@@ -94,6 +94,21 @@ program
94
94
  }
95
95
  });
96
96
 
97
+ program.addHelpText(
98
+ 'after',
99
+ `
100
+ Examples:
101
+ npx moontraze login
102
+ npx moontraze link my-site
103
+ npx moontraze deploy --prod
104
+
105
+ moon moontraze login
106
+ moon moontraze link my-site
107
+ moon moontraze deploy --prod
108
+ moon moontraze --prod
109
+ `
110
+ );
111
+
97
112
  program.parseAsync(process.argv).catch((e) => {
98
113
  console.error(chalk.red(e.message));
99
114
  process.exit(1);
package/lib/auth.js CHANGED
@@ -1,38 +1,221 @@
1
1
  const prompts = require('prompts');
2
2
  const chalk = require('chalk');
3
+ const http = require('http');
4
+ const { URL } = require('url');
3
5
  const { setConfig, getConfig } = require('./config');
4
6
 
5
7
  async function login(tokenArg) {
6
- let token = tokenArg;
7
- if (!token) {
8
- console.log(chalk.cyan('Moontraze Login'));
9
- console.log(chalk.gray('Open app → copy JWT from localStorage "token" (or Settings)'));
10
- console.log('');
11
- const a = await prompts({
8
+ // Power user: --token still works (hidden)
9
+ if (tokenArg && String(tokenArg).trim()) {
10
+ return saveAndValidateToken(String(tokenArg).trim());
11
+ }
12
+
13
+ console.log('');
14
+ console.log(chalk.cyan('Moontraze Login'));
15
+ console.log('');
16
+
17
+ const { method } = await prompts({
18
+ type: 'select',
19
+ name: 'method',
20
+ message: 'How do you want to log in?',
21
+ choices: [
22
+ { title: 'Email & Password', value: 'email' },
23
+ { title: 'MoonID (scan QR with app)', value: 'moonid' },
24
+ ],
25
+ });
26
+
27
+ if (!method) {
28
+ throw new Error('Login cancelled');
29
+ }
30
+
31
+ if (method === 'email') {
32
+ await loginWithEmail();
33
+ } else {
34
+ await loginWithMoonID();
35
+ }
36
+ }
37
+
38
+ async function loginWithEmail() {
39
+ const answers = await prompts([
40
+ {
41
+ type: 'text',
42
+ name: 'email',
43
+ message: 'Email',
44
+ validate: (v) =>
45
+ v && v.includes('@') ? true : 'Enter a valid email',
46
+ },
47
+ {
12
48
  type: 'password',
13
- name: 'token',
14
- message: 'Paste token',
15
- });
16
- token = a.token;
49
+ name: 'password',
50
+ message: 'Password',
51
+ validate: (v) => (v && v.length >= 1 ? true : 'Password required'),
52
+ },
53
+ ]);
54
+
55
+ if (!answers.email || !answers.password) {
56
+ throw new Error('Login cancelled');
17
57
  }
18
- if (!token || !String(token).trim()) {
19
- throw new Error('Token required');
58
+
59
+ const cfg = getConfig();
60
+ const fetch = require('node-fetch');
61
+
62
+ const res = await fetch(`${cfg.apiUrl}/api/platform/login`, {
63
+ method: 'POST',
64
+ headers: { 'Content-Type': 'application/json' },
65
+ body: JSON.stringify({
66
+ email: answers.email.trim(),
67
+ password: answers.password,
68
+ }),
69
+ });
70
+
71
+ const data = await res.json().catch(() => ({}));
72
+
73
+ if (!res.ok) {
74
+ throw new Error(data.error || 'Invalid email or password');
20
75
  }
21
- token = String(token).trim();
22
76
 
77
+ if (!data.token) {
78
+ throw new Error('Login succeeded but no token returned');
79
+ }
80
+
81
+ setConfig({ token: data.token });
82
+ console.log('');
83
+ console.log(chalk.green('✓ Logged in'));
84
+ if (data.user?.email) {
85
+ console.log(chalk.gray(` ${data.user.email}`));
86
+ }
87
+ console.log(chalk.gray(` Config: ~/.moontraze/config.json`));
88
+ console.log('');
89
+ }
90
+
91
+ async function loginWithMoonID() {
92
+ const cfg = getConfig();
93
+ const state = Math.random().toString(36).substring(2, 12);
94
+ const port = 17321 + Math.floor(Math.random() * 100);
95
+ const redirectUri = `http://127.0.0.1:${port}/callback`;
96
+
97
+ const appId = 'com.moonstorage.app';
98
+ const deepLink =
99
+ `moonid://auth` +
100
+ `?app_id=${encodeURIComponent(appId)}` +
101
+ `&action=login` +
102
+ `&redirect_uri=${encodeURIComponent(redirectUri)}` +
103
+ `&state=${encodeURIComponent(state)}`;
104
+
105
+ const qrImageUrl = `https://api.qrserver.com/v1/create-qr-code/?size=220x220&data=${encodeURIComponent(
106
+ deepLink
107
+ )}`;
108
+
109
+ console.log('');
110
+ console.log(chalk.cyan('MoonID Login'));
111
+ console.log(chalk.gray('1. Open MoonID app on your phone'));
112
+ console.log(chalk.gray('2. Scan the QR code (open link below in browser)'));
113
+ console.log(chalk.gray('3. Approve login'));
114
+ console.log('');
115
+ console.log(chalk.yellow('QR code image:'));
116
+ console.log(chalk.underline(qrImageUrl));
117
+ console.log('');
118
+ console.log(chalk.gray('Deep link (if needed):'));
119
+ console.log(chalk.gray(deepLink));
120
+ console.log('');
121
+ console.log(chalk.gray('Waiting for approval... (Ctrl+C to cancel)'));
122
+ console.log('');
123
+
124
+ // Local server — MoonID redirect yahan aayega
125
+ const token = await waitForMoonIDCallback({ port, state, timeoutMs: 120000 });
126
+
127
+ if (!token) {
128
+ throw new Error('MoonID login timed out or denied');
129
+ }
130
+
131
+ await saveAndValidateToken(token);
132
+ }
133
+
134
+ function waitForMoonIDCallback({ port, state, timeoutMs }) {
135
+ return new Promise((resolve, reject) => {
136
+ const server = http.createServer((req, res) => {
137
+ try {
138
+ const u = new URL(req.url, `http://127.0.0.1:${port}`);
139
+
140
+ if (u.pathname !== '/callback') {
141
+ res.writeHead(404);
142
+ res.end('Not found');
143
+ return;
144
+ }
145
+
146
+ const returnedState = u.searchParams.get('state');
147
+ const token =
148
+ u.searchParams.get('token') ||
149
+ u.searchParams.get('access_token') ||
150
+ u.searchParams.get('jwt');
151
+ const error = u.searchParams.get('error');
152
+
153
+ if (error) {
154
+ res.writeHead(200, { 'Content-Type': 'text/html' });
155
+ res.end(
156
+ '<html><body style="font-family:sans-serif;text-align:center;padding:40px"><h2>Login denied</h2><p>You can close this tab.</p></body></html>'
157
+ );
158
+ server.close();
159
+ resolve(null);
160
+ return;
161
+ }
162
+
163
+ if (returnedState && returnedState !== state) {
164
+ res.writeHead(400, { 'Content-Type': 'text/plain' });
165
+ res.end('Invalid state');
166
+ return;
167
+ }
168
+
169
+ if (!token) {
170
+ res.writeHead(400, { 'Content-Type': 'text/html' });
171
+ res.end(
172
+ '<html><body style="font-family:sans-serif;text-align:center;padding:40px"><h2>No token received</h2><p>Close this tab and try again.</p></body></html>'
173
+ );
174
+ return;
175
+ }
176
+
177
+ res.writeHead(200, { 'Content-Type': 'text/html' });
178
+ res.end(
179
+ '<html><body style="font-family:sans-serif;text-align:center;padding:40px"><h2>✓ Login successful</h2><p>You can close this tab and return to the terminal.</p></body></html>'
180
+ );
181
+ server.close();
182
+ resolve(token);
183
+ } catch (e) {
184
+ res.writeHead(500);
185
+ res.end('Error');
186
+ }
187
+ });
188
+
189
+ server.listen(port, '127.0.0.1');
190
+
191
+ const timer = setTimeout(() => {
192
+ try {
193
+ server.close();
194
+ } catch (_) {}
195
+ resolve(null);
196
+ }, timeoutMs);
197
+
198
+ server.on('close', () => clearTimeout(timer));
199
+ });
200
+ }
201
+
202
+ async function saveAndValidateToken(token) {
23
203
  const cfg = getConfig();
24
- // optional: validate token
25
204
  const fetch = require('node-fetch');
205
+
26
206
  const res = await fetch(`${cfg.apiUrl}/api/hosting/quota`, {
27
207
  headers: { Authorization: `Bearer ${token}` },
28
208
  });
209
+
29
210
  if (res.status === 401) {
30
211
  throw new Error('Invalid or expired token');
31
212
  }
32
- // 403/200 both ok enough for login store
213
+
33
214
  setConfig({ token });
215
+ console.log('');
34
216
  console.log(chalk.green('✓ Logged in'));
35
- console.log(chalk.gray(`Config: ~/.moontraze/config.json`));
217
+ console.log(chalk.gray(` Config: ~/.moontraze/config.json`));
218
+ console.log('');
36
219
  }
37
220
 
38
221
  module.exports = { login };
package/lib/config.js CHANGED
@@ -11,17 +11,21 @@ function ensureDir(d) {
11
11
  if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
12
12
  }
13
13
 
14
+ const DEFAULTS = {
15
+ token: null,
16
+ apiUrl: process.env.MOONTRAZE_API || 'https://api.moontraze.com',
17
+ domain: process.env.MOONTRAZE_DOMAIN || 'moontraze.com',
18
+ };
19
+
14
20
  function getConfig() {
15
21
  try {
16
22
  if (fs.existsSync(GLOBAL_FILE)) {
17
- return JSON.parse(fs.readFileSync(GLOBAL_FILE, 'utf8'));
23
+ const saved = JSON.parse(fs.readFileSync(GLOBAL_FILE, 'utf8'));
24
+ // merge defaults so old frelanceo.com cache auto-upgrade on missing keys
25
+ return { ...DEFAULTS, ...saved };
18
26
  }
19
27
  } catch (_) {}
20
- return {
21
- token: null,
22
- apiUrl: process.env.MOONTRAZE_API || 'https://api.frelanceo.com',
23
- domain: process.env.MOONTRAZE_DOMAIN || 'frelanceo.com',
24
- };
28
+ return { ...DEFAULTS };
25
29
  }
26
30
 
27
31
  function setConfig(partial) {
@@ -43,7 +47,6 @@ function getProject() {
43
47
  function setProject(data) {
44
48
  ensureDir(LOCAL_DIR);
45
49
  fs.writeFileSync(LOCAL_FILE, JSON.stringify(data, null, 2));
46
- // ignore file so users don't commit token (project name only is ok)
47
50
  const ignore = path.join(process.cwd(), '.gitignore');
48
51
  try {
49
52
  let g = fs.existsSync(ignore) ? fs.readFileSync(ignore, 'utf8') : '';
package/lib/deploy.js CHANGED
@@ -10,26 +10,29 @@ const { zipCwd } = require('./zip');
10
10
  async function deploy({ prod = true, projectName } = {}) {
11
11
  const cfg = getConfig();
12
12
  if (!cfg.token) {
13
- throw new Error('Not logged in. Run: npx moontraze login');
13
+ throw new Error(
14
+ 'Not logged in. Run: moon moontraze login (or: npx moontraze login)'
15
+ );
14
16
  }
15
17
 
16
18
  const linked = getProject();
17
19
  const name = (projectName || linked?.projectName || '')
18
20
  .toLowerCase()
19
21
  .replace(/[^a-z0-9-]/g, '');
20
-
21
22
  if (!name || name.length < 3) {
22
- throw new Error('No project linked. Run: npx moontraze link my-site');
23
+ throw new Error(
24
+ 'No project linked. Run: moon moontraze link my-site (or: npx moontraze link my-site)'
25
+ );
23
26
  }
24
27
 
25
- const domain = cfg.domain || 'frelanceo.com';
26
- const api = cfg.apiUrl || 'https://api.frelanceo.com';
28
+ const domain = cfg.domain || 'moontraze.com';
29
+ const api = cfg.apiUrl || 'https://api.moontraze.com';
27
30
 
28
31
  console.log('');
29
32
  console.log(chalk.cyan('Moontraze'));
30
- console.log(` Inspect ${api}`);
31
- console.log(` Project ${name}`);
32
- console.log(` Target https://${name}.${domain}`);
33
+ console.log(` Inspect ${api}`);
34
+ console.log(` Project ${name}`);
35
+ console.log(` Target https://${name}.${domain}`);
33
36
  console.log('');
34
37
 
35
38
  const spinner = ora('Packaging...').start();
@@ -39,9 +42,8 @@ async function deploy({ prod = true, projectName } = {}) {
39
42
  try {
40
43
  zipPath = await zipCwd();
41
44
  const mb = (fs.statSync(zipPath).size / 1024 / 1024).toFixed(2);
42
- spinner.text = `Checking project...`;
43
45
 
44
- // 1. Pehle check karo project already exist karta hai ya nahi
46
+ spinner.text = 'Checking project...';
45
47
  let alreadyExists = false;
46
48
  try {
47
49
  const statusRes = await fetch(`${api}/api/hosting/status/${name}`, {
@@ -52,7 +54,7 @@ async function deploy({ prod = true, projectName } = {}) {
52
54
  alreadyExists = !!statusData.exists;
53
55
  }
54
56
  } catch (_) {
55
- // status fail → assume new
57
+ // assume new
56
58
  }
57
59
 
58
60
  spinner.text = `Uploading (${mb} MB)...`;
@@ -64,13 +66,14 @@ async function deploy({ prod = true, projectName } = {}) {
64
66
  contentType: 'application/zip',
65
67
  });
66
68
 
67
- // 2. Exist karta hai to /redeploy, nahi to /deploy
69
+ // existing redeploy; new deploy
68
70
  const endpoint = alreadyExists
69
71
  ? `${api}/api/hosting/redeploy`
70
72
  : `${api}/api/hosting/deploy`;
71
73
 
72
- // /deploy pe redeploy flag bhejo (safety)
73
- if (!alreadyExists) {
74
+ if (alreadyExists) {
75
+ form.append('redeploy', 'true');
76
+ } else {
74
77
  form.append('redeploy', 'false');
75
78
  }
76
79
 
@@ -100,11 +103,10 @@ async function deploy({ prod = true, projectName } = {}) {
100
103
 
101
104
  spinner.succeed(`Ready in ${sec}s`);
102
105
  const url = data.url || `https://${name}.${domain}`;
103
-
104
106
  console.log('');
105
- console.log(chalk.green(` Production ${url}`));
107
+ console.log(chalk.green(` Production ${url}`));
106
108
  if (data.deployment?.version) {
107
- console.log(chalk.gray(` Version ${data.deployment.version}`));
109
+ console.log(chalk.gray(` Version ${data.deployment.version}`));
108
110
  }
109
111
  console.log(
110
112
  chalk.green(alreadyExists ? '✓ Redeployed' : '✓ Deployed')
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "moontraze",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Deploy to Moontraze hosting — like vercel CLI",
5
5
  "bin": {
6
- "moontraze": "./bin/moontraze.js"
6
+ "moontraze": "./bin/moontraze.js",
7
+ "moon": "./bin/moon.js"
7
8
  },
8
9
  "files": ["bin", "lib"],
9
10
  "engines": { "node": ">=18" },
@@ -16,6 +17,6 @@
16
17
  "chalk": "^4.1.2",
17
18
  "prompts": "^2.4.2"
18
19
  },
19
- "keywords": ["moontraze", "deploy", "hosting"],
20
+ "keywords": ["moontraze", "deploy", "hosting", "moon"],
20
21
  "license": "MIT"
21
22
  }