three-blocks-login 0.1.0 → 0.1.2
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 +358 -53
- package/package.json +12 -4
package/bin/login.js
CHANGED
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import os from "node:os";
|
|
7
7
|
import path from "node:path";
|
|
8
|
+
import readline from "node:readline";
|
|
8
9
|
import { createRequire } from "node:module";
|
|
9
10
|
|
|
10
11
|
const require = createRequire(import.meta.url);
|
|
11
12
|
const pkg = require("../package.json");
|
|
13
|
+
const CLI_VERSION = pkg?.version ? String(pkg.version) : "?.?.?";
|
|
12
14
|
|
|
13
15
|
// Simple ANSI color helpers (no deps)
|
|
14
16
|
const ESC = (n) => `\u001b[${n}m`;
|
|
@@ -20,7 +22,209 @@ const green = (s) => ESC(32) + s + reset;
|
|
|
20
22
|
const yellow = (s) => ESC(33) + s + reset;
|
|
21
23
|
const cyan = (s) => ESC(36) + s + reset;
|
|
22
24
|
const plainBanner = `[three-blocks-login@${pkg.version}]`;
|
|
23
|
-
const banner =
|
|
25
|
+
const banner = cyan(plainBanner);
|
|
26
|
+
|
|
27
|
+
const quoteShell = (value) => JSON.stringify(String(value ?? ''));
|
|
28
|
+
|
|
29
|
+
const HEADER_WIDTH = 96;
|
|
30
|
+
const LEFT_WIDTH = 60;
|
|
31
|
+
const RIGHT_WIDTH = HEADER_WIDTH - LEFT_WIDTH - 3;
|
|
32
|
+
|
|
33
|
+
const repeatChar = (ch, len) => ch.repeat(Math.max(0, len));
|
|
34
|
+
const stripAnsi = (value) => String(value ?? '').replace(/\u001b\[[0-9;]*m/g, '');
|
|
35
|
+
const ellipsize = (value, width) => {
|
|
36
|
+
const str = String(value ?? '');
|
|
37
|
+
const plain = stripAnsi(str);
|
|
38
|
+
if (plain.length <= width) return str;
|
|
39
|
+
if (width <= 1) return plain.slice(0, width);
|
|
40
|
+
const truncated = plain.slice(0, width - 1) + '…';
|
|
41
|
+
return plain === str ? truncated : truncated;
|
|
42
|
+
};
|
|
43
|
+
const visibleLength = (value) => stripAnsi(String(value ?? '')).length;
|
|
44
|
+
const padText = (value, width, align = 'left') => {
|
|
45
|
+
const text = ellipsize(value, width);
|
|
46
|
+
const current = visibleLength(text);
|
|
47
|
+
const remaining = width - current;
|
|
48
|
+
if (remaining <= 0) return text;
|
|
49
|
+
if (align === 'center') {
|
|
50
|
+
const left = Math.floor(remaining / 2);
|
|
51
|
+
const right = remaining - left;
|
|
52
|
+
return `${' '.repeat(left)}${text}${' '.repeat(right)}`;
|
|
53
|
+
}
|
|
54
|
+
if (align === 'right') {
|
|
55
|
+
return `${' '.repeat(remaining)}${text}`;
|
|
56
|
+
}
|
|
57
|
+
return `${text}${' '.repeat(remaining)}`;
|
|
58
|
+
};
|
|
59
|
+
const makeHeaderRow = (left, right = '', leftAlign = 'left', rightAlign = 'left') =>
|
|
60
|
+
`│${padText(left, LEFT_WIDTH, leftAlign)}│${padText(right, RIGHT_WIDTH, rightAlign)}│`;
|
|
61
|
+
|
|
62
|
+
const HEADER_COLOR = ESC(33);
|
|
63
|
+
const CONTENT_COLOR = ESC(90); // bright black (grey)
|
|
64
|
+
const reapplyColor = (value, color) => {
|
|
65
|
+
const str = String(value ?? '');
|
|
66
|
+
return `${color}${str.split(reset).join(`${reset}${color}`)}${reset}`;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const applyHeaderColor = (row, { keepContentYellow = false, tintContent = true } = {}) => {
|
|
70
|
+
if (keepContentYellow) return reapplyColor(row, HEADER_COLOR);
|
|
71
|
+
if (!row.startsWith('│') || !row.endsWith('│')) return reapplyColor(row, HEADER_COLOR);
|
|
72
|
+
const match = row.match(/^│(.*)│(.*)│$/);
|
|
73
|
+
if (!match) return reapplyColor(row, HEADER_COLOR);
|
|
74
|
+
const [, leftContent, rightContent] = match;
|
|
75
|
+
const leftSegment = tintContent ? reapplyColor(leftContent, CONTENT_COLOR) : leftContent;
|
|
76
|
+
const rightSegment = tintContent ? reapplyColor(rightContent, CONTENT_COLOR) : rightContent;
|
|
77
|
+
return `${HEADER_COLOR}│${reset}${leftSegment}${HEADER_COLOR}│${reset}${rightSegment}${HEADER_COLOR}│${reset}`;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const formatRegistryShort = (value) => {
|
|
81
|
+
if (!value) return '';
|
|
82
|
+
try {
|
|
83
|
+
const u = new URL(value);
|
|
84
|
+
const pathname = (u.pathname || '').replace(/\/$/, '');
|
|
85
|
+
return `${u.host}${pathname}`;
|
|
86
|
+
} catch {
|
|
87
|
+
return String(value);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const maskLicense = (s) => {
|
|
92
|
+
if (!s) return '••••';
|
|
93
|
+
const v = String(s);
|
|
94
|
+
if (v.length <= 8) return '••••';
|
|
95
|
+
return `${v.slice(0, 4)}••••${v.slice(-4)}`;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const capitalize = (value) => {
|
|
99
|
+
const str = String(value || '').trim();
|
|
100
|
+
if (!str) return '';
|
|
101
|
+
return str[0].toUpperCase() + str.slice(1);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const formatPlanLabel = (value) => {
|
|
105
|
+
const str = String(value || '').trim();
|
|
106
|
+
if (!str) return '';
|
|
107
|
+
return str
|
|
108
|
+
.split(/\s+/)
|
|
109
|
+
.map((part) => (part ? capitalize(part.toLowerCase()) : ''))
|
|
110
|
+
.join(' ')
|
|
111
|
+
.trim();
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const normalizePlan = (plan) => {
|
|
115
|
+
const label = formatPlanLabel(plan);
|
|
116
|
+
if (!label) return 'Developer Plan';
|
|
117
|
+
return label.toLowerCase().includes('plan') ? label : `${label} Plan`;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const formatFirstName = (value) => {
|
|
121
|
+
const name = String(value || '').trim();
|
|
122
|
+
if (!name) return '';
|
|
123
|
+
const first = name.split(/\s+/)[0];
|
|
124
|
+
return capitalize(first.toLowerCase());
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const getUserDisplayName = () => {
|
|
128
|
+
const candidate =
|
|
129
|
+
process.env.THREE_BLOCKS_USER_NAME ||
|
|
130
|
+
process.env.GIT_AUTHOR_NAME ||
|
|
131
|
+
process.env.USER ||
|
|
132
|
+
process.env.LOGNAME;
|
|
133
|
+
if (candidate) return formatFirstName(candidate);
|
|
134
|
+
try {
|
|
135
|
+
return formatFirstName(os.userInfo().username);
|
|
136
|
+
} catch {
|
|
137
|
+
return '';
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const formatExpiryLabel = (iso) => {
|
|
142
|
+
if (!iso) return 'Expires: —';
|
|
143
|
+
const dt = new Date(iso);
|
|
144
|
+
if (Number.isNaN(dt.getTime())) return `Expires: ${iso}`;
|
|
145
|
+
return `Expires: ${dt.toISOString().replace('T', ' ').replace('Z', 'Z')}`;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const USER_DISPLAY_NAME = getUserDisplayName();
|
|
149
|
+
|
|
150
|
+
const renderEnvHeader = ({
|
|
151
|
+
scope,
|
|
152
|
+
registry,
|
|
153
|
+
expiresAt,
|
|
154
|
+
tmpFile,
|
|
155
|
+
licenseMasked,
|
|
156
|
+
licenseId,
|
|
157
|
+
channel,
|
|
158
|
+
status,
|
|
159
|
+
plan,
|
|
160
|
+
teamName,
|
|
161
|
+
teamId,
|
|
162
|
+
repository,
|
|
163
|
+
domain,
|
|
164
|
+
region,
|
|
165
|
+
userDisplayName,
|
|
166
|
+
}) => {
|
|
167
|
+
const title = `─── Three Blocks Login v${CLI_VERSION} `;
|
|
168
|
+
const separatorRow = makeHeaderRow(repeatChar('─', LEFT_WIDTH), repeatChar('─', RIGHT_WIDTH));
|
|
169
|
+
const channelDisplay = String(channel || '').toUpperCase() || 'STABLE';
|
|
170
|
+
const registryShort = formatRegistryShort(registry);
|
|
171
|
+
|
|
172
|
+
const displayName = userDisplayName || USER_DISPLAY_NAME;
|
|
173
|
+
const welcomeLine = displayName ? `Welcome back ${displayName}!` : 'Welcome back!';
|
|
174
|
+
const scopeLine = `Scope: ${scope}`;
|
|
175
|
+
|
|
176
|
+
const planLabel = normalizePlan(plan);
|
|
177
|
+
const teamLabel = teamName || teamId ? ` · Team: ${teamName || teamId}` : '';
|
|
178
|
+
const subscriptionLine = `Plan: ${planLabel}${teamLabel}`;
|
|
179
|
+
const channelLine = `Channel: ${channelDisplay}${region ? ` · Region: ${region}` : ''}`;
|
|
180
|
+
|
|
181
|
+
const repositoryBase = repository ? `Repository: ${repository}` : 'Repository: —';
|
|
182
|
+
const repositoryLine = registryShort ? `${repositoryBase} → ${registryShort}` : repositoryBase;
|
|
183
|
+
const registryLine = `Registry: ${registryShort || (registry || '—')}`;
|
|
184
|
+
|
|
185
|
+
let domainValue = domain || '';
|
|
186
|
+
if (!domainValue && registry) {
|
|
187
|
+
try {
|
|
188
|
+
domainValue = new URL(registry).host;
|
|
189
|
+
} catch {}
|
|
190
|
+
}
|
|
191
|
+
const domainLineText = `Domain: ${domainValue || '—'}`;
|
|
192
|
+
const regionLineText = `Region: ${region || '—'}`;
|
|
193
|
+
|
|
194
|
+
const licenseLine = `License: ${licenseMasked}${licenseId ? ` · ${licenseId}` : ''}`;
|
|
195
|
+
const expiresLine = formatExpiryLabel(expiresAt);
|
|
196
|
+
|
|
197
|
+
const ascii = [
|
|
198
|
+
'THREE.JS',
|
|
199
|
+
' ______ __ ______ ______ __ __ ______ ',
|
|
200
|
+
'/\\ == \\ /\\ \\ /\\ __ \\ /\\ ___\\ /\\ \\/ / /\\ ___\\ ',
|
|
201
|
+
'\\ \\ __< \\ \\ \\____ \\ \\ \\/\\ \\ \\ \\ \\____ \\ \\ _"-. \\ \\___ \\ ',
|
|
202
|
+
' \\ \\_____\\ \\ \\_____\\ \\ \\_____\\ \\ \\_____\\ \\ \\_\\ \\_\\ \\/\\_____\\',
|
|
203
|
+
' \\/_____\/ \\/_____/ \\/_____/ \\/_____/ \\/_/ \/_/ \\/_____/'
|
|
204
|
+
];
|
|
205
|
+
const statusMessage = status?.message || (status?.ok ? 'access granted' : status ? 'no active access' : 'pending');
|
|
206
|
+
const statusText = status?.ok
|
|
207
|
+
? green(bold(`Authenticated — ${statusMessage}`))
|
|
208
|
+
: status
|
|
209
|
+
? red(bold(`Not authenticated — ${statusMessage}`))
|
|
210
|
+
: dim(bold(`Status: ${statusMessage}`));
|
|
211
|
+
|
|
212
|
+
const lines = [
|
|
213
|
+
applyHeaderColor(`╭${title}${repeatChar('─', HEADER_WIDTH - 2 - title.length)}╮`, { keepContentYellow: true }),
|
|
214
|
+
...ascii.map((row) => applyHeaderColor(`│${padText(row, LEFT_WIDTH + RIGHT_WIDTH + 1, 'center')}│`, { keepContentYellow: true })),
|
|
215
|
+
applyHeaderColor(separatorRow, { keepContentYellow: true }),
|
|
216
|
+
applyHeaderColor(makeHeaderRow(welcomeLine, scopeLine, 'center', 'center')),
|
|
217
|
+
applyHeaderColor(separatorRow, { keepContentYellow: true }),
|
|
218
|
+
applyHeaderColor(makeHeaderRow(subscriptionLine, channelLine)),
|
|
219
|
+
applyHeaderColor(makeHeaderRow(repositoryLine, registryLine)),
|
|
220
|
+
applyHeaderColor(makeHeaderRow(domainLineText, regionLineText, 'left', 'center')),
|
|
221
|
+
applyHeaderColor(makeHeaderRow(licenseLine, expiresLine)),
|
|
222
|
+
applyHeaderColor(separatorRow, { keepContentYellow: true }),
|
|
223
|
+
applyHeaderColor(makeHeaderRow(statusText, '', 'left', 'center'), { tintContent: false }),
|
|
224
|
+
applyHeaderColor(`╰${repeatChar('─', HEADER_WIDTH - 2)}╯`, { keepContentYellow: true }),
|
|
225
|
+
];
|
|
226
|
+
for (const row of lines) console.log(row);
|
|
227
|
+
};
|
|
24
228
|
|
|
25
229
|
const args = parseArgs(process.argv.slice(2));
|
|
26
230
|
|
|
@@ -29,6 +233,10 @@ const SCOPE = (args.scope || "@three-blocks").replace(/^\s+|\s+$/g, "");
|
|
|
29
233
|
const MODE = (args.mode || "env").toLowerCase(); // env | project | user
|
|
30
234
|
const QUIET = !!args.quiet;
|
|
31
235
|
const VERBOSE = !!args.verbose;
|
|
236
|
+
let CHANNEL = String(args.channel || process.env.THREE_BLOCKS_CHANNEL || "stable").toLowerCase();
|
|
237
|
+
if (!['stable','alpha','beta'].includes(CHANNEL)) CHANNEL = 'stable';
|
|
238
|
+
const PRINT_SHELL = !!(args['print-shell'] || args.printShell || process.env.THREE_BLOCKS_LOGIN_PRINT_SHELL === '1');
|
|
239
|
+
const NON_INTERACTIVE = !!(args['non-interactive'] || args.nonInteractive || process.env.CI === '1');
|
|
32
240
|
|
|
33
241
|
// Load .env from current working directory (no deps)
|
|
34
242
|
loadEnvFromDotfile(process.cwd());
|
|
@@ -38,32 +246,46 @@ const BROKER_URL =
|
|
|
38
246
|
process.env.THREE_BLOCKS_BROKER_URL ||
|
|
39
247
|
"http://localhost:3000/api/npm/token"; // your Astro broker endpoint
|
|
40
248
|
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
249
|
+
const promptHidden = async (label) => {
|
|
250
|
+
return await new Promise((resolve) => {
|
|
251
|
+
const stdin = process.stdin;
|
|
252
|
+
const stdout = process.stdout;
|
|
253
|
+
let value = '';
|
|
254
|
+
const cleanup = () => {
|
|
255
|
+
stdin.removeListener('data', onData);
|
|
256
|
+
if (stdin.isTTY) stdin.setRawMode(false);
|
|
257
|
+
stdin.pause();
|
|
258
|
+
};
|
|
259
|
+
const onData = (chunk) => {
|
|
260
|
+
const str = String(chunk);
|
|
261
|
+
for (const ch of str) {
|
|
262
|
+
if (ch === '\u0003') { cleanup(); process.exit(1); }
|
|
263
|
+
if (ch === '\r' || ch === '\n' || ch === '\u0004') {
|
|
264
|
+
stdout.write('\n');
|
|
265
|
+
cleanup();
|
|
266
|
+
return resolve(value.trim());
|
|
267
|
+
}
|
|
268
|
+
if (ch === '\b' || ch === '\u007f') {
|
|
269
|
+
if (value.length) {
|
|
270
|
+
value = value.slice(0, -1);
|
|
271
|
+
readline.moveCursor(stdout, -1, 0);
|
|
272
|
+
stdout.write(' ');
|
|
273
|
+
readline.moveCursor(stdout, -1, 0);
|
|
274
|
+
}
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
if (ch === '\u001b') continue;
|
|
278
|
+
value += ch;
|
|
279
|
+
stdout.write('•');
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
stdout.write(label);
|
|
283
|
+
stdin.setEncoding('utf8');
|
|
284
|
+
if (stdin.isTTY) stdin.setRawMode(true);
|
|
285
|
+
stdin.resume();
|
|
286
|
+
stdin.on('data', onData);
|
|
287
|
+
});
|
|
288
|
+
};
|
|
67
289
|
|
|
68
290
|
// Pretty logger (respects --quiet except for errors)
|
|
69
291
|
const log = {
|
|
@@ -75,8 +297,52 @@ const log = {
|
|
|
75
297
|
|
|
76
298
|
(async () => {
|
|
77
299
|
try {
|
|
78
|
-
|
|
79
|
-
|
|
300
|
+
let LICENSE = args.license || process.env.THREE_BLOCKS_SECRET_KEY;
|
|
301
|
+
|
|
302
|
+
if (!LICENSE || String(LICENSE).trim() === "") {
|
|
303
|
+
if (NON_INTERACTIVE) {
|
|
304
|
+
fail("Missing license key. Provide --license <key> or set THREE_BLOCKS_SECRET_KEY in your environment.");
|
|
305
|
+
}
|
|
306
|
+
console.log('');
|
|
307
|
+
log.info(bold(yellow('Three Blocks Login')) + ' ' + dim(`[mode: ${MODE}]`));
|
|
308
|
+
log.info(dim('Enter your license key to retrieve a scoped npm token.'));
|
|
309
|
+
log.info(dim('Tip: paste it here; input is hidden. Press Enter to submit.'));
|
|
310
|
+
LICENSE = await promptHidden(`${cyan('›')} License key ${dim('(tb_…)')}: `);
|
|
311
|
+
if (!LICENSE) fail('License key is required to continue.');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Sanitize + validate license early to avoid header ByteString errors
|
|
315
|
+
const LICENSE_CLEAN = sanitizeLicense(LICENSE);
|
|
316
|
+
if (!LICENSE_CLEAN) {
|
|
317
|
+
fail(
|
|
318
|
+
"Missing license key. Provide --license <key> or set THREE_BLOCKS_SECRET_KEY in your .env file."
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
const invalidIdx = findFirstNonByteChar(LICENSE_CLEAN);
|
|
322
|
+
if (invalidIdx !== -1) {
|
|
323
|
+
if (VERBOSE) log.warn(`[debug] license contains non-ASCII/byte char at index ${invalidIdx}`);
|
|
324
|
+
fail("License appears malformed. Please copy your tb_… key exactly without extra characters.");
|
|
325
|
+
}
|
|
326
|
+
if (!looksLikeLicense(LICENSE_CLEAN)) {
|
|
327
|
+
if (VERBOSE) log.warn(`[debug] license failed format check: ${truncate(LICENSE_CLEAN, 16)}`);
|
|
328
|
+
fail("License appears malformed. Please copy your tb_… key exactly.");
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const tokenData = await fetchToken(BROKER_URL, LICENSE_CLEAN, CHANNEL);
|
|
332
|
+
const {
|
|
333
|
+
registry,
|
|
334
|
+
token,
|
|
335
|
+
expiresAt,
|
|
336
|
+
status: rawStatus,
|
|
337
|
+
plan,
|
|
338
|
+
teamName,
|
|
339
|
+
teamId,
|
|
340
|
+
repository,
|
|
341
|
+
domain,
|
|
342
|
+
region,
|
|
343
|
+
licenseId,
|
|
344
|
+
} = tokenData;
|
|
345
|
+
const authStatus = rawStatus ?? { ok: true, message: 'access granted' };
|
|
80
346
|
|
|
81
347
|
if (!registry || !token) fail("Broker response missing registry/token.");
|
|
82
348
|
|
|
@@ -97,14 +363,44 @@ const log = {
|
|
|
97
363
|
fs.writeFileSync(tmpFile, npmrcContent, { mode: 0o600 });
|
|
98
364
|
|
|
99
365
|
// Print shell exports; caller should `eval "$(npx -y three-blocks-login --mode env)"`
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
366
|
+
const maskedLicense = maskLicense(LICENSE_CLEAN);
|
|
367
|
+
renderEnvHeader({
|
|
368
|
+
scope: SCOPE,
|
|
369
|
+
registry: u.href,
|
|
370
|
+
expiresAt,
|
|
371
|
+
tmpFile,
|
|
372
|
+
licenseMasked: maskedLicense,
|
|
373
|
+
licenseId,
|
|
374
|
+
channel: CHANNEL,
|
|
375
|
+
status: authStatus,
|
|
376
|
+
plan,
|
|
377
|
+
teamName,
|
|
378
|
+
teamId,
|
|
379
|
+
repository,
|
|
380
|
+
domain,
|
|
381
|
+
region,
|
|
382
|
+
userDisplayName: USER_DISPLAY_NAME,
|
|
383
|
+
});
|
|
384
|
+
if (PRINT_SHELL) {
|
|
385
|
+
const exportLines = [
|
|
386
|
+
`export NPM_CONFIG_USERCONFIG=${quoteShell(tmpFile)}`,
|
|
387
|
+
`export npm_config_userconfig=${quoteShell(tmpFile)}`,
|
|
388
|
+
`export THREE_BLOCKS_CHANNEL=${quoteShell(CHANNEL)}`,
|
|
389
|
+
];
|
|
390
|
+
const summaryPrint = [
|
|
391
|
+
`cat <<'EOF'`,
|
|
392
|
+
`${plainBanner} scoped login ready (${authStatus.ok ? 'ok' : 'error'})`,
|
|
393
|
+
` scope : ${SCOPE}`,
|
|
394
|
+
` registry : ${u.href}`,
|
|
395
|
+
` expires : ${expiresAt ?? 'unknown'}`,
|
|
396
|
+
` license : ${maskedLicense}`,
|
|
397
|
+
` npmrc : ${tmpFile}`,
|
|
398
|
+
`EOF`,
|
|
399
|
+
];
|
|
400
|
+
console.log([...exportLines, '', ...summaryPrint].join('\n'));
|
|
401
|
+
} else {
|
|
402
|
+
log.info(`${plainBanner} temp npmrc ready. Use --print-shell to emit export commands.`);
|
|
403
|
+
}
|
|
108
404
|
return;
|
|
109
405
|
}
|
|
110
406
|
|
|
@@ -153,30 +449,39 @@ function ensureTrailingSlash(url) {
|
|
|
153
449
|
|
|
154
450
|
function loadEnvFromDotfile(dir) {
|
|
155
451
|
try {
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
val
|
|
452
|
+
const files = [path.join(dir, ".env.local"), path.join(dir, ".env")];
|
|
453
|
+
for (const file of files) {
|
|
454
|
+
if (!fs.existsSync(file)) continue;
|
|
455
|
+
const txt = fs.readFileSync(file, "utf8");
|
|
456
|
+
for (const raw of txt.split(/\r?\n/)) {
|
|
457
|
+
const line = raw.trim();
|
|
458
|
+
if (!line || line.startsWith("#")) continue;
|
|
459
|
+
const eq = line.indexOf("=");
|
|
460
|
+
if (eq === -1) continue;
|
|
461
|
+
const key = line.slice(0, eq).trim();
|
|
462
|
+
let val = line.slice(eq + 1).trim();
|
|
463
|
+
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
|
|
464
|
+
val = val.slice(1, -1);
|
|
465
|
+
}
|
|
466
|
+
if (process.env[key] === undefined) process.env[key] = val;
|
|
168
467
|
}
|
|
169
|
-
if (process.env[key] === undefined) process.env[key] = val;
|
|
170
468
|
}
|
|
171
469
|
} catch {}
|
|
172
470
|
}
|
|
173
471
|
|
|
174
|
-
async function fetchToken(endpoint, license) {
|
|
175
|
-
|
|
472
|
+
async function fetchToken(endpoint, license, channel) {
|
|
473
|
+
let url = endpoint;
|
|
474
|
+
try {
|
|
475
|
+
const u = new URL(endpoint);
|
|
476
|
+
if (!u.searchParams.get('channel')) u.searchParams.set('channel', channel);
|
|
477
|
+
url = u.toString();
|
|
478
|
+
} catch {}
|
|
479
|
+
const res = await fetch(url, {
|
|
176
480
|
method: "GET",
|
|
177
481
|
headers: {
|
|
178
482
|
"authorization": `Bearer ${license}`,
|
|
179
|
-
"accept": "application/json"
|
|
483
|
+
"accept": "application/json",
|
|
484
|
+
"x-three-blocks-channel": channel
|
|
180
485
|
}
|
|
181
486
|
});
|
|
182
487
|
if (!res.ok) {
|
package/package.json
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "three-blocks-login",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Fetch a short-lived token from the three-blocks broker and configure npm for the current context.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"three-blocks-login": "bin/login.js"
|
|
8
8
|
},
|
|
9
9
|
"license": "MIT",
|
|
10
|
-
"files": [
|
|
11
|
-
|
|
10
|
+
"files": [
|
|
11
|
+
"bin/"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"npm",
|
|
15
|
+
"login",
|
|
16
|
+
"three-blocks",
|
|
17
|
+
"token",
|
|
18
|
+
"ci"
|
|
19
|
+
],
|
|
12
20
|
"dependencies": {},
|
|
13
21
|
"publishConfig": {
|
|
14
22
|
"access": "public"
|
|
15
23
|
}
|
|
16
|
-
}
|
|
24
|
+
}
|