@symbo.ls/cli 2.33.20 → 2.33.25

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/collab.js CHANGED
@@ -35,15 +35,15 @@ function debounce(fn, wait) {
35
35
  }
36
36
 
37
37
  export async function startCollab(options) {
38
+ const symbolsConfig = await loadSymbolsConfig()
39
+ const cliConfig = loadCliConfig()
38
40
  const credManager = new CredentialManager()
39
- const authToken = credManager.ensureAuthToken()
41
+ const authToken = credManager.ensureAuthToken(cliConfig.apiBaseUrl)
40
42
  if (!authToken) {
41
43
  console.log(chalk.yellow('\nAuthentication required. Please run: smbls login\n'))
42
44
  process.exit(1)
43
45
  }
44
46
 
45
- const symbolsConfig = await loadSymbolsConfig()
46
- const cliConfig = loadCliConfig()
47
47
  const lock = readLock()
48
48
  const branch = options.branch || cliConfig.branch || symbolsConfig.branch || 'main'
49
49
  const appKey = cliConfig.projectKey || symbolsConfig.key
package/bin/fetch.js CHANGED
@@ -26,17 +26,16 @@ const debugMsg = chalk.dim(
26
26
  export const fetchFromCli = async (opts) => {
27
27
  const { dev, verbose, prettify, convert: convertOpt, metadata: metadataOpt, update, force } = opts
28
28
 
29
+ const symbolsConfig = await loadSymbolsConfig()
30
+ const cliConfig = loadCliConfig()
29
31
  const credManager = new CredentialManager()
30
- const authToken = credManager.ensureAuthToken()
32
+ const authToken = credManager.ensureAuthToken(cliConfig.apiBaseUrl)
31
33
 
32
34
  if (!authToken) {
33
35
  showAuthRequiredMessages()
34
-
35
36
  process.exit(1)
36
37
  }
37
38
 
38
- const symbolsConfig = await loadSymbolsConfig()
39
- const cliConfig = loadCliConfig()
40
39
  const projectKey = cliConfig.projectKey || symbolsConfig.key
41
40
  const branch = cliConfig.branch || symbolsConfig.branch || 'main'
42
41
  const { framework, distDir, metadata } = symbolsConfig
package/bin/index.js CHANGED
@@ -14,6 +14,7 @@ import './login.js'
14
14
  import './push.js'
15
15
  import './link-packages.js'
16
16
  import './collab.js'
17
+ import './servers.js'
17
18
 
18
19
  const args = process.argv
19
20
  program.parse(args)
@@ -48,14 +48,22 @@ export async function initRepo (dest, framework) {
48
48
  // Copy design system file
49
49
  console.log()
50
50
  const dsfilePath = path.join(dest, 'DesignSystem.js')
51
- console.log('Creating', chalk.bold(dsfilePath))
52
- await fs.promises.copyFile(DESIGN_SYSTEM_FILE_PATH, dsfilePath)
51
+ if (fs.existsSync(dsfilePath)) {
52
+ console.log(chalk.yellow('Skipping existing'), chalk.bold(dsfilePath))
53
+ } else {
54
+ console.log('Creating', chalk.bold(dsfilePath))
55
+ await fs.promises.copyFile(DESIGN_SYSTEM_FILE_PATH, dsfilePath)
56
+ }
53
57
 
54
58
  // Copy design system file
55
59
  const rcfilePath = path.join(dest, 'symbols.json')
56
- console.log('Creating', chalk.bold(rcfilePath))
57
- await fs.promises.copyFile(SYMBOLS_FILE_PATH, rcfilePath)
58
- if (framework !== 'domql') addToJson(SYMBOLS_FILE_PATH, 'framework', framework)
60
+ if (fs.existsSync(rcfilePath)) {
61
+ console.log(chalk.yellow('Skipping existing'), chalk.bold(rcfilePath))
62
+ } else {
63
+ console.log('Creating', chalk.bold(rcfilePath))
64
+ await fs.promises.copyFile(SYMBOLS_FILE_PATH, rcfilePath)
65
+ }
66
+ if (framework !== 'domql') addToJson(rcfilePath, 'framework', framework)
59
67
  console.log()
60
68
 
61
69
  console.log(chalk.green.bold('Initialized project successfully.'))
package/bin/login.js CHANGED
@@ -98,6 +98,7 @@ program
98
98
  // Save credentials
99
99
  const credManager = new CredentialManager()
100
100
  credManager.saveCredentials({
101
+ apiBaseUrl: answers.apiBaseUrl,
101
102
  authToken: token,
102
103
  refreshToken,
103
104
  authTokenExpiresAt: accessTokenExp,
@@ -105,7 +106,7 @@ program
105
106
  email: user?.email || answers.email
106
107
  })
107
108
 
108
- // Persist API base URL to local config
109
+ // Persist API base URL to local config for this project as well
109
110
  saveCliConfig({ apiBaseUrl: answers.apiBaseUrl })
110
111
 
111
112
  console.log(chalk.green('\n✨ Successfully logged in!'))
package/bin/push.js CHANGED
@@ -99,17 +99,17 @@ async function confirmChanges (changes, base, local) {
99
99
 
100
100
  export async function pushProjectChanges(options) {
101
101
  const { verbose, message, type = 'patch' } = options
102
- const credManager = new CredentialManager()
103
- const authToken = credManager.ensureAuthToken()
104
-
105
- if (!authToken) {
106
- showAuthRequiredMessages()
107
- process.exit(1)
108
- }
109
-
110
102
  try {
111
103
  const symbolsConfig = await loadSymbolsConfig()
112
104
  const cliConfig = loadCliConfig()
105
+ const credManager = new CredentialManager()
106
+ const authToken = credManager.ensureAuthToken(cliConfig.apiBaseUrl)
107
+
108
+ if (!authToken) {
109
+ showAuthRequiredMessages()
110
+ process.exit(1)
111
+ }
112
+
113
113
  const lock = readLock()
114
114
  const appKey = cliConfig.projectKey || symbolsConfig.key
115
115
  const branch = cliConfig.branch || symbolsConfig.branch || 'main'
package/bin/servers.js ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+
3
+ import inquirer from 'inquirer'
4
+ import chalk from 'chalk'
5
+ import { program } from './program.js'
6
+ import { CredentialManager } from '../helpers/credentialManager.js'
7
+
8
+ program
9
+ .command('servers')
10
+ .description('List and switch Symbols CLI servers (API base URLs)')
11
+ .option('-s, --select', 'Interactively select active server')
12
+ .action(async (opts) => {
13
+ const cm = new CredentialManager()
14
+ const profiles = cm.getProfiles()
15
+ const current = cm.getCurrentApiBaseUrl()
16
+
17
+ const urls = Object.keys(profiles || {})
18
+
19
+ if (!urls.length) {
20
+ console.log(chalk.yellow('No servers configured yet. Run `smbls login` first.'))
21
+ return
22
+ }
23
+
24
+ console.log(chalk.bold('\nConfigured servers:'))
25
+ urls.forEach((apiBaseUrl) => {
26
+ const profile = profiles[apiBaseUrl] || {}
27
+ const marker = apiBaseUrl === current ? chalk.green('●') : '○'
28
+ const email = profile.email ? chalk.dim(` (${profile.email})`) : ''
29
+ console.log(`${marker} ${chalk.cyan(apiBaseUrl)}${email}`)
30
+ })
31
+
32
+ if (!opts.select) return
33
+
34
+ const { next } = await inquirer.prompt([
35
+ {
36
+ type: 'list',
37
+ name: 'next',
38
+ message: 'Select active server:',
39
+ choices: urls.map((url) => ({
40
+ name: `${url}${url === current ? ' (current)' : ''}`,
41
+ value: url
42
+ }))
43
+ }
44
+ ])
45
+
46
+ cm.setCurrentApiBaseUrl(next)
47
+ console.log(chalk.green(`\nActive server set to ${next}`))
48
+ })
49
+
package/bin/sync.js CHANGED
@@ -148,18 +148,17 @@ async function confirmChanges(localChanges, remoteChanges, base, local, remote)
148
148
  }
149
149
 
150
150
  export async function syncProjectChanges(options) {
151
- const credManager = new CredentialManager()
152
- const authToken = credManager.ensureAuthToken()
153
-
154
- if (!authToken) {
155
- showAuthRequiredMessages()
156
- process.exit(1)
157
- }
158
-
159
151
  try {
160
152
  // Load configuration
161
153
  const symbolsConfig = await loadSymbolsConfig()
162
154
  const cliConfig = loadCliConfig()
155
+ const credManager = new CredentialManager()
156
+ const authToken = credManager.ensureAuthToken(cliConfig.apiBaseUrl)
157
+
158
+ if (!authToken) {
159
+ showAuthRequiredMessages()
160
+ process.exit(1)
161
+ }
163
162
  const lock = readLock()
164
163
  const { projectPath } = getConfigPaths()
165
164
  const { key: legacyKey } = symbolsConfig
package/helpers/config.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import fs from 'fs'
2
2
  import path from 'path'
3
+ import { CredentialManager } from './credentialManager.js'
3
4
 
4
5
  // New configuration layout
5
6
  // .symbols/config.json -> project runtime configuration (apiBaseUrl, projectKey|projectId, branch)
@@ -67,12 +68,21 @@ export function loadCliConfig() {
67
68
  // Legacy rc might include apiUrl
68
69
  const legacyRc = readJsonSafe(LEGACY_RC_PATH) || {}
69
70
 
70
- const apiBaseUrl =
71
+ // Prefer explicit env / project config, then legacy rc, then global current server
72
+ let apiBaseUrl =
71
73
  envApi ||
72
74
  symbolsConfig.apiBaseUrl ||
73
- legacyRc.apiUrl ||
74
- // default to production if nothing set
75
- 'https://api.symbols.app'
75
+ legacyRc.apiUrl
76
+
77
+ if (!apiBaseUrl) {
78
+ const credManager = new CredentialManager()
79
+ apiBaseUrl = credManager.getCurrentApiBaseUrl()
80
+ }
81
+
82
+ if (!apiBaseUrl) {
83
+ // default to production if nothing set anywhere
84
+ apiBaseUrl = 'https://api.symbols.app'
85
+ }
76
86
 
77
87
  const projectKey = envProjectKey || symbolsConfig.projectKey || legacySymbols.key
78
88
  const projectId = envProjectId || symbolsConfig.projectId || null
@@ -3,28 +3,27 @@ import path from 'path'
3
3
  import os from 'os'
4
4
 
5
5
  const RC_FILE = '.smblsrc'
6
+ const DEFAULT_API = 'https://api.symbols.app'
6
7
 
7
8
  export class CredentialManager {
8
9
  constructor() {
9
10
  this.rcPath = path.join(os.homedir(), RC_FILE)
10
11
  }
11
12
 
12
- // Load credentials from rc file
13
- loadCredentials() {
13
+ // --- Low-level helpers ----------------------------------------------------
14
+
15
+ loadRaw() {
14
16
  try {
15
17
  const data = fs.readFileSync(this.rcPath, 'utf8')
16
18
  return JSON.parse(data)
17
- } catch (err) {
19
+ } catch (_) {
18
20
  return {}
19
21
  }
20
22
  }
21
23
 
22
- // Save credentials to rc file
23
- saveCredentials(credentials) {
24
+ saveRaw(obj) {
24
25
  try {
25
- const existing = this.loadCredentials()
26
- const merged = { ...existing, ...credentials }
27
- fs.writeFileSync(this.rcPath, JSON.stringify(merged, null, 2))
26
+ fs.writeFileSync(this.rcPath, JSON.stringify(obj, null, 2))
28
27
  return true
29
28
  } catch (err) {
30
29
  console.error('Failed to save credentials:', err)
@@ -32,35 +31,161 @@ export class CredentialManager {
32
31
  }
33
32
  }
34
33
 
35
- // Get stored auth token
36
- getAuthToken() {
34
+ // Ensure we always work with a "profiles" structure and migrate legacy files
35
+ loadState() {
36
+ const raw = this.loadRaw()
37
+ if (raw && typeof raw === 'object' && raw.profiles) return raw
38
+
39
+ // Legacy shape: single set of credentials at the top level
40
+ const {
41
+ authToken,
42
+ token,
43
+ accessToken,
44
+ jwt,
45
+ refreshToken,
46
+ authTokenExpiresAt,
47
+ userId,
48
+ email,
49
+ apiBaseUrl,
50
+ ...rest
51
+ } = raw || {}
52
+
53
+ const legacyToken = authToken || token || accessToken || jwt
54
+ const hasLegacyCreds =
55
+ legacyToken || refreshToken || authTokenExpiresAt || userId || email
56
+
57
+ if (!hasLegacyCreds) {
58
+ // Nothing to migrate
59
+ return raw || {}
60
+ }
61
+
62
+ const baseUrl = apiBaseUrl || DEFAULT_API
63
+ const profileCreds = {
64
+ authToken: legacyToken || null,
65
+ refreshToken: refreshToken || null,
66
+ authTokenExpiresAt: authTokenExpiresAt || null,
67
+ userId: userId || null,
68
+ email: email || null,
69
+ apiBaseUrl: baseUrl
70
+ }
71
+
72
+ const migrated = {
73
+ ...rest,
74
+ profiles: { [baseUrl]: profileCreds },
75
+ currentApiBaseUrl: baseUrl
76
+ }
77
+
78
+ this.saveRaw(migrated)
79
+ return migrated
80
+ }
81
+
82
+ getProfiles() {
83
+ const state = this.loadState()
84
+ return state.profiles || {}
85
+ }
86
+
87
+ getCurrentApiBaseUrl() {
88
+ const state = this.loadState()
89
+ return state.currentApiBaseUrl || DEFAULT_API
90
+ }
91
+
92
+ setCurrentApiBaseUrl(apiBaseUrl) {
93
+ const state = this.loadState()
94
+ const next = { ...state, currentApiBaseUrl: apiBaseUrl || DEFAULT_API }
95
+ this.saveRaw(next)
96
+ return next
97
+ }
98
+
99
+ // --- High-level token helpers ---------------------------------------------
100
+
101
+ // Get stored auth token for a specific API base URL (or current if omitted)
102
+ getAuthToken(apiBaseUrl) {
37
103
  const envToken = process.env.SYMBOLS_TOKEN || process.env.SMBLS_TOKEN
38
104
  if (envToken) return envToken
39
- const creds = this.loadCredentials()
40
- return creds.authToken || creds.token || creds.accessToken || creds.jwt
105
+
106
+ const state = this.loadState() || {}
107
+ const baseUrl = apiBaseUrl || state.currentApiBaseUrl || DEFAULT_API
108
+
109
+ if (state.profiles && typeof state.profiles === 'object') {
110
+ const profile = state.profiles[baseUrl] || {}
111
+ return (
112
+ profile.authToken ||
113
+ profile.token ||
114
+ profile.accessToken ||
115
+ profile.jwt ||
116
+ null
117
+ )
118
+ }
119
+
120
+ // Fallback: legacy flat shape
121
+ return (
122
+ state.authToken ||
123
+ state.token ||
124
+ state.accessToken ||
125
+ state.jwt ||
126
+ null
127
+ )
41
128
  }
42
129
 
43
130
  // Ensure token presence; returns token or null
44
- ensureAuthToken() {
45
- const token = this.getAuthToken()
131
+ ensureAuthToken(apiBaseUrl) {
132
+ const token = this.getAuthToken(apiBaseUrl)
46
133
  return token || null
47
134
  }
48
135
 
49
136
  // Optional: get refresh token if present
50
- getRefreshToken() {
137
+ getRefreshToken(apiBaseUrl) {
51
138
  const envToken = process.env.SYMBOLS_REFRESH_TOKEN
52
139
  if (envToken) return envToken
53
- const creds = this.loadCredentials()
54
- return creds.refreshToken || null
140
+
141
+ const state = this.loadState() || {}
142
+ const baseUrl = apiBaseUrl || state.currentApiBaseUrl || DEFAULT_API
143
+
144
+ if (state.profiles && typeof state.profiles === 'object') {
145
+ return state.profiles[baseUrl]?.refreshToken || null
146
+ }
147
+
148
+ return state.refreshToken || null
55
149
  }
56
150
 
57
- // Clear stored credentials
58
- clearCredentials() {
59
- try {
60
- fs.unlinkSync(this.rcPath)
61
- return true
62
- } catch (err) {
63
- return false
151
+ // Save credentials for a specific API base URL
152
+ saveCredentials(credentials) {
153
+ const state = this.loadState() || {}
154
+ const baseUrl = credentials.apiBaseUrl || state.currentApiBaseUrl || DEFAULT_API
155
+ const profiles = state.profiles || {}
156
+ const existing = profiles[baseUrl] || {}
157
+
158
+ const mergedProfile = { ...existing, ...credentials, apiBaseUrl: baseUrl }
159
+
160
+ const next = {
161
+ ...state,
162
+ profiles: { ...profiles, [baseUrl]: mergedProfile },
163
+ currentApiBaseUrl: baseUrl
164
+ }
165
+
166
+ return this.saveRaw(next)
167
+ }
168
+
169
+ // Clear stored credentials; if apiBaseUrl provided, clear only that profile
170
+ clearCredentials(apiBaseUrl) {
171
+ const state = this.loadState() || {}
172
+
173
+ if (!state.profiles || typeof state.profiles !== 'object') {
174
+ // Legacy: remove entire file
175
+ try {
176
+ fs.unlinkSync(this.rcPath)
177
+ return true
178
+ } catch (_) {
179
+ return false
180
+ }
64
181
  }
182
+
183
+ const baseUrl = apiBaseUrl || state.currentApiBaseUrl
184
+ if (!baseUrl) return false
185
+
186
+ const { [baseUrl]: _, ...restProfiles } = state.profiles
187
+ const next = { ...state, profiles: restProfiles }
188
+ this.saveRaw(next)
189
+ return true
65
190
  }
66
- }
191
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbo.ls/cli",
3
- "version": "2.33.20",
3
+ "version": "2.33.25",
4
4
  "description": "Fetch your Symbols configuration",
5
5
  "main": "bin/fetch.js",
6
6
  "author": "Symbols",
@@ -15,9 +15,9 @@
15
15
  "vpatch": "npm version patch && npm publish"
16
16
  },
17
17
  "dependencies": {
18
- "@symbo.ls/fetch": "^2.33.20",
19
- "@symbo.ls/init": "^2.33.20",
20
- "@symbo.ls/socket": "^2.33.20",
18
+ "@symbo.ls/fetch": "^2.33.25",
19
+ "@symbo.ls/init": "^2.33.25",
20
+ "@symbo.ls/socket": "^2.33.25",
21
21
  "chalk": "^5.4.1",
22
22
  "chokidar": "^4.0.3",
23
23
  "commander": "^13.1.0",
@@ -28,5 +28,5 @@
28
28
  "socket.io-client": "^4.8.1",
29
29
  "v8-compile-cache": "^2.4.0"
30
30
  },
31
- "gitHead": "717ab2ed8690b295c7d6c79e31831048f20f4e86"
31
+ "gitHead": "b0524b9f29fed6070d8e718d73b290097696ac48"
32
32
  }