@glash/cli 0.2.45 → 0.2.46

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/glash.mjs CHANGED
@@ -88,7 +88,7 @@ ${color.bold('DATABASE')}
88
88
  database upload Restore the latest/local dump into linked glashDB database
89
89
 
90
90
  ${color.bold('PULL')}
91
- pull [<slug>] [--out dir] [--no-env] Pull source + .env from a glashDB project
91
+ pull [<slug>] [--out dir] [--no-env] Unpack a project (asks which, if no slug)
92
92
 
93
93
  ${color.bold('SYSTEM')}
94
94
  update Update @glash/cli to the latest version
package/commands/pull.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  // glash pull
2
2
  //
3
- // Default mode: pulls the linked glashDB project's source + .env to the
4
- // current directory.
3
+ // Unpacks a glashDB project's active deployed image to the current directory,
4
+ // plus its .env. Works however long ago the project was deployed.
5
5
  //
6
- // glash pull → uses the linked project (.glash/project.json)
6
+ // glash pull → pick a project from your glashDB account
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
@@ -16,7 +16,8 @@ import { createWriteStream } from 'node:fs';
16
16
  import { Readable } from 'node:stream';
17
17
  import { argv, cwd, exit, stdout, stderr } from 'node:process';
18
18
  import { API_URL, getToken, readProjectLink } from '../lib/config.mjs';
19
- import { color, info, fail } from '../lib/ui.mjs';
19
+ import { apiGet } from '../lib/api.mjs';
20
+ import { color, info, fail, prompt } from '../lib/ui.mjs';
20
21
 
21
22
  export async function pull(parsed) {
22
23
  const flags = parsed?.flags ?? {};
@@ -25,15 +26,12 @@ export async function pull(parsed) {
25
26
  const token = await getToken();
26
27
  if (!token) return fail('Not signed in. Run `glash login` first.');
27
28
 
28
- // Resolve which project to pull
29
+ // Resolve which project to pull. With no argument, pick from the projects on
30
+ // this glashDB account rather than guessing.
29
31
  let slugOrId = positional[0] ?? flags.project;
30
32
  if (!slugOrId) {
31
- const link = await readProjectLink();
32
- if (link?.slug) slugOrId = link.slug;
33
- else if (link?.projectId) slugOrId = link.projectId;
34
- }
35
- if (!slugOrId) {
36
- return fail('No project specified. Pass `glash pull <slug>` or run `glash link <slug>` first.');
33
+ slugOrId = await chooseProject();
34
+ if (!slugOrId) return;
37
35
  }
38
36
 
39
37
  const outDir = resolve(flags.out ? String(flags.out) : cwd());
@@ -52,6 +50,51 @@ export async function pull(parsed) {
52
50
  stdout.write(color.dim(`\n next: cd ${outDir} && glash deploy\n`));
53
51
  }
54
52
 
53
+ // Lists this account's projects and asks which one to pull. The project linked
54
+ // to the current directory, if any, is the default on Enter.
55
+ async function chooseProject() {
56
+ let projects;
57
+ try {
58
+ projects = await apiGet('/projects');
59
+ } catch (e) {
60
+ fail(`Could not list your projects: ${e.message}`);
61
+ return null;
62
+ }
63
+ if (!projects?.length) {
64
+ fail('This account has no projects yet. Run `glash projects:new` first.');
65
+ return null;
66
+ }
67
+
68
+ const link = await readProjectLink().catch(() => null);
69
+ const linkedIndex = projects.findIndex(
70
+ (p) => (link?.projectId && p.id === link.projectId) || (link?.slug && p.slug === link.slug),
71
+ );
72
+
73
+ stdout.write('\n Your projects:\n\n');
74
+ projects.forEach((p, i) => {
75
+ const marker = i === linkedIndex ? color.dim(' (linked here)') : '';
76
+ stdout.write(` ${String(i + 1).padStart(2)}. ${p.slug}${p.name && p.name !== p.slug ? color.dim(` - ${p.name}`) : ''}${marker}\n`);
77
+ });
78
+
79
+ const fallback = linkedIndex >= 0 ? projects[linkedIndex] : null;
80
+ const suffix = fallback ? ` [${fallback.slug}]` : '';
81
+ const answer = (await prompt(`\n Which project do you want to pull?${suffix} `)).trim();
82
+
83
+ if (!answer) {
84
+ if (fallback) return fallback.slug;
85
+ fail('No project selected.');
86
+ return null;
87
+ }
88
+ const byNumber = Number(answer);
89
+ if (Number.isInteger(byNumber) && byNumber >= 1 && byNumber <= projects.length) {
90
+ return projects[byNumber - 1].slug;
91
+ }
92
+ const byName = projects.find((p) => p.slug === answer || p.id === answer);
93
+ if (byName) return byName.slug;
94
+ fail(`No project named "${answer}" on this account.`);
95
+ return null;
96
+ }
97
+
55
98
  async function downloadAndExtract(token, slugOrId, outDir) {
56
99
  const url = `${API_URL}/projects/${encodeURIComponent(slugOrId)}/source.tar.gz`;
57
100
  const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
@@ -66,7 +109,7 @@ async function downloadAndExtract(token, slugOrId, outDir) {
66
109
  if (child.status !== 0) {
67
110
  throw new Error(`tar extract failed: ${child.stderr?.toString() || 'unknown'}`);
68
111
  }
69
- info(color.dim(` ✓ source written to ${outDir}`));
112
+ info(color.dim(` ✓ project files unpacked to ${outDir}`));
70
113
  }
71
114
 
72
115
  async function downloadEnv(token, slugOrId, outDir, force) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glash/cli",
3
- "version": "0.2.45",
3
+ "version": "0.2.46",
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",