@async/api-contract 0.1.0
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 +5 -0
- package/README.md +150 -0
- package/dist/catalog.d.ts +4 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +62 -0
- package/dist/catalog.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +106 -0
- package/dist/cli.js.map +1 -0
- package/dist/compare.d.ts +4 -0
- package/dist/compare.d.ts.map +1 -0
- package/dist/compare.js +101 -0
- package/dist/compare.js.map +1 -0
- package/dist/derive.d.ts +3 -0
- package/dist/derive.d.ts.map +1 -0
- package/dist/derive.js +61 -0
- package/dist/derive.js.map +1 -0
- package/dist/diff.d.ts +3 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +38 -0
- package/dist/diff.js.map +1 -0
- package/dist/hash.d.ts +8 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +15 -0
- package/dist/hash.js.map +1 -0
- package/dist/impact.d.ts +7 -0
- package/dist/impact.d.ts.map +1 -0
- package/dist/impact.js +25 -0
- package/dist/impact.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/ledger.d.ts +3 -0
- package/dist/ledger.d.ts.map +1 -0
- package/dist/ledger.js +78 -0
- package/dist/ledger.js.map +1 -0
- package/dist/manifest.d.ts +3 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +62 -0
- package/dist/manifest.js.map +1 -0
- package/dist/model.d.ts +125 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +2 -0
- package/dist/model.js.map +1 -0
- package/dist/surface.d.ts +8 -0
- package/dist/surface.d.ts.map +1 -0
- package/dist/surface.js +26 -0
- package/dist/surface.js.map +1 -0
- package/dist/types.d.ts +20 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/usage-scan.d.ts +16 -0
- package/dist/usage-scan.d.ts.map +1 -0
- package/dist/usage-scan.js +66 -0
- package/dist/usage-scan.js.map +1 -0
- package/examples/claims/claim.json +13 -0
- package/examples/db-ledger/API_SURFACE.md +26 -0
- package/examples/db-ledger/api-contract.json +42 -0
- package/examples/pipeline/API_SURFACE.md +37 -0
- package/examples/pipeline/api-contract.json +53 -0
- package/package.json +58 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# @async/api-contract
|
|
2
|
+
|
|
3
|
+
`@async/api-contract` makes API compatibility explicit at the feature level. Instead of asking whether two package versions look compatible, a provider publishes the features it supports, a consumer records the features it requires, and CI checks whether the required surface is still a subset of the supported surface.
|
|
4
|
+
|
|
5
|
+
**30-word value prop:** `@async/api-contract` turns API compatibility into feature-level contracts: publish what packages support, derive what consumers require, and catch breaking changes before versions, docs, or tests drift across Async workspaces during releases.
|
|
6
|
+
|
|
7
|
+
Compatibility is feature-based, not package-version-based:
|
|
8
|
+
|
|
9
|
+
```txt
|
|
10
|
+
required.features subset_of supported.features
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Package versions, release tags, docs text, and metadata do not decide compatibility.
|
|
14
|
+
|
|
15
|
+
## What it produces
|
|
16
|
+
|
|
17
|
+
- `api-contract.json`: the machine-readable contract a package publishes, including catalogs, supported surfaces, required surfaces, emitted surfaces, or usage evidence.
|
|
18
|
+
- `Surface`: normalized runtime data with sorted unique feature ids and a stable hash.
|
|
19
|
+
- `API_SURFACE.md`: a deterministic review ledger for maintainers and docs.
|
|
20
|
+
- Impact reports: a quick way to see which consumers use removed, changed, or deprecated features before running expensive many-repo checks.
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import {
|
|
26
|
+
compareSurface,
|
|
27
|
+
createSurface,
|
|
28
|
+
defineFeatureCatalog,
|
|
29
|
+
deriveSurface
|
|
30
|
+
} from "@async/api-contract";
|
|
31
|
+
|
|
32
|
+
const catalog = defineFeatureCatalog({
|
|
33
|
+
format: "api-contract.catalog.v1",
|
|
34
|
+
contractId: "@async/user-api.response",
|
|
35
|
+
features: [
|
|
36
|
+
{ id: "user.id", title: "User id field", releaseTag: "public", stability: "stable" },
|
|
37
|
+
{ id: "user.email", title: "User email field", releaseTag: "public", stability: "stable" }
|
|
38
|
+
]
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const userResponse = {
|
|
42
|
+
id: "usr_123",
|
|
43
|
+
email: "ada@example.com"
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const required = deriveSurface(userResponse, {
|
|
47
|
+
contractId: "@async/user-api.response",
|
|
48
|
+
catalog,
|
|
49
|
+
strictCatalog: true,
|
|
50
|
+
rules: [
|
|
51
|
+
{
|
|
52
|
+
name: "user-response-fields",
|
|
53
|
+
visit(value, context) {
|
|
54
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
55
|
+
if ("id" in value) context.add("user.id");
|
|
56
|
+
if ("email" in value) context.add("user.email");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const supported = createSurface({
|
|
63
|
+
contractId: "@async/user-api.response",
|
|
64
|
+
features: ["user.id", "user.email"]
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const result = compareSurface(required, supported);
|
|
68
|
+
|
|
69
|
+
console.log(required.features); // ["user.email", "user.id"]
|
|
70
|
+
console.log(result.ok); // true
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Host packages own shape validation and derivation rules. For example, `@async/pipeline` should validate branded declaration nodes before deriving a surface from them.
|
|
74
|
+
|
|
75
|
+
## Policy checks
|
|
76
|
+
|
|
77
|
+
Subset compatibility is the default. When a catalog is available, comparisons can also enforce release and lifecycle policy:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
const result = compareSurface(required, supported, {
|
|
81
|
+
catalog,
|
|
82
|
+
allowedReleaseTags: ["public"],
|
|
83
|
+
deprecated: "warn",
|
|
84
|
+
removed: "error",
|
|
85
|
+
unknownFeatures: "error"
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
if (!result.ok) {
|
|
89
|
+
console.error(result.warnings);
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Type-only contracts
|
|
94
|
+
|
|
95
|
+
Use `@async/api-contract/types` for app code and virtual imports that should express a contract without emitting runtime JavaScript.
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import type {
|
|
99
|
+
AssertCompatible,
|
|
100
|
+
Expect,
|
|
101
|
+
RequiresContract,
|
|
102
|
+
SupportsContract
|
|
103
|
+
} from "@async/api-contract/types";
|
|
104
|
+
|
|
105
|
+
type UsesPipeline = RequiresContract<"@async/pipeline.declaration">;
|
|
106
|
+
|
|
107
|
+
type UsesShellStep = RequiresContract<
|
|
108
|
+
"@async/pipeline.declaration",
|
|
109
|
+
"task.run" | "step.shell"
|
|
110
|
+
>;
|
|
111
|
+
|
|
112
|
+
type HostSupport = SupportsContract<
|
|
113
|
+
"@async/pipeline.declaration",
|
|
114
|
+
"task.run" | "step.shell" | "agent.stdoutTo"
|
|
115
|
+
>;
|
|
116
|
+
|
|
117
|
+
type Check = Expect<AssertCompatible<UsesShellStep, HostSupport>>;
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The default is forward-compatible rather than untyped: known fields stay precise, exact feature unions are supported, and option maps allow future `x-*` extension fields.
|
|
121
|
+
|
|
122
|
+
## Release and stability tags
|
|
123
|
+
|
|
124
|
+
Catalog features can carry API Extractor-style maturity metadata:
|
|
125
|
+
|
|
126
|
+
- `public`
|
|
127
|
+
- `beta`
|
|
128
|
+
- `alpha`
|
|
129
|
+
- `internal`
|
|
130
|
+
|
|
131
|
+
Project-facing stability labels such as `stable`, `preview`, `experimental`, `generated`, `dev-only`, and `internal` are docs and policy metadata. They do not affect `surfaceHash()`.
|
|
132
|
+
|
|
133
|
+
## CLI
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
api-contract check --manifest api-contract.json
|
|
137
|
+
api-contract ledger --manifest api-contract.json --out API_SURFACE.md
|
|
138
|
+
api-contract ledger --manifest api-contract.json --check API_SURFACE.md
|
|
139
|
+
api-contract diff --before old-api-contract.json --after api-contract.json
|
|
140
|
+
api-contract impact --before old-api-contract.json --after api-contract.json --consumers consumers.json
|
|
141
|
+
api-contract usage scan --target src --package-name @async/consumer --dependency @async/pipeline --catalog api-contract.json --out api-usage.json
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`impact` is intended as a cheap preflight for explicit many-repo impact runs. Read the latest consumer manifests or usage files first; then run full dependent repo checks only for consumers that actually use changed features.
|
|
145
|
+
|
|
146
|
+
`usage scan` is a line-oriented source preflight. It records dependency and feature-string evidence, but it is not a full parser or proof of semantic usage.
|
|
147
|
+
|
|
148
|
+
## Relationship to @async/claims
|
|
149
|
+
|
|
150
|
+
`@async/claims` can wrap surfaces with issuer, evidence, trust, policy, and signatures. It should not redefine feature ids, hashes, derivation, or compatibility rules.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAe,MAAM,YAAY,CAAC;AAK9D,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,cAAc,GAAG,cAAc,CA0B5E;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,cAAc,GAAG,GAAG,CAAC,MAAM,CAAC,CAEtE"}
|
package/dist/catalog.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const releaseTags = new Set(["public", "beta", "alpha", "internal"]);
|
|
2
|
+
const lifecycles = new Set(["active", "deprecated", "removed"]);
|
|
3
|
+
export function defineFeatureCatalog(catalog) {
|
|
4
|
+
assertObject(catalog, "catalog");
|
|
5
|
+
if (catalog.format !== "api-contract.catalog.v1") {
|
|
6
|
+
throw new Error("FeatureCatalog.format must be api-contract.catalog.v1");
|
|
7
|
+
}
|
|
8
|
+
assertNonEmptyString(catalog.contractId, "FeatureCatalog.contractId");
|
|
9
|
+
if (!Array.isArray(catalog.features)) {
|
|
10
|
+
throw new Error("FeatureCatalog.features must be an array");
|
|
11
|
+
}
|
|
12
|
+
const seen = new Set();
|
|
13
|
+
const features = catalog.features.map((feature) => normalizeFeatureSpec(feature));
|
|
14
|
+
for (const feature of features) {
|
|
15
|
+
if (seen.has(feature.id)) {
|
|
16
|
+
throw new Error(`Duplicate feature id: ${feature.id}`);
|
|
17
|
+
}
|
|
18
|
+
seen.add(feature.id);
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
...copyExtensionFields(catalog),
|
|
22
|
+
format: "api-contract.catalog.v1",
|
|
23
|
+
contractId: catalog.contractId,
|
|
24
|
+
...(catalog.title === undefined ? {} : { title: catalog.title }),
|
|
25
|
+
features: features.sort((a, b) => a.id.localeCompare(b.id))
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function catalogFeatureIds(catalog) {
|
|
29
|
+
return new Set(catalog.features.map((feature) => feature.id));
|
|
30
|
+
}
|
|
31
|
+
function normalizeFeatureSpec(feature) {
|
|
32
|
+
assertObject(feature, "FeatureSpec");
|
|
33
|
+
assertNonEmptyString(feature.id, "FeatureSpec.id");
|
|
34
|
+
assertNonEmptyString(feature.title, "FeatureSpec.title");
|
|
35
|
+
if (!releaseTags.has(feature.releaseTag)) {
|
|
36
|
+
throw new Error(`FeatureSpec.releaseTag must be one of public, beta, alpha, internal for ${feature.id}`);
|
|
37
|
+
}
|
|
38
|
+
if (feature.lifecycle !== undefined && !lifecycles.has(feature.lifecycle)) {
|
|
39
|
+
throw new Error(`FeatureSpec.lifecycle must be active, deprecated, or removed for ${feature.id}`);
|
|
40
|
+
}
|
|
41
|
+
return { ...feature };
|
|
42
|
+
}
|
|
43
|
+
function assertObject(value, label) {
|
|
44
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
45
|
+
throw new Error(`${label} must be an object`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function assertNonEmptyString(value, label) {
|
|
49
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
50
|
+
throw new Error(`${label} must be a non-empty string`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function copyExtensionFields(value) {
|
|
54
|
+
const out = {};
|
|
55
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
56
|
+
if (key.startsWith("x-")) {
|
|
57
|
+
out[key] = entry;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=catalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.js","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { parsePackageContractManifest } from "./manifest.js";
|
|
4
|
+
import { renderApiSurfaceMarkdown } from "./ledger.js";
|
|
5
|
+
import { diffPackageContracts } from "./diff.js";
|
|
6
|
+
import { createImpactReport } from "./impact.js";
|
|
7
|
+
import { scanUsageTarget } from "./usage-scan.js";
|
|
8
|
+
function readJson(path) {
|
|
9
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
10
|
+
}
|
|
11
|
+
function argValue(args, name) {
|
|
12
|
+
const index = args.indexOf(name);
|
|
13
|
+
return index >= 0 ? args[index + 1] : undefined;
|
|
14
|
+
}
|
|
15
|
+
function readManifest(path) {
|
|
16
|
+
return parsePackageContractManifest(readJson(path));
|
|
17
|
+
}
|
|
18
|
+
function writeJsonOrStdout(value, outPath) {
|
|
19
|
+
const text = `${JSON.stringify(value, null, 2)}
|
|
20
|
+
`;
|
|
21
|
+
if (outPath)
|
|
22
|
+
writeFileSync(outPath, text);
|
|
23
|
+
else
|
|
24
|
+
process.stdout.write(text);
|
|
25
|
+
}
|
|
26
|
+
function readConsumers(path) {
|
|
27
|
+
const value = readJson(path);
|
|
28
|
+
if (Array.isArray(value))
|
|
29
|
+
return value.map(parsePackageContractManifest);
|
|
30
|
+
if (typeof value === "object" && value !== null && Array.isArray(value.manifests)) {
|
|
31
|
+
return value.manifests.map(parsePackageContractManifest);
|
|
32
|
+
}
|
|
33
|
+
return [parsePackageContractManifest(value)];
|
|
34
|
+
}
|
|
35
|
+
const args = process.argv.slice(2);
|
|
36
|
+
const command = args[0];
|
|
37
|
+
try {
|
|
38
|
+
if (command === "check") {
|
|
39
|
+
const manifestPath = argValue(args, "--manifest");
|
|
40
|
+
if (!manifestPath)
|
|
41
|
+
throw new Error("Missing --manifest <file>");
|
|
42
|
+
parsePackageContractManifest(readJson(manifestPath));
|
|
43
|
+
console.log(`ok ${manifestPath}`);
|
|
44
|
+
}
|
|
45
|
+
else if (command === "ledger") {
|
|
46
|
+
const manifestPath = argValue(args, "--manifest");
|
|
47
|
+
if (!manifestPath)
|
|
48
|
+
throw new Error("Missing --manifest <file>");
|
|
49
|
+
const manifest = readManifest(manifestPath);
|
|
50
|
+
const markdown = renderApiSurfaceMarkdown({ manifest });
|
|
51
|
+
const checkPath = argValue(args, "--check");
|
|
52
|
+
if (checkPath) {
|
|
53
|
+
const existing = readFileSync(checkPath, "utf8");
|
|
54
|
+
if (existing !== markdown)
|
|
55
|
+
throw new Error(`${checkPath} is out of date`);
|
|
56
|
+
console.log(`ok ${checkPath}`);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
const outPath = argValue(args, "--out");
|
|
60
|
+
if (outPath)
|
|
61
|
+
writeFileSync(outPath, markdown);
|
|
62
|
+
else
|
|
63
|
+
process.stdout.write(markdown);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else if (command === "diff") {
|
|
67
|
+
const beforePath = argValue(args, "--before");
|
|
68
|
+
const afterPath = argValue(args, "--after");
|
|
69
|
+
if (!beforePath || !afterPath)
|
|
70
|
+
throw new Error("Missing --before <file> or --after <file>");
|
|
71
|
+
writeJsonOrStdout(diffPackageContracts(readManifest(beforePath), readManifest(afterPath)), argValue(args, "--out"));
|
|
72
|
+
}
|
|
73
|
+
else if (command === "impact") {
|
|
74
|
+
const beforePath = argValue(args, "--before");
|
|
75
|
+
const afterPath = argValue(args, "--after");
|
|
76
|
+
const consumersPath = argValue(args, "--consumers");
|
|
77
|
+
if (!beforePath || !afterPath || !consumersPath)
|
|
78
|
+
throw new Error("Missing --before <file>, --after <file>, or --consumers <file>");
|
|
79
|
+
writeJsonOrStdout(createImpactReport({ before: readManifest(beforePath), after: readManifest(afterPath), consumers: readConsumers(consumersPath) }), argValue(args, "--out"));
|
|
80
|
+
}
|
|
81
|
+
else if (command === "usage" && args[1] === "scan") {
|
|
82
|
+
const target = argValue(args, "--target");
|
|
83
|
+
const dependencyName = argValue(args, "--dependency");
|
|
84
|
+
const catalogPath = argValue(args, "--catalog");
|
|
85
|
+
const packageName = argValue(args, "--package-name") ?? "unknown";
|
|
86
|
+
if (!target || !dependencyName || !catalogPath)
|
|
87
|
+
throw new Error("Missing --target <path>, --dependency <name>, or --catalog <manifest>");
|
|
88
|
+
const manifest = readManifest(catalogPath);
|
|
89
|
+
const catalog = manifest.catalogs?.[0];
|
|
90
|
+
if (!catalog)
|
|
91
|
+
throw new Error(`${catalogPath} does not contain a catalog`);
|
|
92
|
+
writeJsonOrStdout({
|
|
93
|
+
format: "api-contract.package.v1",
|
|
94
|
+
packageName,
|
|
95
|
+
usage: [scanUsageTarget({ target, dependencyName, packageName, catalog })]
|
|
96
|
+
}, argValue(args, "--out"));
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
throw new Error("Usage: api-contract check|ledger|diff|impact|usage scan");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
104
|
+
process.exitCode = 1;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ComparePolicy, ContractSet, Surface, SurfaceComparison } from "./model.js";
|
|
2
|
+
export declare function compareSurface(required: Surface, supported: Surface, policy?: ComparePolicy): SurfaceComparison;
|
|
3
|
+
export declare function compareContractSets(required: ContractSet, supported: ContractSet, policy?: ComparePolicy): SurfaceComparison;
|
|
4
|
+
//# sourceMappingURL=compare.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare.d.ts","sourceRoot":"","sources":["../src/compare.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAA0C,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAIjI,wBAAgB,cAAc,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAE,aAAkB,GAAG,iBAAiB,CAsBnH;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,GAAE,aAAkB,GAAG,iBAAiB,CA4BhI"}
|
package/dist/compare.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
export function compareSurface(required, supported, policy = {}) {
|
|
2
|
+
if (required.contractId !== supported.contractId) {
|
|
3
|
+
return {
|
|
4
|
+
ok: false,
|
|
5
|
+
missing: [...required.features],
|
|
6
|
+
unsupported: [...supported.features],
|
|
7
|
+
warnings: [`Contract id mismatch: required ${required.contractId}, supported ${supported.contractId}`]
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
const supportedFeatures = new Set(supported.features);
|
|
11
|
+
const requiredFeatures = new Set(required.features);
|
|
12
|
+
const missing = required.features.filter((feature) => !supportedFeatures.has(feature));
|
|
13
|
+
const unsupported = supported.features.filter((feature) => !requiredFeatures.has(feature));
|
|
14
|
+
const policyResult = evaluatePolicy(required, policy);
|
|
15
|
+
return {
|
|
16
|
+
ok: missing.length === 0 && policyResult.ok,
|
|
17
|
+
missing,
|
|
18
|
+
unsupported,
|
|
19
|
+
warnings: policyResult.warnings
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function compareContractSets(required, supported, policy = {}) {
|
|
23
|
+
const supportedByContract = new Map(supported.surfaces.map((surface) => [surface.contractId, surface]));
|
|
24
|
+
const missing = [];
|
|
25
|
+
const unsupported = [];
|
|
26
|
+
const warnings = [];
|
|
27
|
+
let ok = true;
|
|
28
|
+
for (const requiredSurface of required.surfaces) {
|
|
29
|
+
const supportedSurface = supportedByContract.get(requiredSurface.contractId);
|
|
30
|
+
if (!supportedSurface) {
|
|
31
|
+
ok = false;
|
|
32
|
+
missing.push(...requiredSurface.features.map((feature) => `${requiredSurface.contractId}:${feature}`));
|
|
33
|
+
warnings.push(`Missing supported surface for ${requiredSurface.contractId}`);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const comparison = compareSurface(requiredSurface, supportedSurface, policy);
|
|
37
|
+
if (!comparison.ok)
|
|
38
|
+
ok = false;
|
|
39
|
+
missing.push(...comparison.missing);
|
|
40
|
+
unsupported.push(...comparison.unsupported);
|
|
41
|
+
warnings.push(...comparison.warnings);
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
ok: ok && missing.length === 0,
|
|
45
|
+
missing,
|
|
46
|
+
unsupported,
|
|
47
|
+
warnings
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function evaluatePolicy(required, policy) {
|
|
51
|
+
const features = policyFeatureMap(required.contractId, policy);
|
|
52
|
+
if (!features)
|
|
53
|
+
return { ok: true, warnings: [] };
|
|
54
|
+
const warnings = [];
|
|
55
|
+
const errors = [];
|
|
56
|
+
const report = (action, message) => {
|
|
57
|
+
if (action === "warn")
|
|
58
|
+
warnings.push(message);
|
|
59
|
+
if (action === "error")
|
|
60
|
+
errors.push(message);
|
|
61
|
+
};
|
|
62
|
+
for (const featureId of required.features) {
|
|
63
|
+
const feature = features.get(featureId);
|
|
64
|
+
if (!feature) {
|
|
65
|
+
report(policy.unknownFeatures, `Unknown feature in ${required.contractId}: ${featureId}`);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (policy.allowedReleaseTags && !policy.allowedReleaseTags.includes(feature.releaseTag)) {
|
|
69
|
+
errors.push(`Feature ${featureId} has release tag ${feature.releaseTag}, which is not allowed by policy`);
|
|
70
|
+
}
|
|
71
|
+
if (feature.lifecycle === "deprecated") {
|
|
72
|
+
report(policy.deprecated, `Feature ${featureId} is deprecated`);
|
|
73
|
+
}
|
|
74
|
+
if (feature.lifecycle === "removed") {
|
|
75
|
+
report(policy.removed, `Feature ${featureId} is removed`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
ok: errors.length === 0,
|
|
80
|
+
warnings: [...warnings, ...errors.map((error) => `Policy error: ${error}`)]
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function policyFeatureMap(contractId, policy) {
|
|
84
|
+
const catalogs = policyCatalogs(policy).filter((catalog) => catalog.contractId === contractId);
|
|
85
|
+
if (catalogs.length === 0)
|
|
86
|
+
return undefined;
|
|
87
|
+
const features = new Map();
|
|
88
|
+
for (const catalog of catalogs) {
|
|
89
|
+
for (const feature of catalog.features) {
|
|
90
|
+
features.set(feature.id, feature);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return features;
|
|
94
|
+
}
|
|
95
|
+
function policyCatalogs(policy) {
|
|
96
|
+
return [
|
|
97
|
+
...(policy.catalog ? [policy.catalog] : []),
|
|
98
|
+
...(policy.catalogs ?? [])
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=compare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare.js","sourceRoot":"","sources":["../src/compare.ts"],"names":[],"mappings":"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"}
|
package/dist/derive.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"derive.d.ts","sourceRoot":"","sources":["../src/derive.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAiB,oBAAoB,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE/E,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,oBAAoB,GAAG,OAAO,CA+CpF"}
|
package/dist/derive.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { catalogFeatureIds } from "./catalog.js";
|
|
2
|
+
import { createSurface } from "./surface.js";
|
|
3
|
+
export function deriveSurface(value, options) {
|
|
4
|
+
const features = new Set();
|
|
5
|
+
const knownFeatures = options.catalog ? catalogFeatureIds(options.catalog) : undefined;
|
|
6
|
+
const visited = new WeakSet();
|
|
7
|
+
const addFeature = (feature) => {
|
|
8
|
+
if (options.strictCatalog && knownFeatures && !knownFeatures.has(feature)) {
|
|
9
|
+
throw new Error(`Unknown feature emitted by derivation rule: ${feature}`);
|
|
10
|
+
}
|
|
11
|
+
features.add(feature);
|
|
12
|
+
};
|
|
13
|
+
const walk = (node, path) => {
|
|
14
|
+
if (isObject(node)) {
|
|
15
|
+
if (visited.has(node))
|
|
16
|
+
return;
|
|
17
|
+
visited.add(node);
|
|
18
|
+
}
|
|
19
|
+
const context = { path, add: addFeature };
|
|
20
|
+
const customChildren = [];
|
|
21
|
+
let hasCustomChildren = false;
|
|
22
|
+
for (const rule of options.rules) {
|
|
23
|
+
rule.visit(node, context);
|
|
24
|
+
if (rule.children) {
|
|
25
|
+
hasCustomChildren = true;
|
|
26
|
+
for (const child of rule.children(node, context)) {
|
|
27
|
+
customChildren.push(child);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (hasCustomChildren) {
|
|
32
|
+
for (const [index, child] of customChildren.entries()) {
|
|
33
|
+
walk(child, [...path, index]);
|
|
34
|
+
}
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
for (const [key, child] of defaultChildren(node)) {
|
|
38
|
+
walk(child, [...path, key]);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
walk(value, []);
|
|
42
|
+
return createSurface({ contractId: options.contractId, features });
|
|
43
|
+
}
|
|
44
|
+
function isObject(value) {
|
|
45
|
+
return typeof value === "object" && value !== null;
|
|
46
|
+
}
|
|
47
|
+
function defaultChildren(value) {
|
|
48
|
+
if (!isObject(value))
|
|
49
|
+
return [];
|
|
50
|
+
if (Array.isArray(value)) {
|
|
51
|
+
return value.map((entry, index) => [index, entry]);
|
|
52
|
+
}
|
|
53
|
+
if (value instanceof Map) {
|
|
54
|
+
return [...value.entries()].map(([key, entry]) => [String(key), entry]);
|
|
55
|
+
}
|
|
56
|
+
if (value instanceof Set) {
|
|
57
|
+
return [...value.values()].map((entry, index) => [index, entry]);
|
|
58
|
+
}
|
|
59
|
+
return Object.entries(value);
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=derive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"derive.js","sourceRoot":"","sources":["../src/derive.ts"],"names":[],"mappings":"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"}
|
package/dist/diff.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAa,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAEnF,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,uBAAuB,EAAE,KAAK,EAAE,uBAAuB,GAAG,YAAY,CA0BlH"}
|
package/dist/diff.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export function diffPackageContracts(before, after) {
|
|
2
|
+
const beforeFeatures = featureMap(before);
|
|
3
|
+
const afterFeatures = featureMap(after);
|
|
4
|
+
const addedFeatures = [];
|
|
5
|
+
const removedFeatures = [];
|
|
6
|
+
const changedFeatures = [];
|
|
7
|
+
const deprecatedFeatures = [];
|
|
8
|
+
for (const id of afterFeatures.keys()) {
|
|
9
|
+
if (!beforeFeatures.has(id))
|
|
10
|
+
addedFeatures.push(id);
|
|
11
|
+
}
|
|
12
|
+
for (const id of beforeFeatures.keys()) {
|
|
13
|
+
if (!afterFeatures.has(id))
|
|
14
|
+
removedFeatures.push(id);
|
|
15
|
+
}
|
|
16
|
+
for (const [id, feature] of afterFeatures) {
|
|
17
|
+
const previous = beforeFeatures.get(id);
|
|
18
|
+
if (previous && JSON.stringify(previous) !== JSON.stringify(feature))
|
|
19
|
+
changedFeatures.push(id);
|
|
20
|
+
if (feature.lifecycle === "deprecated")
|
|
21
|
+
deprecatedFeatures.push(id);
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
addedFeatures: addedFeatures.sort(),
|
|
25
|
+
removedFeatures: removedFeatures.sort(),
|
|
26
|
+
changedFeatures: changedFeatures.sort(),
|
|
27
|
+
deprecatedFeatures: deprecatedFeatures.sort()
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function featureMap(manifest) {
|
|
31
|
+
const out = new Map();
|
|
32
|
+
for (const catalog of manifest.catalogs ?? []) {
|
|
33
|
+
for (const feature of catalog.features)
|
|
34
|
+
out.set(feature.id, feature);
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=diff.js.map
|
package/dist/diff.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,oBAAoB,CAAC,MAA+B,EAAE,KAA8B;IAClG,MAAM,cAAc,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,aAAa,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,aAAa,GAAgB,EAAE,CAAC;IACtC,MAAM,eAAe,GAAgB,EAAE,CAAC;IACxC,MAAM,eAAe,GAAgB,EAAE,CAAC;IACxC,MAAM,kBAAkB,GAAgB,EAAE,CAAC;IAE3C,KAAK,MAAM,EAAE,IAAI,aAAa,CAAC,IAAI,EAAE,EAAE,CAAC;QACtC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACtD,CAAC;IACD,KAAK,MAAM,EAAE,IAAI,cAAc,CAAC,IAAI,EAAE,EAAE,CAAC;QACvC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACvD,CAAC;IACD,KAAK,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,aAAa,EAAE,CAAC;QAC1C,MAAM,QAAQ,GAAG,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACxC,IAAI,QAAQ,IAAI,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;YAAE,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC/F,IAAK,OAAkC,CAAC,SAAS,KAAK,YAAY;YAAE,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAClG,CAAC;IAED,OAAO;QACL,aAAa,EAAE,aAAa,CAAC,IAAI,EAAE;QACnC,eAAe,EAAE,eAAe,CAAC,IAAI,EAAE;QACvC,eAAe,EAAE,eAAe,CAAC,IAAI,EAAE;QACvC,kBAAkB,EAAE,kBAAkB,CAAC,IAAI,EAAE;KAC9C,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,QAAiC;IACnD,MAAM,GAAG,GAAG,IAAI,GAAG,EAAmB,CAAC;IACvC,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;QAC9C,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,QAAQ;YAAE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;IACvE,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
package/dist/hash.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ContractId, FeatureId, SurfaceHash } from "./model.js";
|
|
2
|
+
export declare function normalizeFeatureIds(features: Iterable<FeatureId>): FeatureId[];
|
|
3
|
+
export declare function surfaceHash(input: {
|
|
4
|
+
contractId: ContractId;
|
|
5
|
+
features: Iterable<FeatureId>;
|
|
6
|
+
}): SurfaceHash;
|
|
7
|
+
export declare function hashJson(value: unknown): SurfaceHash;
|
|
8
|
+
//# sourceMappingURL=hash.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAErE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,CAAC,SAAS,CAAC,GAAG,SAAS,EAAE,CAE9E;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE;IAAE,UAAU,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAA;CAAE,GAAG,WAAW,CAMzG;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAEpD"}
|
package/dist/hash.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
export function normalizeFeatureIds(features) {
|
|
3
|
+
return [...new Set([...features])].sort((a, b) => a.localeCompare(b));
|
|
4
|
+
}
|
|
5
|
+
export function surfaceHash(input) {
|
|
6
|
+
const payload = JSON.stringify({
|
|
7
|
+
contractId: input.contractId,
|
|
8
|
+
features: normalizeFeatureIds(input.features)
|
|
9
|
+
});
|
|
10
|
+
return `sha256:${createHash("sha256").update(payload).digest("hex")}`;
|
|
11
|
+
}
|
|
12
|
+
export function hashJson(value) {
|
|
13
|
+
return `sha256:${createHash("sha256").update(JSON.stringify(value)).digest("hex")}`;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=hash.js.map
|
package/dist/hash.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.js","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC,MAAM,UAAU,mBAAmB,CAAC,QAA6B;IAC/D,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;AACxE,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAgE;IAC1F,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC;QAC7B,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,QAAQ,EAAE,mBAAmB,CAAC,KAAK,CAAC,QAAQ,CAAC;KAC9C,CAAC,CAAC;IACH,OAAO,UAAU,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAiB,CAAC;AACvF,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,OAAO,UAAU,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAiB,CAAC;AACrG,CAAC"}
|