moontraze 1.0.0 → 1.0.2

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/moontraze.js CHANGED
@@ -10,13 +10,13 @@ const program = new Command();
10
10
  program
11
11
  .name('moontraze')
12
12
  .description('Deploy sites to Moontraze hosting')
13
- .version('1.0.0');
13
+ .version('1.0.2');
14
14
 
15
15
  // 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 MoonID')
19
+ .option('--token <jwt>', 'JWT token (advanced, skip interactive login)')
20
20
  .action(async (opts) => {
21
21
  try {
22
22
  await login(opts.token);
@@ -49,8 +49,14 @@ 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
+ console.log(
53
+ chalk.green(
54
+ `Linked → ${projectName}.${getConfig().domain || 'frelanceo.com'}`
55
+ )
56
+ );
57
+ console.log(
58
+ chalk.gray('(saved .moontraze/project.json — same as .vercel)')
59
+ );
54
60
  } catch (e) {
55
61
  console.error(chalk.red(e.message));
56
62
  process.exit(1);
@@ -58,7 +64,6 @@ program
58
64
  });
59
65
 
60
66
  // npx moontraze deploy --prod
61
- // also: npx moontraze --prod (vercel style)
62
67
  async function runDeploy(opts) {
63
68
  try {
64
69
  await deploy({
@@ -85,7 +90,6 @@ program
85
90
  .option('--production', 'Deploy to production')
86
91
  .option('-p, --project <name>', 'Project name')
87
92
  .action(async (opts, cmd) => {
88
- // only if no subcommand
89
93
  if (cmd.args?.length) return;
90
94
  if (opts.prod || opts.production || opts.project) {
91
95
  await runDeploy(opts);
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/deploy.js CHANGED
@@ -14,7 +14,10 @@ async function deploy({ prod = true, projectName } = {}) {
14
14
  }
15
15
 
16
16
  const linked = getProject();
17
- const name = (projectName || linked?.projectName || '').toLowerCase().replace(/[^a-z0-9-]/g, '');
17
+ const name = (projectName || linked?.projectName || '')
18
+ .toLowerCase()
19
+ .replace(/[^a-z0-9-]/g, '');
20
+
18
21
  if (!name || name.length < 3) {
19
22
  throw new Error('No project linked. Run: npx moontraze link my-site');
20
23
  }
@@ -24,28 +27,54 @@ async function deploy({ prod = true, projectName } = {}) {
24
27
 
25
28
  console.log('');
26
29
  console.log(chalk.cyan('Moontraze'));
27
- console.log(` Inspect ${api}`);
28
- console.log(` Project ${name}`);
29
- console.log(` Target https://${name}.${domain}`);
30
+ console.log(` Inspect ${api}`);
31
+ console.log(` Project ${name}`);
32
+ console.log(` Target https://${name}.${domain}`);
30
33
  console.log('');
31
34
 
32
35
  const spinner = ora('Packaging...').start();
33
36
  const t0 = Date.now();
34
37
  let zipPath;
38
+
35
39
  try {
36
40
  zipPath = await zipCwd();
37
41
  const mb = (fs.statSync(zipPath).size / 1024 / 1024).toFixed(2);
42
+ spinner.text = `Checking project...`;
43
+
44
+ // 1. Pehle check karo project already exist karta hai ya nahi
45
+ let alreadyExists = false;
46
+ try {
47
+ const statusRes = await fetch(`${api}/api/hosting/status/${name}`, {
48
+ headers: { Authorization: `Bearer ${cfg.token}` },
49
+ });
50
+ if (statusRes.ok) {
51
+ const statusData = await statusRes.json();
52
+ alreadyExists = !!statusData.exists;
53
+ }
54
+ } catch (_) {
55
+ // status fail → assume new
56
+ }
57
+
38
58
  spinner.text = `Uploading (${mb} MB)...`;
39
59
 
40
60
  const form = new FormData();
41
61
  form.append('projectName', name);
42
- form.append('redeploy', prod ? 'true' : 'true'); // always overwrite for existing
43
62
  form.append('file', fs.createReadStream(zipPath), {
44
63
  filename: `${name}.zip`,
45
64
  contentType: 'application/zip',
46
65
  });
47
66
 
48
- const res = await fetch(`${api}/api/hosting/deploy`, {
67
+ // 2. Exist karta hai to /redeploy, nahi to /deploy
68
+ const endpoint = alreadyExists
69
+ ? `${api}/api/hosting/redeploy`
70
+ : `${api}/api/hosting/deploy`;
71
+
72
+ // /deploy pe redeploy flag bhejo (safety)
73
+ if (!alreadyExists) {
74
+ form.append('redeploy', 'false');
75
+ }
76
+
77
+ const res = await fetch(endpoint, {
49
78
  method: 'POST',
50
79
  headers: {
51
80
  Authorization: `Bearer ${cfg.token}`,
@@ -71,12 +100,15 @@ async function deploy({ prod = true, projectName } = {}) {
71
100
 
72
101
  spinner.succeed(`Ready in ${sec}s`);
73
102
  const url = data.url || `https://${name}.${domain}`;
103
+
74
104
  console.log('');
75
- console.log(chalk.green(` Production ${url}`));
105
+ console.log(chalk.green(` Production ${url}`));
76
106
  if (data.deployment?.version) {
77
- console.log(chalk.gray(` Version ${data.deployment.version}`));
107
+ console.log(chalk.gray(` Version ${data.deployment.version}`));
78
108
  }
79
- console.log(chalk.green(`✓ Deployed`));
109
+ console.log(
110
+ chalk.green(alreadyExists ? '✓ Redeployed' : '✓ Deployed')
111
+ );
80
112
  console.log('');
81
113
  } finally {
82
114
  if (zipPath && fs.existsSync(zipPath)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moontraze",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Deploy to Moontraze hosting — like vercel CLI",
5
5
  "bin": {
6
6
  "moontraze": "./bin/moontraze.js"