@symbo.ls/cli 2.33.36 → 2.33.38
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/login.js +38 -7
- package/bin/push.js +7 -4
- package/bin/sync.js +5 -3
- package/helpers/credentialManager.js +81 -0
- package/package.json +6 -6
package/bin/login.js
CHANGED
|
@@ -101,6 +101,11 @@ function extractConfirmToken(data) {
|
|
|
101
101
|
)
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
function withCliSessionKeyHeaders(headers, cliSessionKey) {
|
|
105
|
+
if (!cliSessionKey) return headers
|
|
106
|
+
return { ...headers, 'X-Symbols-Cli-Session-Key': cliSessionKey }
|
|
107
|
+
}
|
|
108
|
+
|
|
104
109
|
program
|
|
105
110
|
.command('login')
|
|
106
111
|
.description('Sign in to Symbols')
|
|
@@ -111,12 +116,18 @@ program
|
|
|
111
116
|
|
|
112
117
|
// Prompt for credentials
|
|
113
118
|
const currentConfig = loadCliConfig()
|
|
119
|
+
const credManager = new CredentialManager()
|
|
120
|
+
// If the user selected an active server via `smbls servers -s`, prefer that as the login default.
|
|
121
|
+
// Env vars should still override everything (useful for CI / debugging).
|
|
122
|
+
const envApiBaseUrl = process.env.SYMBOLS_API_BASE_URL || process.env.SMBLS_API_URL
|
|
123
|
+
const defaultApiBaseUrl =
|
|
124
|
+
envApiBaseUrl || credManager.getCurrentApiBaseUrl() || currentConfig.apiBaseUrl || getApiUrl()
|
|
114
125
|
const first = await inquirer.prompt([
|
|
115
126
|
{
|
|
116
127
|
type: 'input',
|
|
117
128
|
name: 'apiBaseUrl',
|
|
118
129
|
message: 'API Base URL:',
|
|
119
|
-
default:
|
|
130
|
+
default: defaultApiBaseUrl,
|
|
120
131
|
validate: input => /^https?:\/\//.test(input) || '❌ Please enter a valid URL'
|
|
121
132
|
},
|
|
122
133
|
{
|
|
@@ -170,6 +181,21 @@ program
|
|
|
170
181
|
throw err
|
|
171
182
|
}
|
|
172
183
|
} else if (first.method === 'google_browser' || first.method === 'github_browser') {
|
|
184
|
+
const defaultCliSessionKey = credManager.getCliSessionKey(apiBaseUrl) || ''
|
|
185
|
+
const { cliSessionKey } = await inquirer.prompt([
|
|
186
|
+
{
|
|
187
|
+
type: 'input',
|
|
188
|
+
name: 'cliSessionKey',
|
|
189
|
+
message: 'CLI session key (for long-lived tokens, optional):',
|
|
190
|
+
default: defaultCliSessionKey,
|
|
191
|
+
filter: (v) => (v || '').trim()
|
|
192
|
+
}
|
|
193
|
+
])
|
|
194
|
+
|
|
195
|
+
// Persist per-server key so users can change it per environment
|
|
196
|
+
// (empty value clears and disables long-lived token request)
|
|
197
|
+
credManager.setCliSessionKey(apiBaseUrl, cliSessionKey)
|
|
198
|
+
|
|
173
199
|
const sessionId = crypto.randomUUID()
|
|
174
200
|
const codeVerifier = randomVerifier(64)
|
|
175
201
|
const codeChallenge = sha256Base64url(codeVerifier)
|
|
@@ -177,11 +203,12 @@ program
|
|
|
177
203
|
console.log(chalk.dim('\nCreating secure sign-in session...'))
|
|
178
204
|
const sessionResp = await fetch(`${apiBaseUrl}/core/auth/session`, {
|
|
179
205
|
method: 'POST',
|
|
180
|
-
headers: { 'Content-Type': 'application/json' },
|
|
206
|
+
headers: withCliSessionKeyHeaders({ 'Content-Type': 'application/json' }, cliSessionKey),
|
|
181
207
|
body: JSON.stringify({
|
|
182
208
|
session_id: sessionId,
|
|
183
209
|
code_challenge: codeChallenge,
|
|
184
|
-
plugin_info: { version: 'cli', figma_env: 'cli' }
|
|
210
|
+
plugin_info: { version: 'cli', figma_env: 'cli' },
|
|
211
|
+
...(cliSessionKey ? { cli_session_key: cliSessionKey } : {})
|
|
185
212
|
})
|
|
186
213
|
})
|
|
187
214
|
|
|
@@ -214,7 +241,8 @@ program
|
|
|
214
241
|
}
|
|
215
242
|
|
|
216
243
|
const statusResp = await fetch(
|
|
217
|
-
`${apiBaseUrl}/core/auth/session/status?session=${encodeURIComponent(sessionId)}
|
|
244
|
+
`${apiBaseUrl}/core/auth/session/status?session=${encodeURIComponent(sessionId)}`,
|
|
245
|
+
{ headers: withCliSessionKeyHeaders({}, cliSessionKey) }
|
|
218
246
|
)
|
|
219
247
|
const statusData = await statusResp.json().catch(async () => ({ message: await statusResp.text() }))
|
|
220
248
|
|
|
@@ -235,8 +263,12 @@ program
|
|
|
235
263
|
console.log(chalk.dim('Confirming session...'))
|
|
236
264
|
const confirmResp = await fetch(`${apiBaseUrl}/core/auth/session/confirm`, {
|
|
237
265
|
method: 'POST',
|
|
238
|
-
headers: { 'Content-Type': 'application/json' },
|
|
239
|
-
body: JSON.stringify({
|
|
266
|
+
headers: withCliSessionKeyHeaders({ 'Content-Type': 'application/json' }, cliSessionKey),
|
|
267
|
+
body: JSON.stringify({
|
|
268
|
+
session_id: sessionId,
|
|
269
|
+
code_verifier: codeVerifier,
|
|
270
|
+
...(cliSessionKey ? { cli_session_key: cliSessionKey } : {})
|
|
271
|
+
})
|
|
240
272
|
})
|
|
241
273
|
|
|
242
274
|
const confirmData = await confirmResp.json().catch(async () => ({ message: await confirmResp.text() }))
|
|
@@ -262,7 +294,6 @@ program
|
|
|
262
294
|
}
|
|
263
295
|
|
|
264
296
|
// Save credentials
|
|
265
|
-
const credManager = new CredentialManager()
|
|
266
297
|
credManager.saveCredentials({
|
|
267
298
|
apiBaseUrl,
|
|
268
299
|
authToken: accessToken,
|
package/bin/push.js
CHANGED
|
@@ -16,6 +16,7 @@ import { loadSymbolsConfig, resolveDistDir } from '../helpers/symbolsConfig.js'
|
|
|
16
16
|
import { loadCliConfig, readLock, writeLock, updateLegacySymbolsJson } from '../helpers/config.js'
|
|
17
17
|
import { stripOrderFields } from '../helpers/orderUtils.js'
|
|
18
18
|
import { augmentProjectWithLocalPackageDependencies, findNearestPackageJson } from '../helpers/dependenciesUtils.js'
|
|
19
|
+
import { stringifyFunctionsForTransport } from '../helpers/transportUtils.js'
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
async function buildLocalProject (distDir) {
|
|
@@ -153,8 +154,10 @@ export async function pushProjectChanges(options) {
|
|
|
153
154
|
console.log(chalk.gray('Server state fetched successfully'))
|
|
154
155
|
|
|
155
156
|
// Calculate coarse local changes vs server snapshot (or base)
|
|
156
|
-
|
|
157
|
-
const
|
|
157
|
+
// Prepare safe, JSON-serialisable snapshots for diffing & transport (stringify functions)
|
|
158
|
+
const base = normalizeKeys(stringifyFunctionsForTransport(stripOrderFields(serverProject || {})))
|
|
159
|
+
const local = normalizeKeys(stringifyFunctionsForTransport(stripOrderFields(localProject)))
|
|
160
|
+
const changes = computeCoarseChanges(base, local)
|
|
158
161
|
|
|
159
162
|
if (!changes.length) {
|
|
160
163
|
console.log(chalk.bold.yellow('\nNo changes to push'))
|
|
@@ -169,7 +172,7 @@ export async function pushProjectChanges(options) {
|
|
|
169
172
|
})
|
|
170
173
|
|
|
171
174
|
// Confirm push
|
|
172
|
-
const shouldProceed = await confirmChanges(changes, base,
|
|
175
|
+
const shouldProceed = await confirmChanges(changes, base, local)
|
|
173
176
|
if (!shouldProceed) {
|
|
174
177
|
console.log(chalk.yellow('Push cancelled'))
|
|
175
178
|
return
|
|
@@ -185,7 +188,7 @@ export async function pushProjectChanges(options) {
|
|
|
185
188
|
const operationId = `cli-${Date.now()}`
|
|
186
189
|
// Derive granular changes against server base and compute orders using local for pending children
|
|
187
190
|
const { granularChanges } = preprocessChanges(base, changes)
|
|
188
|
-
const orders = computeOrdersForTuples(
|
|
191
|
+
const orders = computeOrdersForTuples(local, granularChanges)
|
|
189
192
|
const result = await postProjectChanges(projectId, authToken, {
|
|
190
193
|
branch,
|
|
191
194
|
type,
|
package/bin/sync.js
CHANGED
|
@@ -17,6 +17,7 @@ import { showAuthRequiredMessages, showBuildErrorMessages } from '../helpers/bui
|
|
|
17
17
|
import { loadSymbolsConfig, resolveDistDir } from '../helpers/symbolsConfig.js'
|
|
18
18
|
import { loadCliConfig, readLock, writeLock, getConfigPaths, updateLegacySymbolsJson } from '../helpers/config.js'
|
|
19
19
|
import { stripOrderFields } from '../helpers/orderUtils.js'
|
|
20
|
+
import { stringifyFunctionsForTransport } from '../helpers/transportUtils.js'
|
|
20
21
|
import {
|
|
21
22
|
augmentProjectWithLocalPackageDependencies,
|
|
22
23
|
ensureSchemaDependencies,
|
|
@@ -227,9 +228,10 @@ export async function syncProjectChanges(options) {
|
|
|
227
228
|
ensureSchemaDependencies(localProject)
|
|
228
229
|
|
|
229
230
|
// Generate coarse local and remote changes via simple three-way rebase
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
const
|
|
231
|
+
// Prepare safe, JSON-serialisable snapshots for diffing & transport (stringify functions)
|
|
232
|
+
const base = normalizeKeys(stringifyFunctionsForTransport(stripOrderFields(baseSnapshot || {})))
|
|
233
|
+
const local = normalizeKeys(stringifyFunctionsForTransport(stripOrderFields(localProject || {})))
|
|
234
|
+
const remote = normalizeKeys(stringifyFunctionsForTransport(stripOrderFields(serverProject || {})))
|
|
233
235
|
const { ours, theirs, conflicts, finalChanges } = threeWayRebase(base, local, remote)
|
|
234
236
|
|
|
235
237
|
const localChanges = ours
|
|
@@ -4,12 +4,33 @@ import os from 'os'
|
|
|
4
4
|
|
|
5
5
|
const RC_FILE = '.smblsrc'
|
|
6
6
|
const DEFAULT_API = 'https://api.symbols.app'
|
|
7
|
+
const DEFAULT_LOCAL_CLI_SESSION_KEY = 'supersecret...change-in-production!!!'
|
|
8
|
+
const DEFAULT_PROD_CLI_SESSION_KEY =
|
|
9
|
+
'r5jMdknqYjuGeWAsApTvPjaYHjWkdyw70veJye11Mb_vInBmsBZ9RM4GKU2_rm17Z2qvNahbyIV5gQVXY0V9DlUYdflLKd1jYCKSuc9r_xreC9hVEovfbZqmfbOl-JFCN1w1MXEsPNWkWj48nfF6IFfoWH-0hsdQlPBFjW1mv10'
|
|
7
10
|
|
|
8
11
|
export class CredentialManager {
|
|
9
12
|
constructor() {
|
|
10
13
|
this.rcPath = path.join(os.homedir(), RC_FILE)
|
|
11
14
|
}
|
|
12
15
|
|
|
16
|
+
static defaultCliSessionKeyForApiBaseUrl(apiBaseUrl) {
|
|
17
|
+
const baseUrl = (apiBaseUrl || '').trim()
|
|
18
|
+
if (!baseUrl) return null
|
|
19
|
+
|
|
20
|
+
// Match the environments used by login.js's websiteFromApi:
|
|
21
|
+
// - production: api.symbols.app -> production session key by default
|
|
22
|
+
// - everything else (dev/staging/test/local/custom): local key by default
|
|
23
|
+
try {
|
|
24
|
+
const u = new URL(baseUrl)
|
|
25
|
+
if (u.host === 'api.symbols.app') return DEFAULT_PROD_CLI_SESSION_KEY
|
|
26
|
+
return DEFAULT_LOCAL_CLI_SESSION_KEY
|
|
27
|
+
} catch (_) {
|
|
28
|
+
// Best-effort fallback if apiBaseUrl is malformed
|
|
29
|
+
if (baseUrl.includes('api.symbols.app')) return DEFAULT_PROD_CLI_SESSION_KEY
|
|
30
|
+
return DEFAULT_LOCAL_CLI_SESSION_KEY
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
13
34
|
// --- Low-level helpers ----------------------------------------------------
|
|
14
35
|
|
|
15
36
|
loadRaw() {
|
|
@@ -89,6 +110,66 @@ export class CredentialManager {
|
|
|
89
110
|
return state.currentApiBaseUrl || DEFAULT_API
|
|
90
111
|
}
|
|
91
112
|
|
|
113
|
+
getCliSessionKey(apiBaseUrl) {
|
|
114
|
+
// Allow overriding via env (useful for CI / debugging)
|
|
115
|
+
const envKey =
|
|
116
|
+
process.env.SYMBOLS_CLI_SESSION_KEY ||
|
|
117
|
+
process.env.SMBLS_CLI_SESSION_KEY ||
|
|
118
|
+
process.env.PLUGIN_CLI_SESSION_KEY
|
|
119
|
+
if (envKey) return envKey
|
|
120
|
+
|
|
121
|
+
const state = this.loadState() || {}
|
|
122
|
+
const baseUrl = apiBaseUrl || state.currentApiBaseUrl || DEFAULT_API
|
|
123
|
+
|
|
124
|
+
// Prefer explicit mapping, then per-profile field
|
|
125
|
+
const mapped = state.cliSessionKeys && typeof state.cliSessionKeys === 'object'
|
|
126
|
+
? state.cliSessionKeys[baseUrl]
|
|
127
|
+
: null
|
|
128
|
+
if (mapped) return mapped
|
|
129
|
+
|
|
130
|
+
const profileKey =
|
|
131
|
+
state.profiles && typeof state.profiles === 'object'
|
|
132
|
+
? state.profiles[baseUrl]?.cliSessionKey
|
|
133
|
+
: null
|
|
134
|
+
if (profileKey) return profileKey
|
|
135
|
+
|
|
136
|
+
// Fall back to environment defaults (e.g. localhost)
|
|
137
|
+
return CredentialManager.defaultCliSessionKeyForApiBaseUrl(baseUrl)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
setCliSessionKey(apiBaseUrl, cliSessionKey) {
|
|
141
|
+
const state = this.loadState() || {}
|
|
142
|
+
const baseUrl = apiBaseUrl || state.currentApiBaseUrl || DEFAULT_API
|
|
143
|
+
const nextKey = (cliSessionKey || '').trim()
|
|
144
|
+
|
|
145
|
+
const next = { ...state }
|
|
146
|
+
|
|
147
|
+
// Store in a dedicated map keyed by API base URL
|
|
148
|
+
const prevMap = (state.cliSessionKeys && typeof state.cliSessionKeys === 'object')
|
|
149
|
+
? state.cliSessionKeys
|
|
150
|
+
: {}
|
|
151
|
+
const nextMap = { ...prevMap }
|
|
152
|
+
|
|
153
|
+
if (!nextKey) {
|
|
154
|
+
delete nextMap[baseUrl]
|
|
155
|
+
} else {
|
|
156
|
+
nextMap[baseUrl] = nextKey
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
next.cliSessionKeys = nextMap
|
|
160
|
+
|
|
161
|
+
// Also mirror into the profile (handy for humans inspecting ~/.smblsrc)
|
|
162
|
+
const profiles = (state.profiles && typeof state.profiles === 'object') ? state.profiles : {}
|
|
163
|
+
const existing = profiles[baseUrl] || {}
|
|
164
|
+
const updatedProfile = { ...existing }
|
|
165
|
+
if (!nextKey) delete updatedProfile.cliSessionKey
|
|
166
|
+
else updatedProfile.cliSessionKey = nextKey
|
|
167
|
+
next.profiles = { ...profiles, [baseUrl]: updatedProfile }
|
|
168
|
+
|
|
169
|
+
this.saveRaw(next)
|
|
170
|
+
return next
|
|
171
|
+
}
|
|
172
|
+
|
|
92
173
|
setCurrentApiBaseUrl(apiBaseUrl) {
|
|
93
174
|
const state = this.loadState()
|
|
94
175
|
const next = { ...state, currentApiBaseUrl: apiBaseUrl || DEFAULT_API }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbo.ls/cli",
|
|
3
|
-
"version": "2.33.
|
|
3
|
+
"version": "2.33.38",
|
|
4
4
|
"description": "Fetch your Symbols configuration",
|
|
5
5
|
"main": "bin/fetch.js",
|
|
6
6
|
"author": "Symbols",
|
|
@@ -15,18 +15,18 @@
|
|
|
15
15
|
"vpatch": "npm version patch && npm publish"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@symbo.ls/fetch": "^2.33.
|
|
19
|
-
"@symbo.ls/init": "^2.33.
|
|
20
|
-
"@symbo.ls/socket": "^2.33.
|
|
18
|
+
"@symbo.ls/fetch": "^2.33.38",
|
|
19
|
+
"@symbo.ls/init": "^2.33.38",
|
|
20
|
+
"@symbo.ls/socket": "^2.33.38",
|
|
21
21
|
"chalk": "^5.4.1",
|
|
22
22
|
"chokidar": "^4.0.3",
|
|
23
23
|
"commander": "^13.1.0",
|
|
24
24
|
"diff": "^5.2.0",
|
|
25
|
-
"esbuild": "^0.
|
|
25
|
+
"esbuild": "^0.26.0",
|
|
26
26
|
"inquirer": "^12.9.0",
|
|
27
27
|
"node-fetch": "^3.3.2",
|
|
28
28
|
"socket.io-client": "^4.8.1",
|
|
29
29
|
"v8-compile-cache": "^2.4.0"
|
|
30
30
|
},
|
|
31
|
-
"gitHead": "
|
|
31
|
+
"gitHead": "7048f2fd761c7aa6a4233019c1b91318d88e64ce"
|
|
32
32
|
}
|