@hiyve/cli 1.0.12 → 1.0.14
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/package.json +1 -1
- package/src/commands/list.js +15 -5
- package/src/commands/login.js +21 -7
- package/src/config.js +10 -2
- package/src/utils/npmrc.js +32 -31
- package/src/utils/npmrc.test.js +56 -2
- package/src/utils/output.js +50 -0
package/package.json
CHANGED
package/src/commands/list.js
CHANGED
|
@@ -8,6 +8,7 @@ import chalk from 'chalk';
|
|
|
8
8
|
import ora from 'ora';
|
|
9
9
|
import { getApiUrl } from '../config.js';
|
|
10
10
|
import { getCurrentConfig } from '../utils/npmrc.js';
|
|
11
|
+
import { printDevHint, printDevModeBanner, fetchWithTimeout } from '../utils/output.js';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* List available packages
|
|
@@ -16,6 +17,7 @@ export async function list() {
|
|
|
16
17
|
console.log('');
|
|
17
18
|
console.log(chalk.cyan('Available @hiyve Packages'));
|
|
18
19
|
console.log(chalk.gray('─'.repeat(40)));
|
|
20
|
+
printDevModeBanner();
|
|
19
21
|
console.log('');
|
|
20
22
|
|
|
21
23
|
// Get current config for auth token
|
|
@@ -30,7 +32,7 @@ export async function list() {
|
|
|
30
32
|
const spinner = ora('Fetching packages...').start();
|
|
31
33
|
|
|
32
34
|
try {
|
|
33
|
-
const response = await
|
|
35
|
+
const response = await fetchWithTimeout(`${getApiUrl()}packages`, {
|
|
34
36
|
headers: {
|
|
35
37
|
Authorization: `Bearer ${config.apiKey}`,
|
|
36
38
|
},
|
|
@@ -41,6 +43,7 @@ export async function list() {
|
|
|
41
43
|
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
42
44
|
console.log('');
|
|
43
45
|
console.log(chalk.red(` ${error.error || 'Failed to fetch packages'}`));
|
|
46
|
+
printDevHint('list');
|
|
44
47
|
process.exit(1);
|
|
45
48
|
}
|
|
46
49
|
|
|
@@ -71,10 +74,17 @@ export async function list() {
|
|
|
71
74
|
console.log(chalk.gray(' npm install <package-name>'));
|
|
72
75
|
console.log('');
|
|
73
76
|
} catch (err) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
if (err.name === 'AbortError') {
|
|
78
|
+
spinner.fail('Request timed out');
|
|
79
|
+
console.log('');
|
|
80
|
+
console.log(chalk.gray(' The server did not respond within 15 seconds.'));
|
|
81
|
+
} else {
|
|
82
|
+
spinner.fail('Connection failed');
|
|
83
|
+
console.log('');
|
|
84
|
+
console.log(chalk.red(` ${err.message}`));
|
|
85
|
+
}
|
|
86
|
+
console.log(chalk.gray(' Please check your internet connection.'));
|
|
87
|
+
printDevHint('list');
|
|
78
88
|
process.exit(1);
|
|
79
89
|
}
|
|
80
90
|
}
|
package/src/commands/login.js
CHANGED
|
@@ -9,6 +9,7 @@ import chalk from 'chalk';
|
|
|
9
9
|
import ora from 'ora';
|
|
10
10
|
import { configureNpmrc } from '../utils/npmrc.js';
|
|
11
11
|
import { getApiUrl, getRegistryUrl } from '../config.js';
|
|
12
|
+
import { printDevHint, printDevModeBanner, fetchWithTimeout } from '../utils/output.js';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Login to Hiyve and configure npm
|
|
@@ -21,6 +22,7 @@ export async function login(options) {
|
|
|
21
22
|
console.log('');
|
|
22
23
|
console.log(chalk.cyan('Hiyve SDK Authentication'));
|
|
23
24
|
console.log(chalk.gray('─'.repeat(40)));
|
|
25
|
+
printDevModeBanner();
|
|
24
26
|
console.log('');
|
|
25
27
|
|
|
26
28
|
// Prompt for API key if not provided
|
|
@@ -75,7 +77,7 @@ export async function login(options) {
|
|
|
75
77
|
const spinner = ora('Verifying API key...').start();
|
|
76
78
|
|
|
77
79
|
try {
|
|
78
|
-
const response = await
|
|
80
|
+
const response = await fetchWithTimeout(`${getApiUrl()}verify`, {
|
|
79
81
|
headers: {
|
|
80
82
|
Authorization: `Bearer ${apiKey}`,
|
|
81
83
|
},
|
|
@@ -86,20 +88,32 @@ export async function login(options) {
|
|
|
86
88
|
spinner.fail('API key verification failed');
|
|
87
89
|
console.log('');
|
|
88
90
|
console.log(chalk.red(` ${error.error || 'Invalid API key'}`));
|
|
91
|
+
printDevHint('login');
|
|
89
92
|
process.exit(1);
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
const data = await response.json();
|
|
93
96
|
spinner.succeed('API key verified');
|
|
94
97
|
|
|
98
|
+
// Server returns a masked key — always mask client-side as a safety net
|
|
95
99
|
if (data.apiKey) {
|
|
96
|
-
|
|
100
|
+
const display = data.apiKey.length > 12
|
|
101
|
+
? `${data.apiKey.slice(0, 8)}...${data.apiKey.slice(-4)}`
|
|
102
|
+
: data.apiKey;
|
|
103
|
+
console.log(chalk.gray(` Account: ${display}`));
|
|
97
104
|
}
|
|
98
105
|
} catch (err) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
106
|
+
if (err.name === 'AbortError') {
|
|
107
|
+
spinner.fail('Request timed out');
|
|
108
|
+
console.log('');
|
|
109
|
+
console.log(chalk.gray(' The server did not respond within 15 seconds.'));
|
|
110
|
+
} else {
|
|
111
|
+
spinner.fail('Connection failed');
|
|
112
|
+
console.log('');
|
|
113
|
+
console.log(chalk.red(` ${err.message}`));
|
|
114
|
+
}
|
|
115
|
+
console.log(chalk.gray(' Please check your internet connection.'));
|
|
116
|
+
printDevHint('login');
|
|
103
117
|
process.exit(1);
|
|
104
118
|
}
|
|
105
119
|
|
|
@@ -123,7 +137,7 @@ export async function login(options) {
|
|
|
123
137
|
|
|
124
138
|
// Fetch available packages from registry
|
|
125
139
|
try {
|
|
126
|
-
const packagesResponse = await
|
|
140
|
+
const packagesResponse = await fetchWithTimeout(`${getApiUrl()}packages`, {
|
|
127
141
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
128
142
|
});
|
|
129
143
|
|
package/src/config.js
CHANGED
|
@@ -28,10 +28,17 @@ export function getApiUrl() {
|
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Get the registry URL for ~/.npmrc (where npm fetches tarballs).
|
|
31
|
-
*
|
|
31
|
+
* Matches the API URL — tarballs are served through the same API.
|
|
32
32
|
*/
|
|
33
33
|
export function getRegistryUrl() {
|
|
34
|
-
return
|
|
34
|
+
return getApiUrl();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Check if dev mode is currently active.
|
|
39
|
+
*/
|
|
40
|
+
export function isDevMode() {
|
|
41
|
+
return _devMode;
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
// Backwards-compatible named export
|
|
@@ -42,4 +49,5 @@ export default {
|
|
|
42
49
|
getApiUrl,
|
|
43
50
|
getRegistryUrl,
|
|
44
51
|
setDevMode,
|
|
52
|
+
isDevMode,
|
|
45
53
|
};
|
package/src/utils/npmrc.js
CHANGED
|
@@ -10,6 +10,28 @@ import path from 'path';
|
|
|
10
10
|
|
|
11
11
|
const NPMRC_PATH = path.join(os.homedir(), '.npmrc');
|
|
12
12
|
|
|
13
|
+
// Known Hiyve registry hosts (current and historical)
|
|
14
|
+
const HIYVE_HOSTS = ['//console.hiyve.dev/', '//api.hiyve.dev/', '//api.muziemedia.com/'];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Check if a line in .npmrc is a Hiyve-related config line.
|
|
18
|
+
* Only matches lines that reference a known Hiyve host or @hiyve scope.
|
|
19
|
+
* Will NOT match third-party tokens that happen to start with sk_/pk_ (e.g. Stripe).
|
|
20
|
+
*/
|
|
21
|
+
function isHiyveConfigLine(line) {
|
|
22
|
+
const trimmed = line.trim();
|
|
23
|
+
if (trimmed.startsWith('@hiyve:registry')) return true;
|
|
24
|
+
if (HIYVE_HOSTS.some((host) => trimmed.includes(host))) return true;
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Filter out all Hiyve-related lines from .npmrc content.
|
|
30
|
+
*/
|
|
31
|
+
function filterOutHiyveLines(content) {
|
|
32
|
+
return content.split('\n').filter((line) => !isHiyveConfigLine(line));
|
|
33
|
+
}
|
|
34
|
+
|
|
13
35
|
/**
|
|
14
36
|
* Configure ~/.npmrc for Hiyve registry
|
|
15
37
|
* @param {string} registryUrl - The registry URL (e.g., https://api.hiyve.dev/registry/)
|
|
@@ -23,19 +45,8 @@ export async function configureNpmrc(registryUrl, apiKey) {
|
|
|
23
45
|
content = fs.readFileSync(NPMRC_PATH, 'utf8');
|
|
24
46
|
}
|
|
25
47
|
|
|
26
|
-
// Remove any existing
|
|
27
|
-
const lines = content
|
|
28
|
-
const trimmed = line.trim();
|
|
29
|
-
return (
|
|
30
|
-
!trimmed.startsWith('@hiyve:registry') &&
|
|
31
|
-
!trimmed.includes('//console.hiyve.dev/') &&
|
|
32
|
-
!trimmed.includes('//api.hiyve.dev/') &&
|
|
33
|
-
!trimmed.includes('//api.muziemedia.com/') &&
|
|
34
|
-
!trimmed.includes(':_authToken=mk_') &&
|
|
35
|
-
!trimmed.includes(':_authToken=sk_') &&
|
|
36
|
-
!trimmed.includes(':_authToken=pk_')
|
|
37
|
-
);
|
|
38
|
-
});
|
|
48
|
+
// Remove any existing Hiyve config lines (scoped to known Hiyve hosts)
|
|
49
|
+
const lines = filterOutHiyveLines(content);
|
|
39
50
|
|
|
40
51
|
// Parse the registry URL to derive the auth token path
|
|
41
52
|
const url = new URL(registryUrl);
|
|
@@ -47,7 +58,7 @@ export async function configureNpmrc(registryUrl, apiKey) {
|
|
|
47
58
|
|
|
48
59
|
// Write back, removing empty lines at start/end
|
|
49
60
|
const finalContent = lines.filter((line) => line.trim()).join('\n') + '\n';
|
|
50
|
-
fs.writeFileSync(NPMRC_PATH, finalContent, 'utf8');
|
|
61
|
+
fs.writeFileSync(NPMRC_PATH, finalContent, { encoding: 'utf8', mode: 0o600 });
|
|
51
62
|
}
|
|
52
63
|
|
|
53
64
|
/**
|
|
@@ -60,23 +71,12 @@ export async function removeNpmrc() {
|
|
|
60
71
|
|
|
61
72
|
const content = fs.readFileSync(NPMRC_PATH, 'utf8');
|
|
62
73
|
|
|
63
|
-
// Remove
|
|
64
|
-
const lines = content
|
|
65
|
-
const trimmed = line.trim();
|
|
66
|
-
return (
|
|
67
|
-
!trimmed.startsWith('@hiyve:registry') &&
|
|
68
|
-
!trimmed.includes('//console.hiyve.dev/') &&
|
|
69
|
-
!trimmed.includes('//api.hiyve.dev/') &&
|
|
70
|
-
!trimmed.includes('//api.muziemedia.com/') &&
|
|
71
|
-
!trimmed.includes(':_authToken=mk_') &&
|
|
72
|
-
!trimmed.includes(':_authToken=sk_') &&
|
|
73
|
-
!trimmed.includes(':_authToken=pk_')
|
|
74
|
-
);
|
|
75
|
-
});
|
|
74
|
+
// Remove Hiyve config lines (scoped to known Hiyve hosts)
|
|
75
|
+
const lines = filterOutHiyveLines(content);
|
|
76
76
|
|
|
77
77
|
// Write back
|
|
78
78
|
const finalContent = lines.filter((line) => line.trim()).join('\n');
|
|
79
|
-
fs.writeFileSync(NPMRC_PATH, finalContent ? finalContent + '\n' : '', 'utf8');
|
|
79
|
+
fs.writeFileSync(NPMRC_PATH, finalContent ? finalContent + '\n' : '', { encoding: 'utf8', mode: 0o600 });
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/**
|
|
@@ -94,10 +94,11 @@ export function getCurrentConfig() {
|
|
|
94
94
|
// Find registry line
|
|
95
95
|
const registryLine = lines.find((line) => line.trim().startsWith('@hiyve:registry'));
|
|
96
96
|
|
|
97
|
-
// Find token line
|
|
97
|
+
// Find token line on a known Hiyve host
|
|
98
98
|
const tokenLine = lines.find((line) => {
|
|
99
99
|
const trimmed = line.trim();
|
|
100
|
-
return
|
|
100
|
+
return HIYVE_HOSTS.some((host) => trimmed.includes(host)) &&
|
|
101
|
+
(trimmed.includes(':_authToken=pk_') || trimmed.includes(':_authToken=sk_') || trimmed.includes(':_authToken=mk_'));
|
|
101
102
|
});
|
|
102
103
|
|
|
103
104
|
if (!registryLine || !tokenLine) {
|
|
@@ -114,7 +115,7 @@ export function getCurrentConfig() {
|
|
|
114
115
|
return {
|
|
115
116
|
apiKey,
|
|
116
117
|
maskedApiKey,
|
|
117
|
-
registryUrl: registryLine.
|
|
118
|
+
registryUrl: registryLine.substring(registryLine.indexOf('=') + 1).trim(),
|
|
118
119
|
};
|
|
119
120
|
}
|
|
120
121
|
|
package/src/utils/npmrc.test.js
CHANGED
|
@@ -139,6 +139,39 @@ describe('configureNpmrc', () => {
|
|
|
139
139
|
expect(content).toContain('api.hiyve.dev');
|
|
140
140
|
});
|
|
141
141
|
|
|
142
|
+
it('does not delete third-party sk_ tokens (e.g. Stripe)', async () => {
|
|
143
|
+
const existingContent = [
|
|
144
|
+
'@hiyve:registry=https://api.hiyve.dev/registry/',
|
|
145
|
+
'//api.hiyve.dev/registry/:_authToken=pk_live_hiyvekey123',
|
|
146
|
+
'//npm.stripe.com/:_authToken=sk_live_stripekey456',
|
|
147
|
+
'other-config=value',
|
|
148
|
+
].join('\n');
|
|
149
|
+
|
|
150
|
+
fs.existsSync.mockReturnValue(true);
|
|
151
|
+
fs.readFileSync.mockReturnValue(existingContent);
|
|
152
|
+
fs.writeFileSync.mockImplementation(() => {});
|
|
153
|
+
|
|
154
|
+
await configureNpmrc('https://api.hiyve.dev/registry/', 'pk_live_newkey789');
|
|
155
|
+
|
|
156
|
+
const [, content] = fs.writeFileSync.mock.calls[0];
|
|
157
|
+
// Hiyve lines replaced
|
|
158
|
+
expect(content).not.toContain('pk_live_hiyvekey123');
|
|
159
|
+
expect(content).toContain(':_authToken=pk_live_newkey789');
|
|
160
|
+
// Stripe token preserved
|
|
161
|
+
expect(content).toContain('//npm.stripe.com/:_authToken=sk_live_stripekey456');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('writes correct auth path for dev registry URL', async () => {
|
|
165
|
+
fs.existsSync.mockReturnValue(false);
|
|
166
|
+
fs.writeFileSync.mockImplementation(() => {});
|
|
167
|
+
|
|
168
|
+
await configureNpmrc('https://api.muziemedia.com/registry/', 'pk_test_abc123def456');
|
|
169
|
+
|
|
170
|
+
const [, content] = fs.writeFileSync.mock.calls[0];
|
|
171
|
+
expect(content).toContain('@hiyve:registry=https://api.muziemedia.com/registry/');
|
|
172
|
+
expect(content).toContain('//api.muziemedia.com/registry/:_authToken=pk_test_abc123def456');
|
|
173
|
+
});
|
|
174
|
+
|
|
142
175
|
it('preserves non-hiyve lines', async () => {
|
|
143
176
|
const existingContent = [
|
|
144
177
|
'registry=https://registry.npmjs.org/',
|
|
@@ -163,8 +196,8 @@ describe('configureNpmrc', () => {
|
|
|
163
196
|
|
|
164
197
|
await configureNpmrc('https://api.hiyve.dev/registry/', 'sk_live_1a2b3c4d5e6f');
|
|
165
198
|
|
|
166
|
-
const [, content,
|
|
167
|
-
expect(
|
|
199
|
+
const [, content, options] = fs.writeFileSync.mock.calls[0];
|
|
200
|
+
expect(options).toEqual({ encoding: 'utf8', mode: 0o600 });
|
|
168
201
|
|
|
169
202
|
const lines = content.split('\n').filter((l) => l.trim());
|
|
170
203
|
const registryLine = lines.find((l) => l.startsWith('@hiyve:registry='));
|
|
@@ -281,6 +314,27 @@ describe('removeNpmrc', () => {
|
|
|
281
314
|
expect(content).not.toContain('mk_abc123');
|
|
282
315
|
});
|
|
283
316
|
|
|
317
|
+
it('does not delete third-party sk_ tokens (e.g. Stripe)', async () => {
|
|
318
|
+
const existingContent = [
|
|
319
|
+
'registry=https://registry.npmjs.org/',
|
|
320
|
+
'@hiyve:registry=https://api.hiyve.dev/registry/',
|
|
321
|
+
'//api.hiyve.dev/registry/:_authToken=pk_live_hiyvekey123',
|
|
322
|
+
'//npm.stripe.com/:_authToken=sk_live_stripekey456',
|
|
323
|
+
].join('\n');
|
|
324
|
+
|
|
325
|
+
fs.existsSync.mockReturnValue(true);
|
|
326
|
+
fs.readFileSync.mockReturnValue(existingContent);
|
|
327
|
+
fs.writeFileSync.mockImplementation(() => {});
|
|
328
|
+
|
|
329
|
+
await removeNpmrc();
|
|
330
|
+
|
|
331
|
+
const [, content] = fs.writeFileSync.mock.calls[0];
|
|
332
|
+
expect(content).not.toContain('@hiyve:registry');
|
|
333
|
+
expect(content).not.toContain('pk_live_hiyvekey123');
|
|
334
|
+
// Third-party token preserved
|
|
335
|
+
expect(content).toContain('//npm.stripe.com/:_authToken=sk_live_stripekey456');
|
|
336
|
+
});
|
|
337
|
+
|
|
284
338
|
it('preserves non-hiyve lines', async () => {
|
|
285
339
|
const existingContent = [
|
|
286
340
|
'registry=https://registry.npmjs.org/',
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared CLI output helpers
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { isDevMode } from '../config.js';
|
|
7
|
+
|
|
8
|
+
const FETCH_TIMEOUT_MS = 15_000;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Print a --dev mode hint when a CLI command fails.
|
|
12
|
+
* Suggests the opposite mode to help the user try the right registry.
|
|
13
|
+
* @param {string} command - The CLI command name (e.g., 'login', 'list')
|
|
14
|
+
*/
|
|
15
|
+
export function printDevHint(command) {
|
|
16
|
+
if (isDevMode()) {
|
|
17
|
+
console.log(chalk.gray(' You are using --dev mode (development registry).'));
|
|
18
|
+
console.log(chalk.gray(' If you meant to use production, run without --dev:'));
|
|
19
|
+
console.log(chalk.cyan(` npx @hiyve/cli ${command}`));
|
|
20
|
+
} else {
|
|
21
|
+
console.log(chalk.gray(' If you meant to use the development registry, use --dev:'));
|
|
22
|
+
console.log(chalk.cyan(` npx @hiyve/cli --dev ${command}`));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Print the dev mode banner if --dev is active.
|
|
28
|
+
*/
|
|
29
|
+
export function printDevModeBanner() {
|
|
30
|
+
if (isDevMode()) {
|
|
31
|
+
console.log(chalk.yellow(' Mode: development (--dev)'));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Create a fetch call with a timeout via AbortController.
|
|
37
|
+
* @param {string} url
|
|
38
|
+
* @param {RequestInit} [options]
|
|
39
|
+
* @returns {Promise<Response>}
|
|
40
|
+
*/
|
|
41
|
+
export async function fetchWithTimeout(url, options = {}) {
|
|
42
|
+
const controller = new AbortController();
|
|
43
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
return await fetch(url, { ...options, signal: controller.signal });
|
|
47
|
+
} finally {
|
|
48
|
+
clearTimeout(timeout);
|
|
49
|
+
}
|
|
50
|
+
}
|