uniweb 0.13.16 → 0.14.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.
- package/README.md +1 -1
- package/package.json +7 -7
- package/partials/agents.md +51 -0
- package/src/backend/client.js +173 -40
- package/src/backend/data-bundle.js +15 -3
- package/src/backend/foundation-bring-along.js +76 -21
- package/src/backend/payment-handoff.js +28 -9
- package/src/backend/site-media.js +147 -19
- package/src/backend/site-sync.js +362 -40
- package/src/commands/add.js +575 -273
- package/src/commands/build.js +160 -57
- package/src/commands/clone.js +79 -26
- package/src/commands/content.js +11 -7
- package/src/commands/deploy.js +80 -32
- package/src/commands/dev.js +39 -17
- package/src/commands/docs.js +44 -16
- package/src/commands/doctor.js +338 -82
- package/src/commands/export.js +15 -7
- package/src/commands/handoff.js +6 -2
- package/src/commands/i18n.js +248 -99
- package/src/commands/inspect.js +48 -19
- package/src/commands/invite.js +9 -3
- package/src/commands/org.js +19 -5
- package/src/commands/publish.js +229 -60
- package/src/commands/pull.js +157 -48
- package/src/commands/push.js +144 -42
- package/src/commands/refresh.js +37 -10
- package/src/commands/register.js +235 -64
- package/src/commands/rename.js +126 -46
- package/src/commands/runtime.js +74 -24
- package/src/commands/status.js +48 -15
- package/src/commands/sync.js +7 -2
- package/src/commands/template.js +12 -4
- package/src/commands/update.js +263 -87
- package/src/commands/validate.js +115 -32
- package/src/framework-index.json +16 -14
- package/src/index.js +298 -145
- package/src/templates/fetchers/github.js +7 -7
- package/src/templates/fetchers/npm.js +3 -3
- package/src/templates/fetchers/release.js +21 -18
- package/src/templates/index.js +34 -12
- package/src/templates/processor.js +44 -12
- package/src/templates/resolver.js +42 -15
- package/src/templates/validator.js +14 -7
- package/src/utils/asset-upload.js +90 -22
- package/src/utils/code-upload.js +62 -37
- package/src/utils/config.js +31 -10
- package/src/utils/dep-survey.js +33 -7
- package/src/utils/destination-prompt.js +27 -17
- package/src/utils/git.js +66 -22
- package/src/utils/host-prompt.js +4 -3
- package/src/utils/install-integrity.js +8 -4
- package/src/utils/interactive.js +3 -3
- package/src/utils/json-file.js +6 -2
- package/src/utils/names.js +15 -6
- package/src/utils/placement.js +16 -8
- package/src/utils/registry-auth.js +185 -57
- package/src/utils/registry-orgs.js +142 -53
- package/src/utils/runtime-upload.js +52 -14
- package/src/utils/scaffold.js +55 -14
- package/src/utils/site-content-refs.js +3 -1
- package/src/utils/update-check.js +43 -17
- package/src/utils/workspace.js +13 -7
- package/src/versions.js +18 -7
- package/templates/site/_gitignore +5 -0
|
@@ -37,7 +37,11 @@ export function getAuthDir() {
|
|
|
37
37
|
|
|
38
38
|
/** Normalize a backend URL to a bare origin (for stamping on the session). */
|
|
39
39
|
function normOrigin(u) {
|
|
40
|
-
try {
|
|
40
|
+
try {
|
|
41
|
+
return new URL(u).origin
|
|
42
|
+
} catch {
|
|
43
|
+
return u
|
|
44
|
+
}
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
/** True when a stored session's `expiresAt` is in the past (absent → never expires). */
|
|
@@ -109,22 +113,31 @@ export async function loginToRegistry({ apiBase, username, password } = {}) {
|
|
|
109
113
|
res = await fetch(url, {
|
|
110
114
|
method: 'POST',
|
|
111
115
|
headers: { 'Content-Type': 'application/json' },
|
|
112
|
-
body: JSON.stringify({ username, password })
|
|
116
|
+
body: JSON.stringify({ username, password })
|
|
113
117
|
})
|
|
114
118
|
} catch (err) {
|
|
115
|
-
throw new Error(
|
|
119
|
+
throw new Error(
|
|
120
|
+
`Could not reach the login endpoint at ${url}: ${err.message}`
|
|
121
|
+
)
|
|
116
122
|
}
|
|
117
123
|
|
|
118
124
|
if (!res.ok) {
|
|
119
125
|
let detail = ''
|
|
120
|
-
try {
|
|
121
|
-
|
|
126
|
+
try {
|
|
127
|
+
detail = (await res.text()).slice(0, 300)
|
|
128
|
+
} catch {
|
|
129
|
+
/* ignore */
|
|
130
|
+
}
|
|
131
|
+
const e = new Error(
|
|
132
|
+
`Login failed: HTTP ${res.status} ${res.statusText}${detail ? ` — ${detail}` : ''}`
|
|
133
|
+
)
|
|
122
134
|
e.status = res.status
|
|
123
135
|
throw e
|
|
124
136
|
}
|
|
125
137
|
|
|
126
138
|
const data = await res.json().catch(() => null)
|
|
127
|
-
if (!data?.token)
|
|
139
|
+
if (!data?.token)
|
|
140
|
+
throw new Error('Login succeeded but the response carried no token.')
|
|
128
141
|
|
|
129
142
|
// New-backend login body (agreed with backend, 2026-05-26):
|
|
130
143
|
// { token, expires_at, account: { uuid, username, handle } }
|
|
@@ -157,7 +170,11 @@ export async function loginToRegistry({ apiBase, username, password } = {}) {
|
|
|
157
170
|
* @param {string[]} [options.args] - argv slice; checked for --non-interactive
|
|
158
171
|
* @returns {Promise<string>} bearer token
|
|
159
172
|
*/
|
|
160
|
-
export async function ensureRegistryAuth({
|
|
173
|
+
export async function ensureRegistryAuth({
|
|
174
|
+
apiBase,
|
|
175
|
+
command = 'This command',
|
|
176
|
+
args = []
|
|
177
|
+
} = {}) {
|
|
161
178
|
if (process.env.UNIWEB_TOKEN) return process.env.UNIWEB_TOKEN
|
|
162
179
|
|
|
163
180
|
const stored = await readRegistryAuth()
|
|
@@ -167,24 +184,37 @@ export async function ensureRegistryAuth({ apiBase, command = 'This command', ar
|
|
|
167
184
|
const envUser = process.env.UNIWEB_USERNAME
|
|
168
185
|
const envPass = process.env.UNIWEB_PASSWORD
|
|
169
186
|
if (envUser && envPass) {
|
|
170
|
-
const record = await loginToRegistry({
|
|
187
|
+
const record = await loginToRegistry({
|
|
188
|
+
apiBase,
|
|
189
|
+
username: envUser,
|
|
190
|
+
password: envPass
|
|
191
|
+
})
|
|
171
192
|
return record.token
|
|
172
193
|
}
|
|
173
194
|
|
|
174
195
|
const { isNonInteractive, getCliPrefix } = await import('./interactive.js')
|
|
175
196
|
if (isNonInteractive(args)) {
|
|
176
197
|
const prefix = getCliPrefix()
|
|
177
|
-
const reason =
|
|
178
|
-
|
|
198
|
+
const reason =
|
|
199
|
+
stored && isExpired(stored) ? 'Session expired.' : 'Not logged in.'
|
|
200
|
+
console.error(
|
|
201
|
+
`\x1b[31m✗\x1b[0m ${reason} ${command} requires a Uniweb account, and the CLI is non-interactive (CI / no TTY / --non-interactive).`
|
|
202
|
+
)
|
|
179
203
|
console.error(' Options:')
|
|
180
204
|
console.error(` • Set UNIWEB_TOKEN to a bearer token.`)
|
|
181
|
-
console.error(
|
|
182
|
-
|
|
205
|
+
console.error(
|
|
206
|
+
` • Set UNIWEB_USERNAME + UNIWEB_PASSWORD to log in non-interactively.`
|
|
207
|
+
)
|
|
208
|
+
console.error(
|
|
209
|
+
` • Run \`${prefix} login\` interactively first, then re-run.`
|
|
210
|
+
)
|
|
183
211
|
process.exit(1)
|
|
184
212
|
}
|
|
185
213
|
|
|
186
214
|
if (stored && isExpired(stored)) {
|
|
187
|
-
console.log(
|
|
215
|
+
console.log(
|
|
216
|
+
`\x1b[33mSession expired.\x1b[0m ${command} requires a Uniweb account.\n`
|
|
217
|
+
)
|
|
188
218
|
} else {
|
|
189
219
|
console.log(`${command} requires a Uniweb account.\n`)
|
|
190
220
|
}
|
|
@@ -207,10 +237,12 @@ const BROWSER_AVAILABLE = false
|
|
|
207
237
|
*/
|
|
208
238
|
export async function fetchMe({ apiBase, token }) {
|
|
209
239
|
const res = await fetch(`${apiBase.replace(/\/$/, '')}/dev/auth/me`, {
|
|
210
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
240
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
211
241
|
})
|
|
212
242
|
if (!res.ok) {
|
|
213
|
-
const e = new Error(
|
|
243
|
+
const e = new Error(
|
|
244
|
+
`token check failed: HTTP ${res.status} ${res.statusText}`
|
|
245
|
+
)
|
|
214
246
|
e.status = res.status
|
|
215
247
|
throw e
|
|
216
248
|
}
|
|
@@ -224,13 +256,33 @@ async function loginViaPassword({ apiBase, nonInteractive }) {
|
|
|
224
256
|
let password = process.env.UNIWEB_PASSWORD
|
|
225
257
|
if (!username || !password) {
|
|
226
258
|
if (nonInteractive) {
|
|
227
|
-
throw new Error(
|
|
259
|
+
throw new Error(
|
|
260
|
+
'username/password login needs a terminal — set UNIWEB_USERNAME + UNIWEB_PASSWORD (or UNIWEB_TOKEN).'
|
|
261
|
+
)
|
|
228
262
|
}
|
|
229
263
|
const prompts = (await import('prompts')).default
|
|
230
|
-
const resp = await prompts(
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
264
|
+
const resp = await prompts(
|
|
265
|
+
[
|
|
266
|
+
{
|
|
267
|
+
type: 'text',
|
|
268
|
+
name: 'username',
|
|
269
|
+
message: 'Username:',
|
|
270
|
+
validate: (v) => (v ? true : 'Username is required')
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
type: 'password',
|
|
274
|
+
name: 'password',
|
|
275
|
+
message: 'Password:',
|
|
276
|
+
validate: (v) => (v ? true : 'Password is required')
|
|
277
|
+
}
|
|
278
|
+
],
|
|
279
|
+
{
|
|
280
|
+
onCancel: () => {
|
|
281
|
+
console.log('\nLogin cancelled.')
|
|
282
|
+
process.exit(0)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
)
|
|
234
286
|
username = resp.username
|
|
235
287
|
password = resp.password
|
|
236
288
|
if (!username || !password) process.exit(1)
|
|
@@ -244,9 +296,20 @@ async function loginViaTokenPaste({ apiBase, nonInteractive }) {
|
|
|
244
296
|
throw new Error('token paste needs a terminal — set UNIWEB_TOKEN instead.')
|
|
245
297
|
}
|
|
246
298
|
const prompts = (await import('prompts')).default
|
|
247
|
-
const { token } = await prompts(
|
|
248
|
-
|
|
249
|
-
|
|
299
|
+
const { token } = await prompts(
|
|
300
|
+
{
|
|
301
|
+
type: 'password',
|
|
302
|
+
name: 'token',
|
|
303
|
+
message: 'Paste your token:',
|
|
304
|
+
validate: (v) => (v ? true : 'Token is required')
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
onCancel: () => {
|
|
308
|
+
console.log('\nLogin cancelled.')
|
|
309
|
+
process.exit(0)
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
)
|
|
250
313
|
if (!token) process.exit(1)
|
|
251
314
|
const account = await fetchMe({ apiBase, token }) // throws if the token is invalid
|
|
252
315
|
const record = { token, origin: normOrigin(apiBase) }
|
|
@@ -261,9 +324,12 @@ async function loginViaTokenPaste({ apiBase, nonInteractive }) {
|
|
|
261
324
|
async function openBrowser(url) {
|
|
262
325
|
try {
|
|
263
326
|
const { exec } = await import('node:child_process')
|
|
264
|
-
const cmd =
|
|
265
|
-
|
|
266
|
-
|
|
327
|
+
const cmd =
|
|
328
|
+
process.platform === 'darwin'
|
|
329
|
+
? `open "${url}"`
|
|
330
|
+
: process.platform === 'win32'
|
|
331
|
+
? `start "" "${url}"`
|
|
332
|
+
: `xdg-open "${url}"`
|
|
267
333
|
return await new Promise((resolve) => exec(cmd, (err) => resolve(!err)))
|
|
268
334
|
} catch {
|
|
269
335
|
return false
|
|
@@ -300,24 +366,37 @@ export async function awaitBrowserCallback({
|
|
|
300
366
|
openingLabel = 'Opening your browser…',
|
|
301
367
|
waitingLabel,
|
|
302
368
|
okTitle = 'Done',
|
|
303
|
-
errTitle = 'Something went wrong'
|
|
369
|
+
errTitle = 'Something went wrong'
|
|
304
370
|
} = {}) {
|
|
305
371
|
const result = await new Promise((resolve) => {
|
|
306
372
|
const server = createServer((req, res) => {
|
|
307
373
|
const u = new URL(req.url, 'http://127.0.0.1')
|
|
308
|
-
if (u.pathname !== '/callback') {
|
|
309
|
-
|
|
374
|
+
if (u.pathname !== '/callback') {
|
|
375
|
+
res.writeHead(404)
|
|
376
|
+
res.end('Not found')
|
|
377
|
+
return
|
|
378
|
+
}
|
|
379
|
+
const verdict = validate(u.searchParams) || {
|
|
380
|
+
error: 'no result from the callback.'
|
|
381
|
+
}
|
|
310
382
|
const failed = !!verdict.error
|
|
311
383
|
res.writeHead(failed ? 400 : 200, { 'Content-Type': 'text/html' })
|
|
312
|
-
res.end(
|
|
313
|
-
|
|
314
|
-
|
|
384
|
+
res.end(
|
|
385
|
+
`<!doctype html><html><body style="font-family:system-ui,sans-serif;text-align:center;padding:60px">` +
|
|
386
|
+
`<h2 style="color:${failed ? '#dc2626' : '#16a34a'}">${failed ? errTitle : okTitle}</h2>` +
|
|
387
|
+
`<p>You can close this tab and return to your terminal.</p></body></html>`
|
|
388
|
+
)
|
|
315
389
|
cleanup()
|
|
316
390
|
resolve(failed ? { error: verdict.error } : { value: verdict.value })
|
|
317
391
|
})
|
|
318
392
|
let timer
|
|
319
|
-
function cleanup() {
|
|
320
|
-
|
|
393
|
+
function cleanup() {
|
|
394
|
+
clearTimeout(timer)
|
|
395
|
+
server.close()
|
|
396
|
+
}
|
|
397
|
+
server.on('error', (e) =>
|
|
398
|
+
resolve({ error: `loopback server error: ${e.message}` })
|
|
399
|
+
)
|
|
321
400
|
server.listen(0, '127.0.0.1', async () => {
|
|
322
401
|
const { port } = server.address()
|
|
323
402
|
const redirectUri = `http://127.0.0.1:${port}/callback`
|
|
@@ -325,10 +404,18 @@ export async function awaitBrowserCallback({
|
|
|
325
404
|
console.log(`\x1b[36m→\x1b[0m ${openingLabel}`)
|
|
326
405
|
console.log(` \x1b[2m${url}\x1b[0m`)
|
|
327
406
|
const opened = await openBrowser(url)
|
|
328
|
-
if (!opened)
|
|
329
|
-
|
|
407
|
+
if (!opened)
|
|
408
|
+
console.log(
|
|
409
|
+
'\x1b[33m⚠\x1b[0m Could not open a browser automatically — open the URL above.'
|
|
410
|
+
)
|
|
411
|
+
console.log(
|
|
412
|
+
`\x1b[2m${waitingLabel || `Waiting (${Math.round(timeoutMs / 1000)}s)…`}\x1b[0m`
|
|
413
|
+
)
|
|
330
414
|
})
|
|
331
|
-
timer = setTimeout(() => {
|
|
415
|
+
timer = setTimeout(() => {
|
|
416
|
+
server.close()
|
|
417
|
+
resolve({ error: `timed out (${Math.round(timeoutMs / 1000)}s).` })
|
|
418
|
+
}, timeoutMs)
|
|
332
419
|
})
|
|
333
420
|
if (result.error) throw new Error(result.error)
|
|
334
421
|
return result.value
|
|
@@ -342,7 +429,9 @@ export async function awaitBrowserCallback({
|
|
|
342
429
|
// BROWSER_AVAILABLE until the endpoint is live.
|
|
343
430
|
async function loginViaBrowser({ apiBase }) {
|
|
344
431
|
if (!BROWSER_AVAILABLE) {
|
|
345
|
-
throw new Error(
|
|
432
|
+
throw new Error(
|
|
433
|
+
'browser/social login for the new backend isn’t available yet — use --password or --token-paste.'
|
|
434
|
+
)
|
|
346
435
|
}
|
|
347
436
|
const base = apiBase.replace(/\/$/, '')
|
|
348
437
|
const state = randomBytes(16).toString('hex')
|
|
@@ -352,7 +441,8 @@ async function loginViaBrowser({ apiBase }) {
|
|
|
352
441
|
`${base}/dev/auth/authorize?redirect_uri=${encodeURIComponent(redirectUri)}&state=${state}`,
|
|
353
442
|
validate: (params) => {
|
|
354
443
|
if (params.get('error')) return { error: params.get('error') }
|
|
355
|
-
if (params.get('state') !== state)
|
|
444
|
+
if (params.get('state') !== state)
|
|
445
|
+
return { error: 'state mismatch — please try again.' }
|
|
356
446
|
const tok = params.get('token')
|
|
357
447
|
if (!tok) return { error: 'no token returned by the callback.' }
|
|
358
448
|
return { value: tok }
|
|
@@ -360,11 +450,15 @@ async function loginViaBrowser({ apiBase }) {
|
|
|
360
450
|
openingLabel: 'Opening your browser to sign in…',
|
|
361
451
|
waitingLabel: 'Waiting for sign-in to complete (120s)…',
|
|
362
452
|
okTitle: 'Login successful',
|
|
363
|
-
errTitle: 'Login failed'
|
|
453
|
+
errTitle: 'Login failed'
|
|
364
454
|
})
|
|
365
455
|
|
|
366
456
|
let account = null
|
|
367
|
-
try {
|
|
457
|
+
try {
|
|
458
|
+
account = await fetchMe({ apiBase, token })
|
|
459
|
+
} catch {
|
|
460
|
+
/* identity optional; token is valid */
|
|
461
|
+
}
|
|
368
462
|
const record = { token, origin: normOrigin(apiBase) }
|
|
369
463
|
if (account?.uuid) record.uuid = account.uuid
|
|
370
464
|
if (account?.username) record.username = account.username
|
|
@@ -388,8 +482,13 @@ async function loginViaBrowser({ apiBase }) {
|
|
|
388
482
|
export async function runRegistryLogin({ apiBase, args = [] } = {}) {
|
|
389
483
|
const existing = await readRegistryAuth()
|
|
390
484
|
if (existing?.token && !isExpired(existing)) {
|
|
391
|
-
const who =
|
|
392
|
-
|
|
485
|
+
const who =
|
|
486
|
+
existing.username ||
|
|
487
|
+
existing.handle ||
|
|
488
|
+
(existing.uuid ? `account ${existing.uuid}` : '')
|
|
489
|
+
console.log(
|
|
490
|
+
`Already logged in${who ? ` as \x1b[1m${who}\x1b[0m` : ''}${apiBase ? ` (${apiBase})` : ''}.`
|
|
491
|
+
)
|
|
393
492
|
console.log('\x1b[2mContinuing will replace the existing session.\x1b[0m\n')
|
|
394
493
|
}
|
|
395
494
|
|
|
@@ -407,7 +506,9 @@ export async function runRegistryLogin({ apiBase, args = [] } = {}) {
|
|
|
407
506
|
try {
|
|
408
507
|
account = await fetchMe({ apiBase, token: tokenFlag })
|
|
409
508
|
} catch (err) {
|
|
410
|
-
console.error(
|
|
509
|
+
console.error(
|
|
510
|
+
`\x1b[31m✗\x1b[0m Token rejected by ${apiBase}: ${err.message}`
|
|
511
|
+
)
|
|
411
512
|
process.exit(1)
|
|
412
513
|
}
|
|
413
514
|
const record = { token: tokenFlag, origin: normOrigin(apiBase) }
|
|
@@ -415,34 +516,58 @@ export async function runRegistryLogin({ apiBase, args = [] } = {}) {
|
|
|
415
516
|
if (account?.username) record.username = account.username
|
|
416
517
|
if (account?.handle) record.handle = account.handle
|
|
417
518
|
await writeRegistryAuth(record)
|
|
418
|
-
console.log(
|
|
519
|
+
console.log(
|
|
520
|
+
`\x1b[32m✓\x1b[0m Logged in${account?.username ? ` as \x1b[1m${account.username}\x1b[0m` : ''}${apiBase ? ` (${apiBase})` : ''}`
|
|
521
|
+
)
|
|
419
522
|
return record
|
|
420
523
|
}
|
|
421
524
|
|
|
422
|
-
let method = args.includes('--browser')
|
|
423
|
-
|
|
424
|
-
: args.includes('--
|
|
425
|
-
|
|
525
|
+
let method = args.includes('--browser')
|
|
526
|
+
? 'browser'
|
|
527
|
+
: args.includes('--password')
|
|
528
|
+
? 'password'
|
|
529
|
+
: args.includes('--token-paste')
|
|
530
|
+
? 'token-paste'
|
|
531
|
+
: null
|
|
426
532
|
|
|
427
533
|
if (!method) {
|
|
428
534
|
if (nonInteractive) {
|
|
429
535
|
if (process.env.UNIWEB_USERNAME && process.env.UNIWEB_PASSWORD) {
|
|
430
536
|
method = 'password'
|
|
431
537
|
} else {
|
|
432
|
-
console.error(
|
|
433
|
-
|
|
538
|
+
console.error(
|
|
539
|
+
'\x1b[31m✗\x1b[0m Cannot log in non-interactively without a method.'
|
|
540
|
+
)
|
|
541
|
+
console.error(
|
|
542
|
+
' Set UNIWEB_USERNAME + UNIWEB_PASSWORD, set UNIWEB_TOKEN, or run `uniweb login` in a terminal.'
|
|
543
|
+
)
|
|
434
544
|
console.error(' Or force one: --password / --token-paste.')
|
|
435
545
|
process.exit(1)
|
|
436
546
|
}
|
|
437
547
|
} else {
|
|
438
548
|
const prompts = (await import('prompts')).default
|
|
439
549
|
const choices = []
|
|
440
|
-
if (BROWSER_AVAILABLE)
|
|
550
|
+
if (BROWSER_AVAILABLE)
|
|
551
|
+
choices.push({
|
|
552
|
+
title: 'Browser / social (Google, etc.)',
|
|
553
|
+
value: 'browser'
|
|
554
|
+
})
|
|
441
555
|
choices.push({ title: 'Username and password', value: 'password' })
|
|
442
556
|
choices.push({ title: 'Paste a token', value: 'token-paste' })
|
|
443
|
-
const { picked } = await prompts(
|
|
444
|
-
|
|
445
|
-
|
|
557
|
+
const { picked } = await prompts(
|
|
558
|
+
{
|
|
559
|
+
type: 'select',
|
|
560
|
+
name: 'picked',
|
|
561
|
+
message: 'How do you want to log in?',
|
|
562
|
+
choices
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
onCancel: () => {
|
|
566
|
+
console.log('\nLogin cancelled.')
|
|
567
|
+
process.exit(0)
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
)
|
|
446
571
|
if (!picked) process.exit(0)
|
|
447
572
|
method = picked
|
|
448
573
|
}
|
|
@@ -451,7 +576,8 @@ export async function runRegistryLogin({ apiBase, args = [] } = {}) {
|
|
|
451
576
|
let record
|
|
452
577
|
try {
|
|
453
578
|
if (method === 'browser') record = await loginViaBrowser({ apiBase })
|
|
454
|
-
else if (method === 'token-paste')
|
|
579
|
+
else if (method === 'token-paste')
|
|
580
|
+
record = await loginViaTokenPaste({ apiBase, nonInteractive })
|
|
455
581
|
else record = await loginViaPassword({ apiBase, nonInteractive })
|
|
456
582
|
} catch (err) {
|
|
457
583
|
console.error(`\x1b[31m✗\x1b[0m ${err.message}`)
|
|
@@ -459,7 +585,9 @@ export async function runRegistryLogin({ apiBase, args = [] } = {}) {
|
|
|
459
585
|
}
|
|
460
586
|
|
|
461
587
|
if (record?.token) {
|
|
462
|
-
console.log(
|
|
588
|
+
console.log(
|
|
589
|
+
`\x1b[32m✓\x1b[0m Logged in${record.username ? ` as \x1b[1m${record.username}\x1b[0m` : ''}${apiBase ? ` (${apiBase})` : ''}`
|
|
590
|
+
)
|
|
463
591
|
}
|
|
464
592
|
return record
|
|
465
593
|
}
|