@pushto/cli 0.0.7 → 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.
Files changed (55) hide show
  1. package/dist/cli.d.ts +3 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +2 -1
  4. package/dist/cli.js.map +1 -0
  5. package/dist/commands/deploy.d.ts.map +1 -1
  6. package/dist/commands/deploy.js +36 -7
  7. package/dist/commands/deploy.js.map +1 -1
  8. package/dist/commands/domain.d.ts.map +1 -1
  9. package/dist/commands/domain.js +25 -14
  10. package/dist/commands/domain.js.map +1 -1
  11. package/dist/commands/env.d.ts.map +1 -1
  12. package/dist/commands/env.js +50 -7
  13. package/dist/commands/env.js.map +1 -1
  14. package/dist/commands/init.d.ts.map +1 -1
  15. package/dist/commands/init.js +2 -1
  16. package/dist/commands/init.js.map +1 -1
  17. package/dist/commands/status.d.ts.map +1 -1
  18. package/dist/commands/status.js +3 -2
  19. package/dist/commands/status.js.map +1 -1
  20. package/dist/commands/upgrade.d.ts +2 -0
  21. package/dist/commands/upgrade.d.ts.map +1 -0
  22. package/dist/commands/upgrade.js +19 -0
  23. package/dist/commands/upgrade.js.map +1 -0
  24. package/dist/commands/whoami.d.ts +2 -0
  25. package/dist/commands/whoami.d.ts.map +1 -0
  26. package/dist/commands/whoami.js +35 -0
  27. package/dist/commands/whoami.js.map +1 -0
  28. package/dist/index.d.ts +0 -1
  29. package/dist/index.js +14 -1
  30. package/dist/index.js.map +1 -1
  31. package/dist/lib/api.d.ts.map +1 -1
  32. package/dist/lib/api.js +6 -2
  33. package/dist/lib/api.js.map +1 -1
  34. package/dist/lib/safe-json.d.ts +7 -0
  35. package/dist/lib/safe-json.d.ts.map +1 -0
  36. package/dist/lib/safe-json.js +15 -0
  37. package/dist/lib/safe-json.js.map +1 -0
  38. package/package.json +5 -2
  39. package/.turbo/turbo-build.log +0 -4
  40. package/bin/pushto.js +0 -2
  41. package/src/commands/deploy.ts +0 -125
  42. package/src/commands/doctor.ts +0 -160
  43. package/src/commands/domain.ts +0 -54
  44. package/src/commands/env.ts +0 -128
  45. package/src/commands/init.ts +0 -65
  46. package/src/commands/login.ts +0 -97
  47. package/src/commands/logs.ts +0 -24
  48. package/src/commands/open.ts +0 -27
  49. package/src/commands/rollback.ts +0 -43
  50. package/src/commands/status.ts +0 -58
  51. package/src/index.ts +0 -88
  52. package/src/lib/api.ts +0 -20
  53. package/src/lib/config.ts +0 -30
  54. package/src/lib/resolve-slug.ts +0 -23
  55. package/tsconfig.json +0 -18
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Safely parse a Response as JSON.
3
+ * Returns the parsed object, or a fallback error object if the response
4
+ * isn't valid JSON (e.g., HTML error pages from the server).
5
+ */
6
+ export const safeJson = async (res) => {
7
+ const text = await res.text();
8
+ try {
9
+ return JSON.parse(text);
10
+ }
11
+ catch {
12
+ return { error: `server returned non-JSON: ${text.slice(0, 100)}` };
13
+ }
14
+ };
15
+ //# sourceMappingURL=safe-json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-json.js","sourceRoot":"","sources":["../../src/lib/safe-json.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,KAAK,EAC3B,GAAa,EACD,EAAE;IACd,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAM,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,KAAK,EAAE,6BAA6B,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EAAO,CAAC;IAC3E,CAAC;AACH,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,16 +1,19 @@
1
1
  {
2
2
  "name": "@pushto/cli",
3
- "version": "0.0.7",
3
+ "version": "0.1.0",
4
4
  "description": "Deploy to pushto.host from your terminal",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "pushto": "dist/cli.js"
8
8
  },
9
+ "files": [
10
+ "dist/"
11
+ ],
9
12
  "scripts": {
10
13
  "build": "tsc",
11
14
  "dev": "tsx src/index.ts",
12
15
  "lint": "tsc --noEmit",
13
- "prepublishOnly": "tsc && echo '#!/usr/bin/env node\nimport \"./index.js\";' > dist/cli.js"
16
+ "prepublishOnly": "npm run build"
14
17
  },
15
18
  "dependencies": {
16
19
  "archiver": "^7.0.1",
@@ -1,4 +0,0 @@
1
-
2
- > @pushto/cli@0.0.1 build /Users/scottmccarthy/pushto/apps/cli
3
- > tsc
4
-
package/bin/pushto.js DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- import '../dist/index.js';
@@ -1,125 +0,0 @@
1
- import chalk from 'chalk';
2
- import ora from 'ora';
3
- import fs from 'node:fs';
4
- import path from 'node:path';
5
- import { execSync } from 'node:child_process';
6
- import archiver from 'archiver';
7
- import { getToken } from '../lib/config.js';
8
- import { api } from '../lib/api.js';
9
- import { resolveSlug } from '../lib/resolve-slug.js';
10
-
11
- const IGNORE = [
12
- 'node_modules',
13
- '.git',
14
- '.next',
15
- '.vercel',
16
- '.turbo',
17
- 'dist',
18
- '.pushto',
19
- '.env',
20
- '.env.local',
21
- '.DS_Store',
22
- ];
23
-
24
- const getGitInfo = (): { hash: string | null; message: string | null; branch: string } => {
25
- try {
26
- const hash = execSync('git rev-parse --short HEAD', { stdio: ['pipe', 'pipe', 'ignore'] }).toString().trim();
27
- const message = execSync('git log -1 --pretty=%s', { stdio: ['pipe', 'pipe', 'ignore'] }).toString().trim();
28
- const branch = execSync('git rev-parse --abbrev-ref HEAD', { stdio: ['pipe', 'pipe', 'ignore'] }).toString().trim();
29
- return { hash, message, branch };
30
- } catch {
31
- return { hash: null, message: null, branch: 'main' };
32
- }
33
- };
34
-
35
- const zipDirectory = (dir: string): Promise<Buffer> => {
36
- return new Promise((resolve, reject) => {
37
- const archive = archiver('zip', { zlib: { level: 9 } });
38
- const chunks: Buffer[] = [];
39
-
40
- archive.on('data', (chunk) => chunks.push(chunk));
41
- archive.on('end', () => resolve(Buffer.concat(chunks)));
42
- archive.on('error', reject);
43
-
44
- // Add files, respecting ignores
45
- const addDir = (dirPath: string, prefix: string) => {
46
- const entries = fs.readdirSync(dirPath, { withFileTypes: true });
47
- for (const entry of entries) {
48
- if (IGNORE.includes(entry.name)) continue;
49
- if (entry.name.startsWith('.') && entry.name !== '.htaccess') continue;
50
-
51
- const fullPath = path.join(dirPath, entry.name);
52
- const archivePath = prefix ? `${prefix}/${entry.name}` : entry.name;
53
-
54
- if (entry.isFile()) {
55
- archive.file(fullPath, { name: archivePath });
56
- } else if (entry.isDirectory()) {
57
- addDir(fullPath, archivePath);
58
- }
59
- }
60
- };
61
-
62
- addDir(dir, '');
63
- archive.finalize();
64
- });
65
- };
66
-
67
- export const deploy = async (): Promise<void> => {
68
- const token = getToken();
69
- if (!token) {
70
- console.log(chalk.red('> not logged in.'));
71
- console.log(chalk.dim(' run ') + chalk.cyan('pushto login') + chalk.dim(' first.'));
72
- process.exit(1);
73
- }
74
-
75
- const slug = resolveSlug();
76
- if (!slug) {
77
- console.log(chalk.red('> no project here.'));
78
- console.log(chalk.dim(' run ') + chalk.cyan('pushto init <name>') + chalk.dim(' to get started.'));
79
- process.exit(1);
80
- }
81
-
82
- const spinner = ora('zipping project...').start();
83
-
84
- try {
85
- const git = getGitInfo();
86
-
87
- // Zip the current directory
88
- const zipBuffer = await zipDirectory(process.cwd());
89
- const sizeMB = (zipBuffer.length / 1024 / 1024).toFixed(1);
90
- spinner.text = `uploading ${sizeMB}MB...`;
91
-
92
- // Upload to the deploy endpoint
93
- const res = await api(`/cli/projects/${slug}/upload`, {
94
- method: 'POST',
95
- headers: {
96
- 'Content-Type': 'application/zip',
97
- },
98
- body: new Uint8Array(zipBuffer),
99
- });
100
-
101
- if (!res.ok) {
102
- const data = (await res.json()) as { error?: string };
103
- spinner.fail(chalk.red(`> ${data.error ?? 'deploy failed.'}`));
104
- process.exit(1);
105
- }
106
-
107
- const data = (await res.json()) as {
108
- deployment: { version: number; fileCount: number; buildDuration: number };
109
- url: string;
110
- message: string;
111
- };
112
-
113
- spinner.succeed(chalk.green(`> ${data.message}`));
114
- if (git.hash) {
115
- console.log(chalk.dim(` commit: ${git.hash} — ${git.message ?? ''}`));
116
- }
117
- console.log(chalk.dim(' files: ') + chalk.cyan(String(data.deployment.fileCount)));
118
- console.log(chalk.dim(' time: ') + chalk.cyan(`${data.deployment.buildDuration}s`));
119
- console.log(chalk.dim(' url: ') + chalk.cyan(data.url));
120
- } catch (err) {
121
- const msg = err instanceof Error ? err.message : 'unknown error';
122
- spinner.fail(chalk.red(`> deploy failed: ${msg}`));
123
- process.exit(1);
124
- }
125
- };
@@ -1,160 +0,0 @@
1
- import chalk from 'chalk';
2
- import fs from 'node:fs';
3
- import path from 'node:path';
4
- import { getToken } from '../lib/config.js';
5
- import { resolveSlug } from '../lib/resolve-slug.js';
6
- import { api } from '../lib/api.js';
7
-
8
- interface Check {
9
- name: string;
10
- status: 'pass' | 'warn' | 'fail';
11
- message: string;
12
- }
13
-
14
- export const doctor = async (): Promise<void> => {
15
- console.log(chalk.green('> pushto doctor\n'));
16
-
17
- const checks: Check[] = [];
18
- const cwd = process.cwd();
19
-
20
- // 1. Auth check
21
- const token = getToken();
22
- checks.push(token
23
- ? { name: 'auth', status: 'pass', message: 'logged in' }
24
- : { name: 'auth', status: 'fail', message: 'not logged in. run pushto login' },
25
- );
26
-
27
- // 2. Project linked
28
- const slug = resolveSlug();
29
- checks.push(slug
30
- ? { name: 'project', status: 'pass', message: `linked to ${slug}` }
31
- : { name: 'project', status: 'fail', message: 'no .pushto file. run pushto init <name>' },
32
- );
33
-
34
- // 3. Package.json exists
35
- const pkgPath = path.join(cwd, 'package.json');
36
- const hasPkg = fs.existsSync(pkgPath);
37
- checks.push(hasPkg
38
- ? { name: 'package.json', status: 'pass', message: 'found' }
39
- : { name: 'package.json', status: 'warn', message: 'not found. static site? that works too.' },
40
- );
41
-
42
- // 4. Framework detection
43
- if (hasPkg) {
44
- try {
45
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
46
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
47
- const framework =
48
- deps['next'] ? 'Next.js' :
49
- deps['react'] ? 'React' :
50
- deps['vue'] ? 'Vue' :
51
- deps['svelte'] ? 'Svelte' :
52
- deps['@sveltejs/kit'] ? 'SvelteKit' :
53
- deps['astro'] ? 'Astro' :
54
- deps['nuxt'] ? 'Nuxt' :
55
- deps['express'] ? 'Express' :
56
- deps['fastify'] ? 'Fastify' :
57
- deps['hono'] ? 'Hono' :
58
- null;
59
-
60
- checks.push(framework
61
- ? { name: 'framework', status: 'pass', message: `detected ${framework}` }
62
- : { name: 'framework', status: 'warn', message: 'no known framework detected. nixpacks will figure it out.' },
63
- );
64
-
65
- // 5. Build script
66
- const hasBuild = pkg.scripts?.build;
67
- checks.push(hasBuild
68
- ? { name: 'build script', status: 'pass', message: `"${hasBuild}"` }
69
- : { name: 'build script', status: 'warn', message: 'no build script. might be fine for static sites.' },
70
- );
71
-
72
- // 6. Node version
73
- const nodeVersion = pkg.engines?.node;
74
- if (nodeVersion) {
75
- checks.push({ name: 'node version', status: 'pass', message: nodeVersion });
76
- }
77
- } catch {
78
- checks.push({ name: 'package.json', status: 'warn', message: 'could not parse package.json' });
79
- }
80
- }
81
-
82
- // 7. .gitignore check
83
- const gitignorePath = path.join(cwd, '.gitignore');
84
- if (fs.existsSync(gitignorePath)) {
85
- const gitignore = fs.readFileSync(gitignorePath, 'utf-8');
86
- if (!gitignore.includes('node_modules')) {
87
- checks.push({ name: '.gitignore', status: 'warn', message: 'node_modules not in .gitignore. deploy will be slow.' });
88
- }
89
- if (!gitignore.includes('.env')) {
90
- checks.push({ name: '.gitignore', status: 'warn', message: '.env not in .gitignore. secrets might leak.' });
91
- }
92
- if (!gitignore.includes('.pushto')) {
93
- checks.push({ name: '.gitignore', status: 'warn', message: '.pushto not in .gitignore. add it.' });
94
- }
95
- }
96
-
97
- // 8. Env vars check (if project linked and authed)
98
- if (token && slug) {
99
- try {
100
- const res = await api(`/cli/projects/${slug}`);
101
- if (res.ok) {
102
- checks.push({ name: 'api', status: 'pass', message: 'pushto.host reachable' });
103
- } else {
104
- checks.push({ name: 'api', status: 'fail', message: 'project not found on pushto.host' });
105
- }
106
- } catch {
107
- checks.push({ name: 'api', status: 'fail', message: 'cannot reach pushto.host' });
108
- }
109
- }
110
-
111
- // 9. Large files check
112
- const largeFiles: string[] = [];
113
- const checkDir = (dir: string, depth = 0) => {
114
- if (depth > 3) return;
115
- try {
116
- const entries = fs.readdirSync(dir, { withFileTypes: true });
117
- for (const entry of entries) {
118
- if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === '.next') continue;
119
- const full = path.join(dir, entry.name);
120
- if (entry.isFile()) {
121
- const stat = fs.statSync(full);
122
- if (stat.size > 10 * 1024 * 1024) {
123
- largeFiles.push(`${entry.name} (${Math.round(stat.size / 1024 / 1024)}MB)`);
124
- }
125
- } else if (entry.isDirectory()) {
126
- checkDir(full, depth + 1);
127
- }
128
- }
129
- } catch { /* permission error, skip */ }
130
- };
131
- checkDir(cwd);
132
-
133
- if (largeFiles.length > 0) {
134
- checks.push({ name: 'large files', status: 'warn', message: `found: ${largeFiles.join(', ')}. will slow deploy.` });
135
- }
136
-
137
- // Print results
138
- console.log();
139
- for (const check of checks) {
140
- const icon = check.status === 'pass' ? chalk.green('✓')
141
- : check.status === 'warn' ? chalk.yellow('⚠')
142
- : chalk.red('✗');
143
- const color = check.status === 'pass' ? chalk.dim
144
- : check.status === 'warn' ? chalk.yellow
145
- : chalk.red;
146
- console.log(` ${icon} ${chalk.dim(check.name.padEnd(16))} ${color(check.message)}`);
147
- }
148
-
149
- const fails = checks.filter((c) => c.status === 'fail');
150
- const warns = checks.filter((c) => c.status === 'warn');
151
-
152
- console.log();
153
- if (fails.length > 0) {
154
- console.log(chalk.red(` ${fails.length} issue${fails.length > 1 ? 's' : ''} to fix before deploying.`));
155
- } else if (warns.length > 0) {
156
- console.log(chalk.yellow(` ${warns.length} warning${warns.length > 1 ? 's' : ''}. probably fine. deploy when ready.`));
157
- } else {
158
- console.log(chalk.green(' all good. run pushto to deploy.'));
159
- }
160
- };
@@ -1,54 +0,0 @@
1
- import chalk from 'chalk';
2
- import { getToken } from '../lib/config.js';
3
- import { resolveSlug } from '../lib/resolve-slug.js';
4
-
5
- export const domain = async (action: string, domainName?: string): Promise<void> => {
6
- const token = getToken();
7
- if (!token) {
8
- console.log(chalk.red('> not logged in.'));
9
- process.exit(1);
10
- }
11
-
12
- const slug = resolveSlug();
13
- if (!slug) {
14
- console.log(chalk.red('> no project here. run pushto init first.'));
15
- process.exit(1);
16
- }
17
-
18
- if (action === 'add') {
19
- if (!domainName) {
20
- console.log(chalk.red('> usage: pushto domain add yourdomain.com'));
21
- process.exit(1);
22
- }
23
-
24
- console.log(chalk.green(`> setting up ${domainName} for ${slug}\n`));
25
- console.log(chalk.dim(' add this DNS record at your domain provider:\n'));
26
- console.log(` ${chalk.cyan('Type')} ${chalk.cyan('Name')} ${chalk.cyan('Value')}`);
27
- console.log(` CNAME @ ${slug}.pushto.host`);
28
- console.log();
29
- console.log(chalk.dim(' or if your provider doesn\'t support CNAME on root:\n'));
30
- console.log(` A @ 76.76.21.21`);
31
- console.log();
32
-
33
- // TODO: when Cloudflare DNS API is wired, this will:
34
- // 1. Register the domain in our system
35
- // 2. Provision SSL via Let's Encrypt
36
- // 3. Poll DNS until verified
37
- // 4. Update the project's customDomain field
38
-
39
- console.log(chalk.yellow(' auto-verification coming soon.'));
40
- console.log(chalk.dim(' for now, add the domain in your dashboard too:'));
41
- console.log(chalk.cyan(` https://pushto.host/dashboard/projects/${slug}/settings`));
42
-
43
- } else if (action === 'remove' || action === 'rm') {
44
- console.log(chalk.green(`> removing custom domain from ${slug}`));
45
- console.log(chalk.yellow(' domain management coming soon via CLI.'));
46
- console.log(chalk.dim(' use the dashboard for now:'));
47
- console.log(chalk.cyan(` https://pushto.host/dashboard/projects/${slug}/settings`));
48
-
49
- } else {
50
- console.log(chalk.dim(' usage:'));
51
- console.log(chalk.cyan(' pushto domain add yourdomain.com'));
52
- console.log(chalk.cyan(' pushto domain remove'));
53
- }
54
- };
@@ -1,128 +0,0 @@
1
- import chalk from 'chalk';
2
- import fs from 'node:fs';
3
- import path from 'node:path';
4
- import { getToken } from '../lib/config.js';
5
- import { resolveSlug } from '../lib/resolve-slug.js';
6
- import { api } from '../lib/api.js';
7
-
8
- export const env = async (action: string, args: string[]): Promise<void> => {
9
- const token = getToken();
10
- if (!token) {
11
- console.log(chalk.red('> not logged in.'));
12
- console.log(chalk.dim(' run ') + chalk.cyan('pushto login') + chalk.dim(' first.'));
13
- process.exit(1);
14
- }
15
-
16
- const slug = resolveSlug();
17
- if (!slug) {
18
- console.log(chalk.red('> no project here. run pushto init first.'));
19
- process.exit(1);
20
- }
21
-
22
- if (action === 'set') {
23
- // pushto env set KEY=value
24
- const pair = args[0];
25
- if (!pair || !pair.includes('=')) {
26
- console.log(chalk.red('> usage: pushto env set KEY=value'));
27
- process.exit(1);
28
- }
29
-
30
- const eqIdx = pair.indexOf('=');
31
- const key = pair.slice(0, eqIdx);
32
- const value = pair.slice(eqIdx + 1);
33
-
34
- if (!key) {
35
- console.log(chalk.red('> key is empty.'));
36
- process.exit(1);
37
- }
38
-
39
- // Call the tRPC mutation via REST-like endpoint
40
- // We'll use a simple CLI API endpoint for this
41
- const res = await api(`/cli/projects/${slug}/env`, {
42
- method: 'POST',
43
- body: JSON.stringify({ key, value }),
44
- });
45
-
46
- if (!res.ok) {
47
- const data = (await res.json()) as { error?: string };
48
- console.log(chalk.red(`> ${data.error ?? 'failed to set env var.'}`));
49
- process.exit(1);
50
- }
51
-
52
- console.log(chalk.green(`> ${key} set.`));
53
-
54
- } else if (action === 'list' || action === 'ls') {
55
- // pushto env list
56
- const res = await api(`/cli/projects/${slug}/env`);
57
-
58
- if (!res.ok) {
59
- const data = (await res.json()) as { error?: string };
60
- console.log(chalk.red(`> ${data.error ?? 'failed to list env vars.'}`));
61
- process.exit(1);
62
- }
63
-
64
- const vars = (await res.json()) as { key: string; value: string }[];
65
-
66
- if (vars.length === 0) {
67
- console.log(chalk.dim('> no env vars set.'));
68
- return;
69
- }
70
-
71
- console.log(chalk.green(`> ${slug} env vars\n`));
72
- for (const v of vars) {
73
- console.log(` ${chalk.cyan(v.key)}=${chalk.dim('••••••••')}`);
74
- }
75
- console.log(chalk.dim(`\n ${vars.length} variable${vars.length > 1 ? 's' : ''}`));
76
-
77
- } else if (action === 'pull') {
78
- // pushto env pull — download to .env
79
- const res = await api(`/cli/projects/${slug}/env`);
80
-
81
- if (!res.ok) {
82
- const data = (await res.json()) as { error?: string };
83
- console.log(chalk.red(`> ${data.error ?? 'failed to pull env vars.'}`));
84
- process.exit(1);
85
- }
86
-
87
- const vars = (await res.json()) as { key: string; value: string }[];
88
-
89
- if (vars.length === 0) {
90
- console.log(chalk.dim('> no env vars to pull.'));
91
- return;
92
- }
93
-
94
- const envContent = vars.map((v) => `${v.key}=${v.value}`).join('\n') + '\n';
95
- const envPath = path.join(process.cwd(), '.env');
96
-
97
- fs.writeFileSync(envPath, envContent);
98
- console.log(chalk.green(`> pulled ${vars.length} var${vars.length > 1 ? 's' : ''} to .env`));
99
- console.log(chalk.dim(' make sure .env is in your .gitignore.'));
100
-
101
- } else if (action === 'rm' || action === 'remove' || action === 'delete') {
102
- const key = args[0];
103
- if (!key) {
104
- console.log(chalk.red('> usage: pushto env rm KEY'));
105
- process.exit(1);
106
- }
107
-
108
- const res = await api(`/cli/projects/${slug}/env`, {
109
- method: 'DELETE',
110
- body: JSON.stringify({ key }),
111
- });
112
-
113
- if (!res.ok) {
114
- const data = (await res.json()) as { error?: string };
115
- console.log(chalk.red(`> ${data.error ?? 'failed to delete env var.'}`));
116
- process.exit(1);
117
- }
118
-
119
- console.log(chalk.green(`> ${key} removed.`));
120
-
121
- } else {
122
- console.log(chalk.dim(' usage:'));
123
- console.log(chalk.cyan(' pushto env set KEY=value'));
124
- console.log(chalk.cyan(' pushto env list'));
125
- console.log(chalk.cyan(' pushto env pull'));
126
- console.log(chalk.cyan(' pushto env rm KEY'));
127
- }
128
- };
@@ -1,65 +0,0 @@
1
- import chalk from 'chalk';
2
- import ora from 'ora';
3
- import fs from 'node:fs';
4
- import path from 'node:path';
5
- import { getToken, setProjectSlug } from '../lib/config.js';
6
- import { api } from '../lib/api.js';
7
-
8
- export const init = async (name: string): Promise<void> => {
9
- const token = getToken();
10
- if (!token) {
11
- console.log(chalk.red('> not logged in.'));
12
- console.log(chalk.dim(' run ') + chalk.cyan('pushto login') + chalk.dim(' first.'));
13
- process.exit(1);
14
- }
15
-
16
- const slug = name
17
- .toLowerCase()
18
- .replace(/[^a-z0-9-]/g, '-')
19
- .replace(/-+/g, '-')
20
- .replace(/^-|-$/g, '');
21
-
22
- if (slug.length < 3) {
23
- console.log(chalk.red('> name too short. needs at least 3 characters.'));
24
- process.exit(1);
25
- }
26
-
27
- const spinner = ora('creating project...').start();
28
-
29
- try {
30
- const res = await api('/cli/projects', {
31
- method: 'POST',
32
- body: JSON.stringify({ name, slug }),
33
- });
34
-
35
- const data = (await res.json()) as {
36
- error?: string;
37
- project?: { id: string };
38
- message?: string;
39
- };
40
-
41
- if (!res.ok) {
42
- spinner.fail(chalk.red(`> ${data.error ?? 'something went wrong.'}`));
43
- process.exit(1);
44
- }
45
-
46
- // Write .pushto file in current directory
47
- const pushtoConfig = { slug, projectId: data.project?.id };
48
- fs.writeFileSync(
49
- path.join(process.cwd(), '.pushto'),
50
- JSON.stringify(pushtoConfig, null, 2) + '\n',
51
- );
52
-
53
- // Also store globally so bare `pushto` works from this dir
54
- setProjectSlug(slug);
55
-
56
- spinner.succeed(chalk.green(`> ${slug} is ready.`));
57
- console.log(chalk.dim(` ${data.message ?? 'project created.'}`));
58
- console.log();
59
- console.log(chalk.dim(' deploy anytime with just: ') + chalk.cyan('pushto'));
60
- console.log(chalk.dim(' your url will be: ') + chalk.cyan(`${slug}.pushto.host`));
61
- } catch (err) {
62
- spinner.fail(chalk.red('> could not reach pushto.host. check your internet.'));
63
- process.exit(1);
64
- }
65
- };
@@ -1,97 +0,0 @@
1
- import chalk from 'chalk';
2
- import http from 'node:http';
3
- import { execSync } from 'node:child_process';
4
- import { setToken, config } from '../lib/config.js';
5
- import { api } from '../lib/api.js';
6
-
7
- export const login = async (): Promise<void> => {
8
- const baseUrl = config.get('apiUrl');
9
-
10
- // Start a local server to receive the callback
11
- const server = http.createServer((req, res) => {
12
- // Handle CORS preflight
13
- if (req.method === 'OPTIONS') {
14
- res.writeHead(200, {
15
- 'Access-Control-Allow-Origin': baseUrl,
16
- 'Access-Control-Allow-Methods': 'POST, OPTIONS',
17
- 'Access-Control-Allow-Headers': 'Content-Type',
18
- });
19
- res.end();
20
- return;
21
- }
22
-
23
- if (req.method === 'POST' && req.url === '/callback') {
24
- let body = '';
25
- req.on('data', (chunk) => (body += chunk));
26
- req.on('end', async () => {
27
- res.writeHead(200, {
28
- 'Content-Type': 'application/json',
29
- 'Access-Control-Allow-Origin': baseUrl,
30
- });
31
- res.end(JSON.stringify({ ok: true }));
32
-
33
- try {
34
- const { access_token } = JSON.parse(body);
35
- if (!access_token) {
36
- console.log(chalk.red('\n> auth failed. no token received.'));
37
- process.exit(1);
38
- }
39
-
40
- setToken(access_token);
41
-
42
- // Verify the token
43
- const meRes = await api('/cli/me');
44
- if (meRes.ok) {
45
- const me = (await meRes.json()) as { email: string; tier: string };
46
- console.log(chalk.green(`\n> logged in as ${me.email}`));
47
- console.log(chalk.dim(` tier: ${me.tier.toLowerCase()}`));
48
- } else {
49
- console.log(chalk.green('\n> logged in.'));
50
- }
51
- } catch {
52
- console.log(chalk.green('\n> logged in.'));
53
- }
54
-
55
- server.close();
56
- process.exit(0);
57
- });
58
- return;
59
- }
60
-
61
- res.writeHead(404);
62
- res.end();
63
- });
64
-
65
- // Listen on a random available port
66
- server.listen(0, () => {
67
- const port = (server.address() as { port: number }).port;
68
- const authUrl = `${baseUrl}/cli/auth?port=${port}`;
69
-
70
- console.log(chalk.dim('> opening browser to log you in...'));
71
- console.log(chalk.dim(` if it doesn't open, go to: `) + chalk.cyan(authUrl));
72
-
73
- // Open browser
74
- const open = (url: string) => {
75
- const cmd =
76
- process.platform === 'darwin'
77
- ? `open "${url}"`
78
- : process.platform === 'win32'
79
- ? `start "${url}"`
80
- : `xdg-open "${url}"`;
81
- try {
82
- execSync(cmd, { stdio: 'ignore' });
83
- } catch {
84
- // User will see the URL in the console
85
- }
86
- };
87
-
88
- open(authUrl);
89
-
90
- // Timeout after 2 minutes
91
- setTimeout(() => {
92
- console.log(chalk.red('\n> login timed out. try again.'));
93
- server.close();
94
- process.exit(1);
95
- }, 120_000);
96
- });
97
- };