@aikdna/kdna-core 0.15.5 → 0.15.7
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 +18 -0
- package/package.json +1 -1
- package/schema/bundle-profile-v1.schema.json +39 -0
- package/schema/manifest.schema.json +10 -4
- package/src/lint-pure.js +1 -0
- package/src/types.d.ts +1 -0
- package/src/v1/index.js +235 -15
- package/src/v1/index.mjs +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.15.7 (2026-06-28)
|
|
4
|
+
|
|
5
|
+
Story 6 — dependencies runtime. Supports semver-matching local/registry dependency resolution and topological sort-based loading order.
|
|
6
|
+
|
|
7
|
+
- **Dependencies manifest schema support**: Explicitly declared `"dependencies"` as a first-class manifest property.
|
|
8
|
+
- **Topological Sorting Resolver**: Added a robust, pure post-order DFS topological sorter with circular cycle, missing dependency, and version range mismatch checks.
|
|
9
|
+
- **Topological Prompt Composition**: Enabled recursive, multi-domain prompt composition and flat topological JSON return inside `loadAuthorized`.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## v0.15.6 (2026-06-28)
|
|
13
|
+
|
|
14
|
+
Story 5 — Bundle payload type. Bumps KDNA manifest format schema to v2, introduces bundle-profile-v1 payload profile, and starts KDNA v1 format deprecation window.
|
|
15
|
+
|
|
16
|
+
- **Bundle asset_type support:** added `"bundle"` to `asset_type` enum and `VALID_ASSET_TYPE` check.
|
|
17
|
+
- **V2 Schema support:** bumped manifest schema version to v2.0, supporting `kdna_version` as `"2.0"`.
|
|
18
|
+
- **Bundle profile support:** added `bundle-profile-v1` payload profile and validator.
|
|
19
|
+
|
|
20
|
+
|
|
3
21
|
## v0.15.5 (2026-06-28)
|
|
4
22
|
|
|
5
23
|
Phase 12 audit follow-up. Closes 3 issues filed against this
|
package/package.json
CHANGED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/aikdna/kdna/schema/bundle-profile-v1.schema.json",
|
|
4
|
+
"title": "Bundle Profile v1",
|
|
5
|
+
"description": "Schema for the payload entry of a KDNA bundle container.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["profile", "components"],
|
|
8
|
+
"additionalProperties": true,
|
|
9
|
+
"properties": {
|
|
10
|
+
"profile": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"const": "bundle-profile-v1",
|
|
13
|
+
"description": "Profile identifier. Must be the literal string 'bundle-profile-v1'."
|
|
14
|
+
},
|
|
15
|
+
"components": {
|
|
16
|
+
"type": "array",
|
|
17
|
+
"description": "List of component assets bundled in this container.",
|
|
18
|
+
"items": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"required": ["id", "path"],
|
|
21
|
+
"additionalProperties": true,
|
|
22
|
+
"properties": {
|
|
23
|
+
"id": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"description": "The unique identifier of the component asset."
|
|
26
|
+
},
|
|
27
|
+
"path": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "Relative path to the component asset (.kdna container) within the bundle payload or bundle package."
|
|
30
|
+
},
|
|
31
|
+
"priority": {
|
|
32
|
+
"type": "integer",
|
|
33
|
+
"description": "Optional priority for resolving card conflicts."
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://github.com/aikdna/kdna/schema/manifest.schema.json",
|
|
4
|
-
"title": "KDNA Manifest
|
|
4
|
+
"title": "KDNA Manifest v2",
|
|
5
5
|
"description": "Schema for kdna.json, the public metadata layer of a .kdna container.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": [
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"kdna_version": {
|
|
24
24
|
"type": "string",
|
|
25
25
|
"description": "KDNA Core version this manifest conforms to.",
|
|
26
|
-
"
|
|
26
|
+
"enum": ["1.0", "2.0"]
|
|
27
27
|
},
|
|
28
28
|
"asset_id": {
|
|
29
29
|
"type": "string",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"asset_type": {
|
|
39
39
|
"type": "string",
|
|
40
|
-
"enum": ["domain", "cluster", "tool", "sample", "fixture"],
|
|
40
|
+
"enum": ["domain", "cluster", "tool", "sample", "fixture", "bundle"],
|
|
41
41
|
"description": "What kind of asset this is. Container format is the same; the value tells callers how to interpret the payload."
|
|
42
42
|
},
|
|
43
43
|
"title": {
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"profile": {
|
|
93
93
|
"type": "string",
|
|
94
94
|
"description": "Payload profile identifier (e.g. judgment-profile-v1).",
|
|
95
|
-
"
|
|
95
|
+
"enum": ["judgment-profile-v1", "bundle-profile-v1"]
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
},
|
|
@@ -195,6 +195,12 @@
|
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
197
|
},
|
|
198
|
+
"dependencies": {
|
|
199
|
+
"type": "object",
|
|
200
|
+
"additionalProperties": {
|
|
201
|
+
"type": "string"
|
|
202
|
+
}
|
|
203
|
+
},
|
|
198
204
|
"encryption": { "type": "object" },
|
|
199
205
|
"load_contract": { "$ref": "load-contract.schema.json" },
|
|
200
206
|
"scope": { "type": "object" },
|
package/src/lint-pure.js
CHANGED
package/src/types.d.ts
CHANGED
|
@@ -504,6 +504,7 @@ export interface KDNAV1Checksums {
|
|
|
504
504
|
}
|
|
505
505
|
|
|
506
506
|
export function isV1SourceDir(inputPath: string): boolean;
|
|
507
|
+
export function isV2SourceDir(inputPath: string): boolean;
|
|
507
508
|
export function detectContainerFormat(inputPath: string): 'v1' | 'v2' | null;
|
|
508
509
|
export function inspect(inputPath: string, options?: Record<string, any>): Record<string, any>;
|
|
509
510
|
export function validate(inputPath: string, options?: Record<string, any>): Record<string, any>;
|
package/src/v1/index.js
CHANGED
|
@@ -125,6 +125,9 @@ function loadSchemas() {
|
|
|
125
125
|
const payloadSchema = JSON.parse(
|
|
126
126
|
fs.readFileSync(path.join(schemaDir, 'payload-profile-v1.schema.json'), 'utf8'),
|
|
127
127
|
);
|
|
128
|
+
const bundlePayloadSchema = JSON.parse(
|
|
129
|
+
fs.readFileSync(path.join(schemaDir, 'bundle-profile-v1.schema.json'), 'utf8'),
|
|
130
|
+
);
|
|
128
131
|
const checksumsSchema = JSON.parse(
|
|
129
132
|
fs.readFileSync(path.join(schemaDir, 'checksums.schema.json'), 'utf8'),
|
|
130
133
|
);
|
|
@@ -138,6 +141,7 @@ function loadSchemas() {
|
|
|
138
141
|
_validators = {
|
|
139
142
|
manifest: ajv.compile(manifestSchema),
|
|
140
143
|
payload: ajv.compile(payloadSchema),
|
|
144
|
+
bundlePayload: ajv.compile(bundlePayloadSchema),
|
|
141
145
|
checksums: ajv.compile(checksumsSchema),
|
|
142
146
|
};
|
|
143
147
|
return _validators;
|
|
@@ -159,6 +163,20 @@ function isV1SourceDir(absPath) {
|
|
|
159
163
|
return mime === MIMETYPE_V1;
|
|
160
164
|
}
|
|
161
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Detect whether a directory is a v2 source layout.
|
|
168
|
+
* Required entries: mimetype, kdna.json, payload.kdnab.
|
|
169
|
+
* mimetype content must equal "application/vnd.aikdna.kdna+zip".
|
|
170
|
+
*/
|
|
171
|
+
function isV2SourceDir(absPath) {
|
|
172
|
+
if (!fs.existsSync(absPath) || !fs.statSync(absPath).isDirectory()) return false;
|
|
173
|
+
for (const f of V1_REQUIRED_DIR_ENTRIES) {
|
|
174
|
+
if (!fs.existsSync(path.join(absPath, f))) return false;
|
|
175
|
+
}
|
|
176
|
+
const mime = fs.readFileSync(path.join(absPath, 'mimetype'), 'utf8');
|
|
177
|
+
return mime === MIMETYPE_V2;
|
|
178
|
+
}
|
|
179
|
+
|
|
162
180
|
/**
|
|
163
181
|
* Detect whether a file is a v1 .kdna container.
|
|
164
182
|
* Returns 'v1' | 'v2' | null. null = not a .kdna file or unreadable.
|
|
@@ -559,9 +577,9 @@ function readV1Layout(absPath) {
|
|
|
559
577
|
|
|
560
578
|
// mimetype content must equal the literal v1 media type.
|
|
561
579
|
const mime = map.mimetype.toString('utf8');
|
|
562
|
-
if (mime !== MIMETYPE_V1) {
|
|
580
|
+
if (mime !== MIMETYPE_V1 && mime !== MIMETYPE_V2) {
|
|
563
581
|
throw new Error(
|
|
564
|
-
`not a KDNA
|
|
582
|
+
`not a KDNA layout: mimetype is "${mime}", expected "${MIMETYPE_V1}" or "${MIMETYPE_V2}"`,
|
|
565
583
|
);
|
|
566
584
|
}
|
|
567
585
|
|
|
@@ -668,9 +686,12 @@ function runValidate(v1) {
|
|
|
668
686
|
problems.push(`format: missing required entry ${f}`);
|
|
669
687
|
}
|
|
670
688
|
}
|
|
671
|
-
if (v1.map.mimetype
|
|
672
|
-
|
|
673
|
-
|
|
689
|
+
if (v1.map.mimetype) {
|
|
690
|
+
const mimeStr = v1.map.mimetype.toString('utf8');
|
|
691
|
+
if (mimeStr !== MIMETYPE_V1 && mimeStr !== MIMETYPE_V2) {
|
|
692
|
+
result.format_valid = false;
|
|
693
|
+
problems.push(`format: mimetype is not ${MIMETYPE_V1} or ${MIMETYPE_V2}`);
|
|
694
|
+
}
|
|
674
695
|
}
|
|
675
696
|
|
|
676
697
|
// schema gate — kdna.json against manifest.schema.json
|
|
@@ -717,11 +738,15 @@ function runValidate(v1) {
|
|
|
717
738
|
problems.push('payload: encrypted envelope missing required fields (profile/ciphertext/key material)');
|
|
718
739
|
}
|
|
719
740
|
// Encrypted payload passes payload gate if envelope is structurally valid.
|
|
720
|
-
} else
|
|
741
|
+
} else {
|
|
721
742
|
// Plaintext payload — full schema validation.
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
743
|
+
const isBundleProfile = v1.manifest.compatibility?.profile === 'bundle-profile-v1' || v1.manifest.asset_type === 'bundle';
|
|
744
|
+
const payloadValidator = isBundleProfile ? validators.bundlePayload : validators.payload;
|
|
745
|
+
if (!payloadValidator(payload)) {
|
|
746
|
+
result.payload_valid = false;
|
|
747
|
+
for (const err of payloadValidator.errors) {
|
|
748
|
+
problems.push(`payload: ${err.instancePath || '<root>'} ${err.message}`);
|
|
749
|
+
}
|
|
725
750
|
}
|
|
726
751
|
}
|
|
727
752
|
|
|
@@ -958,14 +983,15 @@ function unpack(inputPath, outputDir) {
|
|
|
958
983
|
const entries = listZipEntries(absIn);
|
|
959
984
|
// Sanity: v1 container must have mimetype as first entry with the v1 media type.
|
|
960
985
|
if (entries.length === 0 || entries[0].name !== 'mimetype') {
|
|
961
|
-
throw new Error('not a KDNA
|
|
986
|
+
throw new Error('not a KDNA container: first entry is not mimetype');
|
|
962
987
|
}
|
|
963
988
|
if (entries[0].method !== 0) {
|
|
964
|
-
throw new Error('not a KDNA
|
|
989
|
+
throw new Error('not a KDNA container: mimetype must be uncompressed');
|
|
965
990
|
}
|
|
966
|
-
|
|
991
|
+
const mime = entries[0].data.toString('utf8');
|
|
992
|
+
if (mime !== MIMETYPE_V1 && mime !== MIMETYPE_V2) {
|
|
967
993
|
throw new Error(
|
|
968
|
-
`not a KDNA
|
|
994
|
+
`not a KDNA container: mimetype is "${mime}", expected "${MIMETYPE_V1}" or "${MIMETYPE_V2}"`,
|
|
969
995
|
);
|
|
970
996
|
}
|
|
971
997
|
const absOut = path.resolve(outputDir);
|
|
@@ -1128,6 +1154,17 @@ function canonicalToV1Layout(asset) {
|
|
|
1128
1154
|
for (const [name, buf] of asset.entries) {
|
|
1129
1155
|
map[name] = buf;
|
|
1130
1156
|
}
|
|
1157
|
+
} else {
|
|
1158
|
+
map['mimetype'] = Buffer.from(asset.mimetype || '', 'utf8');
|
|
1159
|
+
if (asset.manifest) {
|
|
1160
|
+
map['kdna.json'] = Buffer.from(JSON.stringify(asset.manifest), 'utf8');
|
|
1161
|
+
}
|
|
1162
|
+
if (asset.payloadRaw) {
|
|
1163
|
+
map['payload.kdnab'] = asset.payloadRaw;
|
|
1164
|
+
}
|
|
1165
|
+
if (asset.checksums) {
|
|
1166
|
+
map['checksums.json'] = Buffer.from(JSON.stringify(asset.checksums), 'utf8');
|
|
1167
|
+
}
|
|
1131
1168
|
}
|
|
1132
1169
|
return {
|
|
1133
1170
|
kind: asset.sourceKind,
|
|
@@ -1204,6 +1241,46 @@ function planLoad(inputPath, opts = {}) {
|
|
|
1204
1241
|
}
|
|
1205
1242
|
|
|
1206
1243
|
const manifest = v1.manifest;
|
|
1244
|
+
|
|
1245
|
+
// Resolve dependencies if declared (Story 6)
|
|
1246
|
+
const resolvedDeps = [];
|
|
1247
|
+
if (manifest.dependencies && typeof manifest.dependencies === 'object' && Object.keys(manifest.dependencies).length > 0) {
|
|
1248
|
+
if (typeof opts.resolveAsset === 'function') {
|
|
1249
|
+
try {
|
|
1250
|
+
const resolved = resolveDependencies(manifest, opts.resolveAsset);
|
|
1251
|
+
resolvedDeps.push(...resolved);
|
|
1252
|
+
} catch (err) {
|
|
1253
|
+
plan.state = 'invalid';
|
|
1254
|
+
plan.required_action = 'block';
|
|
1255
|
+
plan.can_load_now = false;
|
|
1256
|
+
plan.issues.push(buildLoadPlanIssue(
|
|
1257
|
+
'KDNA_DEPENDENCY_RESOLUTION_FAILED',
|
|
1258
|
+
'blocking',
|
|
1259
|
+
err.message
|
|
1260
|
+
));
|
|
1261
|
+
return finalizeLoadPlan(plan);
|
|
1262
|
+
}
|
|
1263
|
+
} else {
|
|
1264
|
+
plan.state = 'invalid';
|
|
1265
|
+
plan.required_action = 'block';
|
|
1266
|
+
plan.can_load_now = false;
|
|
1267
|
+
plan.issues.push(buildLoadPlanIssue(
|
|
1268
|
+
'KDNA_DEPENDENCY_RESOLVER_MISSING',
|
|
1269
|
+
'blocking',
|
|
1270
|
+
'Asset has dependencies but no dependency resolver callback was provided.'
|
|
1271
|
+
));
|
|
1272
|
+
return finalizeLoadPlan(plan);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
if (resolvedDeps.length > 0) {
|
|
1277
|
+
plan.resolved_dependencies = resolvedDeps.map(dep => ({
|
|
1278
|
+
name: dep.name,
|
|
1279
|
+
version: dep.version,
|
|
1280
|
+
path: dep.path
|
|
1281
|
+
}));
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1207
1284
|
const payloadDeclaredEncrypted =
|
|
1208
1285
|
manifest.payload && manifest.payload.encrypted === true;
|
|
1209
1286
|
const encryptedEntries = Array.isArray(manifest.encryption && manifest.encryption.encrypted_entries)
|
|
@@ -1478,6 +1555,7 @@ module.exports = {
|
|
|
1478
1555
|
MIMETYPE_V2,
|
|
1479
1556
|
V1_REQUIRED_DIR_ENTRIES,
|
|
1480
1557
|
isV1SourceDir,
|
|
1558
|
+
isV2SourceDir,
|
|
1481
1559
|
detectContainerFormat,
|
|
1482
1560
|
readV1Layout,
|
|
1483
1561
|
inspect,
|
|
@@ -1584,7 +1662,8 @@ function loadAuthorized(inputPath, opts = {}) {
|
|
|
1584
1662
|
err.plan = plan;
|
|
1585
1663
|
throw err;
|
|
1586
1664
|
}
|
|
1587
|
-
|
|
1665
|
+
const mergedOpts = { ...opts, resolvedDependencies: plan.resolved_dependencies };
|
|
1666
|
+
return loadV1Unsafe(inputPath, mergedOpts);
|
|
1588
1667
|
}
|
|
1589
1668
|
|
|
1590
1669
|
function loadV1(inputPath, opts = {}) {
|
|
@@ -1765,6 +1844,20 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
1765
1844
|
throw new Error(`unknown load profile: ${profile}`);
|
|
1766
1845
|
}
|
|
1767
1846
|
|
|
1847
|
+
if (opts.resolvedDependencies && opts.resolvedDependencies.length > 0) {
|
|
1848
|
+
result.resolved_dependencies = opts.resolvedDependencies.map(dep => {
|
|
1849
|
+
const depLoaded = loadV1Unsafe(dep.path, { ...opts, resolvedDependencies: [] });
|
|
1850
|
+
return {
|
|
1851
|
+
name: dep.name,
|
|
1852
|
+
version: dep.version,
|
|
1853
|
+
path: dep.path,
|
|
1854
|
+
status: depLoaded.status,
|
|
1855
|
+
profile: depLoaded.profile,
|
|
1856
|
+
content: depLoaded.content
|
|
1857
|
+
};
|
|
1858
|
+
});
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1768
1861
|
if (as === 'prompt') {
|
|
1769
1862
|
const c = result.content;
|
|
1770
1863
|
if (!c || (c.scenarios && c.scenarios.length === 0 && !c.highest_question && !(c.axioms && c.axioms.length) && !(c.boundaries && c.boundaries.length))) {
|
|
@@ -1788,8 +1881,135 @@ function loadV1Unsafe(inputPath, opts = {}) {
|
|
|
1788
1881
|
if (c.failure_modes && c.failure_modes.length) text += 'Failure modes:\n' + c.failure_modes.map((f) => '- ' + renderPromptItem(f)).join('\n') + '\n';
|
|
1789
1882
|
if (c.patterns && c.patterns.length) text += 'Patterns:\n' + c.patterns.map((p) => '- ' + renderPromptItem(p)).join('\n') + '\n';
|
|
1790
1883
|
if (c.note) text += 'Note: ' + c.note + '\n';
|
|
1791
|
-
|
|
1884
|
+
|
|
1885
|
+
let textOut = text.trim();
|
|
1886
|
+
if (opts.resolvedDependencies && opts.resolvedDependencies.length > 0) {
|
|
1887
|
+
for (const dep of opts.resolvedDependencies) {
|
|
1888
|
+
const depLoaded = loadV1Unsafe(dep.path, { ...opts, resolvedDependencies: [] });
|
|
1889
|
+
if (depLoaded.text) {
|
|
1890
|
+
textOut += '\n\n---\n\n' + depLoaded.text;
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
return { status: result.status, profile: result.profile, text: textOut };
|
|
1792
1895
|
}
|
|
1793
1896
|
|
|
1794
1897
|
return result;
|
|
1795
1898
|
}
|
|
1899
|
+
|
|
1900
|
+
// ─── Semver & Dependency Resolution (Story 6) ─────────────────────────
|
|
1901
|
+
|
|
1902
|
+
function parseSemver(v) {
|
|
1903
|
+
if (!v) return null;
|
|
1904
|
+
const match = v.trim().match(/^v?([0-9]+)\.([0-9]+)\.([0-9]+)(?:-[a-zA-Z0-9.]+)?/);
|
|
1905
|
+
if (!match) return null;
|
|
1906
|
+
return {
|
|
1907
|
+
major: parseInt(match[1], 10),
|
|
1908
|
+
minor: parseInt(match[2], 10),
|
|
1909
|
+
patch: parseInt(match[3], 10)
|
|
1910
|
+
};
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
function compareSemver(v1, v2) {
|
|
1914
|
+
const p1 = parseSemver(v1);
|
|
1915
|
+
const p2 = parseSemver(v2);
|
|
1916
|
+
if (!p1 || !p2) return 0;
|
|
1917
|
+
if (p1.major !== p2.major) return p1.major - p2.major;
|
|
1918
|
+
if (p1.minor !== p2.minor) return p1.minor - p2.minor;
|
|
1919
|
+
return p1.patch - p2.patch;
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
function satisfies(version, range) {
|
|
1923
|
+
if (!range || range === '*' || range.trim() === '') return true;
|
|
1924
|
+
const r = range.trim();
|
|
1925
|
+
|
|
1926
|
+
// Specific version comparison (e.g. "1.2.3")
|
|
1927
|
+
if (/^[0-9]/.test(r)) {
|
|
1928
|
+
return compareSemver(version, r) === 0;
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
// ^ range comparison (e.g. "^1.2.3")
|
|
1932
|
+
if (r.startsWith('^')) {
|
|
1933
|
+
const min = r.slice(1);
|
|
1934
|
+
const parsedMin = parseSemver(min);
|
|
1935
|
+
if (!parsedMin) return false;
|
|
1936
|
+
const parsedVer = parseSemver(version);
|
|
1937
|
+
if (!parsedVer) return false;
|
|
1938
|
+
// same major, and version >= min
|
|
1939
|
+
if (parsedVer.major !== parsedMin.major) return false;
|
|
1940
|
+
return compareSemver(version, min) >= 0;
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
// ~ range comparison (e.g. "~1.2.3")
|
|
1944
|
+
if (r.startsWith('~')) {
|
|
1945
|
+
const min = r.slice(1);
|
|
1946
|
+
const parsedMin = parseSemver(min);
|
|
1947
|
+
if (!parsedMin) return false;
|
|
1948
|
+
const parsedVer = parseSemver(version);
|
|
1949
|
+
if (!parsedVer) return false;
|
|
1950
|
+
// same major, same minor, and version >= min
|
|
1951
|
+
if (parsedVer.major !== parsedMin.major || parsedVer.minor !== parsedMin.minor) return false;
|
|
1952
|
+
return compareSemver(version, min) >= 0;
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
// Custom ranges like ">=1.0.0 <2.0.0"
|
|
1956
|
+
if (r.includes(' ')) {
|
|
1957
|
+
const parts = r.split(/\s+/);
|
|
1958
|
+
return parts.every(part => satisfies(version, part));
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
if (r.startsWith('>=')) {
|
|
1962
|
+
return compareSemver(version, r.slice(2)) >= 0;
|
|
1963
|
+
}
|
|
1964
|
+
if (r.startsWith('>')) {
|
|
1965
|
+
return compareSemver(version, r.slice(1)) > 0;
|
|
1966
|
+
}
|
|
1967
|
+
if (r.startsWith('<=')) {
|
|
1968
|
+
return compareSemver(version, r.slice(2)) <= 0;
|
|
1969
|
+
}
|
|
1970
|
+
if (r.startsWith('<')) {
|
|
1971
|
+
return compareSemver(version, r.slice(1)) < 0;
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
return false;
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
function resolveDependencies(manifest, resolveAssetCallback, seen = new Set(), stack = []) {
|
|
1978
|
+
const resolved = [];
|
|
1979
|
+
const deps = manifest.dependencies || {};
|
|
1980
|
+
|
|
1981
|
+
for (const [name, range] of Object.entries(deps)) {
|
|
1982
|
+
if (stack.includes(name)) {
|
|
1983
|
+
throw new Error(`Circular dependency detected: ${stack.join(' -> ')} -> ${name}`);
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
if (seen.has(name)) continue;
|
|
1987
|
+
|
|
1988
|
+
// Resolve the dependency using the callback
|
|
1989
|
+
const depAsset = resolveAssetCallback(name);
|
|
1990
|
+
if (!depAsset) {
|
|
1991
|
+
throw new Error(`Dependency not satisfied: "${name}" matching "${range}" is not installed.`);
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
// Check if the resolved version satisfies the range
|
|
1995
|
+
if (!satisfies(depAsset.version, range)) {
|
|
1996
|
+
throw new Error(`Dependency mismatch: installed "${name}" version "${depAsset.version}" does not satisfy range "${range}".`);
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
// Recurse
|
|
2000
|
+
stack.push(name);
|
|
2001
|
+
const subResolved = resolveDependencies(depAsset.manifest, resolveAssetCallback, seen, stack);
|
|
2002
|
+
stack.pop();
|
|
2003
|
+
|
|
2004
|
+
resolved.push(...subResolved);
|
|
2005
|
+
resolved.push({
|
|
2006
|
+
name,
|
|
2007
|
+
version: depAsset.version,
|
|
2008
|
+
path: depAsset.path,
|
|
2009
|
+
manifest: depAsset.manifest
|
|
2010
|
+
});
|
|
2011
|
+
seen.add(name);
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
return resolved;
|
|
2015
|
+
}
|
package/src/v1/index.mjs
CHANGED
|
@@ -5,6 +5,7 @@ export const MIMETYPE_V1 = v1.MIMETYPE_V1;
|
|
|
5
5
|
export const MIMETYPE_V2 = v1.MIMETYPE_V2;
|
|
6
6
|
export const V1_REQUIRED_DIR_ENTRIES = v1.V1_REQUIRED_DIR_ENTRIES;
|
|
7
7
|
export const isV1SourceDir = v1.isV1SourceDir;
|
|
8
|
+
export const isV2SourceDir = v1.isV2SourceDir;
|
|
8
9
|
export const detectContainerFormat = v1.detectContainerFormat;
|
|
9
10
|
export const readV1Layout = v1.readV1Layout;
|
|
10
11
|
export const inspect = v1.inspect;
|