envstash-cli 0.4.1 → 0.4.3

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
@@ -177,8 +177,9 @@ Monorepos can track nested paths by listing them explicitly:
177
177
 
178
178
  ## Opaque files (service-account keys, certs)
179
179
 
180
- A tracked name whose basename is not `.env` or `.env.*` is stored **whole**, as
181
- one encrypted file, rather than parsed into keys:
180
+ A tracked name whose basename is not `.env`, `.env.*`, or `*.env` is stored
181
+ **whole**, as one encrypted file, rather than parsed into keys — dotenv-shaped
182
+ names, including the `.config/auth.env` convention, are parsed instead:
182
183
 
183
184
  ```json
184
185
  { "project": "demo-app", "files": [".env", ".config/service-account.json"] }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "envstash-cli",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "Git-style CLI for EnvStash — keep your .env files in sync with your cloud account.",
5
5
  "keywords": [
6
6
  "env",
package/src/blob.mjs CHANGED
@@ -2,15 +2,17 @@ import { createHash } from 'node:crypto'
2
2
  import { basename } from 'node:path'
3
3
 
4
4
  /**
5
- * How a tracked name is classified when the cloud has no say yet: `.env` and
6
- * `.env.*` basenames are dotenv files parsed into keys; everything else — a
7
- * service-account JSON, a cert — is an opaque file stored whole. For names the
8
- * cloud already knows, its recorded kind wins; this function only decides the
9
- * kind of a file's first push.
5
+ * How a tracked name is classified when the cloud has no say yet: `.env`,
6
+ * `.env.*`, and `*.env` basenames are dotenv files parsed into keys — the
7
+ * trailing form covers the `.config/auth.env` convention, which is KEY=value
8
+ * content under a descriptive name. Everything else — a service-account JSON,
9
+ * a cert — is an opaque file stored whole. For names the cloud already knows,
10
+ * its recorded kind wins; this function only decides the kind of a file's
11
+ * first push.
10
12
  */
11
13
  export function isEnvName(name) {
12
14
  const base = basename(name)
13
- return base === '.env' || base.startsWith('.env.')
15
+ return base === '.env' || base.startsWith('.env.') || base.endsWith('.env')
14
16
  }
15
17
 
16
18
  /**
@@ -1,4 +1,4 @@
1
- import { execFile } from 'node:child_process'
1
+ import { spawn } from 'node:child_process'
2
2
  import { hostname, userInfo } from 'node:os'
3
3
  import { setTimeout as sleep } from 'node:timers/promises'
4
4
 
@@ -53,7 +53,12 @@ export async function login(flags) {
53
53
  out()
54
54
  out(` Your code: ${style.bold(style.cyan(started.userCode))}`)
55
55
  out()
56
- out(` ${style.dim('Open')} ${style.blue(verificationUrl ?? `${host}/cli`)}`)
56
+ // The URL on its own line, in the same visible cyan as the code above it:
57
+ // blue vanishes on dark terminals, and a line that is only the URL is what
58
+ // lets a terminal's double-click and link detection grab the whole thing.
59
+ out(` ${style.dim('Open this link to approve:')}`)
60
+ out()
61
+ out(` ${style.cyan(verificationUrl ?? `${host}/cli`)}`)
57
62
  out()
58
63
 
59
64
  if (!verificationUrl) {
@@ -217,8 +222,23 @@ function openBrowser(url) {
217
222
  }
218
223
 
219
224
  try {
220
- child = execFile(command.file, command.args, { stdio: 'ignore' }, error => settle(!error))
225
+ // spawn, not execFile: execFile builds its own options object for spawn
226
+ // and does not forward `stdio`, so asking it for 'ignore' silently got
227
+ // the default three pipes instead. The opener passes those on to the
228
+ // browser it exec-s, and child.unref() releases the process handle but
229
+ // not the pipe handles — so the event loop stayed alive on a browser that
230
+ // could outlive the terminal, and `envs login` printed "✓ Signed in" and
231
+ // then hung at the prompt. Detaching as well puts the browser in its own
232
+ // process group, so Ctrl-C on a still-waiting login does not take it down
233
+ // (not on Windows, where detached means a new console window).
234
+ child = spawn(command.file, command.args, {
235
+ stdio: 'ignore',
236
+ detached: process.platform !== 'win32',
237
+ windowsHide: true,
238
+ })
239
+
221
240
  child.on('error', () => settle(false))
241
+ child.on('exit', code => settle(code === 0))
222
242
  } catch {
223
243
  settle(false)
224
244
  return