@holokai/harbor-sdk 0.3.0 → 0.4.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/dist/build-inputs.json +6 -0
- package/dist/package.d.ts +160 -3
- package/dist/package.js +215 -10
- package/dist/runtime.d.ts +13 -5
- package/dist/runtime.js +1 -1
- package/package.json +1 -1
package/dist/package.d.ts
CHANGED
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
* The package contract: what makes an npm package an extension Harbor can run, and the installed catalog a tree carries.
|
|
3
3
|
* It imports nothing at run time, so Harbor's main process, the extension host and the build scripts all read it.
|
|
4
4
|
*/
|
|
5
|
+
import type { DeclaredGrant } from './contract';
|
|
5
6
|
import type { ExtensionManifest } from './manifest';
|
|
6
7
|
/** The extension-to-host API version. A host runs only extensions that declare it. */
|
|
7
8
|
export declare const EXTENSION_API_VERSION = 1;
|
|
8
|
-
/** Packages a tree holds exactly once: host-provided contracts and shared sign-in state. */
|
|
9
|
-
export declare const SINGLETON_PACKAGES: readonly ["@holokai/harbor-sdk", "@holokai/harbor-kit", "@holokai/harbor-google", "@holokai/harbor-microsoft", "@holokai/harbor-atlassian", "@holokai/harbor-relay"];
|
|
10
9
|
/** The `harbor` field of a published package's package.json: which kind of package it is. */
|
|
11
10
|
export type HarborPackageField = {
|
|
12
11
|
type: 'extension';
|
|
@@ -14,6 +13,17 @@ export type HarborPackageField = {
|
|
|
14
13
|
} | {
|
|
15
14
|
type: 'library';
|
|
16
15
|
};
|
|
16
|
+
/**
|
|
17
|
+
* Whether a tree must hold exactly one copy of a package: the SDK and every library. A library carries what extensions
|
|
18
|
+
* share across package lines: the host-provided contracts, a connection's sign-in state, and error classes one
|
|
19
|
+
* package throws and another matches with `instanceof`. A second copy splits any of them.
|
|
20
|
+
*/
|
|
21
|
+
export declare function isSingletonPackage(metadata: {
|
|
22
|
+
name: string;
|
|
23
|
+
harbor?: {
|
|
24
|
+
type?: string;
|
|
25
|
+
};
|
|
26
|
+
}): boolean;
|
|
17
27
|
/** The package.json fields Harbor reads. */
|
|
18
28
|
export interface ExtensionPackageMetadata {
|
|
19
29
|
name: string;
|
|
@@ -33,7 +43,7 @@ export interface InstalledCatalogEntry {
|
|
|
33
43
|
/** `pages/<name>/app.html` */
|
|
34
44
|
page: string;
|
|
35
45
|
}
|
|
36
|
-
/** `
|
|
46
|
+
/** `extension-release/catalog.json`: what one installed tree holds. */
|
|
37
47
|
export interface InstalledCatalog {
|
|
38
48
|
apiVersion: number;
|
|
39
49
|
/** The SDK version installed in this tree. */
|
|
@@ -77,3 +87,150 @@ export declare function catalogFor(input: {
|
|
|
77
87
|
manifest: unknown;
|
|
78
88
|
}[];
|
|
79
89
|
}): InstalledCatalog;
|
|
90
|
+
/** The folder an installed tree lives in: the shipped tree, an installed set and a set's archive all hold one. */
|
|
91
|
+
export declare const RELEASE_TREE_DIR = "extension-release";
|
|
92
|
+
/** The release metadata file's name inside a tree, written last when the tree is staged. */
|
|
93
|
+
export declare const RELEASE_METADATA_FILE = "release-metadata.json";
|
|
94
|
+
/** `extension-release/release-metadata.json`: what a staged tree is and what it was built for. */
|
|
95
|
+
export interface ReleaseMetadata {
|
|
96
|
+
/** A release set id, `shipped` for a tree staged from the pins, or `local` for one packed from the workspace. */
|
|
97
|
+
setId: string;
|
|
98
|
+
/** `<platform>-<arch>`, such as `darwin-arm64`. */
|
|
99
|
+
target: string;
|
|
100
|
+
apiVersion: number;
|
|
101
|
+
sdkVersion: string;
|
|
102
|
+
/** The Harbor-to-host IPC protocol of the tree's host (`EXTENSIONS_HEALTH.protocol`). */
|
|
103
|
+
hostProtocol: number;
|
|
104
|
+
/** Each extension's name and version. */
|
|
105
|
+
extensions: Record<string, string>;
|
|
106
|
+
/** The repository commit the tree was staged from. */
|
|
107
|
+
commit: string;
|
|
108
|
+
/** The sha256 of what the tree was staged from; a different value means the tree is stale. */
|
|
109
|
+
inputs: string;
|
|
110
|
+
/** A workspace tree's build digest of each package, by package name: which packages a restage replaces. */
|
|
111
|
+
packages?: Record<string, string>;
|
|
112
|
+
stagedAt: string;
|
|
113
|
+
}
|
|
114
|
+
/** Null for anything that is not release metadata with every field in shape. */
|
|
115
|
+
export declare function parseReleaseMetadata(value: unknown): ReleaseMetadata | null;
|
|
116
|
+
/** The targets a release set is built for: the released apps. A Linux build gets no runtime updates. */
|
|
117
|
+
export declare const RELEASE_TARGETS: readonly ["darwin-arm64", "win32-x64"];
|
|
118
|
+
export type ReleaseTarget = (typeof RELEASE_TARGETS)[number];
|
|
119
|
+
/** The lanes a release manifest is published on. */
|
|
120
|
+
export declare const RELEASE_LANES: readonly ["production", "beta"];
|
|
121
|
+
export type ReleaseLane = (typeof RELEASE_LANES)[number];
|
|
122
|
+
/** One extension a release set holds. */
|
|
123
|
+
export interface ReleaseSetExtension {
|
|
124
|
+
version: string;
|
|
125
|
+
/** The provider decides the process, and so the shared sign-in, the extension runs in. */
|
|
126
|
+
provider: string;
|
|
127
|
+
grants: readonly DeclaredGrant[];
|
|
128
|
+
}
|
|
129
|
+
/** One target's archive of a release set. */
|
|
130
|
+
export interface ReleaseArtifact {
|
|
131
|
+
/** Always `setArchiveKey(id, target)`: a manifest cannot point a download anywhere else. */
|
|
132
|
+
path: string;
|
|
133
|
+
/** `sha512-<base64>`, the form npm's integrity field uses. */
|
|
134
|
+
sha512: string;
|
|
135
|
+
bytes: number;
|
|
136
|
+
}
|
|
137
|
+
/** A set record: immutable, identical in the set's set.json and in every lane manifest that offers it. */
|
|
138
|
+
export interface ReleaseSet {
|
|
139
|
+
/** `ext-YYYY-MM-DD-NNN`: the UTC date of the build and the next free number that day, so ids sort by age. */
|
|
140
|
+
id: string;
|
|
141
|
+
apiVersion: number;
|
|
142
|
+
sdkVersion: string;
|
|
143
|
+
hostProtocol: number;
|
|
144
|
+
minHarborVersion: string;
|
|
145
|
+
maxHarborVersion?: string;
|
|
146
|
+
/** The sha256 of the set's package-lock.json; every target build checks it before `npm ci`. */
|
|
147
|
+
lockfileSha256: string;
|
|
148
|
+
/** By extension name. */
|
|
149
|
+
extensions: Record<string, ReleaseSetExtension>;
|
|
150
|
+
/** By target. */
|
|
151
|
+
artifacts: Record<string, ReleaseArtifact>;
|
|
152
|
+
}
|
|
153
|
+
/** release-manifest.json for one lane, signed on its own. */
|
|
154
|
+
export interface ReleaseManifest {
|
|
155
|
+
lane: ReleaseLane;
|
|
156
|
+
/** This lane's metadata version, raised by every publication; unrelated to set ids. */
|
|
157
|
+
sequence: number;
|
|
158
|
+
issuedAt: string;
|
|
159
|
+
/** Newest first; older sets stay listed for Harbor versions the newest does not fit. */
|
|
160
|
+
sets: ReleaseSet[];
|
|
161
|
+
}
|
|
162
|
+
/** The detached signature of a lane manifest: Ed25519 over exactly its served bytes. */
|
|
163
|
+
export interface ReleaseSignature {
|
|
164
|
+
keyId: string;
|
|
165
|
+
/** base64 */
|
|
166
|
+
signature: string;
|
|
167
|
+
}
|
|
168
|
+
/** A lane's latest.json: an untrusted pointer to its newest manifest. The signed manifest's own sequence is trusted. */
|
|
169
|
+
export interface LanePointer {
|
|
170
|
+
sequence: number;
|
|
171
|
+
}
|
|
172
|
+
/** Whether a value is a release set id: `ext-YYYY-MM-DD-NNN`. */
|
|
173
|
+
export declare const isReleaseSetId: (value: unknown) => value is string;
|
|
174
|
+
/** Whether set `a` is newer than set `b`. Set ids sort by age, so the later id is the newer set. */
|
|
175
|
+
export declare const isNewerSetId: (a: string, b: string) => boolean;
|
|
176
|
+
/** The store key of a set's archive for one target. */
|
|
177
|
+
export declare const setArchiveKey: (id: string, target: string) => string;
|
|
178
|
+
/** The store key of a set's one resolved closure. */
|
|
179
|
+
export declare const setLockfileKey: (id: string) => string;
|
|
180
|
+
/** The store key of a set's record. */
|
|
181
|
+
export declare const setRecordKey: (id: string) => string;
|
|
182
|
+
/** The store key of one lane manifest; the sequence is zero-padded to six digits. */
|
|
183
|
+
export declare const laneManifestKey: (lane: ReleaseLane, sequence: number) => string;
|
|
184
|
+
/** The store key of one lane manifest's signature. */
|
|
185
|
+
export declare const laneSignatureKey: (lane: ReleaseLane, sequence: number) => string;
|
|
186
|
+
/** The store key of a lane's pointer, the one object in the store that is replaced. */
|
|
187
|
+
export declare const lanePointerKey: (lane: ReleaseLane) => string;
|
|
188
|
+
/** A set record, or null when any field is out of shape. Only ReleaseSet's fields survive. */
|
|
189
|
+
export declare function parseReleaseSet(value: unknown): ReleaseSet | null;
|
|
190
|
+
/** A lane manifest, or null when it or any set it lists is out of shape, or it lists one set id twice. */
|
|
191
|
+
export declare function parseReleaseManifest(value: unknown): ReleaseManifest | null;
|
|
192
|
+
/** A lane manifest's signature file, or null when out of shape. */
|
|
193
|
+
export declare function parseReleaseSignature(value: unknown): ReleaseSignature | null;
|
|
194
|
+
/** A lane's latest.json, or null when out of shape. */
|
|
195
|
+
export declare function parseLanePointer(value: unknown): LanePointer | null;
|
|
196
|
+
/**
|
|
197
|
+
* Semver precedence of two versions: negative, zero or positive. A prerelease sorts before its release
|
|
198
|
+
* (`2.0.3-rc.1` < `2.0.3`). Throws on a value that is not `major.minor.patch[-prerelease]`.
|
|
199
|
+
*/
|
|
200
|
+
export declare function compareVersions(a: string, b: string): number;
|
|
201
|
+
/**
|
|
202
|
+
* Whether a version satisfies a caret range such as `^0.3.0`: at least the range's version, and below the next
|
|
203
|
+
* breaking one (`^1.2.3` < 2.0.0, `^0.3.0` < 0.4.0, `^0.0.3` < 0.0.4). A prerelease of that next breaking version is
|
|
204
|
+
* below it too. False for a range that is not a caret range.
|
|
205
|
+
*/
|
|
206
|
+
export declare function satisfiesCaret(version: string, range: string): boolean;
|
|
207
|
+
/** Why a set cannot run here, by protocol alone. */
|
|
208
|
+
export type Incompatibility = 'api_version' | 'sdk_version' | 'host_protocol' | 'harbor_version' | 'older_than_running' | 'no_artifact';
|
|
209
|
+
/**
|
|
210
|
+
* The first reason, in the order of Incompatibility, a set cannot run here, or null when it can. An extension the
|
|
211
|
+
* running tree holds that the set leaves out is not judged here: that is the rollout policy's `removed_extension`.
|
|
212
|
+
*/
|
|
213
|
+
export declare function incompatibility(set: ReleaseSet, here: {
|
|
214
|
+
apiVersion: number;
|
|
215
|
+
/** A caret range, such as `^0.3.0`. */
|
|
216
|
+
sdkRange: string;
|
|
217
|
+
hostProtocols: readonly number[];
|
|
218
|
+
harborVersion: string;
|
|
219
|
+
target: string;
|
|
220
|
+
/** Each extension's name and version in the tree in force. */
|
|
221
|
+
running: Record<string, string>;
|
|
222
|
+
}): Incompatibility | null;
|
|
223
|
+
/** Why a release policy refuses a set that is protocol-compatible. */
|
|
224
|
+
export type PolicyRefusal = 'new_extension' | 'removed_extension' | 'changes_provider' | 'organization';
|
|
225
|
+
/** Rollout and organization policy, apart from protocol. */
|
|
226
|
+
export interface ExtensionReleasePolicy {
|
|
227
|
+
/** The reason the set may not replace the running extensions, or null when it may. */
|
|
228
|
+
refusal(set: ReleaseSet, running: ReleaseSet['extensions']): PolicyRefusal | null;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* The first-party default: a set that adds an extension, drops one the running tree holds, or moves one to another
|
|
232
|
+
* provider waits for an app release.
|
|
233
|
+
*/
|
|
234
|
+
export declare const FIRST_PARTY_ROLLOUT: ExtensionReleasePolicy;
|
|
235
|
+
/** The grants of `declared` that `authorized` holds, by stable id; the rest are withheld. */
|
|
236
|
+
export declare function authorizedGrants(declared: readonly DeclaredGrant[], authorized: readonly DeclaredGrant[]): DeclaredGrant[];
|
package/dist/package.js
CHANGED
|
@@ -2,14 +2,9 @@ import {createRequire as __harborCreateRequire} from 'node:module'; const requir
|
|
|
2
2
|
|
|
3
3
|
// src/package.ts
|
|
4
4
|
var EXTENSION_API_VERSION = 1;
|
|
5
|
-
|
|
6
|
-
"@holokai/harbor-sdk"
|
|
7
|
-
|
|
8
|
-
"@holokai/harbor-google",
|
|
9
|
-
"@holokai/harbor-microsoft",
|
|
10
|
-
"@holokai/harbor-atlassian",
|
|
11
|
-
"@holokai/harbor-relay"
|
|
12
|
-
];
|
|
5
|
+
function isSingletonPackage(metadata) {
|
|
6
|
+
return metadata.name === "@holokai/harbor-sdk" || metadata.harbor?.type === "library";
|
|
7
|
+
}
|
|
13
8
|
var CATALOG_FILE = "catalog.json";
|
|
14
9
|
var NAME = /^[a-z][a-z0-9-]*$/;
|
|
15
10
|
var VERSION_CORE = /^\d+\.\d+\.\d+$/;
|
|
@@ -119,13 +114,223 @@ function catalogFor(input) {
|
|
|
119
114
|
extensions.sort((a, b) => a.name.localeCompare(b.name));
|
|
120
115
|
return { apiVersion: EXTENSION_API_VERSION, sdkVersion: input.sdkVersion, releaseSet: input.releaseSet, extensions };
|
|
121
116
|
}
|
|
117
|
+
var RELEASE_TREE_DIR = "extension-release";
|
|
118
|
+
var RELEASE_METADATA_FILE = "release-metadata.json";
|
|
119
|
+
var SHA256 = /^[0-9a-f]{64}$/;
|
|
120
|
+
var isNumber = (value) => typeof value === "number" && Number.isInteger(value);
|
|
121
|
+
function parseReleaseMetadata(value) {
|
|
122
|
+
if (!isRecord(value)) return null;
|
|
123
|
+
const { setId, target, apiVersion, sdkVersion, hostProtocol, extensions, commit, inputs, packages, stagedAt } = value;
|
|
124
|
+
if (!isString(setId) || setId === "" || !isString(target) || !/^[a-z0-9]+-[a-z0-9]+$/.test(target)) return null;
|
|
125
|
+
if (!isNumber(apiVersion) || !isNumber(hostProtocol) || !isString(sdkVersion) || !isVersion(sdkVersion))
|
|
126
|
+
return null;
|
|
127
|
+
if (!isString(commit) || !isString(inputs) || !SHA256.test(inputs) || !isString(stagedAt)) return null;
|
|
128
|
+
if (!isRecord(extensions)) return null;
|
|
129
|
+
const versions = {};
|
|
130
|
+
for (const [name, version] of Object.entries(extensions)) {
|
|
131
|
+
if (!NAME.test(name) || !isString(version) || !isVersion(version)) return null;
|
|
132
|
+
versions[name] = version;
|
|
133
|
+
}
|
|
134
|
+
if (packages !== void 0 && !(isRecord(packages) && Object.values(packages).every((d) => isString(d) && SHA256.test(d)))) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
setId,
|
|
139
|
+
target,
|
|
140
|
+
apiVersion,
|
|
141
|
+
sdkVersion,
|
|
142
|
+
hostProtocol,
|
|
143
|
+
extensions: versions,
|
|
144
|
+
commit,
|
|
145
|
+
inputs,
|
|
146
|
+
...packages === void 0 ? {} : { packages: { ...packages } },
|
|
147
|
+
stagedAt
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
var RELEASE_TARGETS = ["darwin-arm64", "win32-x64"];
|
|
151
|
+
var RELEASE_LANES = ["production", "beta"];
|
|
152
|
+
var SET_ID = /^ext-\d{4}-\d{2}-\d{2}-\d{3}$/;
|
|
153
|
+
var TARGET = /^[a-z0-9]+-[a-z0-9]+$/;
|
|
154
|
+
var SHA512_INTEGRITY = /^sha512-[A-Za-z0-9+/]+={0,2}$/;
|
|
155
|
+
var BASE64 = /^[A-Za-z0-9+/]+={0,2}$/;
|
|
156
|
+
var isPositiveInteger = (value) => isNumber(value) && value > 0;
|
|
157
|
+
var isReleaseSetId = (value) => isString(value) && SET_ID.test(value);
|
|
158
|
+
var isNewerSetId = (a, b) => a > b;
|
|
159
|
+
var setArchiveKey = (id, target) => `extensions/sets/${id}/${id}-${target}.tar.gz`;
|
|
160
|
+
var setLockfileKey = (id) => `extensions/sets/${id}/package-lock.json`;
|
|
161
|
+
var setRecordKey = (id) => `extensions/sets/${id}/set.json`;
|
|
162
|
+
var laneManifestKey = (lane, sequence) => `extensions/lanes/${lane}/manifests/${String(sequence).padStart(6, "0")}.json`;
|
|
163
|
+
var laneSignatureKey = (lane, sequence) => `${laneManifestKey(lane, sequence)}.sig`;
|
|
164
|
+
var lanePointerKey = (lane) => `extensions/lanes/${lane}/latest.json`;
|
|
165
|
+
function parseSetExtension(value) {
|
|
166
|
+
if (!isRecord(value)) return null;
|
|
167
|
+
const { version, provider, grants } = value;
|
|
168
|
+
if (!isString(version) || !isVersion(version) || !isString(provider) || !NAME.test(provider)) return null;
|
|
169
|
+
if (!isStringArray(grants) || !grants.every((grant) => DECLARED_GRANTS.has(grant))) return null;
|
|
170
|
+
return { version, provider, grants: [...grants] };
|
|
171
|
+
}
|
|
172
|
+
function parseArtifact(value, id, target) {
|
|
173
|
+
if (!isRecord(value)) return null;
|
|
174
|
+
const { path, sha512, bytes } = value;
|
|
175
|
+
if (path !== setArchiveKey(id, target)) return null;
|
|
176
|
+
if (!isString(sha512) || !SHA512_INTEGRITY.test(sha512) || !isPositiveInteger(bytes)) return null;
|
|
177
|
+
return { path, sha512, bytes };
|
|
178
|
+
}
|
|
179
|
+
function parseReleaseSet(value) {
|
|
180
|
+
if (!isRecord(value)) return null;
|
|
181
|
+
const { id, apiVersion, sdkVersion, hostProtocol, minHarborVersion, maxHarborVersion, lockfileSha256 } = value;
|
|
182
|
+
if (!isReleaseSetId(id) || !isPositiveInteger(apiVersion) || !isPositiveInteger(hostProtocol)) return null;
|
|
183
|
+
if (!isString(sdkVersion) || !isVersion(sdkVersion)) return null;
|
|
184
|
+
if (!isString(minHarborVersion) || !isVersion(minHarborVersion)) return null;
|
|
185
|
+
if (maxHarborVersion !== void 0 && !(isString(maxHarborVersion) && isVersion(maxHarborVersion))) return null;
|
|
186
|
+
if (!isString(lockfileSha256) || !SHA256.test(lockfileSha256)) return null;
|
|
187
|
+
if (!isRecord(value.extensions) || !isRecord(value.artifacts)) return null;
|
|
188
|
+
const extensions = {};
|
|
189
|
+
for (const [name, raw] of Object.entries(value.extensions)) {
|
|
190
|
+
const extension = parseSetExtension(raw);
|
|
191
|
+
if (!NAME.test(name) || extension === null) return null;
|
|
192
|
+
extensions[name] = extension;
|
|
193
|
+
}
|
|
194
|
+
const artifacts = {};
|
|
195
|
+
for (const [target, raw] of Object.entries(value.artifacts)) {
|
|
196
|
+
const artifact = TARGET.test(target) ? parseArtifact(raw, id, target) : null;
|
|
197
|
+
if (artifact === null) return null;
|
|
198
|
+
artifacts[target] = artifact;
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
id,
|
|
202
|
+
apiVersion,
|
|
203
|
+
sdkVersion,
|
|
204
|
+
hostProtocol,
|
|
205
|
+
minHarborVersion,
|
|
206
|
+
...maxHarborVersion === void 0 ? {} : { maxHarborVersion },
|
|
207
|
+
lockfileSha256,
|
|
208
|
+
extensions,
|
|
209
|
+
artifacts
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function parseReleaseManifest(value) {
|
|
213
|
+
if (!isRecord(value) || !Array.isArray(value.sets)) return null;
|
|
214
|
+
const { lane, sequence, issuedAt } = value;
|
|
215
|
+
if (!RELEASE_LANES.includes(lane) || !isPositiveInteger(sequence)) return null;
|
|
216
|
+
if (!isString(issuedAt) || issuedAt === "") return null;
|
|
217
|
+
const sets = [];
|
|
218
|
+
const seen = /* @__PURE__ */ new Set();
|
|
219
|
+
for (const raw of value.sets) {
|
|
220
|
+
const set = parseReleaseSet(raw);
|
|
221
|
+
if (set === null || seen.has(set.id)) return null;
|
|
222
|
+
seen.add(set.id);
|
|
223
|
+
sets.push(set);
|
|
224
|
+
}
|
|
225
|
+
return { lane, sequence, issuedAt, sets };
|
|
226
|
+
}
|
|
227
|
+
function parseReleaseSignature(value) {
|
|
228
|
+
if (!isRecord(value)) return null;
|
|
229
|
+
const { keyId, signature } = value;
|
|
230
|
+
if (!isString(keyId) || keyId === "" || !isString(signature)) return null;
|
|
231
|
+
if (!BASE64.test(signature) || signature.length % 4 !== 0) return null;
|
|
232
|
+
return { keyId, signature };
|
|
233
|
+
}
|
|
234
|
+
function parseLanePointer(value) {
|
|
235
|
+
if (!isRecord(value) || !isPositiveInteger(value.sequence)) return null;
|
|
236
|
+
return { sequence: value.sequence };
|
|
237
|
+
}
|
|
238
|
+
function parseVersion(value) {
|
|
239
|
+
if (!isVersion(value)) return null;
|
|
240
|
+
const dash = value.indexOf("-");
|
|
241
|
+
const core = (dash === -1 ? value : value.slice(0, dash)).split(".").map(Number);
|
|
242
|
+
return { core, prerelease: dash === -1 ? [] : value.slice(dash + 1).split(".") };
|
|
243
|
+
}
|
|
244
|
+
function compareIdentifiers(a, b) {
|
|
245
|
+
const aNumeric = /^\d+$/.test(a);
|
|
246
|
+
const bNumeric = /^\d+$/.test(b);
|
|
247
|
+
if (aNumeric && bNumeric) return Number(a) - Number(b);
|
|
248
|
+
if (aNumeric !== bNumeric) return aNumeric ? -1 : 1;
|
|
249
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
250
|
+
}
|
|
251
|
+
function compareParsed(a, b) {
|
|
252
|
+
for (let i = 0; i < 3; i++) {
|
|
253
|
+
const difference = a.core[i] - b.core[i];
|
|
254
|
+
if (difference !== 0) return Math.sign(difference);
|
|
255
|
+
}
|
|
256
|
+
if (a.prerelease.length === 0 || b.prerelease.length === 0) {
|
|
257
|
+
return Math.sign(b.prerelease.length) - Math.sign(a.prerelease.length);
|
|
258
|
+
}
|
|
259
|
+
for (let i = 0; i < Math.min(a.prerelease.length, b.prerelease.length); i++) {
|
|
260
|
+
const difference = compareIdentifiers(a.prerelease[i], b.prerelease[i]);
|
|
261
|
+
if (difference !== 0) return Math.sign(difference);
|
|
262
|
+
}
|
|
263
|
+
return Math.sign(a.prerelease.length - b.prerelease.length);
|
|
264
|
+
}
|
|
265
|
+
function compareVersions(a, b) {
|
|
266
|
+
const left = parseVersion(a);
|
|
267
|
+
const right = parseVersion(b);
|
|
268
|
+
if (left === null || right === null) throw new Error(`not a version: ${left === null ? a : b}`);
|
|
269
|
+
return compareParsed(left, right);
|
|
270
|
+
}
|
|
271
|
+
function satisfiesCaret(version, range) {
|
|
272
|
+
const floor = range.startsWith("^") ? parseVersion(range.slice(1)) : null;
|
|
273
|
+
const parsed = parseVersion(version);
|
|
274
|
+
if (floor === null || parsed === null || compareParsed(parsed, floor) < 0) return false;
|
|
275
|
+
const [major, minor, patch] = floor.core;
|
|
276
|
+
const ceiling = major > 0 ? [major + 1, 0, 0] : minor > 0 ? [0, minor + 1, 0] : [0, 0, patch + 1];
|
|
277
|
+
return compareParsed({ core: parsed.core, prerelease: [] }, { core: ceiling, prerelease: [] }) < 0;
|
|
278
|
+
}
|
|
279
|
+
function incompatibility(set, here) {
|
|
280
|
+
if (set.apiVersion !== here.apiVersion) return "api_version";
|
|
281
|
+
if (!satisfiesCaret(set.sdkVersion, here.sdkRange)) return "sdk_version";
|
|
282
|
+
if (!here.hostProtocols.includes(set.hostProtocol)) return "host_protocol";
|
|
283
|
+
if (compareVersions(here.harborVersion, set.minHarborVersion) < 0 || set.maxHarborVersion !== void 0 && compareVersions(here.harborVersion, set.maxHarborVersion) > 0) {
|
|
284
|
+
return "harbor_version";
|
|
285
|
+
}
|
|
286
|
+
for (const [name, version] of Object.entries(here.running)) {
|
|
287
|
+
const offered = set.extensions[name];
|
|
288
|
+
if (offered !== void 0 && compareVersions(offered.version, version) < 0) return "older_than_running";
|
|
289
|
+
}
|
|
290
|
+
if (set.artifacts[here.target] === void 0) return "no_artifact";
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
var FIRST_PARTY_ROLLOUT = {
|
|
294
|
+
refusal(set, running) {
|
|
295
|
+
if (Object.keys(set.extensions).some((name) => running[name] === void 0)) return "new_extension";
|
|
296
|
+
if (Object.keys(running).some((name) => set.extensions[name] === void 0)) return "removed_extension";
|
|
297
|
+
const moved = Object.entries(set.extensions).some(([name, ext]) => running[name]?.provider !== ext.provider);
|
|
298
|
+
return moved ? "changes_provider" : null;
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
function authorizedGrants(declared, authorized) {
|
|
302
|
+
const allowed = new Set(authorized);
|
|
303
|
+
return declared.filter((grant) => allowed.has(grant));
|
|
304
|
+
}
|
|
122
305
|
export {
|
|
123
306
|
CATALOG_FILE,
|
|
124
307
|
EXTENSION_API_VERSION,
|
|
125
|
-
|
|
308
|
+
FIRST_PARTY_ROLLOUT,
|
|
309
|
+
RELEASE_LANES,
|
|
310
|
+
RELEASE_METADATA_FILE,
|
|
311
|
+
RELEASE_TARGETS,
|
|
312
|
+
RELEASE_TREE_DIR,
|
|
313
|
+
authorizedGrants,
|
|
126
314
|
catalogFor,
|
|
315
|
+
compareVersions,
|
|
127
316
|
entryPathOf,
|
|
317
|
+
incompatibility,
|
|
318
|
+
isNewerSetId,
|
|
319
|
+
isReleaseSetId,
|
|
320
|
+
isSingletonPackage,
|
|
321
|
+
laneManifestKey,
|
|
322
|
+
lanePointerKey,
|
|
323
|
+
laneSignatureKey,
|
|
128
324
|
pagePathOf,
|
|
129
325
|
parseExtensionManifest,
|
|
130
|
-
parseInstalledCatalog
|
|
326
|
+
parseInstalledCatalog,
|
|
327
|
+
parseLanePointer,
|
|
328
|
+
parseReleaseManifest,
|
|
329
|
+
parseReleaseMetadata,
|
|
330
|
+
parseReleaseSet,
|
|
331
|
+
parseReleaseSignature,
|
|
332
|
+
satisfiesCaret,
|
|
333
|
+
setArchiveKey,
|
|
334
|
+
setLockfileKey,
|
|
335
|
+
setRecordKey
|
|
131
336
|
};
|
package/dist/runtime.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export declare const EXTENSIONS_ENV: {
|
|
|
31
31
|
readonly connection: "EXTENSIONS_CONNECTION";
|
|
32
32
|
/** The dev instance this server runs for. Its sign-in states name it, so the shared return reaches it. */
|
|
33
33
|
readonly instance: "EXTENSIONS_INSTANCE";
|
|
34
|
-
/** The installed catalog the server loads its extensions from (`
|
|
34
|
+
/** The installed catalog the server loads its extensions from (`extension-release/catalog.json`). */
|
|
35
35
|
readonly catalog: "EXTENSIONS_CATALOG";
|
|
36
36
|
};
|
|
37
37
|
/**
|
|
@@ -57,10 +57,18 @@ export interface CatalogLoadedMessage {
|
|
|
57
57
|
type: typeof EXTENSIONS_MESSAGE.catalogLoaded;
|
|
58
58
|
loaded: string[];
|
|
59
59
|
skipped: CatalogSkip[];
|
|
60
|
-
failed:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
failed: CatalogLoadFailure[];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* One catalog entry the server did not run: its module did not import (`import`, with the error's class name in
|
|
64
|
+
* `error`), its default export is not an extension (`not_extension`), or its definition names another extension
|
|
65
|
+
* (`name`) or another sign-in provider (`provider`) than its catalog entry. The others in the catalog still run.
|
|
66
|
+
*/
|
|
67
|
+
export interface CatalogLoadFailure {
|
|
68
|
+
name: string;
|
|
69
|
+
package: string;
|
|
70
|
+
reason?: 'import' | 'not_extension' | 'name' | 'provider';
|
|
71
|
+
error?: string;
|
|
64
72
|
}
|
|
65
73
|
/** An extension's tool list changed, so harbor lists that extension's tools again. */
|
|
66
74
|
export interface ToolsChangedMessage {
|
package/dist/runtime.js
CHANGED
|
@@ -18,7 +18,7 @@ var EXTENSIONS_ENV = {
|
|
|
18
18
|
connection: "EXTENSIONS_CONNECTION",
|
|
19
19
|
/** The dev instance this server runs for. Its sign-in states name it, so the shared return reaches it. */
|
|
20
20
|
instance: "EXTENSIONS_INSTANCE",
|
|
21
|
-
/** The installed catalog the server loads its extensions from (`
|
|
21
|
+
/** The installed catalog the server loads its extensions from (`extension-release/catalog.json`). */
|
|
22
22
|
catalog: "EXTENSIONS_CATALOG"
|
|
23
23
|
};
|
|
24
24
|
var EXTENSIONS_MESSAGE = {
|
package/package.json
CHANGED