vercel-account-manager 0.1.0

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.
Files changed (3) hide show
  1. package/README.md +121 -0
  2. package/bin/vcm +341 -0
  3. package/package.json +34 -0
package/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # vcm
2
+
3
+ `vcm` is a small account and team manager for the [Vercel CLI](https://vercel.com/docs/cli). It gives separate Vercel logins names, remembers a global default account, and can pin an account and team to a project.
4
+
5
+ ## Install with npm
6
+
7
+ Install the [Vercel CLI](https://vercel.com/docs/cli) first, then install `vcm` from npm:
8
+
9
+ ```sh
10
+ npm install -g vercel
11
+ npm install -g vercel-account-manager
12
+ ```
13
+
14
+ The package exposes the `vcm` command on macOS and Linux. You can also install directly from [GitHub](https://github.com/breadoncee/vercel-account-manager) with `npm install -g github:breadoncee/vercel-account-manager`.
15
+
16
+ ## Install from a clone
17
+
18
+ Clone this repository and run:
19
+
20
+ ```sh
21
+ ./install.sh
22
+ ```
23
+
24
+ The installer copies `vcm` to `~/.local/bin`. If that directory is not on your `PATH`, add this line to your shell setup file (for example, `~/.zshrc`) and open a new terminal:
25
+
26
+ ```sh
27
+ export PATH="$HOME/.local/bin:$PATH"
28
+ ```
29
+
30
+ To update a previous installation after pulling changes, run `./install.sh --force`. Set `VCM_INSTALL_DIR` to an absolute path if you prefer a different installation directory.
31
+
32
+ ## Add accounts
33
+
34
+ If you are already logged in with the Vercel CLI, register that login without authenticating again:
35
+
36
+ ```sh
37
+ vcm add personal --default
38
+ ```
39
+
40
+ Add another account with a one-time login. The new account becomes the global default when login completes:
41
+
42
+ ```sh
43
+ vcm add work
44
+ ```
45
+
46
+ If an account already has its own Vercel global config directory, register that exact directory without logging in again:
47
+
48
+ ```sh
49
+ vcm add work "$HOME/.config/vercel-work"
50
+ ```
51
+
52
+ Use `vcm login work` if you need to refresh that account's login later.
53
+
54
+ ## Use an account and team for a project
55
+
56
+ From the project directory, run:
57
+
58
+ ```sh
59
+ vcm use work my-team
60
+ vcm status
61
+ vcm deploy
62
+ ```
63
+
64
+ `vcm use` writes `.vcmrc` in the current directory:
65
+
66
+ ```text
67
+ account=work
68
+ team=my-team
69
+ ```
70
+
71
+ Commands run in that directory or its subdirectories use this account and team automatically. The closest `.vcmrc` wins. The file contains only an account name and team slug, not credentials. Commit it if your collaborators use the same account names; otherwise add `.vcmrc` to that project's `.gitignore`.
72
+
73
+ Remove the project's `.vcmrc` when you want it to use the global fallback again.
74
+
75
+ Project team selection is passed to Vercel as `--scope` for each command, so it does not change the account's global team. Explicit `--scope` or `--team` flags on a command take precedence. An account can also be pinned without a team using `vcm use work`; in that case Vercel's default team for that account applies.
76
+
77
+ ## Set the global fallback
78
+
79
+ Outside projects with a `.vcmrc`, `vcm` uses the global default account. Set it from anywhere with:
80
+
81
+ ```sh
82
+ vcm use --global personal
83
+ vcm use --global work my-team
84
+ ```
85
+
86
+ When a team is given with `--global`, `vcm` uses Vercel's `switch` command to save that team for the account. Each account retains its own global team selection.
87
+
88
+ ## Other commands
89
+
90
+ ```sh
91
+ vcm list # Show saved accounts; * marks the global default
92
+ vcm current # Print the effective account name
93
+ vcm current --global # Print the global default account name
94
+ vcm status # Show effective account, team, and config source
95
+ vcm whoami # Ask Vercel which user is logged in
96
+
97
+ vcm teams # List teams on the effective account
98
+ vcm team my-team # Change the current project's team, or global team outside a project
99
+ vcm team --global my-team # Change the global default account's team
100
+ vcm team # Choose a team interactively
101
+
102
+ vcm deploy # Pass any other command to Vercel
103
+ ```
104
+
105
+ Vercel saves a globally selected team in that account's [global CLI configuration](https://vercel.com/docs/project-configuration/global-configuration).
106
+
107
+ When working in an already linked project, check its local `.vercel/project.json` before deploying. The [project link](https://vercel.com/docs/cli/project-linking) identifies a specific Vercel project and organization; changing accounts does not change that link.
108
+
109
+ ## Where data is stored
110
+
111
+ `vcm` stores account names and its global default account under `~/.config/vcm`. New account logins are saved in separate Vercel config directories below that location. Set `VCM_HOME` to use a different directory. Existing installations using the earlier `vercel-account-switcher` or `vercel-account-manager` directories are read automatically.
112
+
113
+ Authentication files stay in your user config directory, outside this repository. The installer only copies the script.
114
+
115
+ ## Test
116
+
117
+ ```sh
118
+ sh tests/run.sh
119
+ ```
120
+
121
+ The test uses a fake `vercel` command; it does not access your accounts.
package/bin/vcm ADDED
@@ -0,0 +1,341 @@
1
+ #!/bin/sh
2
+ # Named account profiles for the Vercel CLI.
3
+
4
+ set -eu
5
+ umask 077
6
+
7
+ config_home=${XDG_CONFIG_HOME:-$HOME/.config}
8
+ if [ -n "${VCM_HOME:-}" ]; then
9
+ state_dir=$VCM_HOME
10
+ elif [ -n "${VERCEL_ACCOUNT_MANAGER_HOME:-}" ]; then
11
+ state_dir=$VERCEL_ACCOUNT_MANAGER_HOME
12
+ elif [ -n "${VCA_STATE_DIR:-}" ]; then
13
+ state_dir=$VCA_STATE_DIR
14
+ elif [ -d "$config_home/vercel-account-switcher" ]; then
15
+ state_dir=$config_home/vercel-account-switcher
16
+ elif [ -d "$config_home/vercel-account-manager" ]; then
17
+ state_dir=$config_home/vercel-account-manager
18
+ else
19
+ state_dir=$config_home/vcm
20
+ fi
21
+ profiles_dir=$state_dir/profiles
22
+ active_file=$state_dir/active
23
+
24
+ usage() {
25
+ cat <<'EOF'
26
+ Usage:
27
+ vcm add NAME [--default | CONFIG_DIR] Add an account and make it the global default
28
+ vcm use NAME [TEAM] Set account/team for the current project
29
+ vcm use --global NAME [TEAM] Set the global default account/team
30
+ vcm team [TEAM] Set the team for the current context
31
+ vcm team --global [TEAM] Set the global default account's team
32
+ vcm teams List teams for the effective account
33
+ vcm current [--global] Show the effective or global account
34
+ vcm status [--global] Show account, team, and config source
35
+ vcm list List registered accounts
36
+ vcm login [NAME] Refresh an account's login
37
+ vcm VERCEL_ARGS... Run a Vercel CLI command
38
+
39
+ Examples:
40
+ vcm add personal --default Keep your existing Vercel login
41
+ vcm add work Log in once and save the work account
42
+ vcm use --global personal Set the fallback account
43
+ vcm use work my-team Pin this project to work/my-team
44
+ vcm status See which config is in effect
45
+ vcm deploy Run Vercel with that account/team
46
+
47
+ For an already saved custom login, use its exact config directory:
48
+ vcm add work "$HOME/.config/vercel-work"
49
+ EOF
50
+ }
51
+
52
+ die() {
53
+ printf 'vcm: %s\n' "$*" >&2
54
+ exit 1
55
+ }
56
+
57
+ valid_name() {
58
+ case $1 in
59
+ ''|*[!a-zA-Z0-9_-]*) return 1 ;;
60
+ *) return 0 ;;
61
+ esac
62
+ }
63
+
64
+ profile_path() {
65
+ valid_name "$1" || die "invalid account name: $1"
66
+ printf '%s/%s\n' "$profiles_dir" "$1"
67
+ }
68
+
69
+ read_profile() {
70
+ profile_file=$(profile_path "$1")
71
+ [ -f "$profile_file" ] || die "unknown account '$1'; run 'vcm list'"
72
+ IFS= read -r config_dir < "$profile_file" || die "cannot read account '$1'"
73
+ [ -n "$config_dir" ] || die "empty config for account '$1'"
74
+ }
75
+
76
+ selected_global_account() {
77
+ [ -f "$active_file" ] || die "no global account selected; run 'vcm add NAME' or 'vcm use --global NAME'"
78
+ IFS= read -r account < "$active_file" || die 'cannot read selected account'
79
+ read_profile "$account"
80
+ }
81
+
82
+ valid_team() {
83
+ case $1 in
84
+ ''|*[!a-zA-Z0-9_.-]*) return 1 ;;
85
+ *) return 0 ;;
86
+ esac
87
+ }
88
+
89
+ find_project_file() {
90
+ project_file=
91
+ search_dir=$(pwd -P)
92
+ while :; do
93
+ if [ -f "$search_dir/.vcmrc" ]; then
94
+ project_file=$search_dir/.vcmrc
95
+ return 0
96
+ fi
97
+ [ "$search_dir" != / ] || return 0
98
+ search_dir=${search_dir%/*}
99
+ [ -n "$search_dir" ] || search_dir=/
100
+ done
101
+ }
102
+
103
+ read_project_file() {
104
+ project_account=
105
+ project_team=
106
+ seen_account=0
107
+ seen_team=0
108
+ while IFS= read -r line || [ -n "$line" ]; do
109
+ case $line in
110
+ ''|\#*) ;;
111
+ account=*)
112
+ [ "$seen_account" -eq 0 ] || die "duplicate account in $project_file"
113
+ project_account=${line#account=}
114
+ seen_account=1
115
+ ;;
116
+ team=*)
117
+ [ "$seen_team" -eq 0 ] || die "duplicate team in $project_file"
118
+ project_team=${line#team=}
119
+ seen_team=1
120
+ ;;
121
+ *) die "invalid line in $project_file: $line" ;;
122
+ esac
123
+ done < "$project_file"
124
+ [ "$seen_account" -eq 1 ] && valid_name "$project_account" || die "invalid account in $project_file"
125
+ [ -z "$project_team" ] || valid_team "$project_team" || die "invalid team in $project_file"
126
+ }
127
+
128
+ resolve_context() {
129
+ find_project_file
130
+ if [ -n "$project_file" ]; then
131
+ read_project_file
132
+ account=$project_account
133
+ team=$project_team
134
+ context_source=$project_file
135
+ read_profile "$account"
136
+ else
137
+ selected_global_account
138
+ team=
139
+ context_source=global
140
+ fi
141
+ }
142
+
143
+ write_project_file() {
144
+ target_file=$1
145
+ target_account=$2
146
+ target_team=$3
147
+ temp_file=$target_file.tmp.$$
148
+ printf 'account=%s\nteam=%s\n' "$target_account" "$target_team" > "$temp_file"
149
+ mv "$temp_file" "$target_file"
150
+ }
151
+
152
+ invoke_vercel() {
153
+ if [ "$config_dir" = :default: ]; then
154
+ vercel "$@"
155
+ else
156
+ vercel --global-config "$config_dir" "$@"
157
+ fi
158
+ }
159
+
160
+ run_vercel() {
161
+ if [ -n "${team:-}" ]; then
162
+ for option do
163
+ case $option in
164
+ --scope|--scope=*|-S|--team|--team=*|-T) team=; break ;;
165
+ esac
166
+ done
167
+ fi
168
+ if [ "$config_dir" = :default: ]; then
169
+ if [ -n "${team:-}" ]; then
170
+ exec vercel --scope "$team" "$@"
171
+ else
172
+ exec vercel "$@"
173
+ fi
174
+ else
175
+ if [ -n "${team:-}" ]; then
176
+ exec vercel --global-config "$config_dir" --scope "$team" "$@"
177
+ else
178
+ exec vercel --global-config "$config_dir" "$@"
179
+ fi
180
+ fi
181
+ }
182
+
183
+ select_account() {
184
+ mkdir -p "$state_dir"
185
+ printf '%s\n' "$1" > "$active_file"
186
+ printf 'Global default account: %s.\n' "$1"
187
+ }
188
+
189
+ case ${1:-} in
190
+ add)
191
+ [ "$#" -ge 2 ] && [ "$#" -le 3 ] || die 'usage: vcm add NAME [--default | CONFIG_DIR]'
192
+ name=$2
193
+ profile_file=$(profile_path "$name")
194
+ [ ! -e "$profile_file" ] || die "account '$name' already exists"
195
+ mkdir -p "$profiles_dir"
196
+ case ${3:-} in
197
+ --default) config_dir=:default: ;;
198
+ '') config_dir=$state_dir/auth/$name; mkdir -p "$config_dir" ;;
199
+ /*) config_dir=$3; [ -d "$config_dir" ] || die "config directory does not exist: $config_dir" ;;
200
+ *) die 'CONFIG_DIR must be an absolute path' ;;
201
+ esac
202
+ printf '%s\n' "$config_dir" > "$profile_file"
203
+ printf 'Added %s.\n' "$name"
204
+ if [ "$#" -eq 2 ]; then
205
+ printf 'Complete the one-time Vercel login for %s.\n' "$name"
206
+ if ! invoke_vercel login; then
207
+ printf "vcm: login was not completed; retry with 'vcm login %s'.\n" "$name" >&2
208
+ exit 1
209
+ fi
210
+ fi
211
+ select_account "$name"
212
+ ;;
213
+ login)
214
+ [ "$#" -le 2 ] || die 'usage: vcm login [NAME]'
215
+ if [ "$#" -eq 2 ]; then
216
+ read_profile "$2"
217
+ else
218
+ resolve_context
219
+ fi
220
+ invoke_vercel login
221
+ ;;
222
+ use)
223
+ if [ "${2:-}" = --global ]; then
224
+ [ "$#" -ge 3 ] && [ "$#" -le 4 ] || die 'usage: vcm use --global NAME [TEAM]'
225
+ name=$3
226
+ target_team=${4:-}
227
+ read_profile "$name"
228
+ if [ -n "$target_team" ]; then
229
+ valid_team "$target_team" || die "invalid team: $target_team"
230
+ invoke_vercel switch "$target_team"
231
+ fi
232
+ select_account "$name"
233
+ else
234
+ [ "$#" -ge 2 ] && [ "$#" -le 3 ] || die 'usage: vcm use NAME [TEAM]'
235
+ name=$2
236
+ target_team=${3:-}
237
+ read_profile "$name"
238
+ [ -z "$target_team" ] || valid_team "$target_team" || die "invalid team: $target_team"
239
+ project_file=$(pwd -P)/.vcmrc
240
+ write_project_file "$project_file" "$name" "$target_team"
241
+ printf 'Project config: %s\n' "$project_file"
242
+ fi
243
+ ;;
244
+ list)
245
+ [ "$#" -eq 1 ] || die 'usage: vcm list'
246
+ if [ ! -d "$profiles_dir" ]; then
247
+ printf 'No accounts registered. Run: vcm add NAME\n'
248
+ exit 0
249
+ fi
250
+ current=
251
+ if [ -f "$active_file" ]; then
252
+ IFS= read -r current < "$active_file" || true
253
+ fi
254
+ found=0
255
+ for file in "$profiles_dir"/*; do
256
+ [ -f "$file" ] || continue
257
+ found=1
258
+ name=${file##*/}
259
+ IFS= read -r dir < "$file" || die "cannot read account '$name'"
260
+ [ "$dir" != :default: ] || dir='existing Vercel login'
261
+ marker=' '
262
+ [ "$name" != "$current" ] || marker='*'
263
+ printf '%s %-16s %s\n' "$marker" "$name" "$dir"
264
+ done
265
+ [ "$found" -ne 0 ] || printf 'No accounts registered. Run: vcm add NAME\n'
266
+ ;;
267
+ current)
268
+ [ "$#" -le 2 ] || die 'usage: vcm current [--global]'
269
+ if [ "$#" -eq 2 ]; then
270
+ [ "$2" = --global ] || die 'usage: vcm current [--global]'
271
+ selected_global_account
272
+ else
273
+ resolve_context
274
+ fi
275
+ printf '%s\n' "$account"
276
+ ;;
277
+ status)
278
+ [ "$#" -le 2 ] || die 'usage: vcm status [--global]'
279
+ if [ "$#" -eq 2 ]; then
280
+ [ "$2" = --global ] || die 'usage: vcm status [--global]'
281
+ selected_global_account
282
+ team=
283
+ context_source=global
284
+ else
285
+ resolve_context
286
+ fi
287
+ printf 'Account: %s\n' "$account"
288
+ if [ -n "$team" ]; then
289
+ printf 'Team: %s\n' "$team"
290
+ else
291
+ printf 'Team: Vercel CLI default for this account\n'
292
+ fi
293
+ printf 'Source: %s\n' "$context_source"
294
+ ;;
295
+ teams)
296
+ [ "$#" -eq 1 ] || die 'usage: vcm teams'
297
+ resolve_context
298
+ invoke_vercel teams list
299
+ ;;
300
+ team)
301
+ if [ "${2:-}" = --global ]; then
302
+ [ "$#" -le 3 ] || die 'usage: vcm team --global [TEAM]'
303
+ selected_global_account
304
+ project_file=
305
+ target_team=${3:-}
306
+ else
307
+ [ "$#" -le 2 ] || die 'usage: vcm team [TEAM]'
308
+ resolve_context
309
+ target_team=${2:-}
310
+ fi
311
+ if [ -n "$project_file" ]; then
312
+ if [ -z "$target_team" ]; then
313
+ [ -t 0 ] || die 'usage: vcm team TEAM (interactive choice requires a terminal)'
314
+ invoke_vercel teams list
315
+ printf 'Team slug: ' >&2
316
+ IFS= read -r target_team || die 'team selection canceled'
317
+ fi
318
+ valid_team "$target_team" || die "invalid team: $target_team"
319
+ write_project_file "$project_file" "$account" "$target_team"
320
+ printf 'Project team: %s (%s)\n' "$target_team" "$project_file"
321
+ elif [ -n "$target_team" ]; then
322
+ valid_team "$target_team" || die "invalid team: $target_team"
323
+ invoke_vercel switch "$target_team"
324
+ else
325
+ invoke_vercel switch
326
+ fi
327
+ ;;
328
+ help|--help|-h|'')
329
+ usage
330
+ ;;
331
+ --)
332
+ shift
333
+ [ "$#" -gt 0 ] || die 'missing Vercel command after --'
334
+ resolve_context
335
+ run_vercel "$@"
336
+ ;;
337
+ *)
338
+ resolve_context
339
+ run_vercel "$@"
340
+ ;;
341
+ esac
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "vercel-account-manager",
3
+ "version": "0.1.0",
4
+ "description": "Switch Vercel CLI accounts and teams with project-specific defaults",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/breadoncee/vercel-account-manager.git"
8
+ },
9
+ "homepage": "https://github.com/breadoncee/vercel-account-manager#readme",
10
+ "bugs": "https://github.com/breadoncee/vercel-account-manager/issues",
11
+ "publishConfig": {
12
+ "registry": "https://registry.npmjs.org/"
13
+ },
14
+ "bin": {
15
+ "vcm": "bin/vcm"
16
+ },
17
+ "files": [
18
+ "bin/vcm",
19
+ "README.md"
20
+ ],
21
+ "os": [
22
+ "darwin",
23
+ "linux"
24
+ ],
25
+ "scripts": {
26
+ "test": "sh tests/run.sh"
27
+ },
28
+ "keywords": [
29
+ "vercel",
30
+ "cli",
31
+ "account-switcher",
32
+ "team-switcher"
33
+ ]
34
+ }