envstash-cli 0.3.0 → 0.4.1

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
@@ -26,18 +26,19 @@ envs pull
26
26
 
27
27
  ## Commands
28
28
 
29
- | Command | What it does |
30
- | -------- | ------------------------------------------------- |
31
- | `login` | Authorize this machine with a browser device code |
32
- | `logout` | Revoke this machine's token |
33
- | `whoami` | Show the signed-in account |
34
- | `init` | Create a project and link this repo to it |
35
- | `link` | Link this repo to a project that already exists |
36
- | `list` | List every project in your account |
37
- | `status` | Compare cloud files against local files |
38
- | `diff` | Show which keys differ, and how |
39
- | `pull` | Write the cloud's version of each file to disk |
40
- | `push` | Send local files to the cloud |
29
+ | Command | What it does |
30
+ | --------- | ------------------------------------------------- |
31
+ | `login` | Authorize this machine with a browser device code |
32
+ | `logout` | Revoke this machine's token |
33
+ | `whoami` | Show the signed-in account |
34
+ | `init` | Create a project and link this repo to it |
35
+ | `link` | Link this repo to a project that already exists |
36
+ | `list` | List every project in your account |
37
+ | `status` | Compare cloud files against local files |
38
+ | `diff` | Show which keys differ, and how |
39
+ | `pull` | Write the cloud's version of each file to disk |
40
+ | `push` | Send local files to the cloud |
41
+ | `version` | Show the CLI version and its environment |
41
42
 
42
43
  Run `envs help <command>` for details on any one of them.
43
44
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "envstash-cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Git-style CLI for EnvStash — keep your .env files in sync with your cloud account.",
5
5
  "keywords": [
6
6
  "env",
@@ -1,5 +1,9 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { dirname, join } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+
1
5
  import { api } from '../api.mjs'
2
- import { getStoredEmail } from '../config.mjs'
6
+ import { configDir, getStoredEmail } from '../config.mjs'
3
7
  import {
4
8
  discoverTrackedFiles,
5
9
  findRepoRoot,
@@ -7,9 +11,70 @@ import {
7
11
  resolveProject,
8
12
  writeLink,
9
13
  } from '../project.mjs'
10
- import { requireHostToken, resolveHostFor } from '../session.mjs'
14
+ import { requireHostToken, resolveHostFor, resolveHostSource } from '../session.mjs'
11
15
  import { out, pad, pluralize, style } from '../ui.mjs'
12
16
 
17
+ /**
18
+ * `envs version` — the version, and the environment it would act in.
19
+ *
20
+ * `--version` and `-v` stay a bare semver for scripts to parse; this command is
21
+ * the human-facing one. The host line goes through the same resolution the sync
22
+ * commands use, so "which server would this talk to" is answered by the same
23
+ * code that would talk to it.
24
+ */
25
+ export async function version(flags) {
26
+ const packageRoot = join(dirname(fileURLToPath(import.meta.url)), '..', '..')
27
+
28
+ let manifest = {}
29
+ try {
30
+ manifest = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'))
31
+ } catch {
32
+ // A broken install still deserves an answer; every field below has a fallback.
33
+ }
34
+
35
+ const { host, source } = resolveHostSource(flags)
36
+ const details = {
37
+ name: manifest.name ?? 'envstash-cli',
38
+ version: manifest.version ?? '0.0.0',
39
+ node: process.version,
40
+ platform: `${process.platform} ${process.arch}`,
41
+ installed: packageRoot,
42
+ host,
43
+ hostSource: source,
44
+ configDir: configDir(),
45
+ }
46
+
47
+ if (flags.json) {
48
+ out(JSON.stringify(details, null, 2))
49
+ return 0
50
+ }
51
+
52
+ const width = 12
53
+
54
+ out()
55
+ out(` ${style.bold(details.name)} ${style.dim(`v${details.version}`)}`)
56
+ out()
57
+ out(` ${pad('node', width)}${style.dim(details.node)}`)
58
+ out(` ${pad('platform', width)}${style.dim(details.platform)}`)
59
+ out(` ${pad('installed', width)}${style.dim(details.installed)}`)
60
+ out(` ${pad('host', width)}${style.dim(`${details.host} (${sourceLabel(source)})`)}`)
61
+ out(` ${pad('config', width)}${style.dim(details.configDir)}`)
62
+ out()
63
+
64
+ return 0
65
+ }
66
+
67
+ function sourceLabel(source) {
68
+ return (
69
+ {
70
+ flag: 'from --host or --port',
71
+ env: 'from the environment',
72
+ link: 'from .envstash.json',
73
+ default: 'default',
74
+ }[source] ?? source
75
+ )
76
+ }
77
+
13
78
  /** `envs whoami` — which account this machine is signed in as. */
14
79
  export async function whoami(flags) {
15
80
  const { host, token } = requireHostToken(flags)
package/src/index.mjs CHANGED
@@ -4,7 +4,7 @@ import { fileURLToPath } from 'node:url'
4
4
 
5
5
  import { diff } from './commands/diff.mjs'
6
6
  import { login, logout } from './commands/login.mjs'
7
- import { init, link, list, whoami } from './commands/misc.mjs'
7
+ import { init, link, list, version as versionCommand, whoami } from './commands/misc.mjs'
8
8
  import { pull } from './commands/pull.mjs'
9
9
  import { push } from './commands/push.mjs'
10
10
  import { status } from './commands/status.mjs'
@@ -22,7 +22,19 @@ const VALUE_FLAGS = new Set(['host', 'port', 'project', 'label', 'cwd'])
22
22
  const SHORT_VALUE_ALIASES = { C: 'cwd' }
23
23
 
24
24
  /** The implementation of each command, by name. */
25
- const HANDLERS = { login, logout, whoami, init, link, list, status, diff, pull, push }
25
+ const HANDLERS = {
26
+ login,
27
+ logout,
28
+ whoami,
29
+ init,
30
+ link,
31
+ list,
32
+ status,
33
+ diff,
34
+ pull,
35
+ push,
36
+ version: versionCommand,
37
+ }
26
38
 
27
39
  /**
28
40
  * Summaries and aliases come from the manifest rather than being repeated here,
@@ -127,7 +139,9 @@ export async function run(argv) {
127
139
  const { flags, args } = parseArgs(argv)
128
140
  const [name, ...rest] = args
129
141
 
130
- if (flags.version || flags.v || name === 'version') {
142
+ // Bare semver only, for scripts to parse. `envs version` — the command — is
143
+ // the detailed, human-facing answer.
144
+ if (flags.version || flags.v) {
131
145
  out(version())
132
146
  return finish(0)
133
147
  }
@@ -242,7 +256,9 @@ function printHelp(topic) {
242
256
  out(` --project <name> ${style.dim('Target a project other than the linked one')}`)
243
257
  out(` --host <url> ${style.dim('Target another EnvStash server')}`)
244
258
  out(` --port <n> ${style.dim('Target a dev server on localhost at this port')}`)
245
- out(` --json ${style.dim('Machine-readable output (status, list, whoami)')}`)
259
+ out(
260
+ ` --json ${style.dim('Machine-readable output (status, list, whoami, version)')}`
261
+ )
246
262
  out(` --exit-zero ${style.dim('Always exit 0, even when out of sync (status, diff)')}`)
247
263
  out(` --dry-run ${style.dim('Show what would happen (pull, push)')}`)
248
264
  out(` --dev ${style.dim('Log in to a local dev server if one is running')}`)
@@ -338,6 +354,13 @@ const COMMAND_HELP = {
338
354
  link: ['Link this repo to a project that already exists.', '', ' envs link my-app'],
339
355
  list: ['List every project in your account.', '', ' envs list', ' envs list --json'],
340
356
  whoami: ['Show which account this machine is signed in as.', '', ' envs whoami'],
357
+ version: [
358
+ 'Show the version, runtime, install path, and which server commands would',
359
+ 'talk to. --version and -v print the bare version for scripts.',
360
+ '',
361
+ ' envs version',
362
+ ' envs version --json',
363
+ ],
341
364
  }
342
365
 
343
366
  function printCommandHelp(name) {
package/src/manifest.mjs CHANGED
@@ -27,6 +27,7 @@ export const CLI_COMMANDS = [
27
27
  { name: 'diff', summary: 'Show which keys differ, and how' },
28
28
  { name: 'pull', summary: 'Write the cloud’s version of each file to disk' },
29
29
  { name: 'push', summary: 'Send local files to the cloud' },
30
+ { name: 'version', summary: 'Show the CLI version and its environment' },
30
31
  ]
31
32
 
32
33
  /**
package/src/ui.mjs CHANGED
@@ -32,6 +32,22 @@ export const style = {
32
32
  cyan: wrap(36, 39),
33
33
  }
34
34
 
35
+ /**
36
+ * `envs status | head` closes stdout after a few lines, and every write after
37
+ * that raises EPIPE — which Node treats as a crash, stack trace and all. A
38
+ * closed pipe is the reader saying "I have enough", not a failure, so leave
39
+ * the way a SIGPIPE-terminated process would: immediately and silently.
40
+ */
41
+ for (const stream of [process.stdout, process.stderr]) {
42
+ stream.on('error', error => {
43
+ if (error?.code === 'EPIPE') {
44
+ process.exit(0)
45
+ }
46
+
47
+ throw error
48
+ })
49
+ }
50
+
35
51
  export function out(message = '') {
36
52
  process.stdout.write(`${message}\n`)
37
53
  }