cognitive-modules-cli 2.2.7 → 2.2.9
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/CHANGELOG.md +12 -0
- package/README.md +25 -3
- package/bin.js +30 -0
- package/dist/audit.d.ts +13 -0
- package/dist/audit.js +25 -0
- package/dist/cli.js +190 -2
- package/dist/commands/add.js +68 -1
- package/dist/commands/compose.d.ts +2 -0
- package/dist/commands/compose.js +60 -1
- package/dist/commands/core.d.ts +31 -0
- package/dist/commands/core.js +338 -0
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.js +1 -0
- package/dist/commands/pipe.js +45 -2
- package/dist/commands/run.js +99 -17
- package/dist/commands/search.js +13 -3
- package/dist/commands/update.js +4 -1
- package/dist/modules/composition.d.ts +15 -2
- package/dist/modules/composition.js +16 -6
- package/dist/modules/loader.d.ts +10 -0
- package/dist/modules/loader.js +168 -0
- package/dist/modules/runner.d.ts +3 -1
- package/dist/modules/runner.js +121 -2
- package/dist/profile.d.ts +8 -0
- package/dist/profile.js +59 -0
- package/dist/provenance.d.ts +50 -0
- package/dist/provenance.js +137 -0
- package/dist/registry/assets.d.ts +48 -0
- package/dist/registry/assets.js +723 -0
- package/dist/registry/client.d.ts +8 -1
- package/dist/registry/client.js +83 -29
- package/dist/types.d.ts +31 -0
- package/package.json +4 -3
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import { createReadStream } from 'node:fs';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
export const PROVENANCE_FILENAME = 'provenance.json';
|
|
6
|
+
export const PROVENANCE_SPEC = 'cognitive.module.provenance/v1';
|
|
7
|
+
const DEFAULT_INTEGRITY_LIMITS = {
|
|
8
|
+
maxFiles: 5_000,
|
|
9
|
+
maxTotalBytes: 50 * 1024 * 1024, // 50MB
|
|
10
|
+
maxSingleFileBytes: 20 * 1024 * 1024, // 20MB
|
|
11
|
+
};
|
|
12
|
+
async function hashFileSha256(filePath, size) {
|
|
13
|
+
const h = createHash('sha256');
|
|
14
|
+
await new Promise((resolve, reject) => {
|
|
15
|
+
const rs = createReadStream(filePath);
|
|
16
|
+
rs.on('data', (chunk) => h.update(chunk));
|
|
17
|
+
rs.on('error', reject);
|
|
18
|
+
rs.on('end', resolve);
|
|
19
|
+
});
|
|
20
|
+
// Include size in the hash domain separation to make accidental truncation obvious.
|
|
21
|
+
h.update(`\nsize:${size}\n`);
|
|
22
|
+
return h.digest('hex');
|
|
23
|
+
}
|
|
24
|
+
async function walkFiles(rootDir, relDir) {
|
|
25
|
+
const absDir = path.join(rootDir, relDir);
|
|
26
|
+
const entries = await fs.readdir(absDir, { withFileTypes: true });
|
|
27
|
+
const out = [];
|
|
28
|
+
for (const ent of entries) {
|
|
29
|
+
const rel = relDir ? path.posix.join(relDir.replace(/\\/g, '/'), ent.name) : ent.name;
|
|
30
|
+
const abs = path.join(rootDir, rel);
|
|
31
|
+
// Never hash our own provenance.
|
|
32
|
+
if (rel === PROVENANCE_FILENAME)
|
|
33
|
+
continue;
|
|
34
|
+
if (ent.name === '.DS_Store' || ent.name === '__MACOSX')
|
|
35
|
+
continue;
|
|
36
|
+
if (ent.isSymbolicLink()) {
|
|
37
|
+
// Refuse to hash symlinks. Tar extraction also rejects them.
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (ent.isDirectory()) {
|
|
41
|
+
out.push(...await walkFiles(rootDir, rel));
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (ent.isFile()) {
|
|
45
|
+
out.push(rel);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
export async function computeModuleIntegrity(moduleDir, options = {}) {
|
|
51
|
+
const limits = {
|
|
52
|
+
...DEFAULT_INTEGRITY_LIMITS,
|
|
53
|
+
...options,
|
|
54
|
+
};
|
|
55
|
+
const relFiles = (await walkFiles(moduleDir, '')).sort((a, b) => a.localeCompare(b));
|
|
56
|
+
if (relFiles.length > limits.maxFiles) {
|
|
57
|
+
throw new Error(`Module has too many files to hash (max ${limits.maxFiles}): ${relFiles.length}`);
|
|
58
|
+
}
|
|
59
|
+
const files = {};
|
|
60
|
+
let totalBytes = 0;
|
|
61
|
+
for (const rel of relFiles) {
|
|
62
|
+
const abs = path.join(moduleDir, rel);
|
|
63
|
+
const st = await fs.stat(abs);
|
|
64
|
+
if (!st.isFile())
|
|
65
|
+
continue;
|
|
66
|
+
if (st.size > limits.maxSingleFileBytes) {
|
|
67
|
+
throw new Error(`Module file too large for integrity hashing (max ${limits.maxSingleFileBytes} bytes): ${rel}`);
|
|
68
|
+
}
|
|
69
|
+
totalBytes += st.size;
|
|
70
|
+
if (totalBytes > limits.maxTotalBytes) {
|
|
71
|
+
throw new Error(`Module too large for integrity hashing (max ${limits.maxTotalBytes} bytes)`);
|
|
72
|
+
}
|
|
73
|
+
const sha256 = await hashFileSha256(abs, st.size);
|
|
74
|
+
files[rel] = sha256;
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
algorithm: 'sha256',
|
|
78
|
+
maxFiles: limits.maxFiles,
|
|
79
|
+
maxTotalBytes: limits.maxTotalBytes,
|
|
80
|
+
maxSingleFileBytes: limits.maxSingleFileBytes,
|
|
81
|
+
totalBytes,
|
|
82
|
+
files,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export async function writeModuleProvenance(moduleDir, prov) {
|
|
86
|
+
const filePath = path.join(moduleDir, PROVENANCE_FILENAME);
|
|
87
|
+
const json = JSON.stringify(prov, null, 2) + '\n';
|
|
88
|
+
await fs.writeFile(filePath, json, 'utf-8');
|
|
89
|
+
}
|
|
90
|
+
export async function readModuleProvenance(moduleDir) {
|
|
91
|
+
const filePath = path.join(moduleDir, PROVENANCE_FILENAME);
|
|
92
|
+
try {
|
|
93
|
+
const raw = await fs.readFile(filePath, 'utf-8');
|
|
94
|
+
const parsed = JSON.parse(raw);
|
|
95
|
+
if (!parsed || typeof parsed !== 'object')
|
|
96
|
+
return null;
|
|
97
|
+
if (parsed.spec !== PROVENANCE_SPEC)
|
|
98
|
+
return null;
|
|
99
|
+
if (!parsed.source || typeof parsed.source !== 'object')
|
|
100
|
+
return null;
|
|
101
|
+
if (!parsed.integrity || typeof parsed.integrity !== 'object')
|
|
102
|
+
return null;
|
|
103
|
+
return parsed;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
export async function verifyModuleIntegrity(moduleDir, prov) {
|
|
110
|
+
const expected = prov.integrity?.files ?? {};
|
|
111
|
+
const expectedKeys = Object.keys(expected).sort();
|
|
112
|
+
if (expectedKeys.length === 0) {
|
|
113
|
+
return { ok: false, reason: 'Provenance integrity.files is empty' };
|
|
114
|
+
}
|
|
115
|
+
const computed = await computeModuleIntegrity(moduleDir, {
|
|
116
|
+
maxFiles: prov.integrity.maxFiles,
|
|
117
|
+
maxTotalBytes: prov.integrity.maxTotalBytes,
|
|
118
|
+
maxSingleFileBytes: prov.integrity.maxSingleFileBytes,
|
|
119
|
+
});
|
|
120
|
+
const computedKeys = Object.keys(computed.files).sort();
|
|
121
|
+
if (expectedKeys.length !== computedKeys.length) {
|
|
122
|
+
return { ok: false, reason: 'Integrity file list changed (file count mismatch)' };
|
|
123
|
+
}
|
|
124
|
+
for (let i = 0; i < expectedKeys.length; i++) {
|
|
125
|
+
if (expectedKeys[i] !== computedKeys[i]) {
|
|
126
|
+
return { ok: false, reason: `Integrity file list changed (mismatch at ${i}: ${expectedKeys[i]} vs ${computedKeys[i]})` };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
for (const rel of expectedKeys) {
|
|
130
|
+
const a = expected[rel];
|
|
131
|
+
const b = computed.files[rel];
|
|
132
|
+
if (a !== b) {
|
|
133
|
+
return { ok: false, reason: `Integrity mismatch for ${rel}` };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return { ok: true };
|
|
137
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export interface BuildRegistryOptions {
|
|
2
|
+
tag?: string | null;
|
|
3
|
+
tarballBaseUrl?: string | null;
|
|
4
|
+
modulesDir: string;
|
|
5
|
+
v1RegistryPath: string;
|
|
6
|
+
outDir: string;
|
|
7
|
+
registryOut: string;
|
|
8
|
+
namespace: string;
|
|
9
|
+
runtimeMin: string;
|
|
10
|
+
repository: string;
|
|
11
|
+
homepage: string;
|
|
12
|
+
license: string;
|
|
13
|
+
timestamp?: string | null;
|
|
14
|
+
only?: string[];
|
|
15
|
+
}
|
|
16
|
+
export interface VerifyRegistryOptions {
|
|
17
|
+
registryIndexPath: string;
|
|
18
|
+
assetsDir?: string;
|
|
19
|
+
maxTarballBytes?: number;
|
|
20
|
+
remote?: boolean;
|
|
21
|
+
fetchTimeoutMs?: number;
|
|
22
|
+
maxIndexBytes?: number;
|
|
23
|
+
concurrency?: number;
|
|
24
|
+
}
|
|
25
|
+
export interface RegistryBuildResult {
|
|
26
|
+
registryOut: string;
|
|
27
|
+
outDir: string;
|
|
28
|
+
updated: string;
|
|
29
|
+
modules: Array<{
|
|
30
|
+
name: string;
|
|
31
|
+
version: string;
|
|
32
|
+
file: string;
|
|
33
|
+
sha256: string;
|
|
34
|
+
size_bytes: number;
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
37
|
+
export interface RegistryVerifyResult {
|
|
38
|
+
ok: boolean;
|
|
39
|
+
checked: number;
|
|
40
|
+
passed: number;
|
|
41
|
+
failed: number;
|
|
42
|
+
failures: Array<{
|
|
43
|
+
module: string;
|
|
44
|
+
reason: string;
|
|
45
|
+
}>;
|
|
46
|
+
}
|
|
47
|
+
export declare function buildRegistryAssets(opts: BuildRegistryOptions): Promise<RegistryBuildResult>;
|
|
48
|
+
export declare function verifyRegistryAssets(opts: VerifyRegistryOptions): Promise<RegistryVerifyResult>;
|