@tutti-os/desktop-update-admission 0.0.274 → 0.0.275
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 +35 -2
- package/dist/chunk-2GH725V2.js +59 -0
- package/dist/chunk-2GH725V2.js.map +1 -0
- package/dist/{chunk-TRALK3GT.js → chunk-BESTTWBK.js} +8 -2
- package/dist/chunk-BESTTWBK.js.map +1 -0
- package/dist/chunk-DBINBWFF.js +185 -0
- package/dist/chunk-DBINBWFF.js.map +1 -0
- package/dist/{chunk-ZQTIYDGZ.js → chunk-K55GAWZZ.js} +23 -4
- package/dist/chunk-K55GAWZZ.js.map +1 -0
- package/dist/contracts/index.d.ts +27 -1
- package/dist/contracts/index.js +3 -1
- package/dist/development/index.d.ts +2 -0
- package/dist/development/index.js +3 -1
- package/dist/development/index.js.map +1 -1
- package/dist/development/mock-server-cli.js +2 -1
- package/dist/development/mock-server-cli.js.map +1 -1
- package/dist/electron-main/index.d.ts +18 -3
- package/dist/electron-main/index.js +67 -5
- package/dist/electron-main/index.js.map +1 -1
- package/dist/feature-availability/index.d.ts +24 -0
- package/dist/feature-availability/index.js +19 -0
- package/dist/feature-availability/index.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +19 -1
- package/dist/preload/index.d.ts +7 -2
- package/dist/preload/index.js +22 -1
- package/dist/preload/index.js.map +1 -1
- package/package.json +7 -3
- package/dist/chunk-TRALK3GT.js.map +0 -1
- package/dist/chunk-ZQTIYDGZ.js.map +0 -1
package/README.md
CHANGED
|
@@ -5,8 +5,9 @@ shared by Tutti Desktop and TSH Desktop.
|
|
|
5
5
|
|
|
6
6
|
The package owns the client contract, response validation, startup and
|
|
7
7
|
foreground admission lifecycle, mandatory updater lease, Electron upgrade
|
|
8
|
-
window binding,
|
|
9
|
-
|
|
8
|
+
window binding, feature-availability validation and persistent cache, trusted
|
|
9
|
+
main IPC registration, preload API factories, shared React presentation, and
|
|
10
|
+
default i18n resources.
|
|
10
11
|
|
|
11
12
|
Consumers still own:
|
|
12
13
|
|
|
@@ -50,6 +51,7 @@ standalone loopback server, never by both:
|
|
|
50
51
|
| `DESKTOP_UPDATE_ADMISSION_POLICY` | Selects one policy outcome. |
|
|
51
52
|
| `DESKTOP_UPDATE_ADMISSION_POLICY_SEQUENCE` | Selects a comma-separated per-client outcome sequence such as `upgradeRequired@1.1.0,disabled`. |
|
|
52
53
|
| `DESKTOP_UPDATE_ADMISSION_SCENARIO` | Selects one named policy scenario instead of individual policy fields. |
|
|
54
|
+
| `DESKTOP_UPDATE_ADMISSION_FEATURE_KEYS` | Supplies a comma-separated feature key list returned by the policy mock. |
|
|
53
55
|
| `DESKTOP_UPDATE_ADMISSION_MOCK_SERVER_PORT` | Selects the loopback CLI port; omit it for an ephemeral port. |
|
|
54
56
|
|
|
55
57
|
The shortest startup-blocking scenario is:
|
|
@@ -110,3 +112,34 @@ accidentally gain a second policy source.
|
|
|
110
112
|
The server binds only to `127.0.0.1`. Simulated installation never invokes a
|
|
111
113
|
real installer or application restart and is rendered as a distinct
|
|
112
114
|
development-only completion state.
|
|
115
|
+
|
|
116
|
+
## Feature availability
|
|
117
|
+
|
|
118
|
+
The optional `featureAvailability.keys` response envelope is independent from
|
|
119
|
+
the minimum-version decision. A valid envelope replaces the current in-memory
|
|
120
|
+
snapshot and the exact-identity cache. A missing or invalid envelope retains
|
|
121
|
+
the previous snapshot and cannot change admission behavior.
|
|
122
|
+
|
|
123
|
+
Desktop hosts create one runtime with the same product, platform,
|
|
124
|
+
architecture, and current version used by the admission request. The cache is
|
|
125
|
+
stored at `<userData>/desktop-feature-availability-v1.json`, uses atomic
|
|
126
|
+
replacement, has no time expiry, and is accepted only when all identity fields
|
|
127
|
+
match. It stores no minimum version or admission decision. With no matching
|
|
128
|
+
cache every feature query returns `false`.
|
|
129
|
+
|
|
130
|
+
Main and renderer consumers use the shared runtime and preload API:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
const snapshot = await desktopApi.featureAvailability.getSnapshot();
|
|
134
|
+
const enabled = await desktopApi.featureAvailability.isSupported(
|
|
135
|
+
"workspace.exampleFeature"
|
|
136
|
+
);
|
|
137
|
+
const unsubscribe = desktopApi.featureAvailability.onChanged((next) => {
|
|
138
|
+
console.log(next.keys);
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Hosts must register the shared IPC handlers with a sender check that admits
|
|
143
|
+
only their trusted business-window preload. Product code should consume keys
|
|
144
|
+
explicitly; the runtime does not merge remote keys into user preferences or
|
|
145
|
+
existing feature-flag systems.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// src/feature-availability/core.ts
|
|
2
|
+
var featureKeyPattern = /^[A-Za-z][A-Za-z0-9]*(?:[._-][A-Za-z0-9]+)*$/u;
|
|
3
|
+
var maximumFeatureKeyLength = 128;
|
|
4
|
+
var maximumFeatureKeys = 256;
|
|
5
|
+
function isValidDesktopFeatureKey(value) {
|
|
6
|
+
return value.length <= maximumFeatureKeyLength && featureKeyPattern.test(value);
|
|
7
|
+
}
|
|
8
|
+
function normalizeDesktopFeatureKeys(value) {
|
|
9
|
+
if (!Array.isArray(value) || value.length > maximumFeatureKeys) {
|
|
10
|
+
throw new Error(
|
|
11
|
+
"feature availability keys must be an array of at most 256 keys"
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
const keys = value.map((entry) => {
|
|
15
|
+
if (typeof entry !== "string" || !isValidDesktopFeatureKey(entry)) {
|
|
16
|
+
throw new Error("feature availability contains an invalid key");
|
|
17
|
+
}
|
|
18
|
+
return entry;
|
|
19
|
+
});
|
|
20
|
+
const sorted = [...new Set(keys)].sort();
|
|
21
|
+
if (sorted.length !== keys.length) {
|
|
22
|
+
throw new Error("feature availability contains duplicate keys");
|
|
23
|
+
}
|
|
24
|
+
return Object.freeze(sorted);
|
|
25
|
+
}
|
|
26
|
+
function parseDesktopFeatureAvailability(response) {
|
|
27
|
+
if (!response || typeof response !== "object") {
|
|
28
|
+
throw new Error("desktop version response must be an object");
|
|
29
|
+
}
|
|
30
|
+
const value = response.featureAvailability;
|
|
31
|
+
if (value === void 0) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
35
|
+
throw new Error("feature availability must be an object");
|
|
36
|
+
}
|
|
37
|
+
const record = value;
|
|
38
|
+
if (Object.keys(record).some((key) => key !== "keys") || !Object.prototype.hasOwnProperty.call(record, "keys")) {
|
|
39
|
+
throw new Error("feature availability has invalid fields");
|
|
40
|
+
}
|
|
41
|
+
return Object.freeze({
|
|
42
|
+
keys: normalizeDesktopFeatureKeys(record.keys)
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function desktopFeatureAvailabilityIdentityMatches(snapshot, identity) {
|
|
46
|
+
return snapshot.product === identity.product && snapshot.platform === identity.platform && snapshot.architecture === identity.architecture && snapshot.currentVersion === identity.currentVersion;
|
|
47
|
+
}
|
|
48
|
+
function isDesktopFeatureSupported(snapshot, key) {
|
|
49
|
+
return isValidDesktopFeatureKey(key) && snapshot.keys.includes(key);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export {
|
|
53
|
+
isValidDesktopFeatureKey,
|
|
54
|
+
normalizeDesktopFeatureKeys,
|
|
55
|
+
parseDesktopFeatureAvailability,
|
|
56
|
+
desktopFeatureAvailabilityIdentityMatches,
|
|
57
|
+
isDesktopFeatureSupported
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=chunk-2GH725V2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/feature-availability/core.ts"],"sourcesContent":["import type {\n DesktopFeatureAvailability,\n DesktopFeatureAvailabilitySnapshot,\n DesktopProduct,\n MinimumVersionCheckRequest\n} from \"../contracts/index.ts\";\n\nconst featureKeyPattern = /^[A-Za-z][A-Za-z0-9]*(?:[._-][A-Za-z0-9]+)*$/u;\nconst maximumFeatureKeyLength = 128;\nconst maximumFeatureKeys = 256;\n\nexport function isValidDesktopFeatureKey(value: string): boolean {\n return (\n value.length <= maximumFeatureKeyLength && featureKeyPattern.test(value)\n );\n}\n\nexport function normalizeDesktopFeatureKeys(value: unknown): readonly string[] {\n if (!Array.isArray(value) || value.length > maximumFeatureKeys) {\n throw new Error(\n \"feature availability keys must be an array of at most 256 keys\"\n );\n }\n const keys = value.map((entry) => {\n if (typeof entry !== \"string\" || !isValidDesktopFeatureKey(entry)) {\n throw new Error(\"feature availability contains an invalid key\");\n }\n return entry;\n });\n const sorted = [...new Set(keys)].sort();\n if (sorted.length !== keys.length) {\n throw new Error(\"feature availability contains duplicate keys\");\n }\n return Object.freeze(sorted);\n}\n\nexport function parseDesktopFeatureAvailability(\n response: unknown\n): DesktopFeatureAvailability | null {\n if (!response || typeof response !== \"object\") {\n throw new Error(\"desktop version response must be an object\");\n }\n const value = (response as Record<string, unknown>).featureAvailability;\n if (value === undefined) {\n return null;\n }\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(\"feature availability must be an object\");\n }\n const record = value as Record<string, unknown>;\n if (\n Object.keys(record).some((key) => key !== \"keys\") ||\n !Object.prototype.hasOwnProperty.call(record, \"keys\")\n ) {\n throw new Error(\"feature availability has invalid fields\");\n }\n return Object.freeze({\n keys: normalizeDesktopFeatureKeys(record.keys)\n });\n}\n\nexport function desktopFeatureAvailabilityIdentityMatches<\n TProduct extends DesktopProduct\n>(\n snapshot: DesktopFeatureAvailabilitySnapshot,\n identity: MinimumVersionCheckRequest<TProduct>\n): snapshot is DesktopFeatureAvailabilitySnapshot<TProduct> {\n return (\n snapshot.product === identity.product &&\n snapshot.platform === identity.platform &&\n snapshot.architecture === identity.architecture &&\n snapshot.currentVersion === identity.currentVersion\n );\n}\n\nexport function isDesktopFeatureSupported(\n snapshot: Pick<DesktopFeatureAvailabilitySnapshot, \"keys\">,\n key: string\n): boolean {\n return isValidDesktopFeatureKey(key) && snapshot.keys.includes(key);\n}\n"],"mappings":";AAOA,IAAM,oBAAoB;AAC1B,IAAM,0BAA0B;AAChC,IAAM,qBAAqB;AAEpB,SAAS,yBAAyB,OAAwB;AAC/D,SACE,MAAM,UAAU,2BAA2B,kBAAkB,KAAK,KAAK;AAE3E;AAEO,SAAS,4BAA4B,OAAmC;AAC7E,MAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,oBAAoB;AAC9D,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,QAAM,OAAO,MAAM,IAAI,CAAC,UAAU;AAChC,QAAI,OAAO,UAAU,YAAY,CAAC,yBAAyB,KAAK,GAAG;AACjE,YAAM,IAAI,MAAM,8CAA8C;AAAA,IAChE;AACA,WAAO;AAAA,EACT,CAAC;AACD,QAAM,SAAS,CAAC,GAAG,IAAI,IAAI,IAAI,CAAC,EAAE,KAAK;AACvC,MAAI,OAAO,WAAW,KAAK,QAAQ;AACjC,UAAM,IAAI,MAAM,8CAA8C;AAAA,EAChE;AACA,SAAO,OAAO,OAAO,MAAM;AAC7B;AAEO,SAAS,gCACd,UACmC;AACnC,MAAI,CAAC,YAAY,OAAO,aAAa,UAAU;AAC7C,UAAM,IAAI,MAAM,4CAA4C;AAAA,EAC9D;AACA,QAAM,QAAS,SAAqC;AACpD,MAAI,UAAU,QAAW;AACvB,WAAO;AAAA,EACT;AACA,MAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;AAC/D,UAAM,IAAI,MAAM,wCAAwC;AAAA,EAC1D;AACA,QAAM,SAAS;AACf,MACE,OAAO,KAAK,MAAM,EAAE,KAAK,CAAC,QAAQ,QAAQ,MAAM,KAChD,CAAC,OAAO,UAAU,eAAe,KAAK,QAAQ,MAAM,GACpD;AACA,UAAM,IAAI,MAAM,yCAAyC;AAAA,EAC3D;AACA,SAAO,OAAO,OAAO;AAAA,IACnB,MAAM,4BAA4B,OAAO,IAAI;AAAA,EAC/C,CAAC;AACH;AAEO,SAAS,0CAGd,UACA,UAC0D;AAC1D,SACE,SAAS,YAAY,SAAS,WAC9B,SAAS,aAAa,SAAS,YAC/B,SAAS,iBAAiB,SAAS,gBACnC,SAAS,mBAAmB,SAAS;AAEzC;AAEO,SAAS,0BACd,UACA,KACS;AACT,SAAO,yBAAyB,GAAG,KAAK,SAAS,KAAK,SAAS,GAAG;AACpE;","names":[]}
|
|
@@ -22,12 +22,18 @@ var desktopUpdateAdmissionIpcChannels = {
|
|
|
22
22
|
start: "desktop-update-admission:start",
|
|
23
23
|
state: "desktop-update-admission:state"
|
|
24
24
|
};
|
|
25
|
+
var desktopFeatureAvailabilityIpcChannels = {
|
|
26
|
+
changed: "desktop-feature-availability:changed",
|
|
27
|
+
getSnapshot: "desktop-feature-availability:get-snapshot",
|
|
28
|
+
isSupported: "desktop-feature-availability:is-supported"
|
|
29
|
+
};
|
|
25
30
|
|
|
26
31
|
export {
|
|
27
32
|
desktopUpdatePolicies,
|
|
28
33
|
desktopUpdateChannels,
|
|
29
34
|
desktopUpdateStatuses,
|
|
30
35
|
desktopProducts,
|
|
31
|
-
desktopUpdateAdmissionIpcChannels
|
|
36
|
+
desktopUpdateAdmissionIpcChannels,
|
|
37
|
+
desktopFeatureAvailabilityIpcChannels
|
|
32
38
|
};
|
|
33
|
-
//# sourceMappingURL=chunk-
|
|
39
|
+
//# sourceMappingURL=chunk-BESTTWBK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/contracts/index.ts"],"sourcesContent":["export const desktopUpdatePolicies = [\"off\", \"prompt\", \"auto\"] as const;\nexport type DesktopUpdatePolicy = (typeof desktopUpdatePolicies)[number];\n\nexport const desktopUpdateChannels = [\"stable\", \"rc\"] as const;\nexport type DesktopUpdateChannel = (typeof desktopUpdateChannels)[number];\n\nexport const desktopUpdateStatuses = [\n \"disabled\",\n \"unsupported\",\n \"idle\",\n \"checking\",\n \"available\",\n \"downloading\",\n \"downloaded\",\n \"up_to_date\",\n \"error\"\n] as const;\nexport type DesktopUpdateStatus = (typeof desktopUpdateStatuses)[number];\n\nexport interface ConfigureDesktopUpdatesInput {\n channel: DesktopUpdateChannel;\n policy: DesktopUpdatePolicy;\n}\n\nexport interface DesktopUpdateState {\n channel: DesktopUpdateChannel;\n checkedAt: string | null;\n currentVersion: string;\n downloadedBytes: number | null;\n downloadPercent: number | null;\n latestVersion: string | null;\n message: string | null;\n policy: DesktopUpdatePolicy;\n releaseDate: string | null;\n releaseName: string | null;\n releaseNotesUrl: string | null;\n status: DesktopUpdateStatus;\n totalBytes: number | null;\n}\n\nexport const desktopProducts = [\"tsh-desktop\", \"tutti-desktop\"] as const;\nexport type DesktopProduct = (typeof desktopProducts)[number];\nexport type DesktopPlatform = \"macos\" | \"windows\" | \"linux\";\nexport type DesktopArchitecture = \"arm64\" | \"x64\";\n\nexport interface MinimumVersionCheckRequest<\n TProduct extends DesktopProduct = DesktopProduct\n> {\n product: TProduct;\n platform: DesktopPlatform;\n architecture: DesktopArchitecture;\n currentVersion: string;\n}\n\nexport type MinimumVersionDecision =\n | \"allowed\"\n | \"upgradeRequired\"\n | \"notApplicable\";\n\nexport interface MinimumVersionCheckResponse<\n TProduct extends DesktopProduct = DesktopProduct\n> extends MinimumVersionCheckRequest<TProduct> {\n channel: DesktopUpdateChannel | \"unmanaged\";\n minimumVersion: string;\n decision: MinimumVersionDecision;\n reason:\n | \"unmanagedPrerelease\"\n | \"productDisabled\"\n | \"unsupportedRelease\"\n | \"belowMinimum\"\n | \"meetsMinimum\";\n policySource: \"\" | \"defaultMinimum\" | \"platformOverride\";\n policyRevision: string;\n featureAvailability?: DesktopFeatureAvailability;\n}\n\nexport interface DesktopFeatureAvailability {\n keys: readonly string[];\n}\n\nexport type DesktopFeatureAvailabilitySource = \"remote\" | \"cache\" | \"empty\";\n\nexport interface DesktopFeatureAvailabilitySnapshot<\n TProduct extends DesktopProduct = DesktopProduct\n> extends MinimumVersionCheckRequest<TProduct> {\n policyRevision: string | null;\n fetchedAt: string | null;\n source: DesktopFeatureAvailabilitySource;\n keys: readonly string[];\n}\n\nexport interface DesktopFeatureAvailabilityRuntime<\n TProduct extends DesktopProduct = DesktopProduct\n> {\n getSnapshot(): DesktopFeatureAvailabilitySnapshot<TProduct>;\n isSupported(key: string): boolean;\n subscribe(\n listener: (snapshot: DesktopFeatureAvailabilitySnapshot<TProduct>) => void\n ): () => void;\n}\n\nexport interface DesktopFeatureAvailabilityApi {\n getSnapshot(): Promise<DesktopFeatureAvailabilitySnapshot>;\n isSupported(key: string): Promise<boolean>;\n onChanged(\n listener: (snapshot: DesktopFeatureAvailabilitySnapshot) => void\n ): () => void;\n}\n\nexport type MinimumVersionUpgradePhase =\n | \"blocked\"\n | \"checking\"\n | \"ready\"\n | \"downloading\"\n | \"downloaded\"\n | \"simulationComplete\"\n | \"error\"\n | \"released\";\n\nexport type MinimumVersionUpgradeError =\n | \"releaseBelowMinimum\"\n | \"updateUnavailable\"\n | \"policyCheckFailed\"\n | \"installFailed\"\n | \"updateFailed\";\n\nexport interface MinimumVersionUpgradeState<\n TProduct extends DesktopProduct = DesktopProduct\n> {\n phase: MinimumVersionUpgradePhase;\n check: MinimumVersionCheckResponse<TProduct>;\n update: DesktopUpdateState;\n message: MinimumVersionUpgradeError | null;\n}\n\nexport interface MandatoryDesktopUpdateTarget {\n channel: DesktopUpdateChannel;\n minimumVersion: string;\n policyRevision: string;\n}\n\nexport interface MandatoryDesktopUpdateSession {\n retarget(input: MandatoryDesktopUpdateTarget): void;\n prepare(): Promise<DesktopUpdateState>;\n downloadUpdate(): Promise<DesktopUpdateState>;\n installUpdate(): Promise<void>;\n release(options?: { restoreNormal?: boolean }): Promise<void>;\n}\n\nexport interface MinimumVersionAppUpdateService {\n getState(): DesktopUpdateState;\n acquireMandatorySession(\n input: MandatoryDesktopUpdateTarget\n ): Promise<MandatoryDesktopUpdateSession>;\n subscribe(listener: (state: DesktopUpdateState) => void): () => void;\n}\n\nexport interface DesktopUpdateAdmissionRuntime {\n checksEnabled: boolean;\n currentVersion: string;\n development: boolean;\n foregroundCheckIntervalMs: number;\n}\n\nexport const desktopUpdateAdmissionIpcChannels = {\n exit: \"desktop-update-admission:exit\",\n getState: \"desktop-update-admission:get-state\",\n later: \"desktop-update-admission:later\",\n manualDownload: \"desktop-update-admission:manual-download\",\n retry: \"desktop-update-admission:retry\",\n start: \"desktop-update-admission:start\",\n state: \"desktop-update-admission:state\"\n} as const;\n\nexport const desktopFeatureAvailabilityIpcChannels = {\n changed: \"desktop-feature-availability:changed\",\n getSnapshot: \"desktop-feature-availability:get-snapshot\",\n isSupported: \"desktop-feature-availability:is-supported\"\n} as const;\n\nexport interface DesktopMinimumVersionApi {\n getState(): Promise<MinimumVersionUpgradeState | null>;\n start(): Promise<MinimumVersionUpgradeState | null>;\n retry(): Promise<MinimumVersionUpgradeState | null>;\n later(): Promise<void>;\n openManualDownload(): Promise<void>;\n exit(): Promise<void>;\n onState(listener: (state: MinimumVersionUpgradeState) => void): () => void;\n}\n"],"mappings":";AAAO,IAAM,wBAAwB,CAAC,OAAO,UAAU,MAAM;AAGtD,IAAM,wBAAwB,CAAC,UAAU,IAAI;AAG7C,IAAM,wBAAwB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAwBO,IAAM,kBAAkB,CAAC,eAAe,eAAe;AA4HvD,IAAM,oCAAoC;AAAA,EAC/C,MAAM;AAAA,EACN,UAAU;AAAA,EACV,OAAO;AAAA,EACP,gBAAgB;AAAA,EAChB,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACT;AAEO,IAAM,wCAAwC;AAAA,EACnD,SAAS;AAAA,EACT,aAAa;AAAA,EACb,aAAa;AACf;","names":[]}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import {
|
|
2
|
+
desktopFeatureAvailabilityIdentityMatches,
|
|
3
|
+
isDesktopFeatureSupported,
|
|
4
|
+
normalizeDesktopFeatureKeys,
|
|
5
|
+
parseDesktopFeatureAvailability
|
|
6
|
+
} from "./chunk-2GH725V2.js";
|
|
7
|
+
|
|
8
|
+
// src/feature-availability/runtime.ts
|
|
9
|
+
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
10
|
+
import { dirname } from "node:path";
|
|
11
|
+
var cacheSchemaVersion = "tutti.desktop-feature-availability-cache.v1";
|
|
12
|
+
function log(logger, level, details) {
|
|
13
|
+
logger[level](`[desktop-feature-availability] ${JSON.stringify(details)}`);
|
|
14
|
+
}
|
|
15
|
+
function emptySnapshot(identity) {
|
|
16
|
+
return Object.freeze({
|
|
17
|
+
...identity,
|
|
18
|
+
fetchedAt: null,
|
|
19
|
+
keys: Object.freeze([]),
|
|
20
|
+
policyRevision: null,
|
|
21
|
+
source: "empty"
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function parseCacheDocument(raw, identity) {
|
|
25
|
+
const value = JSON.parse(raw);
|
|
26
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
27
|
+
throw new Error("feature availability cache must be an object");
|
|
28
|
+
}
|
|
29
|
+
const record = value;
|
|
30
|
+
if (record.schemaVersion !== cacheSchemaVersion || typeof record.product !== "string" || typeof record.platform !== "string" || typeof record.architecture !== "string" || typeof record.currentVersion !== "string" || typeof record.policyRevision !== "string" || !record.policyRevision.trim() || typeof record.fetchedAt !== "string" || !record.fetchedAt.trim()) {
|
|
31
|
+
throw new Error("feature availability cache has invalid metadata");
|
|
32
|
+
}
|
|
33
|
+
const snapshot = {
|
|
34
|
+
architecture: record.architecture,
|
|
35
|
+
currentVersion: record.currentVersion,
|
|
36
|
+
fetchedAt: record.fetchedAt,
|
|
37
|
+
keys: normalizeDesktopFeatureKeys(record.keys),
|
|
38
|
+
platform: record.platform,
|
|
39
|
+
policyRevision: record.policyRevision,
|
|
40
|
+
product: record.product,
|
|
41
|
+
source: "cache"
|
|
42
|
+
};
|
|
43
|
+
if (!desktopFeatureAvailabilityIdentityMatches(snapshot, identity)) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
return Object.freeze(snapshot);
|
|
47
|
+
}
|
|
48
|
+
async function loadCache(cacheFilePath, identity, logger) {
|
|
49
|
+
try {
|
|
50
|
+
const raw = await readFile(cacheFilePath, "utf8");
|
|
51
|
+
const cached = parseCacheDocument(raw, identity);
|
|
52
|
+
if (!cached) {
|
|
53
|
+
log(logger, "info", {
|
|
54
|
+
result: "ignored",
|
|
55
|
+
stage: "cache-read",
|
|
56
|
+
reason: "identityMismatch"
|
|
57
|
+
});
|
|
58
|
+
return emptySnapshot(identity);
|
|
59
|
+
}
|
|
60
|
+
log(logger, "info", {
|
|
61
|
+
count: cached.keys.length,
|
|
62
|
+
policyRevision: cached.policyRevision,
|
|
63
|
+
result: "success",
|
|
64
|
+
stage: "cache-read"
|
|
65
|
+
});
|
|
66
|
+
return cached;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
|
|
69
|
+
if (code !== "ENOENT") {
|
|
70
|
+
log(logger, "error", {
|
|
71
|
+
error: error instanceof Error ? error.message : String(error),
|
|
72
|
+
result: "failure",
|
|
73
|
+
stage: "cache-read"
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return emptySnapshot(identity);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function writeCache(cacheFilePath, snapshot) {
|
|
80
|
+
await mkdir(dirname(cacheFilePath), { recursive: true });
|
|
81
|
+
const temporaryPath = `${cacheFilePath}.${process.pid}.${Date.now()}.tmp`;
|
|
82
|
+
const document = {
|
|
83
|
+
architecture: snapshot.architecture,
|
|
84
|
+
currentVersion: snapshot.currentVersion,
|
|
85
|
+
fetchedAt: snapshot.fetchedAt,
|
|
86
|
+
keys: snapshot.keys,
|
|
87
|
+
platform: snapshot.platform,
|
|
88
|
+
policyRevision: snapshot.policyRevision,
|
|
89
|
+
product: snapshot.product,
|
|
90
|
+
schemaVersion: cacheSchemaVersion
|
|
91
|
+
};
|
|
92
|
+
try {
|
|
93
|
+
await writeFile(temporaryPath, `${JSON.stringify(document)}
|
|
94
|
+
`, {
|
|
95
|
+
encoding: "utf8",
|
|
96
|
+
mode: 384
|
|
97
|
+
});
|
|
98
|
+
await rename(temporaryPath, cacheFilePath);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
await rm(temporaryPath, { force: true }).catch(() => void 0);
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function createDesktopFeatureAvailabilityRuntime(input) {
|
|
105
|
+
let snapshot = await loadCache(
|
|
106
|
+
input.cacheFilePath,
|
|
107
|
+
input.identity,
|
|
108
|
+
input.logger
|
|
109
|
+
);
|
|
110
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
111
|
+
let pendingWrite = Promise.resolve();
|
|
112
|
+
let disposed = false;
|
|
113
|
+
return {
|
|
114
|
+
acceptRemoteResponse(response, policyRevision) {
|
|
115
|
+
if (disposed) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const availability = parseDesktopFeatureAvailability(response);
|
|
119
|
+
if (!availability) {
|
|
120
|
+
log(input.logger, "info", {
|
|
121
|
+
result: "retained",
|
|
122
|
+
stage: "remote-response",
|
|
123
|
+
reason: "featureAvailabilityMissing"
|
|
124
|
+
});
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const next = Object.freeze({
|
|
128
|
+
...input.identity,
|
|
129
|
+
fetchedAt: (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
130
|
+
keys: availability.keys,
|
|
131
|
+
policyRevision,
|
|
132
|
+
source: "remote"
|
|
133
|
+
});
|
|
134
|
+
snapshot = next;
|
|
135
|
+
for (const listener of listeners) {
|
|
136
|
+
try {
|
|
137
|
+
listener(next);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
log(input.logger, "error", {
|
|
140
|
+
error: error instanceof Error ? error.message : String(error),
|
|
141
|
+
result: "failure",
|
|
142
|
+
stage: "subscriber-notify"
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
pendingWrite = pendingWrite.then(() => writeCache(input.cacheFilePath, next)).then(() => {
|
|
147
|
+
log(input.logger, "info", {
|
|
148
|
+
count: next.keys.length,
|
|
149
|
+
policyRevision,
|
|
150
|
+
result: "success",
|
|
151
|
+
stage: "cache-write"
|
|
152
|
+
});
|
|
153
|
+
}).catch((error) => {
|
|
154
|
+
log(input.logger, "error", {
|
|
155
|
+
error: error instanceof Error ? error.message : String(error),
|
|
156
|
+
result: "failure",
|
|
157
|
+
stage: "cache-write"
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
},
|
|
161
|
+
async dispose() {
|
|
162
|
+
disposed = true;
|
|
163
|
+
listeners.clear();
|
|
164
|
+
await pendingWrite;
|
|
165
|
+
},
|
|
166
|
+
getSnapshot() {
|
|
167
|
+
return snapshot;
|
|
168
|
+
},
|
|
169
|
+
isSupported(key) {
|
|
170
|
+
return isDesktopFeatureSupported(snapshot, key);
|
|
171
|
+
},
|
|
172
|
+
subscribe(listener) {
|
|
173
|
+
if (disposed) {
|
|
174
|
+
return () => void 0;
|
|
175
|
+
}
|
|
176
|
+
listeners.add(listener);
|
|
177
|
+
return () => listeners.delete(listener);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export {
|
|
183
|
+
createDesktopFeatureAvailabilityRuntime
|
|
184
|
+
};
|
|
185
|
+
//# sourceMappingURL=chunk-DBINBWFF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/feature-availability/runtime.ts"],"sourcesContent":["import { mkdir, readFile, rename, rm, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport type {\n DesktopFeatureAvailabilityRuntime,\n DesktopFeatureAvailabilitySnapshot,\n DesktopProduct,\n MinimumVersionCheckRequest\n} from \"../contracts/index.ts\";\nimport {\n desktopFeatureAvailabilityIdentityMatches,\n isDesktopFeatureSupported,\n normalizeDesktopFeatureKeys,\n parseDesktopFeatureAvailability\n} from \"./core.ts\";\n\nconst cacheSchemaVersion = \"tutti.desktop-feature-availability-cache.v1\";\n\nexport interface DesktopFeatureAvailabilityLogger {\n info(message: string): void;\n error(message: string): void;\n}\n\nexport interface MutableDesktopFeatureAvailabilityRuntime<\n TProduct extends DesktopProduct = DesktopProduct\n> extends DesktopFeatureAvailabilityRuntime<TProduct> {\n acceptRemoteResponse(response: unknown, policyRevision: string): void;\n dispose(): Promise<void>;\n}\n\ninterface CacheDocument {\n schemaVersion: typeof cacheSchemaVersion;\n product: DesktopProduct;\n platform: MinimumVersionCheckRequest[\"platform\"];\n architecture: MinimumVersionCheckRequest[\"architecture\"];\n currentVersion: string;\n policyRevision: string;\n fetchedAt: string;\n keys: readonly string[];\n}\n\nfunction log(\n logger: DesktopFeatureAvailabilityLogger,\n level: \"info\" | \"error\",\n details: Record<string, unknown>\n): void {\n logger[level](`[desktop-feature-availability] ${JSON.stringify(details)}`);\n}\n\nfunction emptySnapshot<TProduct extends DesktopProduct>(\n identity: MinimumVersionCheckRequest<TProduct>\n): DesktopFeatureAvailabilitySnapshot<TProduct> {\n return Object.freeze({\n ...identity,\n fetchedAt: null,\n keys: Object.freeze([]),\n policyRevision: null,\n source: \"empty\"\n });\n}\n\nfunction parseCacheDocument<TProduct extends DesktopProduct>(\n raw: string,\n identity: MinimumVersionCheckRequest<TProduct>\n): DesktopFeatureAvailabilitySnapshot<TProduct> | null {\n const value: unknown = JSON.parse(raw);\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(\"feature availability cache must be an object\");\n }\n const record = value as Record<string, unknown>;\n if (\n record.schemaVersion !== cacheSchemaVersion ||\n typeof record.product !== \"string\" ||\n typeof record.platform !== \"string\" ||\n typeof record.architecture !== \"string\" ||\n typeof record.currentVersion !== \"string\" ||\n typeof record.policyRevision !== \"string\" ||\n !record.policyRevision.trim() ||\n typeof record.fetchedAt !== \"string\" ||\n !record.fetchedAt.trim()\n ) {\n throw new Error(\"feature availability cache has invalid metadata\");\n }\n const snapshot: DesktopFeatureAvailabilitySnapshot = {\n architecture:\n record.architecture as MinimumVersionCheckRequest[\"architecture\"],\n currentVersion: record.currentVersion,\n fetchedAt: record.fetchedAt,\n keys: normalizeDesktopFeatureKeys(record.keys),\n platform: record.platform as MinimumVersionCheckRequest[\"platform\"],\n policyRevision: record.policyRevision,\n product: record.product as DesktopProduct,\n source: \"cache\"\n };\n if (!desktopFeatureAvailabilityIdentityMatches(snapshot, identity)) {\n return null;\n }\n return Object.freeze(snapshot);\n}\n\nasync function loadCache<TProduct extends DesktopProduct>(\n cacheFilePath: string,\n identity: MinimumVersionCheckRequest<TProduct>,\n logger: DesktopFeatureAvailabilityLogger\n): Promise<DesktopFeatureAvailabilitySnapshot<TProduct>> {\n try {\n const raw = await readFile(cacheFilePath, \"utf8\");\n const cached = parseCacheDocument(raw, identity);\n if (!cached) {\n log(logger, \"info\", {\n result: \"ignored\",\n stage: \"cache-read\",\n reason: \"identityMismatch\"\n });\n return emptySnapshot(identity);\n }\n log(logger, \"info\", {\n count: cached.keys.length,\n policyRevision: cached.policyRevision,\n result: \"success\",\n stage: \"cache-read\"\n });\n return cached;\n } catch (error) {\n const code =\n error && typeof error === \"object\" && \"code\" in error\n ? String(error.code)\n : \"\";\n if (code !== \"ENOENT\") {\n log(logger, \"error\", {\n error: error instanceof Error ? error.message : String(error),\n result: \"failure\",\n stage: \"cache-read\"\n });\n }\n return emptySnapshot(identity);\n }\n}\n\nasync function writeCache(\n cacheFilePath: string,\n snapshot: DesktopFeatureAvailabilitySnapshot\n): Promise<void> {\n await mkdir(dirname(cacheFilePath), { recursive: true });\n const temporaryPath = `${cacheFilePath}.${process.pid}.${Date.now()}.tmp`;\n const document: CacheDocument = {\n architecture: snapshot.architecture,\n currentVersion: snapshot.currentVersion,\n fetchedAt: snapshot.fetchedAt!,\n keys: snapshot.keys,\n platform: snapshot.platform,\n policyRevision: snapshot.policyRevision!,\n product: snapshot.product,\n schemaVersion: cacheSchemaVersion\n };\n try {\n await writeFile(temporaryPath, `${JSON.stringify(document)}\\n`, {\n encoding: \"utf8\",\n mode: 0o600\n });\n await rename(temporaryPath, cacheFilePath);\n } catch (error) {\n await rm(temporaryPath, { force: true }).catch(() => undefined);\n throw error;\n }\n}\n\nexport async function createDesktopFeatureAvailabilityRuntime<\n TProduct extends DesktopProduct\n>(input: {\n cacheFilePath: string;\n identity: MinimumVersionCheckRequest<TProduct>;\n logger: DesktopFeatureAvailabilityLogger;\n now?: () => Date;\n}): Promise<MutableDesktopFeatureAvailabilityRuntime<TProduct>> {\n let snapshot = await loadCache(\n input.cacheFilePath,\n input.identity,\n input.logger\n );\n const listeners = new Set<\n (value: DesktopFeatureAvailabilitySnapshot<TProduct>) => void\n >();\n let pendingWrite: Promise<void> = Promise.resolve();\n let disposed = false;\n\n return {\n acceptRemoteResponse(response, policyRevision) {\n if (disposed) {\n return;\n }\n const availability = parseDesktopFeatureAvailability(response);\n if (!availability) {\n log(input.logger, \"info\", {\n result: \"retained\",\n stage: \"remote-response\",\n reason: \"featureAvailabilityMissing\"\n });\n return;\n }\n const next: DesktopFeatureAvailabilitySnapshot<TProduct> = Object.freeze({\n ...input.identity,\n fetchedAt: (input.now?.() ?? new Date()).toISOString(),\n keys: availability.keys,\n policyRevision,\n source: \"remote\"\n });\n snapshot = next;\n for (const listener of listeners) {\n try {\n listener(next);\n } catch (error) {\n log(input.logger, \"error\", {\n error: error instanceof Error ? error.message : String(error),\n result: \"failure\",\n stage: \"subscriber-notify\"\n });\n }\n }\n pendingWrite = pendingWrite\n .then(() => writeCache(input.cacheFilePath, next))\n .then(() => {\n log(input.logger, \"info\", {\n count: next.keys.length,\n policyRevision,\n result: \"success\",\n stage: \"cache-write\"\n });\n })\n .catch((error) => {\n log(input.logger, \"error\", {\n error: error instanceof Error ? error.message : String(error),\n result: \"failure\",\n stage: \"cache-write\"\n });\n });\n },\n async dispose() {\n disposed = true;\n listeners.clear();\n await pendingWrite;\n },\n getSnapshot() {\n return snapshot;\n },\n isSupported(key) {\n return isDesktopFeatureSupported(snapshot, key);\n },\n subscribe(listener) {\n if (disposed) {\n return () => undefined;\n }\n listeners.add(listener);\n return () => listeners.delete(listener);\n }\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
validateMinimumVersionResponse
|
|
3
3
|
} from "./chunk-LV67FBCG.js";
|
|
4
|
+
import {
|
|
5
|
+
normalizeDesktopFeatureKeys
|
|
6
|
+
} from "./chunk-2GH725V2.js";
|
|
4
7
|
|
|
5
8
|
// src/development/environment.ts
|
|
6
9
|
var desktopUpdateAdmissionDevelopmentEnvironment = {
|
|
@@ -8,6 +11,7 @@ var desktopUpdateAdmissionDevelopmentEnvironment = {
|
|
|
8
11
|
development: "DESKTOP_UPDATE_ADMISSION_DEV",
|
|
9
12
|
download: "DESKTOP_UPDATE_ADMISSION_DOWNLOAD",
|
|
10
13
|
foregroundIntervalMs: "DESKTOP_UPDATE_ADMISSION_FOREGROUND_INTERVAL_MS",
|
|
14
|
+
featureKeys: "DESKTOP_UPDATE_ADMISSION_FEATURE_KEYS",
|
|
11
15
|
install: "DESKTOP_UPDATE_ADMISSION_INSTALL",
|
|
12
16
|
latestVersion: "DESKTOP_UPDATE_ADMISSION_LATEST_VERSION",
|
|
13
17
|
minimumVersion: "DESKTOP_UPDATE_ADMISSION_MINIMUM_VERSION",
|
|
@@ -108,6 +112,15 @@ function compareDevelopmentManagedVersions(left, right) {
|
|
|
108
112
|
}
|
|
109
113
|
|
|
110
114
|
// src/development/policyScenario.ts
|
|
115
|
+
function resolveFeatureKeys(env) {
|
|
116
|
+
const raw = env[desktopUpdateAdmissionDevelopmentEnvironment.featureKeys]?.trim();
|
|
117
|
+
if (!raw) {
|
|
118
|
+
return Object.freeze([]);
|
|
119
|
+
}
|
|
120
|
+
return normalizeDesktopFeatureKeys(
|
|
121
|
+
raw.split(",").map((value) => value.trim())
|
|
122
|
+
);
|
|
123
|
+
}
|
|
111
124
|
function parseOutcome(value) {
|
|
112
125
|
switch (value) {
|
|
113
126
|
case "allowed":
|
|
@@ -300,9 +313,11 @@ function resolveDesktopUpdateDevelopmentPolicyScenario(input) {
|
|
|
300
313
|
return null;
|
|
301
314
|
}
|
|
302
315
|
const scenario = {
|
|
316
|
+
featureKeys: resolveFeatureKeys(input.env),
|
|
303
317
|
policySteps: resolvePolicySteps(input.env)
|
|
304
318
|
};
|
|
305
319
|
return Object.freeze({
|
|
320
|
+
featureKeys: scenario.featureKeys,
|
|
306
321
|
policySteps: Object.freeze(
|
|
307
322
|
scenario.policySteps.map(
|
|
308
323
|
(step) => Object.freeze(
|
|
@@ -345,11 +360,14 @@ function waitForAbort(signal) {
|
|
|
345
360
|
});
|
|
346
361
|
});
|
|
347
362
|
}
|
|
348
|
-
function responseForStep(request, step, revision) {
|
|
363
|
+
function responseForStep(request, step, revision, featureKeys) {
|
|
349
364
|
const channel = developmentChannel(request.currentVersion);
|
|
350
365
|
const base = {
|
|
351
366
|
...request,
|
|
352
367
|
channel,
|
|
368
|
+
featureAvailability: {
|
|
369
|
+
keys: featureKeys
|
|
370
|
+
},
|
|
353
371
|
minimumVersion: "",
|
|
354
372
|
policyRevision: `development-policy-${revision}`,
|
|
355
373
|
policySource: ""
|
|
@@ -415,6 +433,7 @@ function responseForStep(request, step, revision) {
|
|
|
415
433
|
}
|
|
416
434
|
function createDevelopmentMinimumVersionChecker(policy, options = {}) {
|
|
417
435
|
const checkIndexes = /* @__PURE__ */ new Map();
|
|
436
|
+
const policyFeatureKeys = policy.featureKeys;
|
|
418
437
|
return async (request, signal) => {
|
|
419
438
|
if (options.expectedCurrentVersion && request.currentVersion !== options.expectedCurrentVersion) {
|
|
420
439
|
throw new Error(
|
|
@@ -437,10 +456,10 @@ function createDevelopmentMinimumVersionChecker(policy, options = {}) {
|
|
|
437
456
|
throw new Error(step.message);
|
|
438
457
|
}
|
|
439
458
|
validateDevelopmentPolicyScenarioForCurrentVersion(
|
|
440
|
-
{ policySteps: [step] },
|
|
459
|
+
{ featureKeys: policyFeatureKeys, policySteps: [step] },
|
|
441
460
|
request.currentVersion
|
|
442
461
|
);
|
|
443
|
-
return responseForStep(request, step, stepIndex + 1);
|
|
462
|
+
return responseForStep(request, step, stepIndex + 1, policyFeatureKeys);
|
|
444
463
|
};
|
|
445
464
|
}
|
|
446
465
|
|
|
@@ -600,4 +619,4 @@ export {
|
|
|
600
619
|
createDevelopmentMinimumVersionChecker,
|
|
601
620
|
startDesktopUpdateDevelopmentMockServer
|
|
602
621
|
};
|
|
603
|
-
//# sourceMappingURL=chunk-
|
|
622
|
+
//# sourceMappingURL=chunk-K55GAWZZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/development/environment.ts","../src/development/version.ts","../src/development/policyScenario.ts","../src/development/policyChecker.ts","../src/development/mockServer.ts"],"sourcesContent":["export const desktopUpdateAdmissionDevelopmentEnvironment = {\n currentVersion: \"DESKTOP_UPDATE_ADMISSION_CURRENT_VERSION\",\n development: \"DESKTOP_UPDATE_ADMISSION_DEV\",\n download: \"DESKTOP_UPDATE_ADMISSION_DOWNLOAD\",\n foregroundIntervalMs: \"DESKTOP_UPDATE_ADMISSION_FOREGROUND_INTERVAL_MS\",\n featureKeys: \"DESKTOP_UPDATE_ADMISSION_FEATURE_KEYS\",\n install: \"DESKTOP_UPDATE_ADMISSION_INSTALL\",\n latestVersion: \"DESKTOP_UPDATE_ADMISSION_LATEST_VERSION\",\n minimumVersion: \"DESKTOP_UPDATE_ADMISSION_MINIMUM_VERSION\",\n mockServerUrl: \"DESKTOP_UPDATE_ADMISSION_MOCK_SERVER_URL\",\n policy: \"DESKTOP_UPDATE_ADMISSION_POLICY\",\n policySequence: \"DESKTOP_UPDATE_ADMISSION_POLICY_SEQUENCE\",\n scenario: \"DESKTOP_UPDATE_ADMISSION_SCENARIO\",\n transport: \"DESKTOP_UPDATE_ADMISSION_TRANSPORT\",\n updater: \"DESKTOP_UPDATE_ADMISSION_UPDATER\"\n} as const;\n\nexport function invalidDevelopmentScenario(message: string): never {\n throw new Error(`invalid desktop update development scenario: ${message}`);\n}\n\nexport function readRequiredDevelopmentEnvironment(\n env: Readonly<Record<string, string | undefined>>,\n name: string\n): string {\n const value = env[name]?.trim();\n if (!value) {\n return invalidDevelopmentScenario(`${name} is required`);\n }\n return value;\n}\n\nexport function readDevelopmentEnabled(\n env: Readonly<Record<string, string | undefined>>\n): boolean {\n const name = desktopUpdateAdmissionDevelopmentEnvironment.development;\n const value = env[name]?.trim().toLowerCase();\n if (!value || [\"0\", \"false\", \"no\", \"off\"].includes(value)) {\n return false;\n }\n if ([\"1\", \"true\", \"yes\", \"on\"].includes(value)) {\n return true;\n }\n return invalidDevelopmentScenario(`${name} must be a boolean flag`);\n}\n","import type { DesktopUpdateChannel } from \"../contracts/index.ts\";\nimport { invalidDevelopmentScenario } from \"./environment.ts\";\n\nconst strictSemVerPattern =\n /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?(?:\\+([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?$/u;\nconst managedStablePattern =\n /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$/u;\nconst managedRcPattern =\n /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)-rc\\.(0|[1-9]\\d*)$/u;\n\nexport type DevelopmentManagedVersion = {\n channel: DesktopUpdateChannel;\n core: [string, string, string];\n rc: string | null;\n};\n\nexport function validateStrictDevelopmentSemVer(\n value: string,\n name: string\n): string {\n const normalized = value.trim();\n const match = strictSemVerPattern.exec(normalized);\n if (!match) {\n return invalidDevelopmentScenario(`${name} must be valid SemVer`);\n }\n const prerelease = match[4];\n if (prerelease) {\n for (const identifier of prerelease.split(\".\")) {\n if (\n /^\\d+$/u.test(identifier) &&\n identifier.length > 1 &&\n identifier[0] === \"0\"\n ) {\n return invalidDevelopmentScenario(`${name} must be valid SemVer`);\n }\n }\n }\n return normalized;\n}\n\nexport function parseDevelopmentManagedVersion(\n value: string\n): DevelopmentManagedVersion | null {\n const stable = managedStablePattern.exec(value);\n if (stable) {\n return {\n channel: \"stable\",\n core: [stable[1]!, stable[2]!, stable[3]!],\n rc: null\n };\n }\n const rc = managedRcPattern.exec(value);\n if (!rc) {\n return null;\n }\n return {\n channel: \"rc\",\n core: [rc[1]!, rc[2]!, rc[3]!],\n rc: rc[4]!\n };\n}\n\nfunction compareNumericIdentifier(left: string, right: string): number {\n if (left.length !== right.length) {\n return left.length < right.length ? -1 : 1;\n }\n return left === right ? 0 : left < right ? -1 : 1;\n}\n\nexport function compareDevelopmentManagedVersions(\n left: DevelopmentManagedVersion,\n right: DevelopmentManagedVersion\n): number {\n for (const index of [0, 1, 2] as const) {\n const compared = compareNumericIdentifier(\n left.core[index],\n right.core[index]\n );\n if (compared !== 0) {\n return compared;\n }\n }\n if (left.rc === right.rc) {\n return 0;\n }\n if (left.rc === null) {\n return 1;\n }\n if (right.rc === null) {\n return -1;\n }\n return compareNumericIdentifier(left.rc, right.rc);\n}\n","import {\n desktopUpdateAdmissionDevelopmentEnvironment,\n invalidDevelopmentScenario,\n readDevelopmentEnabled\n} from \"./environment.ts\";\nimport {\n compareDevelopmentManagedVersions,\n parseDevelopmentManagedVersion,\n validateStrictDevelopmentSemVer\n} from \"./version.ts\";\nimport { normalizeDesktopFeatureKeys } from \"../feature-availability/core.ts\";\n\nexport type DesktopUpdateDevelopmentPolicyOutcome =\n | \"allowed\"\n | \"upgradeRequired\"\n | \"disabled\"\n | \"unsupported\"\n | \"unmanagedPrerelease\"\n | \"error\"\n | \"timeout\";\n\nexport type DesktopUpdateDevelopmentPolicyMinimum =\n | {\n kind: \"configured\";\n version: string;\n }\n | {\n kind: \"requestCurrentVersion\";\n };\n\nexport type DesktopUpdateDevelopmentPolicyStep =\n | {\n outcome: \"allowed\" | \"upgradeRequired\";\n minimum: DesktopUpdateDevelopmentPolicyMinimum;\n policySource: \"defaultMinimum\" | \"platformOverride\";\n }\n | {\n outcome: \"disabled\" | \"unsupported\" | \"unmanagedPrerelease\";\n }\n | {\n outcome: \"error\";\n message: string;\n }\n | {\n outcome: \"timeout\";\n };\n\nexport interface DesktopUpdateDevelopmentPolicyScenario {\n featureKeys: readonly string[];\n policySteps: readonly DesktopUpdateDevelopmentPolicyStep[];\n}\n\nfunction resolveFeatureKeys(\n env: Readonly<Record<string, string | undefined>>\n): readonly string[] {\n const raw =\n env[desktopUpdateAdmissionDevelopmentEnvironment.featureKeys]?.trim();\n if (!raw) {\n return Object.freeze([]);\n }\n return normalizeDesktopFeatureKeys(\n raw.split(\",\").map((value) => value.trim())\n );\n}\n\nfunction parseOutcome(value: string): DesktopUpdateDevelopmentPolicyOutcome {\n switch (value) {\n case \"allowed\":\n case \"upgradeRequired\":\n case \"disabled\":\n case \"unsupported\":\n case \"unmanagedPrerelease\":\n case \"error\":\n case \"timeout\":\n return value;\n default:\n return invalidDevelopmentScenario(\n `unknown policy outcome ${JSON.stringify(value)}`\n );\n }\n}\n\nfunction configuredMinimum(\n value: string,\n name: string\n): DesktopUpdateDevelopmentPolicyMinimum {\n const version = validateStrictDevelopmentSemVer(value, name);\n if (!parseDevelopmentManagedVersion(version)) {\n return invalidDevelopmentScenario(\n `${name} must be a managed stable or RC version`\n );\n }\n return { kind: \"configured\", version };\n}\n\nfunction createPolicyStep(\n token: string,\n fallbackMinimumVersion: string | undefined\n): DesktopUpdateDevelopmentPolicyStep {\n const [rawOutcome, rawMinimumVersion, ...extra] = token.split(\"@\");\n if (!rawOutcome || extra.length > 0) {\n return invalidDevelopmentScenario(\n `invalid policy step ${JSON.stringify(token)}`\n );\n }\n const outcome = parseOutcome(rawOutcome.trim());\n if (outcome === \"allowed\" || outcome === \"upgradeRequired\") {\n return {\n minimum: configuredMinimum(\n rawMinimumVersion?.trim() || fallbackMinimumVersion || \"\",\n desktopUpdateAdmissionDevelopmentEnvironment.minimumVersion\n ),\n outcome,\n policySource: \"defaultMinimum\"\n };\n }\n if (rawMinimumVersion !== undefined) {\n return invalidDevelopmentScenario(\n `policy outcome ${outcome} must not include a minimum version`\n );\n }\n if (outcome === \"error\") {\n return {\n message: \"Development minimum-version policy check failed\",\n outcome\n };\n }\n return { outcome };\n}\n\nfunction createPresetPolicySteps(\n name: string,\n minimumVersion: string | undefined\n): readonly DesktopUpdateDevelopmentPolicyStep[] {\n const requiredMinimum = (): DesktopUpdateDevelopmentPolicyMinimum =>\n configuredMinimum(\n minimumVersion || \"\",\n desktopUpdateAdmissionDevelopmentEnvironment.minimumVersion\n );\n switch (name) {\n case \"startup-force-success\":\n case \"startup-updater-unavailable\":\n case \"startup-target-below-minimum\":\n case \"startup-download-error\":\n return [\n {\n minimum: requiredMinimum(),\n outcome: \"upgradeRequired\",\n policySource: \"defaultMinimum\"\n }\n ];\n case \"startup-policy-timeout\":\n return [{ outcome: \"timeout\" }];\n case \"retry-policy-released\":\n return [\n {\n minimum: requiredMinimum(),\n outcome: \"upgradeRequired\",\n policySource: \"defaultMinimum\"\n },\n { outcome: \"disabled\" }\n ];\n case \"foreground-upgrade-required\":\n return [\n {\n minimum: { kind: \"requestCurrentVersion\" },\n outcome: \"allowed\",\n policySource: \"defaultMinimum\"\n },\n {\n minimum: requiredMinimum(),\n outcome: \"upgradeRequired\",\n policySource: \"defaultMinimum\"\n }\n ];\n default:\n return invalidDevelopmentScenario(\n `unknown named scenario ${JSON.stringify(name)}`\n );\n }\n}\n\nfunction resolvePolicySteps(\n env: Readonly<Record<string, string | undefined>>\n): readonly DesktopUpdateDevelopmentPolicyStep[] {\n const names = desktopUpdateAdmissionDevelopmentEnvironment;\n const fallbackMinimumVersion = env[names.minimumVersion]?.trim();\n const preset = env[names.scenario]?.trim();\n if (preset) {\n for (const conflictingName of [names.policy, names.policySequence]) {\n if (env[conflictingName]?.trim()) {\n return invalidDevelopmentScenario(\n `${names.scenario} and ${conflictingName} are mutually exclusive`\n );\n }\n }\n return createPresetPolicySteps(preset, fallbackMinimumVersion);\n }\n const sequence = env[names.policySequence]?.trim();\n const single = env[names.policy]?.trim();\n if (sequence && single) {\n return invalidDevelopmentScenario(\n `${names.policy} and ${names.policySequence} are mutually exclusive`\n );\n }\n const rawSteps = sequence\n ? sequence.split(\",\").map((value) => value.trim())\n : single\n ? [single]\n : invalidDevelopmentScenario(\n `${names.policy} or ${names.policySequence} is required`\n );\n if (rawSteps.some((value) => value.length === 0)) {\n return invalidDevelopmentScenario(\n `${names.policySequence} contains an empty step`\n );\n }\n return rawSteps.map((token) =>\n createPolicyStep(token, fallbackMinimumVersion)\n );\n}\n\nfunction configuredVersion(\n minimum: DesktopUpdateDevelopmentPolicyMinimum,\n currentVersion: string\n): string {\n return minimum.kind === \"configured\" ? minimum.version : currentVersion;\n}\n\nexport function validateDevelopmentPolicyScenarioForCurrentVersion(\n scenario: DesktopUpdateDevelopmentPolicyScenario,\n currentVersion: string\n): void {\n const normalizedCurrent = validateStrictDevelopmentSemVer(\n currentVersion,\n desktopUpdateAdmissionDevelopmentEnvironment.currentVersion\n );\n const current = parseDevelopmentManagedVersion(normalizedCurrent);\n for (const step of scenario.policySteps) {\n if (step.outcome === \"unmanagedPrerelease\") {\n if (current) {\n invalidDevelopmentScenario(\n \"unmanagedPrerelease requires an unmanaged currentVersion\"\n );\n }\n continue;\n }\n if (step.outcome !== \"allowed\" && step.outcome !== \"upgradeRequired\") {\n if (\n (step.outcome === \"disabled\" || step.outcome === \"unsupported\") &&\n !current\n ) {\n invalidDevelopmentScenario(\n `${step.outcome} requires a managed currentVersion`\n );\n }\n continue;\n }\n if (!current) {\n invalidDevelopmentScenario(\n `${step.outcome} requires a managed currentVersion`\n );\n }\n const minimumVersion = configuredVersion(step.minimum, normalizedCurrent);\n const minimum = parseDevelopmentManagedVersion(minimumVersion);\n if (!minimum || minimum.channel !== current.channel) {\n invalidDevelopmentScenario(\n `minimumVersion must use the ${current.channel} channel`\n );\n }\n const compared = compareDevelopmentManagedVersions(current, minimum);\n if (step.outcome === \"allowed\" && compared < 0) {\n invalidDevelopmentScenario(\n \"allowed requires currentVersion to meet minimumVersion\"\n );\n }\n if (step.outcome === \"upgradeRequired\" && compared >= 0) {\n invalidDevelopmentScenario(\n \"upgradeRequired requires currentVersion below minimumVersion\"\n );\n }\n }\n}\n\nexport function resolveDesktopUpdateDevelopmentPolicyScenario(input: {\n env: Readonly<Record<string, string | undefined>>;\n}): DesktopUpdateDevelopmentPolicyScenario | null {\n if (!readDevelopmentEnabled(input.env)) {\n return null;\n }\n const scenario: DesktopUpdateDevelopmentPolicyScenario = {\n featureKeys: resolveFeatureKeys(input.env),\n policySteps: resolvePolicySteps(input.env)\n };\n return Object.freeze({\n featureKeys: scenario.featureKeys,\n policySteps: Object.freeze(\n scenario.policySteps.map((step) =>\n Object.freeze(\n \"minimum\" in step\n ? {\n ...step,\n minimum: Object.freeze({ ...step.minimum })\n }\n : { ...step }\n )\n )\n )\n });\n}\n","import type {\n DesktopProduct,\n MinimumVersionCheckRequest,\n MinimumVersionCheckResponse\n} from \"../contracts/index.ts\";\nimport { validateMinimumVersionResponse } from \"../core/index.ts\";\nimport type {\n DesktopUpdateDevelopmentPolicyMinimum,\n DesktopUpdateDevelopmentPolicyScenario,\n DesktopUpdateDevelopmentPolicyStep\n} from \"./policyScenario.ts\";\nimport { validateDevelopmentPolicyScenarioForCurrentVersion } from \"./policyScenario.ts\";\n\nfunction developmentChannel(\n currentVersion: string\n): \"stable\" | \"rc\" | \"unmanaged\" {\n if (\n /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)-rc\\.(0|[1-9]\\d*)$/u.test(\n currentVersion\n )\n ) {\n return \"rc\";\n }\n if (\n /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$/u.test(\n currentVersion\n )\n ) {\n return \"stable\";\n }\n return \"unmanaged\";\n}\n\nfunction abortError(): Error {\n const error = new Error(\"development minimum-version policy check aborted\");\n error.name = \"AbortError\";\n return error;\n}\n\nfunction waitForAbort(signal: AbortSignal): Promise<never> {\n if (signal.aborted) {\n return Promise.reject(abortError());\n }\n return new Promise<never>((_resolve, reject) => {\n signal.addEventListener(\"abort\", () => reject(abortError()), {\n once: true\n });\n });\n}\n\nfunction responseForStep<TProduct extends DesktopProduct>(\n request: MinimumVersionCheckRequest<TProduct>,\n step: DesktopUpdateDevelopmentPolicyStep,\n revision: number,\n featureKeys: readonly string[]\n): MinimumVersionCheckResponse<TProduct> {\n const channel = developmentChannel(request.currentVersion);\n const base = {\n ...request,\n channel,\n featureAvailability: {\n keys: featureKeys\n },\n minimumVersion: \"\",\n policyRevision: `development-policy-${revision}`,\n policySource: \"\"\n } as const;\n const minimumVersion = (\n minimum: DesktopUpdateDevelopmentPolicyMinimum\n ): string =>\n minimum.kind === \"configured\" ? minimum.version : request.currentVersion;\n switch (step.outcome) {\n case \"allowed\":\n return validateMinimumVersionResponse(\n {\n ...base,\n channel,\n decision: \"allowed\",\n minimumVersion: minimumVersion(step.minimum),\n policySource: step.policySource,\n reason: \"meetsMinimum\"\n },\n request\n );\n case \"upgradeRequired\":\n return validateMinimumVersionResponse(\n {\n ...base,\n channel,\n decision: \"upgradeRequired\",\n minimumVersion: minimumVersion(step.minimum),\n policySource: step.policySource,\n reason: \"belowMinimum\"\n },\n request\n );\n case \"disabled\":\n return validateMinimumVersionResponse(\n {\n ...base,\n decision: \"notApplicable\",\n reason: \"productDisabled\"\n },\n request\n );\n case \"unsupported\":\n return validateMinimumVersionResponse(\n {\n ...base,\n decision: \"notApplicable\",\n reason: \"unsupportedRelease\"\n },\n request\n );\n case \"unmanagedPrerelease\":\n return validateMinimumVersionResponse(\n {\n ...base,\n channel: \"unmanaged\",\n decision: \"notApplicable\",\n reason: \"unmanagedPrerelease\"\n },\n request\n );\n case \"error\":\n case \"timeout\":\n throw new Error(`cannot synthesize ${step.outcome} as a policy response`);\n }\n}\n\nexport function createDevelopmentMinimumVersionChecker(\n policy: DesktopUpdateDevelopmentPolicyScenario,\n options: {\n expectedCurrentVersion?: string;\n } = {}\n): <TProduct extends DesktopProduct>(\n request: MinimumVersionCheckRequest<TProduct>,\n signal: AbortSignal\n) => Promise<MinimumVersionCheckResponse<TProduct>> {\n const checkIndexes = new Map<string, number>();\n const policyFeatureKeys = policy.featureKeys;\n return async <TProduct extends DesktopProduct>(\n request: MinimumVersionCheckRequest<TProduct>,\n signal: AbortSignal\n ): Promise<MinimumVersionCheckResponse<TProduct>> => {\n if (\n options.expectedCurrentVersion &&\n request.currentVersion !== options.expectedCurrentVersion\n ) {\n throw new Error(\n `development policy expected currentVersion ${options.expectedCurrentVersion}, received ${request.currentVersion}`\n );\n }\n const requestIdentity = [\n request.product,\n request.platform,\n request.architecture\n ].join(\":\");\n const checkIndex = checkIndexes.get(requestIdentity) ?? 0;\n const stepIndex = Math.min(checkIndex, policy.policySteps.length - 1);\n const step = policy.policySteps[stepIndex]!;\n checkIndexes.set(requestIdentity, checkIndex + 1);\n if (step.outcome === \"timeout\") {\n return await waitForAbort(signal);\n }\n if (step.outcome === \"error\") {\n throw new Error(step.message);\n }\n validateDevelopmentPolicyScenarioForCurrentVersion(\n { featureKeys: policyFeatureKeys, policySteps: [step] },\n request.currentVersion\n );\n return responseForStep(request, step, stepIndex + 1, policyFeatureKeys);\n };\n}\n","import {\n createServer,\n type IncomingMessage,\n type ServerResponse\n} from \"node:http\";\nimport type {\n DesktopProduct,\n MinimumVersionCheckRequest\n} from \"../contracts/index.ts\";\nimport { createDevelopmentMinimumVersionChecker } from \"./policyChecker.ts\";\nimport type { DesktopUpdateDevelopmentPolicyScenario } from \"./policyScenario.ts\";\n\nconst maximumRequestBodyBytes = 64 * 1_024;\nconst minimumVersionPath = \"/api/desktop/v1/public/desktop-version/check\";\n\nexport interface DesktopUpdateDevelopmentMockServer {\n readonly baseUrl: string;\n close(): Promise<void>;\n}\n\nclass InvalidDevelopmentMockRequestError extends Error {\n public constructor(message: string) {\n super(message);\n this.name = \"InvalidDevelopmentMockRequestError\";\n }\n}\n\nfunction invalidRequest(message: string): never {\n throw new InvalidDevelopmentMockRequestError(message);\n}\n\nfunction writeJson(\n response: ServerResponse,\n statusCode: number,\n value: unknown\n): void {\n const body = JSON.stringify(value);\n response.writeHead(statusCode, {\n \"content-length\": Buffer.byteLength(body),\n \"content-type\": \"application/json; charset=utf-8\"\n });\n response.end(body);\n}\n\nasync function readJson(request: IncomingMessage): Promise<unknown> {\n let size = 0;\n const chunks: Buffer[] = [];\n for await (const value of request) {\n const chunk = Buffer.isBuffer(value) ? value : Buffer.from(value);\n size += chunk.byteLength;\n if (size > maximumRequestBodyBytes) {\n return invalidRequest(\"request body exceeds 64 KiB\");\n }\n chunks.push(chunk);\n }\n try {\n return JSON.parse(Buffer.concat(chunks).toString(\"utf8\"));\n } catch {\n return invalidRequest(\"request body must contain valid JSON\");\n }\n}\n\nfunction normalizeRequest(\n value: unknown\n): MinimumVersionCheckRequest<DesktopProduct> {\n if (!value || typeof value !== \"object\") {\n return invalidRequest(\"request must be an object\");\n }\n const candidate = value as Record<string, unknown>;\n if (\n candidate.product !== \"tsh-desktop\" &&\n candidate.product !== \"tutti-desktop\"\n ) {\n return invalidRequest(\"request has an invalid product\");\n }\n if (\n candidate.platform !== \"macos\" &&\n candidate.platform !== \"windows\" &&\n candidate.platform !== \"linux\"\n ) {\n return invalidRequest(\"request has an invalid platform\");\n }\n if (candidate.architecture !== \"arm64\" && candidate.architecture !== \"x64\") {\n return invalidRequest(\"request has an invalid architecture\");\n }\n if (\n typeof candidate.currentVersion !== \"string\" ||\n candidate.currentVersion.trim() === \"\"\n ) {\n return invalidRequest(\"request has an invalid currentVersion\");\n }\n return {\n architecture: candidate.architecture,\n currentVersion: candidate.currentVersion,\n platform: candidate.platform,\n product: candidate.product\n };\n}\n\nexport async function startDesktopUpdateDevelopmentMockServer(input: {\n port?: number;\n policy: DesktopUpdateDevelopmentPolicyScenario;\n}): Promise<DesktopUpdateDevelopmentMockServer> {\n const checker = createDevelopmentMinimumVersionChecker(input.policy);\n const server = createServer(async (request, response) => {\n if (request.url === \"/healthz\") {\n if (request.method !== \"GET\") {\n response.writeHead(405, { allow: \"GET\" });\n response.end();\n return;\n }\n writeJson(response, 200, { status: \"ok\" });\n return;\n }\n if (request.url !== minimumVersionPath) {\n writeJson(response, 404, { error: \"not found\" });\n return;\n }\n if (request.method !== \"POST\") {\n response.writeHead(405, { allow: \"POST\" });\n response.end();\n return;\n }\n if (\n !String(request.headers[\"content-type\"] ?? \"\")\n .toLowerCase()\n .startsWith(\"application/json\")\n ) {\n writeJson(response, 415, {\n error: \"content-type must be application/json\"\n });\n return;\n }\n const abortController = new AbortController();\n request.once(\"aborted\", () => abortController.abort());\n response.once(\"close\", () => {\n if (!response.writableEnded) {\n abortController.abort();\n }\n });\n try {\n const policyRequest = normalizeRequest(await readJson(request));\n const result = await checker(policyRequest, abortController.signal);\n if (!response.destroyed) {\n writeJson(response, 200, result);\n }\n } catch (error) {\n if (abortController.signal.aborted || response.destroyed) {\n return;\n }\n const message = error instanceof Error ? error.message : String(error);\n const statusCode =\n error instanceof InvalidDevelopmentMockRequestError ? 400 : 500;\n writeJson(response, statusCode, { error: message });\n }\n });\n const port = input.port ?? 0;\n if (!Number.isSafeInteger(port) || port < 0 || port > 65_535) {\n throw new Error(\"development mock server port must be between 0 and 65535\");\n }\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(port, \"127.0.0.1\", () => {\n server.removeListener(\"error\", reject);\n resolve();\n });\n });\n const address = server.address();\n if (!address || typeof address === \"string\") {\n await new Promise<void>((resolve) => server.close(() => resolve()));\n throw new Error(\"development mock server did not expose a TCP address\");\n }\n return {\n baseUrl: `http://127.0.0.1:${address.port}`,\n close: () =>\n new Promise<void>((resolve, reject) => {\n server.close((error) => {\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n });\n })\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -41,6 +41,27 @@ interface MinimumVersionCheckResponse<TProduct extends DesktopProduct = DesktopP
|
|
|
41
41
|
reason: "unmanagedPrerelease" | "productDisabled" | "unsupportedRelease" | "belowMinimum" | "meetsMinimum";
|
|
42
42
|
policySource: "" | "defaultMinimum" | "platformOverride";
|
|
43
43
|
policyRevision: string;
|
|
44
|
+
featureAvailability?: DesktopFeatureAvailability;
|
|
45
|
+
}
|
|
46
|
+
interface DesktopFeatureAvailability {
|
|
47
|
+
keys: readonly string[];
|
|
48
|
+
}
|
|
49
|
+
type DesktopFeatureAvailabilitySource = "remote" | "cache" | "empty";
|
|
50
|
+
interface DesktopFeatureAvailabilitySnapshot<TProduct extends DesktopProduct = DesktopProduct> extends MinimumVersionCheckRequest<TProduct> {
|
|
51
|
+
policyRevision: string | null;
|
|
52
|
+
fetchedAt: string | null;
|
|
53
|
+
source: DesktopFeatureAvailabilitySource;
|
|
54
|
+
keys: readonly string[];
|
|
55
|
+
}
|
|
56
|
+
interface DesktopFeatureAvailabilityRuntime<TProduct extends DesktopProduct = DesktopProduct> {
|
|
57
|
+
getSnapshot(): DesktopFeatureAvailabilitySnapshot<TProduct>;
|
|
58
|
+
isSupported(key: string): boolean;
|
|
59
|
+
subscribe(listener: (snapshot: DesktopFeatureAvailabilitySnapshot<TProduct>) => void): () => void;
|
|
60
|
+
}
|
|
61
|
+
interface DesktopFeatureAvailabilityApi {
|
|
62
|
+
getSnapshot(): Promise<DesktopFeatureAvailabilitySnapshot>;
|
|
63
|
+
isSupported(key: string): Promise<boolean>;
|
|
64
|
+
onChanged(listener: (snapshot: DesktopFeatureAvailabilitySnapshot) => void): () => void;
|
|
44
65
|
}
|
|
45
66
|
type MinimumVersionUpgradePhase = "blocked" | "checking" | "ready" | "downloading" | "downloaded" | "simulationComplete" | "error" | "released";
|
|
46
67
|
type MinimumVersionUpgradeError = "releaseBelowMinimum" | "updateUnavailable" | "policyCheckFailed" | "installFailed" | "updateFailed";
|
|
@@ -84,6 +105,11 @@ declare const desktopUpdateAdmissionIpcChannels: {
|
|
|
84
105
|
readonly start: "desktop-update-admission:start";
|
|
85
106
|
readonly state: "desktop-update-admission:state";
|
|
86
107
|
};
|
|
108
|
+
declare const desktopFeatureAvailabilityIpcChannels: {
|
|
109
|
+
readonly changed: "desktop-feature-availability:changed";
|
|
110
|
+
readonly getSnapshot: "desktop-feature-availability:get-snapshot";
|
|
111
|
+
readonly isSupported: "desktop-feature-availability:is-supported";
|
|
112
|
+
};
|
|
87
113
|
interface DesktopMinimumVersionApi {
|
|
88
114
|
getState(): Promise<MinimumVersionUpgradeState | null>;
|
|
89
115
|
start(): Promise<MinimumVersionUpgradeState | null>;
|
|
@@ -94,4 +120,4 @@ interface DesktopMinimumVersionApi {
|
|
|
94
120
|
onState(listener: (state: MinimumVersionUpgradeState) => void): () => void;
|
|
95
121
|
}
|
|
96
122
|
|
|
97
|
-
export { type ConfigureDesktopUpdatesInput, type DesktopArchitecture, type DesktopMinimumVersionApi, type DesktopPlatform, type DesktopProduct, type DesktopUpdateAdmissionRuntime, type DesktopUpdateChannel, type DesktopUpdatePolicy, type DesktopUpdateState, type DesktopUpdateStatus, type MandatoryDesktopUpdateSession, type MandatoryDesktopUpdateTarget, type MinimumVersionAppUpdateService, type MinimumVersionCheckRequest, type MinimumVersionCheckResponse, type MinimumVersionDecision, type MinimumVersionUpgradeError, type MinimumVersionUpgradePhase, type MinimumVersionUpgradeState, desktopProducts, desktopUpdateAdmissionIpcChannels, desktopUpdateChannels, desktopUpdatePolicies, desktopUpdateStatuses };
|
|
123
|
+
export { type ConfigureDesktopUpdatesInput, type DesktopArchitecture, type DesktopFeatureAvailability, type DesktopFeatureAvailabilityApi, type DesktopFeatureAvailabilityRuntime, type DesktopFeatureAvailabilitySnapshot, type DesktopFeatureAvailabilitySource, type DesktopMinimumVersionApi, type DesktopPlatform, type DesktopProduct, type DesktopUpdateAdmissionRuntime, type DesktopUpdateChannel, type DesktopUpdatePolicy, type DesktopUpdateState, type DesktopUpdateStatus, type MandatoryDesktopUpdateSession, type MandatoryDesktopUpdateTarget, type MinimumVersionAppUpdateService, type MinimumVersionCheckRequest, type MinimumVersionCheckResponse, type MinimumVersionDecision, type MinimumVersionUpgradeError, type MinimumVersionUpgradePhase, type MinimumVersionUpgradeState, desktopFeatureAvailabilityIpcChannels, desktopProducts, desktopUpdateAdmissionIpcChannels, desktopUpdateChannels, desktopUpdatePolicies, desktopUpdateStatuses };
|
package/dist/contracts/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
+
desktopFeatureAvailabilityIpcChannels,
|
|
2
3
|
desktopProducts,
|
|
3
4
|
desktopUpdateAdmissionIpcChannels,
|
|
4
5
|
desktopUpdateChannels,
|
|
5
6
|
desktopUpdatePolicies,
|
|
6
7
|
desktopUpdateStatuses
|
|
7
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-BESTTWBK.js";
|
|
8
9
|
export {
|
|
10
|
+
desktopFeatureAvailabilityIpcChannels,
|
|
9
11
|
desktopProducts,
|
|
10
12
|
desktopUpdateAdmissionIpcChannels,
|
|
11
13
|
desktopUpdateChannels,
|
|
@@ -20,6 +20,7 @@ type DesktopUpdateDevelopmentPolicyStep = {
|
|
|
20
20
|
outcome: "timeout";
|
|
21
21
|
};
|
|
22
22
|
interface DesktopUpdateDevelopmentPolicyScenario {
|
|
23
|
+
featureKeys: readonly string[];
|
|
23
24
|
policySteps: readonly DesktopUpdateDevelopmentPolicyStep[];
|
|
24
25
|
}
|
|
25
26
|
declare function resolveDesktopUpdateDevelopmentPolicyScenario(input: {
|
|
@@ -119,6 +120,7 @@ declare const desktopUpdateAdmissionDevelopmentEnvironment: {
|
|
|
119
120
|
readonly development: "DESKTOP_UPDATE_ADMISSION_DEV";
|
|
120
121
|
readonly download: "DESKTOP_UPDATE_ADMISSION_DOWNLOAD";
|
|
121
122
|
readonly foregroundIntervalMs: "DESKTOP_UPDATE_ADMISSION_FOREGROUND_INTERVAL_MS";
|
|
123
|
+
readonly featureKeys: "DESKTOP_UPDATE_ADMISSION_FEATURE_KEYS";
|
|
122
124
|
readonly install: "DESKTOP_UPDATE_ADMISSION_INSTALL";
|
|
123
125
|
readonly latestVersion: "DESKTOP_UPDATE_ADMISSION_LATEST_VERSION";
|
|
124
126
|
readonly minimumVersion: "DESKTOP_UPDATE_ADMISSION_MINIMUM_VERSION";
|