@glash/cli 0.2.44 → 0.2.45

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
@@ -109,13 +109,11 @@ glash deploy --asset-mode auto --asset-threshold 100mb
109
109
  Every deploy prints a live monitor link. Open it to watch the black-screen deployment flow while the CLI is packaging, uploading, building, routing, and checking the live URL.
110
110
  Public asset offload prepares assets in small batches and uses multipart uploads for large files, so image/video-heavy `public/` folders can upload without forcing the source bundle over the platform limit.
111
111
 
112
- Before a local source upload, `glash deploy` automatically runs the project's
113
- `build` script with the package manager declared in `package.json` or detected
114
- from its lockfile. The progress display keeps the progress bar on top and shows
115
- the current two-word action underneath it. A failed local build
116
- stops the deployment before source is uploaded. Use `--no-build` only when you
117
- intentionally need to bypass this pre-deploy check. Remote Git deployments
118
- continue to build on the Glash build worker.
112
+ The Glash build worker compiles uploaded source, so `glash deploy` does not run
113
+ the same build locally by default. Use `glash deploy --build` when you want an
114
+ additional local preflight build before upload. The package manager comes from
115
+ `package.json` or the project's lockfile, and a failed preflight stops before
116
+ source is uploaded. `--no-build` remains accepted for backward compatibility.
119
117
 
120
118
  Framework/runtime checks:
121
119
 
@@ -150,8 +148,8 @@ For glashjs projects, `glash deploy` detects `glashjs` or `glash.config.mjs`, ma
150
148
  | `glash projects:rm <slug>` | Delete a project |
151
149
  | `glash link <slug>` | Link the current directory to an existing project |
152
150
  | `glash open` | Open the linked project's primary domain |
153
- | `glash deploy [--prod] [--detach]` | Build and deploy the linked project; streams the pipeline until live |
154
- | `glash deploy --no-build` | Skip the automatic local build before uploading source |
151
+ | `glash deploy [--prod] [--detach]` | Upload and deploy the linked project; streams the pipeline until live |
152
+ | `glash deploy --build` | Run an optional local preflight build before uploading source |
155
153
  | `glash deploy --asset-mode offload` | Upload `public/` assets to object storage/CDN before zipping source |
156
154
  | `glash deploy --source-upload chunked` | Stage the source zip through multipart object storage to bypass Cloudflare's single-request cap |
157
155
  | `glash deployments` | List recent deployments |
package/bin/glash.mjs CHANGED
@@ -68,7 +68,7 @@ ${color.bold('PROJECTS')}
68
68
  open Open the project's primary domain in a browser
69
69
 
70
70
  ${color.bold('DEPLOY')}
71
- deploy [--prod] [--detach] Build and deploy the linked project, syncing .env files
71
+ deploy [--prod] [--detach] Upload and deploy the linked project, syncing .env files
72
72
  new deploy Deploy to a brand new project, ignoring any existing link
73
73
  deployments List recent deployments
74
74
  status <id> Show deployment pipeline + events
@@ -98,7 +98,8 @@ ${color.bold('FLAGS')}
98
98
  --environment <name> Env-var environment (default: production)
99
99
  --env-file <file[,file]> Env file(s) to sync before deploy
100
100
  --no-env Skip automatic env sync during deploy
101
- --no-build Skip the automatic local build before source upload
101
+ --build Run an optional local preflight build before upload
102
+ --no-build Skip local build (legacy compatibility; now the default)
102
103
  --asset-mode <mode> Public asset handling: inline, auto, offload
103
104
  --asset-threshold <size> Auto-offload threshold (default: 100mb)
104
105
  --source-upload <mode> Source upload: auto, direct, chunked
@@ -181,7 +181,11 @@ export async function deploy({ flags }) {
181
181
  try {
182
182
  localBuild = await buildProjectBeforeDeploy(process.cwd(), {
183
183
  packageJson: projectProfile.runtime?.pkg,
184
- skip: Boolean(flags['no-build']) || process.env.GLASH_CLI_BUILD_RUNNING === '1',
184
+ // The Glash build host compiles the uploaded source, so doing the same
185
+ // work locally on every deploy doubles build time. Keep local preflight
186
+ // validation available as an explicit --build option.
187
+ skip: !flags.build || Boolean(flags['no-build']) || process.env.GLASH_CLI_BUILD_RUNNING === '1',
188
+ skipReason: flags['no-build'] ? 'disabled' : 'not-requested',
185
189
  onStart: () => indicator.setPhase('prebuilding'),
186
190
  });
187
191
  report.localBuild = localBuild;
@@ -1305,6 +1309,7 @@ function formatLocalBuildSummary(build) {
1305
1309
  }
1306
1310
  if (build.status === 'failed') return `${build.command || 'build command'} failed`;
1307
1311
  if (build.reason === 'disabled') return 'skipped (--no-build)';
1312
+ if (build.reason === 'not-requested') return 'remote build (use --build for local preflight)';
1308
1313
  if (build.reason === 'no-package-json') return 'skipped (no package.json)';
1309
1314
  if (build.reason === 'no-build-script') return 'skipped (no build script)';
1310
1315
  return build.status || '-';
package/lib/api.mjs CHANGED
@@ -21,13 +21,11 @@ function retryAfterMs(headers) {
21
21
 
22
22
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
23
23
 
24
- // Transient HTTP statuses worth retrying. These mean the request almost
25
- // certainly never reached app logic (gateway/edge hiccup, cold start,
26
- // rate-limit), so retrying is safe even for non-idempotent POSTs and is the
27
- // main reason `glash deploy` survives flaky networks, VPNs and corporate
28
- // proxies across machines.
29
- const RETRYABLE_STATUS = new Set([408, 425, 429, 500, 502, 503, 504, 522, 524]);
30
- const DEFAULT_RETRIES = 4;
24
+ // Transient edge and rate-limit statuses worth a short retry budget. A plain
25
+ // application HTTP 500 is intentionally excluded: retrying it four times made
26
+ // deterministic failures slow and hid useful Cloudflare 1101 diagnostics.
27
+ const RETRYABLE_STATUS = new Set([408, 425, 429, 502, 503, 504, 522, 524]);
28
+ const DEFAULT_RETRIES = 2;
31
29
  // Per-request wall-clock ceiling. Without this a contended/wedged backend
32
30
  // leaves `glash deploy` hanging forever after "source bundle X MB" with no
33
31
  // output. On timeout we abort and retry instead of hanging. Uploads (FormData)
@@ -43,6 +41,38 @@ function backoffMs(attempt, retryAfter) {
43
41
  return Math.round(jitter);
44
42
  }
45
43
 
44
+ export function sanitizeRemoteMessage(value, maxLength = 500) {
45
+ const clean = String(value ?? '')
46
+ .replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, '')
47
+ .replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, '')
48
+ .replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, '')
49
+ .replace(/<[^>]*>/g, ' ')
50
+ .replace(/\s+/g, ' ')
51
+ .trim();
52
+ if (!clean) return '';
53
+ return clean.length > maxLength ? `${clean.slice(0, maxLength - 1)}…` : clean;
54
+ }
55
+
56
+ export function responseErrorMessage(status, rawBody) {
57
+ const raw = String(rawBody ?? '');
58
+ if (/\berror(?:\s+code\s*:|\s+)\s*1101\b/i.test(raw)) {
59
+ return 'GlashDB API is temporarily unavailable (Cloudflare error 1101). The backend container did not start.';
60
+ }
61
+ try {
62
+ const data = JSON.parse(raw);
63
+ const remote = Array.isArray(data?.message) ? data.message.join(', ') : data?.message;
64
+ const safe = sanitizeRemoteMessage(remote);
65
+ if (safe) return safe;
66
+ } catch {}
67
+ // Do not echo arbitrary HTML or terminal control sequences returned by a
68
+ // compromised proxy. Plain short text is useful; markup is not.
69
+ if (!/[<>]/.test(raw)) {
70
+ const safe = sanitizeRemoteMessage(raw, 240);
71
+ if (safe) return `HTTP ${status}: ${safe}`;
72
+ }
73
+ return `HTTP ${status}`;
74
+ }
75
+
46
76
  export async function api(method, path, { body, token, query, retries = DEFAULT_RETRIES, signal, timeoutMs } = {}) {
47
77
  const headers = {
48
78
  Accept: 'application/json',
@@ -115,12 +145,8 @@ export async function api(method, path, { body, token, query, retries = DEFAULT_
115
145
  attempt += 1;
116
146
  continue;
117
147
  }
118
- let message = `HTTP ${res.status}`;
119
- try {
120
- const data = await res.json();
121
- if (typeof data?.message === 'string') message = data.message;
122
- else if (Array.isArray(data?.message)) message = data.message.join(', ');
123
- } catch {}
148
+ const rawBody = await res.text().catch(() => '');
149
+ let message = responseErrorMessage(res.status, rawBody);
124
150
  if (res.status === 401 && !path.includes('/auth/login') && !path.includes('/auth/cli/login')) {
125
151
  message = `${message}. Run \`glash login\` again, then retry the command.`;
126
152
  }
@@ -38,12 +38,13 @@ export async function planProjectBuild(cwd, { packageJson } = {}) {
38
38
  export async function buildProjectBeforeDeploy(cwd, {
39
39
  packageJson,
40
40
  skip = false,
41
+ skipReason = 'disabled',
41
42
  onStart,
42
43
  onLine,
43
44
  spawnImpl = spawn,
44
45
  env = process.env,
45
46
  } = {}) {
46
- if (skip) return { status: 'skipped', reason: 'disabled' };
47
+ if (skip) return { status: 'skipped', reason: skipReason };
47
48
 
48
49
  const plan = await planProjectBuild(cwd, { packageJson });
49
50
  if (plan.status !== 'ready') return plan;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glash/cli",
3
- "version": "0.2.44",
3
+ "version": "0.2.45",
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",