@glash/cli 0.2.2 → 0.2.4

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/README.md CHANGED
@@ -17,6 +17,8 @@ glash deploy
17
17
  | `glash whoami` | Show the signed-in user |
18
18
  | `glash projects` | List your projects |
19
19
  | `glash projects:new <name>` | Create a project and link it to the current directory |
20
+ | `glash new <name>` | Alias for `projects:new` |
21
+ | `glash new deploy` | Deploy to a brand new project, ignoring any existing link |
20
22
  | `glash projects:rm <slug>` | Delete a project |
21
23
  | `glash link <slug>` | Link the current directory to an existing project |
22
24
  | `glash open` | Open the linked project's primary domain |
@@ -31,7 +33,7 @@ glash deploy
31
33
  | `glash env:rm KEY` | Remove an env var |
32
34
  | `glash env:pull [--out .env.local]` | Pull env vars to a local file |
33
35
  | `glash env:push [<file>]` | Bulk upload from a `.env` file |
34
- | `glash pull --from vercel --project <id>` | Import a Vercel project (source + env) |
36
+ | `glash update` | Update `@glash/cli` to the latest version |
35
37
 
36
38
  Pass `--project <slug>` to any command to operate on a project other than
37
39
  the one linked in the current directory.
package/bin/glash.mjs CHANGED
@@ -3,7 +3,7 @@ import { argv, exit } from 'node:process';
3
3
  import { parseArgs } from '../lib/args.mjs';
4
4
  import { info, fail, color } from '../lib/ui.mjs';
5
5
 
6
- const VERSION = '0.2.2';
6
+ const VERSION = '0.2.4';
7
7
 
8
8
  const sub = argv[2];
9
9
  const rest = argv.slice(3);
@@ -19,6 +19,14 @@ const commands = {
19
19
  'projects:rm': () => import('../commands/projects.mjs').then((m) => m.removeProject(parsed)),
20
20
  link: () => import('../commands/projects.mjs').then((m) => m.linkProject(parsed)),
21
21
 
22
+ new: () => {
23
+ if (parsed.positional[0] === 'deploy') {
24
+ const nextParsed = { ...parsed, flags: { ...parsed.flags, new: true } };
25
+ return import('../commands/deploy.mjs').then((m) => m.deploy(nextParsed));
26
+ }
27
+ return import('../commands/projects.mjs').then((m) => m.createProject(parsed));
28
+ },
29
+
22
30
  deploy: () => import('../commands/deploy.mjs').then((m) => m.deploy(parsed)),
23
31
  deployments: () => import('../commands/deployments.mjs').then((m) => m.listDeployments(parsed)),
24
32
  status: () => import('../commands/deployments.mjs').then((m) => m.status(parsed)),
@@ -34,6 +42,7 @@ const commands = {
34
42
 
35
43
  pull: () => import('../commands/pull.mjs').then((m) => m.pull(parsed)),
36
44
  open: () => import('../commands/open.mjs').then((m) => m.open(parsed)),
45
+ update: () => import('../commands/update.mjs').then((m) => m.update(parsed)),
37
46
  };
38
47
 
39
48
  function usage() {
@@ -50,12 +59,14 @@ ${color.bold('AUTH')}
50
59
  ${color.bold('PROJECTS')}
51
60
  projects List your projects
52
61
  projects:new <name> Create a project (auto-links to cwd)
62
+ new <name> Alias for projects:new
53
63
  projects:rm <slug> Delete a project (--yes to skip confirm)
54
64
  link <slug> Link the current directory to a project
55
65
  open Open the project's primary domain in a browser
56
66
 
57
67
  ${color.bold('DEPLOY')}
58
68
  deploy [--prod] [--detach] Deploy the linked project, syncing .env files first
69
+ new deploy Deploy to a brand new project, ignoring any existing link
59
70
  deployments List recent deployments
60
71
  status <id> Show deployment pipeline + events
61
72
  logs <id> Stream deployment events (alias of status)
@@ -71,7 +82,9 @@ ${color.bold('ENV VARS')}
71
82
 
72
83
  ${color.bold('PULL')}
73
84
  pull [<slug>] [--out dir] [--no-env] Pull source + .env from a glashDB project
74
- pull --from vercel --project <id> Pull source + env from a Vercel project
85
+
86
+ ${color.bold('SYSTEM')}
87
+ update Update @glash/cli to the latest version
75
88
 
76
89
  ${color.bold('FLAGS')}
77
90
  --project <slug|id> Override the linked project for this command
@@ -20,19 +20,69 @@ function currentBranch() {
20
20
  }
21
21
 
22
22
  async function resolveProjectId(flags) {
23
- if (flags.project) {
24
- if (/^[0-9a-f-]{36}$/.test(flags.project)) return flags.project;
25
- const list = await apiGet('/projects');
26
- const found = list.find((p) => p.slug === flags.project || p.id === flags.project);
27
- if (!found) throw new Error(`No project matches "${flags.project}". Run \`glash projects\`.`);
28
- return found.id;
29
- }
30
- const link = await readProjectLink();
31
- if (link?.projectId) {
32
- return link.projectId;
23
+ if (flags.new) {
24
+ info(color.dim('Initiating new project deployment...'));
25
+ } else if (flags.project) {
26
+ if (/^[0-9a-f-]{36}$/.test(flags.project)) {
27
+ try {
28
+ await apiGet(`/projects/${flags.project}`);
29
+ return flags.project;
30
+ } catch (err) {
31
+ if (err.status === 404 || err.message?.toLowerCase().includes('not found')) {
32
+ warn(`Project "${flags.project}" is no longer available or was deleted.`);
33
+ info('Let\'s set up a new project.');
34
+ } else {
35
+ throw err;
36
+ }
37
+ }
38
+ } else {
39
+ const list = await apiGet('/projects');
40
+ const found = list.find((p) => p.slug === flags.project || p.id === flags.project);
41
+ if (!found) {
42
+ warn(`No project matches "${flags.project}".`);
43
+ info('Let\'s set up a new project.');
44
+ } else {
45
+ return found.id;
46
+ }
47
+ }
48
+ } else {
49
+ const link = await readProjectLink();
50
+ if (link?.projectId) {
51
+ const confirm = await prompt(`Deploy to linked project "${color.bold(link.slug || link.projectId)}"? [Y/n/select] `);
52
+ if (confirm.toLowerCase() === 'y' || confirm === '') {
53
+ try {
54
+ await apiGet(`/projects/${link.projectId}`);
55
+ return link.projectId;
56
+ } catch (err) {
57
+ if (err.status === 404 || err.message?.toLowerCase().includes('not found')) {
58
+ warn(`Linked project "${link.slug || link.projectId}" is no longer available or was deleted.`);
59
+ info('Let\'s set up a new project.');
60
+ } else {
61
+ throw err;
62
+ }
63
+ }
64
+ } else if (confirm.toLowerCase() === 'n') {
65
+ throw new Error('Deployment aborted.');
66
+ } else {
67
+ // If they chose 'select' / 's', let them pick from existing projects
68
+ const list = await apiGet('/projects');
69
+ if (list.length === 0) {
70
+ info('No existing projects found. Proceeding to create a new one.');
71
+ } else {
72
+ info('\nYour existing projects:');
73
+ list.forEach((p, i) => info(` ${color.dim(i + 1 + '.')} ${color.bold(p.name)} ${color.dim('(' + p.slug + ')')}`));
74
+ const choice = await prompt('\nWhich project do you want to deploy to? (enter number or slug): ');
75
+ const p = list.find((x, i) => x.slug === choice || String(i + 1) === choice);
76
+ if (!p) throw new Error(`Project "${choice}" not found.`);
77
+ await writeProjectLink({ projectId: p.id, slug: p.slug, name: p.name });
78
+ ok(`Linked to ${p.name} (${p.slug})`);
79
+ return p.id;
80
+ }
81
+ }
82
+ }
33
83
  }
34
84
 
35
- // INTERACTIVE PROJECT SETUP (Vercel-style)
85
+ // INTERACTIVE PROJECT SETUP
36
86
  const dirName = basename(process.cwd());
37
87
  const setup = await prompt(`Set up and deploy "${dirName}"? [Y/n] `);
38
88
  if (setup.toLowerCase() === 'n') {
@@ -50,7 +100,7 @@ async function resolveProjectId(flags) {
50
100
  const choice = await prompt('\nWhich project do you want to link to? (enter number or slug): ');
51
101
  const p = list.find((x, i) => x.slug === choice || String(i + 1) === choice);
52
102
  if (!p) throw new Error(`Project "${choice}" not found.`);
53
- await writeProjectLink({ projectId: p.id, slug: p.slug });
103
+ await writeProjectLink({ projectId: p.id, slug: p.slug, name: p.name });
54
104
  ok(`Linked to ${p.name} (${p.slug})`);
55
105
  return p.id;
56
106
  }
@@ -62,7 +112,7 @@ async function resolveProjectId(flags) {
62
112
 
63
113
  info(color.dim(`Creating project "${name}"...`));
64
114
  const p = await apiPost('/projects', { name, slug });
65
- await writeProjectLink({ projectId: p.id, slug: p.slug });
115
+ await writeProjectLink({ projectId: p.id, slug: p.slug, name: p.name });
66
116
  ok(`Created and linked to ${p.name} (${p.slug})`);
67
117
  return p.id;
68
118
  }
package/commands/pull.mjs CHANGED
@@ -7,9 +7,6 @@
7
7
  // glash pull <slug> → pulls a specific project by slug
8
8
  // glash pull --out ../somewhere → write to a different directory
9
9
  // glash pull --no-env → skip writing .env
10
- //
11
- // Vercel mode (unchanged): glash pull --from vercel --project <id> [--team <id>]
12
- // delegates to the existing reference implementation in glash-pull.mjs.
13
10
 
14
11
  import { spawnSync } from 'node:child_process';
15
12
  import { fileURLToPath } from 'node:url';
@@ -25,14 +22,6 @@ export async function pull(parsed) {
25
22
  const flags = parsed?.flags ?? {};
26
23
  const positional = parsed?.positional ?? [];
27
24
 
28
- // Vercel-import compatibility path.
29
- if (flags.from === 'vercel') {
30
- const here = dirname(fileURLToPath(import.meta.url));
31
- const script = join(here, '..', 'glash-pull.mjs');
32
- const r = spawnSync(process.execPath, [script, ...argv.slice(3)], { stdio: 'inherit' });
33
- exit(r.status ?? 0);
34
- }
35
-
36
25
  const token = await getToken();
37
26
  if (!token) return fail('Not signed in. Run `glash login` first.');
38
27
 
@@ -0,0 +1,11 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { ok, fail, info, color } from '../lib/ui.mjs';
3
+
4
+ export async function update() {
5
+ info(color.dim('→ updating @glash/cli to latest...'));
6
+ const child = spawnSync('npm', ['install', '-g', '@glash/cli@latest'], { stdio: 'inherit' });
7
+ if (child.status !== 0) {
8
+ return fail(`Update failed with exit code ${child.status ?? 'unknown'}. Please run \`npm install -g @glash/cli@latest\` manually.`);
9
+ }
10
+ ok('Successfully updated @glash/cli to the latest version.');
11
+ }
package/lib/zip.mjs CHANGED
@@ -23,7 +23,6 @@ export async function zipDirectory(sourceDir, outPath, { ignore = [] } = {}) {
23
23
  '.nuxt*/**',
24
24
  '.output/**',
25
25
  '.svelte-kit/**',
26
- '.vercel/**',
27
26
  '.wrangler/**',
28
27
  '.netlify/**',
29
28
  'dist/**',
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@glash/cli",
3
- "version": "0.2.2",
4
- "description": "glashDB command-line interface — deploy projects, pull source + env, manage env vars, import from Vercel.",
3
+ "version": "0.2.4",
4
+ "description": "glashDB command-line interface — deploy projects, pull source + env, manage env vars.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://glashdb.com",
7
7
  "repository": {
@@ -20,7 +20,6 @@
20
20
  "bin",
21
21
  "lib",
22
22
  "commands",
23
- "glash-pull.mjs",
24
23
  "README.md"
25
24
  ],
26
25
  "keywords": [
package/glash-pull.mjs DELETED
@@ -1,132 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * glash pull — reference implementation
4
- *
5
- * glash pull --from vercel --project <id> [--team <id>] [--token <vercel-token>] [--out .]
6
- *
7
- * Pulls the latest production deployment from Vercel into the current
8
- * working directory:
9
- * • walks /v6/deployments/<uid>/files
10
- * • downloads each file via /v7/deployments/<uid>/files/<file-uid>
11
- * • writes them at the same paths locally
12
- * • dumps env vars to .env.local
13
- *
14
- * Token sources, in order: --token flag, $VERCEL_TOKEN, ~/.glash/credentials.json (vercelToken).
15
- *
16
- * No external deps — pure Node 20+. The shipping `@glashdb/cli` wraps this
17
- * with a nicer UX (interactive prompts, progress bars, `glash login`-aware).
18
- */
19
-
20
- import { argv, env, exit, cwd, stdout, stderr } from 'node:process';
21
- import { readFile, writeFile, mkdir } from 'node:fs/promises';
22
- import { homedir } from 'node:os';
23
- import { join, dirname } from 'node:path';
24
-
25
- const VERCEL = 'https://api.vercel.com';
26
-
27
- function parseArgs(argv) {
28
- const out = {};
29
- for (let i = 2; i < argv.length; i++) {
30
- const a = argv[i];
31
- if (a.startsWith('--')) {
32
- const k = a.slice(2);
33
- const v = argv[i + 1] && !argv[i + 1].startsWith('--') ? argv[++i] : 'true';
34
- out[k] = v;
35
- }
36
- }
37
- return out;
38
- }
39
-
40
- async function readToken(flag) {
41
- if (flag) return flag;
42
- if (env.VERCEL_TOKEN) return env.VERCEL_TOKEN;
43
- try {
44
- const raw = await readFile(join(homedir(), '.glash', 'credentials.json'), 'utf8');
45
- const j = JSON.parse(raw);
46
- if (j.vercelToken) return j.vercelToken;
47
- } catch {}
48
- stderr.write("error: no Vercel token. Pass --token, set VERCEL_TOKEN, or run `glash login --vercel`.\n");
49
- exit(2);
50
- }
51
-
52
- async function vercel(token, path, teamId) {
53
- const url = `${VERCEL}${path}${teamId ? (path.includes('?') ? '&' : '?') + 'teamId=' + encodeURIComponent(teamId) : ''}`;
54
- const r = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
55
- if (!r.ok) throw new Error(`Vercel ${r.status} ${r.statusText} on ${path}`);
56
- return r.json();
57
- }
58
-
59
- async function vercelRaw(token, path, teamId) {
60
- const url = `${VERCEL}${path}${teamId ? (path.includes('?') ? '&' : '?') + 'teamId=' + encodeURIComponent(teamId) : ''}`;
61
- const r = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
62
- if (!r.ok) throw new Error(`Vercel ${r.status} on ${path}`);
63
- return Buffer.from(await r.arrayBuffer());
64
- }
65
-
66
- async function walk(token, deploymentUid, teamId, tree, prefix, out) {
67
- for (const node of tree) {
68
- const rel = prefix ? `${prefix}/${node.name}` : node.name;
69
- if (node.type === 'directory' && node.children) {
70
- await walk(token, deploymentUid, teamId, node.children, rel, out);
71
- } else if (node.type === 'file' && node.uid) {
72
- out.push({ rel, uid: node.uid });
73
- }
74
- }
75
- }
76
-
77
- async function main() {
78
- const args = parseArgs(argv);
79
- if (args.from !== 'vercel') {
80
- stderr.write("usage: glash pull --from vercel --project <id> [--team <id>] [--out .]\n");
81
- exit(2);
82
- }
83
- if (!args.project) { stderr.write("error: --project <vercel-project-id> required\n"); exit(2); }
84
-
85
- const token = await readToken(args.token);
86
- const team = args.team;
87
- const outDir = args.out || cwd();
88
-
89
- stdout.write(`→ pulling vercel project ${args.project}${team ? ` (team ${team})` : ''} into ${outDir}\n`);
90
-
91
- // 1) latest production deployment
92
- const list = await vercel(token, `/v6/deployments?projectId=${encodeURIComponent(args.project)}&limit=1&target=production&state=READY`, team);
93
- const dep = list.deployments?.[0];
94
- if (!dep) { stderr.write("error: no production deployment on Vercel for that project.\n"); exit(1); }
95
- stdout.write(`✓ deployment ${dep.uid}\n`);
96
-
97
- // 2) file tree
98
- const tree = await vercel(token, `/v6/deployments/${dep.uid}/files`, team);
99
- const files = [];
100
- await walk(token, dep.uid, team, tree, '', files);
101
- stdout.write(`✓ ${files.length} files to download\n`);
102
-
103
- // 3) write each file
104
- let n = 0;
105
- for (const f of files) {
106
- const buf = await vercelRaw(token, `/v7/deployments/${dep.uid}/files/${f.uid}`, team);
107
- const dest = join(outDir, f.rel);
108
- await mkdir(dirname(dest), { recursive: true });
109
- await writeFile(dest, buf);
110
- n++;
111
- if (n % 10 === 0 || n === files.length) stdout.write(` ${n}/${files.length}\r`);
112
- }
113
- stdout.write(`\n✓ wrote ${n} files\n`);
114
-
115
- // 4) env vars → .env.local
116
- try {
117
- const env = await vercel(token, `/v9/projects/${encodeURIComponent(args.project)}/env?decrypt=true`, team);
118
- const lines = (env.envs ?? [])
119
- .filter((e) => !e.target || e.target.includes('production'))
120
- .map((e) => `${e.key}=${JSON.stringify(e.value ?? '')}`);
121
- if (lines.length) {
122
- await writeFile(join(outDir, '.env.local'), lines.join('\n') + '\n');
123
- stdout.write(`✓ wrote ${lines.length} env vars to .env.local\n`);
124
- }
125
- } catch (e) {
126
- stdout.write(`! skipped env vars: ${e.message}\n`);
127
- }
128
-
129
- stdout.write(`done.\n next: cd ${outDir} && glash deploy\n`);
130
- }
131
-
132
- main().catch((e) => { stderr.write(`error: ${e.message}\n`); exit(1); });