@knolo/cli 5.2.0 → 5.2.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/README.md +75 -29
- package/bin/knolo.mjs +9 -6
- package/bin/registry/artifacts.mjs +21 -5
- package/bin/registry/commands.mjs +850 -87
- package/bin/registry/errors.mjs +3 -1
- package/bin/registry/http.mjs +72 -18
- package/bin/registry/lockfile.mjs +16 -5
- package/bin/registry/manifest.mjs +6 -0
- package/package.json +3 -2
package/bin/registry/errors.mjs
CHANGED
|
@@ -18,8 +18,10 @@ export class RegistryNetworkError extends Error {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export function registryErrorMessage({ status, code, body, url }) {
|
|
21
|
+
if (status === 401 || code === 'unauthenticated') {
|
|
22
|
+
return 'Hub authentication failed. Use Authorization: Bearer kno_…; a revoked token can also return 401. GitHub sign-in is only required to mint tokens.';
|
|
23
|
+
}
|
|
21
24
|
if (typeof body?.error === 'string' && body.error.trim()) return body.error.trim();
|
|
22
|
-
if (status === 401 || code === 'unauthenticated') return 'Registry authentication is required.';
|
|
23
25
|
if (status === 404 || code === 'not_found') return 'Registry resource not found.';
|
|
24
26
|
if (status === 410 || code === 'yanked') return 'Registry version is yanked.';
|
|
25
27
|
if (status === 413 || code === 'too_large') return 'Registry artifact is too large.';
|
package/bin/registry/http.mjs
CHANGED
|
@@ -1,17 +1,57 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
RegistryError,
|
|
3
|
+
RegistryNetworkError,
|
|
4
|
+
registryErrorMessage,
|
|
5
|
+
} from './errors.mjs';
|
|
2
6
|
|
|
3
|
-
export async function getJson(
|
|
4
|
-
|
|
7
|
+
export async function getJson(
|
|
8
|
+
url,
|
|
9
|
+
{ fetchImpl = globalThis.fetch, headers = {} } = {}
|
|
10
|
+
) {
|
|
11
|
+
return requestJson(url, { fetchImpl, headers, method: 'GET' });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function postJson(
|
|
15
|
+
url,
|
|
16
|
+
body,
|
|
17
|
+
{ fetchImpl = globalThis.fetch, headers = {} } = {}
|
|
18
|
+
) {
|
|
19
|
+
return requestJson(url, { fetchImpl, headers, method: 'POST', body });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function requestJson(
|
|
23
|
+
url,
|
|
24
|
+
{
|
|
25
|
+
fetchImpl = globalThis.fetch,
|
|
26
|
+
headers = {},
|
|
27
|
+
method = 'GET',
|
|
28
|
+
body: requestBody,
|
|
29
|
+
} = {}
|
|
30
|
+
) {
|
|
31
|
+
if (typeof fetchImpl !== 'function')
|
|
32
|
+
throw new Error('This Node runtime does not provide fetch.');
|
|
5
33
|
|
|
6
34
|
let response;
|
|
7
35
|
try {
|
|
8
36
|
response = await fetchImpl(url, {
|
|
9
|
-
method
|
|
37
|
+
method,
|
|
10
38
|
redirect: 'follow',
|
|
11
|
-
headers: {
|
|
39
|
+
headers: {
|
|
40
|
+
accept: 'application/json',
|
|
41
|
+
...(requestBody !== undefined
|
|
42
|
+
? { 'content-type': 'application/json' }
|
|
43
|
+
: {}),
|
|
44
|
+
...headers,
|
|
45
|
+
},
|
|
46
|
+
...(requestBody !== undefined
|
|
47
|
+
? { body: JSON.stringify(requestBody) }
|
|
48
|
+
: {}),
|
|
12
49
|
});
|
|
13
50
|
} catch (error) {
|
|
14
|
-
throw new RegistryNetworkError(
|
|
51
|
+
throw new RegistryNetworkError(
|
|
52
|
+
`Could not reach registry at ${new URL(url).origin}.`,
|
|
53
|
+
{ url: String(url), cause: error }
|
|
54
|
+
);
|
|
15
55
|
}
|
|
16
56
|
|
|
17
57
|
const text = await readResponseText(response, url);
|
|
@@ -21,11 +61,14 @@ export async function getJson(url, { fetchImpl = globalThis.fetch, headers = {}
|
|
|
21
61
|
body = JSON.parse(text);
|
|
22
62
|
} catch {
|
|
23
63
|
if (!response.ok) {
|
|
24
|
-
throw new RegistryError(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
64
|
+
throw new RegistryError(
|
|
65
|
+
`Registry returned HTTP ${response.status} with a non-JSON response.`,
|
|
66
|
+
{
|
|
67
|
+
status: response.status,
|
|
68
|
+
url: String(url),
|
|
69
|
+
body: text.slice(0, 240),
|
|
70
|
+
}
|
|
71
|
+
);
|
|
29
72
|
}
|
|
30
73
|
throw new RegistryError('Registry returned invalid JSON.', {
|
|
31
74
|
status: response.status,
|
|
@@ -38,12 +81,20 @@ export async function getJson(url, { fetchImpl = globalThis.fetch, headers = {}
|
|
|
38
81
|
|
|
39
82
|
if (!response.ok) {
|
|
40
83
|
const code = typeof body?.code === 'string' ? body.code : undefined;
|
|
41
|
-
throw new RegistryError(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
84
|
+
throw new RegistryError(
|
|
85
|
+
registryErrorMessage({
|
|
86
|
+
status: response.status,
|
|
87
|
+
code,
|
|
88
|
+
body,
|
|
89
|
+
url: String(url),
|
|
90
|
+
}),
|
|
91
|
+
{
|
|
92
|
+
status: response.status,
|
|
93
|
+
code,
|
|
94
|
+
url: String(url),
|
|
95
|
+
body,
|
|
96
|
+
}
|
|
97
|
+
);
|
|
47
98
|
}
|
|
48
99
|
|
|
49
100
|
return body;
|
|
@@ -53,6 +104,9 @@ async function readResponseText(response, url) {
|
|
|
53
104
|
try {
|
|
54
105
|
return await response.text();
|
|
55
106
|
} catch (error) {
|
|
56
|
-
throw new RegistryNetworkError(
|
|
107
|
+
throw new RegistryNetworkError(
|
|
108
|
+
`Could not read registry response from ${new URL(url).origin}.`,
|
|
109
|
+
{ url: String(url), cause: error }
|
|
110
|
+
);
|
|
57
111
|
}
|
|
58
112
|
}
|
|
@@ -37,13 +37,23 @@ export async function validateLockfileUpdate({ cwd = process.cwd(), registry, ma
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
export async function upsertLockfile({ cwd = process.cwd(), registry, manifest, force = false } = {}) {
|
|
40
|
+
const staged = await stageLockfileUpdate({ cwd, registry, manifest, force });
|
|
41
|
+
try {
|
|
42
|
+
await rename(staged.tempPath, staged.path);
|
|
43
|
+
} finally {
|
|
44
|
+
await unlink(staged.tempPath).catch(() => {});
|
|
45
|
+
}
|
|
46
|
+
return { path: staged.path, entry: staged.entry };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function stageLockfileUpdate({ cwd = process.cwd(), registry, manifest, force = false } = {}) {
|
|
40
50
|
const { targetPath, entry, packs } = await validateLockfileUpdate({ cwd, registry, manifest, force });
|
|
41
51
|
const next = {
|
|
42
52
|
registry,
|
|
43
53
|
packs: { ...packs, [manifest.name]: entry },
|
|
44
54
|
};
|
|
45
|
-
await
|
|
46
|
-
return { path: targetPath, entry };
|
|
55
|
+
const tempPath = await writeJsonTemp(targetPath, next);
|
|
56
|
+
return { path: targetPath, tempPath, entry };
|
|
47
57
|
}
|
|
48
58
|
|
|
49
59
|
async function readLockfile(filePath) {
|
|
@@ -66,13 +76,14 @@ async function readLockfile(filePath) {
|
|
|
66
76
|
return value;
|
|
67
77
|
}
|
|
68
78
|
|
|
69
|
-
async function
|
|
79
|
+
async function writeJsonTemp(filePath, value) {
|
|
70
80
|
await mkdir(path.dirname(filePath), { recursive: true });
|
|
71
81
|
const tempPath = `${filePath}.tmp-${process.pid}-${randomUUID()}`;
|
|
72
82
|
try {
|
|
73
83
|
await writeFile(tempPath, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o644 });
|
|
74
|
-
|
|
75
|
-
}
|
|
84
|
+
return tempPath;
|
|
85
|
+
} catch (error) {
|
|
76
86
|
await unlink(tempPath).catch(() => {});
|
|
87
|
+
throw error;
|
|
77
88
|
}
|
|
78
89
|
}
|
|
@@ -20,6 +20,12 @@ export function validatePackManifest(value, { expectedName, requestedVersion } =
|
|
|
20
20
|
if (value.stateRoot !== undefined && typeof value.stateRoot !== 'string') {
|
|
21
21
|
throw new Error('Registry manifest stateRoot must be a string when present.');
|
|
22
22
|
}
|
|
23
|
+
if (value.format !== undefined && !['V4', 'V5'].includes(value.format)) {
|
|
24
|
+
throw new Error('Registry manifest format must be V4 or V5 when present.');
|
|
25
|
+
}
|
|
26
|
+
if (value.format === 'V5' && (!value.stateRoot || typeof value.stateRoot !== 'string')) {
|
|
27
|
+
throw new Error('Registry manifest stateRoot is required for V5 manifests.');
|
|
28
|
+
}
|
|
23
29
|
if (value.license !== undefined && typeof value.license !== 'string') {
|
|
24
30
|
throw new Error('Registry manifest license must be a string when present.');
|
|
25
31
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knolo/cli",
|
|
3
|
-
"version": "5.2.
|
|
3
|
+
"version": "5.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"knolo": "bin/knolo.mjs"
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
"test:hub": "node --test test/hub.integration.test.mjs"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@knolo/core": "^5.1.0"
|
|
35
|
+
"@knolo/core": "^5.1.0",
|
|
36
|
+
"@vercel/blob": "2.8.0"
|
|
36
37
|
},
|
|
37
38
|
"private": false
|
|
38
39
|
}
|