moontraze 1.0.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,100 @@
1
+ #!/usr/bin/env node
2
+ const { Command } = require('commander');
3
+ const chalk = require('chalk');
4
+ const { login } = require('../lib/auth');
5
+ const { getConfig, setConfig, getProject, setProject } = require('../lib/config');
6
+ const { deploy } = require('../lib/deploy');
7
+
8
+ const program = new Command();
9
+
10
+ program
11
+ .name('moontraze')
12
+ .description('Deploy sites to Moontraze hosting')
13
+ .version('1.0.0');
14
+
15
+ // npx moontraze login
16
+ program
17
+ .command('login')
18
+ .description('Save your API token (from app dashboard)')
19
+ .option('--token <jwt>', 'JWT token (or prompt)')
20
+ .action(async (opts) => {
21
+ try {
22
+ await login(opts.token);
23
+ } catch (e) {
24
+ console.error(chalk.red('Login failed:'), e.message);
25
+ process.exit(1);
26
+ }
27
+ });
28
+
29
+ // npx moontraze link [name]
30
+ program
31
+ .command('link [name]')
32
+ .description('Link this folder to a hosting project name')
33
+ .action(async (name) => {
34
+ try {
35
+ const prompts = require('prompts');
36
+ let projectName = name;
37
+ if (!projectName) {
38
+ const a = await prompts({
39
+ type: 'text',
40
+ name: 'n',
41
+ message: 'Project name (subdomain)',
42
+ validate: (v) =>
43
+ /^[a-z0-9-]{3,}$/.test(v || '')
44
+ ? true
45
+ : 'lowercase letters, numbers, hyphens (min 3)',
46
+ });
47
+ projectName = a.n;
48
+ }
49
+ if (!projectName) process.exit(1);
50
+ projectName = projectName.toLowerCase().replace(/[^a-z0-9-]/g, '');
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)'));
54
+ } catch (e) {
55
+ console.error(chalk.red(e.message));
56
+ process.exit(1);
57
+ }
58
+ });
59
+
60
+ // npx moontraze deploy --prod
61
+ // also: npx moontraze --prod (vercel style)
62
+ async function runDeploy(opts) {
63
+ try {
64
+ await deploy({
65
+ prod: !!(opts.prod || opts.production),
66
+ projectName: opts.project || opts.name,
67
+ });
68
+ } catch (e) {
69
+ console.error(chalk.red('\nDeploy failed:'), e.message);
70
+ process.exit(1);
71
+ }
72
+ }
73
+
74
+ program
75
+ .command('deploy')
76
+ .description('Deploy current folder')
77
+ .option('--prod', 'Production redeploy')
78
+ .option('--production', 'Alias for --prod')
79
+ .option('-p, --project <name>', 'Project name (or use moontraze link)')
80
+ .action(runDeploy);
81
+
82
+ // vercel-style: npx moontraze --prod
83
+ program
84
+ .option('--prod', 'Deploy to production')
85
+ .option('--production', 'Deploy to production')
86
+ .option('-p, --project <name>', 'Project name')
87
+ .action(async (opts, cmd) => {
88
+ // only if no subcommand
89
+ if (cmd.args?.length) return;
90
+ if (opts.prod || opts.production || opts.project) {
91
+ await runDeploy(opts);
92
+ } else {
93
+ program.help();
94
+ }
95
+ });
96
+
97
+ program.parseAsync(process.argv).catch((e) => {
98
+ console.error(chalk.red(e.message));
99
+ process.exit(1);
100
+ });
package/lib/auth.js ADDED
@@ -0,0 +1,38 @@
1
+ const prompts = require('prompts');
2
+ const chalk = require('chalk');
3
+ const { setConfig, getConfig } = require('./config');
4
+
5
+ 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({
12
+ type: 'password',
13
+ name: 'token',
14
+ message: 'Paste token',
15
+ });
16
+ token = a.token;
17
+ }
18
+ if (!token || !String(token).trim()) {
19
+ throw new Error('Token required');
20
+ }
21
+ token = String(token).trim();
22
+
23
+ const cfg = getConfig();
24
+ // optional: validate token
25
+ const fetch = require('node-fetch');
26
+ const res = await fetch(`${cfg.apiUrl}/api/hosting/quota`, {
27
+ headers: { Authorization: `Bearer ${token}` },
28
+ });
29
+ if (res.status === 401) {
30
+ throw new Error('Invalid or expired token');
31
+ }
32
+ // 403/200 both ok enough for login store
33
+ setConfig({ token });
34
+ console.log(chalk.green('✓ Logged in'));
35
+ console.log(chalk.gray(`Config: ~/.moontraze/config.json`));
36
+ }
37
+
38
+ module.exports = { login };
package/lib/config.js ADDED
@@ -0,0 +1,57 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ const GLOBAL_DIR = path.join(os.homedir(), '.moontraze');
6
+ const GLOBAL_FILE = path.join(GLOBAL_DIR, 'config.json');
7
+ const LOCAL_DIR = path.join(process.cwd(), '.moontraze');
8
+ const LOCAL_FILE = path.join(LOCAL_DIR, 'project.json');
9
+
10
+ function ensureDir(d) {
11
+ if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
12
+ }
13
+
14
+ function getConfig() {
15
+ try {
16
+ if (fs.existsSync(GLOBAL_FILE)) {
17
+ return JSON.parse(fs.readFileSync(GLOBAL_FILE, 'utf8'));
18
+ }
19
+ } 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
+ };
25
+ }
26
+
27
+ function setConfig(partial) {
28
+ ensureDir(GLOBAL_DIR);
29
+ const next = { ...getConfig(), ...partial };
30
+ fs.writeFileSync(GLOBAL_FILE, JSON.stringify(next, null, 2));
31
+ return next;
32
+ }
33
+
34
+ function getProject() {
35
+ try {
36
+ if (fs.existsSync(LOCAL_FILE)) {
37
+ return JSON.parse(fs.readFileSync(LOCAL_FILE, 'utf8'));
38
+ }
39
+ } catch (_) {}
40
+ return null;
41
+ }
42
+
43
+ function setProject(data) {
44
+ ensureDir(LOCAL_DIR);
45
+ fs.writeFileSync(LOCAL_FILE, JSON.stringify(data, null, 2));
46
+ // ignore file so users don't commit token (project name only is ok)
47
+ const ignore = path.join(process.cwd(), '.gitignore');
48
+ try {
49
+ let g = fs.existsSync(ignore) ? fs.readFileSync(ignore, 'utf8') : '';
50
+ if (!g.includes('.moontraze')) {
51
+ fs.appendFileSync(ignore, '\n.moontraze/\n');
52
+ }
53
+ } catch (_) {}
54
+ return data;
55
+ }
56
+
57
+ module.exports = { getConfig, setConfig, getProject, setProject, GLOBAL_DIR };
package/lib/deploy.js ADDED
@@ -0,0 +1,90 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const fetch = require('node-fetch');
4
+ const FormData = require('form-data');
5
+ const ora = require('ora');
6
+ const chalk = require('chalk');
7
+ const { getConfig, getProject } = require('./config');
8
+ const { zipCwd } = require('./zip');
9
+
10
+ async function deploy({ prod = true, projectName } = {}) {
11
+ const cfg = getConfig();
12
+ if (!cfg.token) {
13
+ throw new Error('Not logged in. Run: npx moontraze login');
14
+ }
15
+
16
+ const linked = getProject();
17
+ const name = (projectName || linked?.projectName || '').toLowerCase().replace(/[^a-z0-9-]/g, '');
18
+ if (!name || name.length < 3) {
19
+ throw new Error('No project linked. Run: npx moontraze link my-site');
20
+ }
21
+
22
+ const domain = cfg.domain || 'frelanceo.com';
23
+ const api = cfg.apiUrl || 'https://api.frelanceo.com';
24
+
25
+ console.log('');
26
+ 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('');
31
+
32
+ const spinner = ora('Packaging...').start();
33
+ const t0 = Date.now();
34
+ let zipPath;
35
+ try {
36
+ zipPath = await zipCwd();
37
+ const mb = (fs.statSync(zipPath).size / 1024 / 1024).toFixed(2);
38
+ spinner.text = `Uploading (${mb} MB)...`;
39
+
40
+ const form = new FormData();
41
+ form.append('projectName', name);
42
+ form.append('redeploy', prod ? 'true' : 'true'); // always overwrite for existing
43
+ form.append('file', fs.createReadStream(zipPath), {
44
+ filename: `${name}.zip`,
45
+ contentType: 'application/zip',
46
+ });
47
+
48
+ const res = await fetch(`${api}/api/hosting/deploy`, {
49
+ method: 'POST',
50
+ headers: {
51
+ Authorization: `Bearer ${cfg.token}`,
52
+ ...form.getHeaders(),
53
+ },
54
+ body: form,
55
+ });
56
+
57
+ const text = await res.text();
58
+ let data;
59
+ try {
60
+ data = JSON.parse(text);
61
+ } catch {
62
+ data = { error: text };
63
+ }
64
+
65
+ const sec = ((Date.now() - t0) / 1000).toFixed(1);
66
+
67
+ if (!res.ok) {
68
+ spinner.fail(`Failed (${sec}s)`);
69
+ throw new Error(data.error || `HTTP ${res.status}`);
70
+ }
71
+
72
+ spinner.succeed(`Ready in ${sec}s`);
73
+ const url = data.url || `https://${name}.${domain}`;
74
+ console.log('');
75
+ console.log(chalk.green(` Production ${url}`));
76
+ if (data.deployment?.version) {
77
+ console.log(chalk.gray(` Version ${data.deployment.version}`));
78
+ }
79
+ console.log(chalk.green(`✓ Deployed`));
80
+ console.log('');
81
+ } finally {
82
+ if (zipPath && fs.existsSync(zipPath)) {
83
+ try {
84
+ fs.unlinkSync(zipPath);
85
+ } catch (_) {}
86
+ }
87
+ }
88
+ }
89
+
90
+ module.exports = { deploy };
package/lib/zip.js ADDED
@@ -0,0 +1,53 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+ const archiver = require('archiver');
5
+
6
+ const SKIP = new Set([
7
+ 'node_modules',
8
+ '.git',
9
+ '.next',
10
+ 'dist',
11
+ 'build',
12
+ 'out',
13
+ '.vercel',
14
+ '.moontraze',
15
+ '.netlify',
16
+ 'coverage',
17
+ ]);
18
+
19
+ function shouldSkip(rel) {
20
+ const parts = rel.split(/[/\\]/);
21
+ return parts.some((p) => SKIP.has(p));
22
+ }
23
+
24
+ function zipCwd() {
25
+ return new Promise((resolve, reject) => {
26
+ const outPath = path.join(
27
+ os.tmpdir(),
28
+ `moontraze-${Date.now()}.zip`
29
+ );
30
+ const output = fs.createWriteStream(outPath);
31
+ const archive = archiver('zip', { zlib: { level: 9 } });
32
+
33
+ output.on('close', () => resolve(outPath));
34
+ archive.on('error', reject);
35
+ archive.pipe(output);
36
+
37
+ const walk = (dir, base = '') => {
38
+ for (const name of fs.readdirSync(dir)) {
39
+ const full = path.join(dir, name);
40
+ const rel = base ? path.join(base, name) : name;
41
+ if (shouldSkip(rel)) continue;
42
+ const st = fs.statSync(full);
43
+ if (st.isDirectory()) walk(full, rel);
44
+ else archive.file(full, { name: rel.replace(/\\/g, '/') });
45
+ }
46
+ };
47
+
48
+ walk(process.cwd());
49
+ archive.finalize();
50
+ });
51
+ }
52
+
53
+ module.exports = { zipCwd };
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "moontraze",
3
+ "version": "1.0.0",
4
+ "description": "Deploy to Moontraze hosting — like vercel CLI",
5
+ "bin": {
6
+ "moontraze": "./bin/moontraze.js"
7
+ },
8
+ "files": ["bin", "lib"],
9
+ "engines": { "node": ">=18" },
10
+ "dependencies": {
11
+ "archiver": "^7.0.1",
12
+ "commander": "^12.1.0",
13
+ "form-data": "^4.0.1",
14
+ "node-fetch": "^2.7.0",
15
+ "ora": "^5.4.1",
16
+ "chalk": "^4.1.2",
17
+ "prompts": "^2.4.2"
18
+ },
19
+ "keywords": ["moontraze", "deploy", "hosting"],
20
+ "license": "MIT"
21
+ }