@hiyve/cli 1.0.13 → 1.0.17
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 +13 -1
- package/bin/hiyve.js +1 -1
- package/package.json +3 -3
- package/src/commands/init.js +3 -3
- package/src/commands/list.js +14 -23
- package/src/commands/login.js +20 -25
- package/src/config.js +30 -6
- package/src/config.test.js +15 -7
- package/src/utils/npmrc.js +42 -32
- package/src/utils/npmrc.test.js +56 -2
- package/src/utils/output.js +50 -0
package/README.md
CHANGED
|
@@ -107,6 +107,18 @@ Set `HIYVE_API_KEY` in your CI environment (GitHub Actions secrets, AWS SSM, etc
|
|
|
107
107
|
- Check your internet connection
|
|
108
108
|
- The registry may be temporarily unavailable
|
|
109
109
|
|
|
110
|
+
### "401 Unauthorized" when running `npx @hiyve/cli login`
|
|
111
|
+
|
|
112
|
+
If you've previously logged in and your token has expired, `npx` may fail because npm routes all `@hiyve/*` packages (including the CLI) through the private registry. To fix this, remove the stale registry line from your `~/.npmrc` and re-run login:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# Remove the stale @hiyve registry config
|
|
116
|
+
npm config delete @hiyve:registry
|
|
117
|
+
|
|
118
|
+
# Now login will work again
|
|
119
|
+
npx @hiyve/cli login
|
|
120
|
+
```
|
|
121
|
+
|
|
110
122
|
### Packages not installing
|
|
111
123
|
|
|
112
124
|
After login, verify your configuration:
|
|
@@ -129,6 +141,6 @@ cat ~/.npmrc | grep hiyve
|
|
|
129
141
|
|
|
130
142
|
## Support
|
|
131
143
|
|
|
132
|
-
- Documentation: https://
|
|
144
|
+
- Documentation: https://sdk.hiyve.dev
|
|
133
145
|
- Issues: https://github.com/hiyve/hiyve-sdk/issues
|
|
134
146
|
- Email: support@hiyve.io
|
package/bin/hiyve.js
CHANGED
|
@@ -18,7 +18,7 @@ program
|
|
|
18
18
|
.name('hiyve')
|
|
19
19
|
.description('Hiyve SDK CLI - Configure npm for private @hiyve packages')
|
|
20
20
|
.version('1.0.0')
|
|
21
|
-
.option('--dev', '
|
|
21
|
+
.option('--dev', 'Retained for compatibility — there is no separate dev registry; same host as production')
|
|
22
22
|
.hook('preAction', () => {
|
|
23
23
|
if (program.opts().dev) {
|
|
24
24
|
setDevMode(true);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hiyve/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.17",
|
|
4
4
|
"description": "Hiyve SDK CLI - Configure npm for private @hiyve packages",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"src"
|
|
13
13
|
],
|
|
14
14
|
"scripts": {
|
|
15
|
-
"test": "vitest run",
|
|
15
|
+
"test": "vitest run --pool=forks --poolOptions.forks.singleFork",
|
|
16
16
|
"test:watch": "vitest",
|
|
17
17
|
"deploy": "./deploy.sh"
|
|
18
18
|
},
|
|
@@ -44,6 +44,6 @@
|
|
|
44
44
|
"prompts": "^2.4.2"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"vitest": "^2.
|
|
47
|
+
"vitest": "^3.2.6"
|
|
48
48
|
}
|
|
49
49
|
}
|
package/src/commands/init.js
CHANGED
|
@@ -217,7 +217,7 @@ export default defineConfig({
|
|
|
217
217
|
}
|
|
218
218
|
|
|
219
219
|
function generateEnvExample(template) {
|
|
220
|
-
let env = `# Hiyve API Key (from
|
|
220
|
+
let env = `# Hiyve API Key (from cloud.hiyve.io)
|
|
221
221
|
HIYVE_API_KEY=pk_live_your_api_key_here
|
|
222
222
|
|
|
223
223
|
# Room configuration
|
|
@@ -227,7 +227,7 @@ HIYVE_SIGNALING_URL=https://signal.hiyve.dev
|
|
|
227
227
|
if (template.features.intelligence) {
|
|
228
228
|
env += `
|
|
229
229
|
# Cloud API (for AI features)
|
|
230
|
-
HIYVE_CLOUD_URL=https://
|
|
230
|
+
HIYVE_CLOUD_URL=https://cloud.hiyve.io
|
|
231
231
|
`;
|
|
232
232
|
}
|
|
233
233
|
|
|
@@ -383,7 +383,7 @@ app.use(express.json());
|
|
|
383
383
|
|
|
384
384
|
const API_KEY = process.env.HIYVE_API_KEY;
|
|
385
385
|
const SIGNALING_URL = process.env.HIYVE_SIGNALING_URL || 'https://signal.hiyve.dev';
|
|
386
|
-
${hasCloud ? `const CLOUD_URL = process.env.HIYVE_CLOUD_URL || 'https://
|
|
386
|
+
${hasCloud ? `const CLOUD_URL = process.env.HIYVE_CLOUD_URL || 'https://cloud.hiyve.io';\n` : ''}
|
|
387
387
|
/**
|
|
388
388
|
* Generate a room token for the client.
|
|
389
389
|
* In production, add your own authentication before issuing tokens.
|
package/src/commands/list.js
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import ora from 'ora';
|
|
9
|
-
import { getApiUrl
|
|
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,9 +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)));
|
|
19
|
-
|
|
20
|
-
console.log(chalk.yellow(' Mode: development (--dev)'));
|
|
21
|
-
}
|
|
20
|
+
printDevModeBanner();
|
|
22
21
|
console.log('');
|
|
23
22
|
|
|
24
23
|
// Get current config for auth token
|
|
@@ -33,7 +32,7 @@ export async function list() {
|
|
|
33
32
|
const spinner = ora('Fetching packages...').start();
|
|
34
33
|
|
|
35
34
|
try {
|
|
36
|
-
const response = await
|
|
35
|
+
const response = await fetchWithTimeout(`${getApiUrl()}packages`, {
|
|
37
36
|
headers: {
|
|
38
37
|
Authorization: `Bearer ${config.apiKey}`,
|
|
39
38
|
},
|
|
@@ -44,14 +43,7 @@ export async function list() {
|
|
|
44
43
|
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
45
44
|
console.log('');
|
|
46
45
|
console.log(chalk.red(` ${error.error || 'Failed to fetch packages'}`));
|
|
47
|
-
|
|
48
|
-
console.log(chalk.gray(' You are using --dev mode (development registry).'));
|
|
49
|
-
console.log(chalk.gray(' If you meant to use production, run without --dev:'));
|
|
50
|
-
console.log(chalk.cyan(' npx @hiyve/cli list'));
|
|
51
|
-
} else {
|
|
52
|
-
console.log(chalk.gray(' If you meant to use the development registry, use --dev:'));
|
|
53
|
-
console.log(chalk.cyan(' npx @hiyve/cli --dev list'));
|
|
54
|
-
}
|
|
46
|
+
printDevHint('list');
|
|
55
47
|
process.exit(1);
|
|
56
48
|
}
|
|
57
49
|
|
|
@@ -82,18 +74,17 @@ export async function list() {
|
|
|
82
74
|
console.log(chalk.gray(' npm install <package-name>'));
|
|
83
75
|
console.log('');
|
|
84
76
|
} catch (err) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (isDevMode()) {
|
|
90
|
-
console.log(chalk.gray(' You are using --dev mode (development registry).'));
|
|
91
|
-
console.log(chalk.gray(' If you meant to use production, run without --dev:'));
|
|
92
|
-
console.log(chalk.cyan(' npx @hiyve/cli list'));
|
|
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.'));
|
|
93
81
|
} else {
|
|
94
|
-
|
|
95
|
-
console.log(
|
|
82
|
+
spinner.fail('Connection failed');
|
|
83
|
+
console.log('');
|
|
84
|
+
console.log(chalk.red(` ${err.message}`));
|
|
96
85
|
}
|
|
86
|
+
console.log(chalk.gray(' Please check your internet connection.'));
|
|
87
|
+
printDevHint('list');
|
|
97
88
|
process.exit(1);
|
|
98
89
|
}
|
|
99
90
|
}
|
package/src/commands/login.js
CHANGED
|
@@ -8,7 +8,8 @@ import prompts from 'prompts';
|
|
|
8
8
|
import chalk from 'chalk';
|
|
9
9
|
import ora from 'ora';
|
|
10
10
|
import { configureNpmrc } from '../utils/npmrc.js';
|
|
11
|
-
import { getApiUrl, getRegistryUrl
|
|
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,9 +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)));
|
|
24
|
-
|
|
25
|
-
console.log(chalk.yellow(' Mode: development (--dev)'));
|
|
26
|
-
}
|
|
25
|
+
printDevModeBanner();
|
|
27
26
|
console.log('');
|
|
28
27
|
|
|
29
28
|
// Prompt for API key if not provided
|
|
@@ -78,7 +77,7 @@ export async function login(options) {
|
|
|
78
77
|
const spinner = ora('Verifying API key...').start();
|
|
79
78
|
|
|
80
79
|
try {
|
|
81
|
-
const response = await
|
|
80
|
+
const response = await fetchWithTimeout(`${getApiUrl()}verify`, {
|
|
82
81
|
headers: {
|
|
83
82
|
Authorization: `Bearer ${apiKey}`,
|
|
84
83
|
},
|
|
@@ -89,36 +88,32 @@ export async function login(options) {
|
|
|
89
88
|
spinner.fail('API key verification failed');
|
|
90
89
|
console.log('');
|
|
91
90
|
console.log(chalk.red(` ${error.error || 'Invalid API key'}`));
|
|
92
|
-
|
|
93
|
-
console.log(chalk.gray(' You are using --dev mode (development registry).'));
|
|
94
|
-
console.log(chalk.gray(' If you meant to use production, run without --dev:'));
|
|
95
|
-
console.log(chalk.cyan(' npx @hiyve/cli login'));
|
|
96
|
-
} else {
|
|
97
|
-
console.log(chalk.gray(' If you are using a test key (pk_test_*), use --dev mode:'));
|
|
98
|
-
console.log(chalk.cyan(' npx @hiyve/cli --dev login'));
|
|
99
|
-
}
|
|
91
|
+
printDevHint('login');
|
|
100
92
|
process.exit(1);
|
|
101
93
|
}
|
|
102
94
|
|
|
103
95
|
const data = await response.json();
|
|
104
96
|
spinner.succeed('API key verified');
|
|
105
97
|
|
|
98
|
+
// Server returns a masked key — always mask client-side as a safety net
|
|
106
99
|
if (data.apiKey) {
|
|
107
|
-
|
|
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}`));
|
|
108
104
|
}
|
|
109
105
|
} catch (err) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (isDevMode()) {
|
|
115
|
-
console.log(chalk.gray(' You are using --dev mode (development registry).'));
|
|
116
|
-
console.log(chalk.gray(' If you meant to use production, run without --dev:'));
|
|
117
|
-
console.log(chalk.cyan(' npx @hiyve/cli login'));
|
|
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.'));
|
|
118
110
|
} else {
|
|
119
|
-
|
|
120
|
-
console.log(
|
|
111
|
+
spinner.fail('Connection failed');
|
|
112
|
+
console.log('');
|
|
113
|
+
console.log(chalk.red(` ${err.message}`));
|
|
121
114
|
}
|
|
115
|
+
console.log(chalk.gray(' Please check your internet connection.'));
|
|
116
|
+
printDevHint('login');
|
|
122
117
|
process.exit(1);
|
|
123
118
|
}
|
|
124
119
|
|
|
@@ -142,7 +137,7 @@ export async function login(options) {
|
|
|
142
137
|
|
|
143
138
|
// Fetch available packages from registry
|
|
144
139
|
try {
|
|
145
|
-
const packagesResponse = await
|
|
140
|
+
const packagesResponse = await fetchWithTimeout(`${getApiUrl()}packages`, {
|
|
146
141
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
147
142
|
});
|
|
148
143
|
|
package/src/config.js
CHANGED
|
@@ -6,13 +6,36 @@
|
|
|
6
6
|
* - Registry URL: what goes into ~/.npmrc for npm to fetch tarballs. Always prod.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
// The CLI's own API — key verification and the package catalogue, served by
|
|
10
|
+
// hiyve-cloud at /registry/{verify,packages}.
|
|
11
|
+
const PROD_API_URL = 'https://cloud.hiyve.io/registry/';
|
|
12
|
+
// No separate development cloud exists — kept so --dev stays a valid flag,
|
|
13
|
+
// but it reaches the SAME live host (2026-08-24).
|
|
14
|
+
const DEV_API_URL = 'https://cloud.hiyve.io/registry/';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Where npm actually fetches tarballs. DELIBERATELY NOT the API URL.
|
|
18
|
+
*
|
|
19
|
+
* This used to return getApiUrl(), on the assumption that "tarballs are
|
|
20
|
+
* served through the same API". They are not: the cloud's /registry proxy
|
|
21
|
+
* serves a stale catalogue whose `dist.tarball` URLs still name
|
|
22
|
+
* api.muziemedia.com — a host with no DNS record — so a packument fetch
|
|
23
|
+
* succeeded and the download that followed failed with ENOTFOUND. npm does
|
|
24
|
+
* not rewrite tarball hosts for a non-npmjs registry, so the URL in the
|
|
25
|
+
* metadata is the URL it uses.
|
|
26
|
+
*
|
|
27
|
+
* registry.muziemedia.com is the Verdaccio registry `changeset publish`
|
|
28
|
+
* actually publishes to. Verified 2026-08-24 with a customer pk_ key: it
|
|
29
|
+
* authenticates API keys natively (the proxy is not needed for auth),
|
|
30
|
+
* serves current versions, its tarball URLs point at itself, and the
|
|
31
|
+
* download returns a valid package.
|
|
32
|
+
*/
|
|
33
|
+
const REGISTRY_URL_VALUE = 'https://registry.muziemedia.com/';
|
|
11
34
|
|
|
12
35
|
let _devMode = false;
|
|
13
36
|
|
|
14
37
|
/**
|
|
15
|
-
* Enable dev mode (
|
|
38
|
+
* Enable dev mode (there is no separate dev cloud — same host as prod)
|
|
16
39
|
*/
|
|
17
40
|
export function setDevMode(enabled) {
|
|
18
41
|
_devMode = enabled;
|
|
@@ -28,10 +51,10 @@ export function getApiUrl() {
|
|
|
28
51
|
|
|
29
52
|
/**
|
|
30
53
|
* Get the registry URL for ~/.npmrc (where npm fetches tarballs).
|
|
31
|
-
*
|
|
54
|
+
* Independent of the API URL, and of --dev: there is one registry.
|
|
32
55
|
*/
|
|
33
56
|
export function getRegistryUrl() {
|
|
34
|
-
return
|
|
57
|
+
return REGISTRY_URL_VALUE;
|
|
35
58
|
}
|
|
36
59
|
|
|
37
60
|
/**
|
|
@@ -42,7 +65,8 @@ export function isDevMode() {
|
|
|
42
65
|
}
|
|
43
66
|
|
|
44
67
|
// Backwards-compatible named export
|
|
45
|
-
|
|
68
|
+
// The npm registry, NOT the CLI API (they are different hosts — see above).
|
|
69
|
+
export const REGISTRY_URL = REGISTRY_URL_VALUE;
|
|
46
70
|
|
|
47
71
|
export default {
|
|
48
72
|
REGISTRY_URL,
|
package/src/config.test.js
CHANGED
|
@@ -3,30 +3,38 @@ import config, { REGISTRY_URL, getApiUrl, getRegistryUrl, setDevMode } from './c
|
|
|
3
3
|
|
|
4
4
|
describe('config', () => {
|
|
5
5
|
it('exports REGISTRY_URL as a named export', () => {
|
|
6
|
-
expect(REGISTRY_URL).toBe('https://
|
|
6
|
+
expect(REGISTRY_URL).toBe('https://registry.muziemedia.com/');
|
|
7
7
|
});
|
|
8
8
|
|
|
9
9
|
it('default export contains REGISTRY_URL', () => {
|
|
10
10
|
expect(config).toHaveProperty('REGISTRY_URL');
|
|
11
|
-
expect(config.REGISTRY_URL).toBe('https://
|
|
11
|
+
expect(config.REGISTRY_URL).toBe('https://registry.muziemedia.com/');
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
it('getApiUrl returns prod URL by default', () => {
|
|
15
15
|
setDevMode(false);
|
|
16
|
-
expect(getApiUrl()).toBe('https://
|
|
16
|
+
expect(getApiUrl()).toBe('https://cloud.hiyve.io/registry/');
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
it('getApiUrl returns dev URL when dev mode is enabled', () => {
|
|
20
20
|
setDevMode(true);
|
|
21
|
-
expect(getApiUrl()).toBe('https://
|
|
21
|
+
expect(getApiUrl()).toBe('https://cloud.hiyve.io/registry/');
|
|
22
22
|
setDevMode(false);
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
it('getRegistryUrl
|
|
25
|
+
it('getRegistryUrl is the npm registry, NOT the API host', () => {
|
|
26
|
+
// These are different services: the API serves /verify and /packages
|
|
27
|
+
// from hiyve-cloud; npm fetches tarballs from Verdaccio. Conflating them
|
|
28
|
+
// shipped a registry whose tarball URLs pointed at a dead host.
|
|
29
|
+
expect(getRegistryUrl()).toBe('https://registry.muziemedia.com/');
|
|
30
|
+
expect(getRegistryUrl()).not.toBe(getApiUrl());
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('getRegistryUrl does NOT switch with dev mode — there is one registry', () => {
|
|
26
34
|
setDevMode(false);
|
|
27
|
-
|
|
35
|
+
const prod = getRegistryUrl();
|
|
28
36
|
setDevMode(true);
|
|
29
|
-
expect(getRegistryUrl()).toBe(
|
|
37
|
+
expect(getRegistryUrl()).toBe(prod);
|
|
30
38
|
setDevMode(false);
|
|
31
39
|
});
|
|
32
40
|
});
|
package/src/utils/npmrc.js
CHANGED
|
@@ -10,9 +10,40 @@ 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 = [
|
|
15
|
+
'//registry.muziemedia.com/',
|
|
16
|
+
'//cloud.hiyve.io/',
|
|
17
|
+
// Historical hosts stay listed FOREVER: this array's job is to strip
|
|
18
|
+
// stale entries from a developer's .npmrc, so dropping a retired host
|
|
19
|
+
// leaves its dead token line behind.
|
|
20
|
+
'//console.hiyve.dev/',
|
|
21
|
+
'//api.hiyve.dev/',
|
|
22
|
+
'//api.muziemedia.com/',
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Check if a line in .npmrc is a Hiyve-related config line.
|
|
27
|
+
* Only matches lines that reference a known Hiyve host or @hiyve scope.
|
|
28
|
+
* Will NOT match third-party tokens that happen to start with sk_/pk_ (e.g. Stripe).
|
|
29
|
+
*/
|
|
30
|
+
function isHiyveConfigLine(line) {
|
|
31
|
+
const trimmed = line.trim();
|
|
32
|
+
if (trimmed.startsWith('@hiyve:registry')) return true;
|
|
33
|
+
if (HIYVE_HOSTS.some((host) => trimmed.includes(host))) return true;
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Filter out all Hiyve-related lines from .npmrc content.
|
|
39
|
+
*/
|
|
40
|
+
function filterOutHiyveLines(content) {
|
|
41
|
+
return content.split('\n').filter((line) => !isHiyveConfigLine(line));
|
|
42
|
+
}
|
|
43
|
+
|
|
13
44
|
/**
|
|
14
45
|
* Configure ~/.npmrc for Hiyve registry
|
|
15
|
-
* @param {string} registryUrl - The registry URL (e.g., https://
|
|
46
|
+
* @param {string} registryUrl - The registry URL (e.g., https://registry.muziemedia.com/)
|
|
16
47
|
* @param {string} apiKey - The API key for authentication
|
|
17
48
|
*/
|
|
18
49
|
export async function configureNpmrc(registryUrl, apiKey) {
|
|
@@ -23,19 +54,8 @@ export async function configureNpmrc(registryUrl, apiKey) {
|
|
|
23
54
|
content = fs.readFileSync(NPMRC_PATH, 'utf8');
|
|
24
55
|
}
|
|
25
56
|
|
|
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
|
-
});
|
|
57
|
+
// Remove any existing Hiyve config lines (scoped to known Hiyve hosts)
|
|
58
|
+
const lines = filterOutHiyveLines(content);
|
|
39
59
|
|
|
40
60
|
// Parse the registry URL to derive the auth token path
|
|
41
61
|
const url = new URL(registryUrl);
|
|
@@ -47,7 +67,7 @@ export async function configureNpmrc(registryUrl, apiKey) {
|
|
|
47
67
|
|
|
48
68
|
// Write back, removing empty lines at start/end
|
|
49
69
|
const finalContent = lines.filter((line) => line.trim()).join('\n') + '\n';
|
|
50
|
-
fs.writeFileSync(NPMRC_PATH, finalContent, 'utf8');
|
|
70
|
+
fs.writeFileSync(NPMRC_PATH, finalContent, { encoding: 'utf8', mode: 0o600 });
|
|
51
71
|
}
|
|
52
72
|
|
|
53
73
|
/**
|
|
@@ -60,23 +80,12 @@ export async function removeNpmrc() {
|
|
|
60
80
|
|
|
61
81
|
const content = fs.readFileSync(NPMRC_PATH, 'utf8');
|
|
62
82
|
|
|
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
|
-
});
|
|
83
|
+
// Remove Hiyve config lines (scoped to known Hiyve hosts)
|
|
84
|
+
const lines = filterOutHiyveLines(content);
|
|
76
85
|
|
|
77
86
|
// Write back
|
|
78
87
|
const finalContent = lines.filter((line) => line.trim()).join('\n');
|
|
79
|
-
fs.writeFileSync(NPMRC_PATH, finalContent ? finalContent + '\n' : '', 'utf8');
|
|
88
|
+
fs.writeFileSync(NPMRC_PATH, finalContent ? finalContent + '\n' : '', { encoding: 'utf8', mode: 0o600 });
|
|
80
89
|
}
|
|
81
90
|
|
|
82
91
|
/**
|
|
@@ -94,10 +103,11 @@ export function getCurrentConfig() {
|
|
|
94
103
|
// Find registry line
|
|
95
104
|
const registryLine = lines.find((line) => line.trim().startsWith('@hiyve:registry'));
|
|
96
105
|
|
|
97
|
-
// Find token line
|
|
106
|
+
// Find token line on a known Hiyve host
|
|
98
107
|
const tokenLine = lines.find((line) => {
|
|
99
108
|
const trimmed = line.trim();
|
|
100
|
-
return
|
|
109
|
+
return HIYVE_HOSTS.some((host) => trimmed.includes(host)) &&
|
|
110
|
+
(trimmed.includes(':_authToken=pk_') || trimmed.includes(':_authToken=sk_') || trimmed.includes(':_authToken=mk_'));
|
|
101
111
|
});
|
|
102
112
|
|
|
103
113
|
if (!registryLine || !tokenLine) {
|
|
@@ -114,7 +124,7 @@ export function getCurrentConfig() {
|
|
|
114
124
|
return {
|
|
115
125
|
apiKey,
|
|
116
126
|
maskedApiKey,
|
|
117
|
-
registryUrl: registryLine.
|
|
127
|
+
registryUrl: registryLine.substring(registryLine.indexOf('=') + 1).trim(),
|
|
118
128
|
};
|
|
119
129
|
}
|
|
120
130
|
|
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
|
+
// No longer suggests the opposite mode: both use the same host since
|
|
18
|
+
// 2026-08-24, so "try without --dev" sent people hunting a difference
|
|
19
|
+
// that cannot exist.
|
|
20
|
+
console.log(chalk.gray(' --dev no longer selects a different registry — same host as the'));
|
|
21
|
+
console.log(chalk.gray(' default, so re-running without it will not change this result.'));
|
|
22
|
+
console.log(chalk.cyan(` npx @hiyve/cli ${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
|
+
}
|