@todesktop/shared 7.191.1 → 7.193.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/lib/cjs/desktopify.d.ts +26 -0
- package/lib/cjs/index.d.ts +2 -0
- package/lib/cjs/index.js +3 -1
- package/lib/cjs/introspection/__tests__/breakpoints.test.d.ts +1 -0
- package/lib/cjs/introspection/__tests__/breakpoints.test.js +59 -0
- package/lib/cjs/introspection/breakpoints.d.ts +45 -0
- package/lib/cjs/introspection/breakpoints.js +48 -0
- package/lib/cjs/introspection/status.d.ts +18 -0
- package/lib/cjs/introspection/status.js +82 -0
- package/lib/esm/desktopify.d.ts +26 -0
- package/lib/esm/index.d.ts +2 -0
- package/lib/esm/index.js +2 -0
- package/lib/esm/introspection/__tests__/breakpoints.test.d.ts +1 -0
- package/lib/esm/introspection/__tests__/breakpoints.test.js +57 -0
- package/lib/esm/introspection/breakpoints.d.ts +45 -0
- package/lib/esm/introspection/breakpoints.js +42 -0
- package/lib/esm/introspection/status.d.ts +18 -0
- package/lib/esm/introspection/status.js +77 -0
- package/package.json +4 -2
- package/src/desktopify.ts +46 -4
- package/src/index.cjs.ts +3 -1
- package/src/index.ts +2 -0
- package/src/introspection/__tests__/breakpoints.test.ts +92 -0
- package/src/introspection/breakpoints.ts +137 -0
- package/src/introspection/status.ts +101 -0
- package/vitest.config.ts +9 -0
package/lib/cjs/desktopify.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Configuration, PackagerOptions, PublishOptions } from 'app-builder-lib';
|
|
2
2
|
import { IApp2 } from './desktopify2';
|
|
3
|
+
import { BreakpointPauseLease, BreakpointQueueEntry, CurrentBreakpointState } from './introspection/breakpoints';
|
|
3
4
|
import { IApp } from './toDesktop';
|
|
4
5
|
type appBuilderLib = PackagerOptions & PublishOptions;
|
|
5
6
|
export interface IAppBuilderLib extends appBuilderLib {
|
|
@@ -92,6 +93,30 @@ export interface PlatformBuild {
|
|
|
92
93
|
status: BuildStatus;
|
|
93
94
|
}
|
|
94
95
|
export type CIRunner = 'azure' | 'circle';
|
|
96
|
+
export type IntrospectBreakpointStatus = 'error' | 'finished' | 'initializing' | 'paused' | 'ready' | 'resuming';
|
|
97
|
+
export type IntrospectShellStatus = 'connected' | 'connecting' | 'disconnected' | 'error' | 'initializing' | 'ready';
|
|
98
|
+
export interface IntrospectPlatformData {
|
|
99
|
+
breakpointQueue?: BreakpointQueueEntry[];
|
|
100
|
+
breakpointStatus: IntrospectBreakpointStatus;
|
|
101
|
+
connectedAt?: Date;
|
|
102
|
+
connectedUserId?: string;
|
|
103
|
+
createdAt: ISODate;
|
|
104
|
+
currentBreakpoint?: CurrentBreakpointState;
|
|
105
|
+
disconnectedAt?: Date;
|
|
106
|
+
enabled: boolean;
|
|
107
|
+
error?: string;
|
|
108
|
+
jtiExpiresAt?: Date;
|
|
109
|
+
pauseLease?: BreakpointPauseLease;
|
|
110
|
+
sessionJti?: string;
|
|
111
|
+
shellStatus: IntrospectShellStatus;
|
|
112
|
+
tunnelUrl?: string;
|
|
113
|
+
usedJti?: string;
|
|
114
|
+
}
|
|
115
|
+
export interface IntrospectData {
|
|
116
|
+
linux: IntrospectPlatformData;
|
|
117
|
+
mac: IntrospectPlatformData;
|
|
118
|
+
windows: IntrospectPlatformData;
|
|
119
|
+
}
|
|
95
120
|
export interface Build {
|
|
96
121
|
appCustomDomain?: string;
|
|
97
122
|
appName: string;
|
|
@@ -118,6 +143,7 @@ export interface Build {
|
|
|
118
143
|
hash?: string;
|
|
119
144
|
icon?: string;
|
|
120
145
|
id: string;
|
|
146
|
+
introspect?: IntrospectData;
|
|
121
147
|
isArtifactsPruned?: boolean;
|
|
122
148
|
isBeingCancelled?: boolean;
|
|
123
149
|
linux?: PlatformBuild;
|
package/lib/cjs/index.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export * from './desktopify';
|
|
|
3
3
|
export * from './desktopify2';
|
|
4
4
|
export * from './getSiteInfo';
|
|
5
5
|
export * from './hsm';
|
|
6
|
+
export * from './introspection/breakpoints';
|
|
7
|
+
export * from './introspection/status';
|
|
6
8
|
export * from './invitePermissionLabels';
|
|
7
9
|
export * from './personalAccessTokens';
|
|
8
10
|
export * from './plans';
|
package/lib/cjs/index.js
CHANGED
|
@@ -15,7 +15,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
exports.schemaVersion = void 0;
|
|
18
|
-
/* eslint-disable @typescript-eslint/no-
|
|
18
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
19
19
|
// CJS-specific entry point
|
|
20
20
|
// Path is relative to lib/cjs/ output directory
|
|
21
21
|
const packageJson = require('../../package.json');
|
|
@@ -24,6 +24,8 @@ __exportStar(require("./desktopify"), exports);
|
|
|
24
24
|
__exportStar(require("./desktopify2"), exports);
|
|
25
25
|
__exportStar(require("./getSiteInfo"), exports);
|
|
26
26
|
__exportStar(require("./hsm"), exports);
|
|
27
|
+
__exportStar(require("./introspection/breakpoints"), exports);
|
|
28
|
+
__exportStar(require("./introspection/status"), exports);
|
|
27
29
|
__exportStar(require("./invitePermissionLabels"), exports);
|
|
28
30
|
__exportStar(require("./personalAccessTokens"), exports);
|
|
29
31
|
__exportStar(require("./plans"), exports);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const breakpoints_1 = require("../breakpoints");
|
|
5
|
+
(0, vitest_1.describe)('isSameBreakpoint', () => {
|
|
6
|
+
(0, vitest_1.it)('matches identical phase breakpoints', () => {
|
|
7
|
+
(0, vitest_1.expect)((0, breakpoints_1.isSameBreakpoint)({ id: 'beforeInstall', type: 'phase' }, { id: 'beforeInstall', type: 'phase' })).toBe(true);
|
|
8
|
+
});
|
|
9
|
+
(0, vitest_1.it)('fails when breakpoint types differ', () => {
|
|
10
|
+
(0, vitest_1.expect)((0, breakpoints_1.isSameBreakpoint)({ id: 'beforeInstall', type: 'phase' }, { id: 'todesktop:beforeInstall', position: 'before', type: 'hook' })).toBe(false);
|
|
11
|
+
});
|
|
12
|
+
(0, vitest_1.it)('considers hook positions', () => {
|
|
13
|
+
(0, vitest_1.expect)((0, breakpoints_1.isSameBreakpoint)({ id: 'todesktop:beforeBuild', position: 'before', type: 'hook' }, { id: 'todesktop:beforeBuild', position: 'after', type: 'hook' })).toBe(false);
|
|
14
|
+
});
|
|
15
|
+
(0, vitest_1.it)('returns false when either side is undefined', () => {
|
|
16
|
+
(0, vitest_1.expect)((0, breakpoints_1.isSameBreakpoint)(undefined, { id: 'afterInstall', type: 'phase' })).toBe(false);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
(0, vitest_1.describe)('cloneBreakpointQueue', () => {
|
|
20
|
+
(0, vitest_1.it)('produces a deep copy of the queue', () => {
|
|
21
|
+
const original = [
|
|
22
|
+
{ hitAt: undefined, id: 'beforeInstall', type: 'phase' },
|
|
23
|
+
{
|
|
24
|
+
id: 'todesktop:afterPack',
|
|
25
|
+
position: 'after',
|
|
26
|
+
skipped: true,
|
|
27
|
+
type: 'hook',
|
|
28
|
+
},
|
|
29
|
+
];
|
|
30
|
+
const copy = (0, breakpoints_1.cloneBreakpointQueue)(original);
|
|
31
|
+
(0, vitest_1.expect)(copy).toEqual(original);
|
|
32
|
+
(0, vitest_1.expect)(copy).not.toBe(original);
|
|
33
|
+
(0, vitest_1.expect)(copy[0]).not.toBe(original[0]);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
(0, vitest_1.describe)('markQueueSkipped', () => {
|
|
37
|
+
(0, vitest_1.it)('marks the starting entry as skipped and adds fallback timestamps', () => {
|
|
38
|
+
const queue = [
|
|
39
|
+
{ id: 'beforeInstall', type: 'phase' },
|
|
40
|
+
{ id: 'afterInstall', type: 'phase' },
|
|
41
|
+
];
|
|
42
|
+
const fallbackHitAt = '2024-01-01T00:00:00.000Z';
|
|
43
|
+
const updated = (0, breakpoints_1.markQueueSkipped)(queue, 0, { fallbackHitAt });
|
|
44
|
+
(0, vitest_1.expect)(updated[0]).toMatchObject({
|
|
45
|
+
hitAt: fallbackHitAt,
|
|
46
|
+
skipped: true,
|
|
47
|
+
});
|
|
48
|
+
(0, vitest_1.expect)(updated[1]).toMatchObject({ skipped: true });
|
|
49
|
+
(0, vitest_1.expect)(queue[0].skipped).toBeUndefined();
|
|
50
|
+
});
|
|
51
|
+
(0, vitest_1.it)('returns a cloned queue when index is out of bounds', () => {
|
|
52
|
+
const queue = [
|
|
53
|
+
{ id: 'beforeInstall', type: 'phase' },
|
|
54
|
+
];
|
|
55
|
+
const updated = (0, breakpoints_1.markQueueSkipped)(queue, 2);
|
|
56
|
+
(0, vitest_1.expect)(updated).toEqual(queue);
|
|
57
|
+
(0, vitest_1.expect)(updated).not.toBe(queue);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export declare const BREAKPOINT_DEFAULT_LEASE_MS: number;
|
|
2
|
+
export declare const BREAKPOINT_RENEW_INCREMENT_MS: number;
|
|
3
|
+
export declare const BREAKPOINT_MAX_REMAINING_MS: number;
|
|
4
|
+
export type PhaseBreakpointId = 'afterInstall' | 'afterPack' | 'afterSourcePrep' | 'afterUpload' | 'beforeFinalize' | 'beforeInstall' | 'beforePack' | 'beforeUpload';
|
|
5
|
+
export type HookBreakpointName = 'todesktop:afterPack' | 'todesktop:beforeBuild' | 'todesktop:beforeInstall';
|
|
6
|
+
export type HookBreakpointPosition = 'after' | 'before';
|
|
7
|
+
interface PhaseBreakpointConfig {
|
|
8
|
+
id: PhaseBreakpointId;
|
|
9
|
+
type: 'phase';
|
|
10
|
+
}
|
|
11
|
+
interface HookBreakpointConfig {
|
|
12
|
+
id: HookBreakpointName;
|
|
13
|
+
position: HookBreakpointPosition;
|
|
14
|
+
type: 'hook';
|
|
15
|
+
}
|
|
16
|
+
export type BreakpointConfig = HookBreakpointConfig | PhaseBreakpointConfig;
|
|
17
|
+
export type BreakpointQueueEntry = {
|
|
18
|
+
autoResumed?: boolean;
|
|
19
|
+
hitAt?: string;
|
|
20
|
+
skipped?: boolean;
|
|
21
|
+
} & BreakpointConfig;
|
|
22
|
+
export interface BreakpointRenewal {
|
|
23
|
+
renewedAt: string;
|
|
24
|
+
renewedByUserId: string;
|
|
25
|
+
}
|
|
26
|
+
export type CurrentBreakpointState = {
|
|
27
|
+
createdAt: string;
|
|
28
|
+
expiresAt: string;
|
|
29
|
+
renewals?: BreakpointRenewal[];
|
|
30
|
+
resumedAt?: string;
|
|
31
|
+
resumedByUserId?: string;
|
|
32
|
+
skipped?: boolean;
|
|
33
|
+
wasAutoResumed?: boolean;
|
|
34
|
+
} & BreakpointConfig;
|
|
35
|
+
export interface BreakpointPauseLease {
|
|
36
|
+
createdAt: string;
|
|
37
|
+
createdByUserId: string;
|
|
38
|
+
defaultDurationMs: number;
|
|
39
|
+
}
|
|
40
|
+
export declare function isSameBreakpoint(a: BreakpointConfig | BreakpointQueueEntry | CurrentBreakpointState | undefined, b: BreakpointConfig | BreakpointQueueEntry | CurrentBreakpointState | undefined): boolean;
|
|
41
|
+
export declare function cloneBreakpointQueue(queue?: null | ReadonlyArray<BreakpointQueueEntry>): BreakpointQueueEntry[];
|
|
42
|
+
export declare function markQueueSkipped(queue: ReadonlyArray<BreakpointQueueEntry>, startIndex: number, options?: {
|
|
43
|
+
fallbackHitAt?: string;
|
|
44
|
+
}): BreakpointQueueEntry[];
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BREAKPOINT_MAX_REMAINING_MS = exports.BREAKPOINT_RENEW_INCREMENT_MS = exports.BREAKPOINT_DEFAULT_LEASE_MS = void 0;
|
|
4
|
+
exports.isSameBreakpoint = isSameBreakpoint;
|
|
5
|
+
exports.cloneBreakpointQueue = cloneBreakpointQueue;
|
|
6
|
+
exports.markQueueSkipped = markQueueSkipped;
|
|
7
|
+
const MINUTE = 60 * 1000;
|
|
8
|
+
exports.BREAKPOINT_DEFAULT_LEASE_MS = 20 * MINUTE; // 20 minutes
|
|
9
|
+
exports.BREAKPOINT_RENEW_INCREMENT_MS = 10 * MINUTE; // 10 minutes
|
|
10
|
+
exports.BREAKPOINT_MAX_REMAINING_MS = 20 * MINUTE; // 20 minutes
|
|
11
|
+
function isSameBreakpoint(a, b) {
|
|
12
|
+
if (!a || !b) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
if (a.type !== b.type) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
if (a.id !== b.id) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
if (isHookBreakpointConfig(a) && isHookBreakpointConfig(b)) {
|
|
22
|
+
return a.position === b.position;
|
|
23
|
+
}
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
function isHookBreakpointConfig(config) {
|
|
27
|
+
return config.type === 'hook';
|
|
28
|
+
}
|
|
29
|
+
function cloneBreakpointQueue(queue) {
|
|
30
|
+
return Array.isArray(queue) ? queue.map((entry) => (Object.assign({}, entry))) : [];
|
|
31
|
+
}
|
|
32
|
+
function markQueueSkipped(queue, startIndex, options = {}) {
|
|
33
|
+
var _a;
|
|
34
|
+
const updated = cloneBreakpointQueue(queue);
|
|
35
|
+
const { fallbackHitAt } = options;
|
|
36
|
+
if (startIndex < 0 || startIndex >= updated.length) {
|
|
37
|
+
return updated;
|
|
38
|
+
}
|
|
39
|
+
const current = updated[startIndex];
|
|
40
|
+
const hitAt = (_a = current.hitAt) !== null && _a !== void 0 ? _a : fallbackHitAt;
|
|
41
|
+
updated[startIndex] = Object.assign(Object.assign(Object.assign({}, current), { skipped: true }), (hitAt ? { hitAt } : {}));
|
|
42
|
+
for (let i = startIndex + 1; i < updated.length; i += 1) {
|
|
43
|
+
if (!updated[i].hitAt) {
|
|
44
|
+
updated[i] = Object.assign(Object.assign({}, updated[i]), { skipped: true });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return updated;
|
|
48
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Build } from '../desktopify';
|
|
2
|
+
import type { IntrospectBreakpointStatus, IntrospectShellStatus } from '../desktopify';
|
|
3
|
+
export type IntrospectPlatformName = 'mac' | 'windows' | 'linux';
|
|
4
|
+
/**
|
|
5
|
+
* Check if a build was introspected (has connectedAt or disconnectedAt on any platform).
|
|
6
|
+
* Builds that were introspected cannot be released.
|
|
7
|
+
*/
|
|
8
|
+
export declare function wasIntrospected(build: Build): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Get list of platform names that were introspected (have connectedAt or disconnectedAt).
|
|
11
|
+
* Returns capitalized platform names (e.g., ['Mac', 'Windows']).
|
|
12
|
+
*/
|
|
13
|
+
export declare function getIntrospectedPlatformNames(build: Build): string[];
|
|
14
|
+
/**
|
|
15
|
+
* Format introspection status as a human-readable string.
|
|
16
|
+
* Combines breakpoint status and shell status into a single label.
|
|
17
|
+
*/
|
|
18
|
+
export declare function formatIntrospectionStatus(breakpointStatus?: IntrospectBreakpointStatus, shellStatus?: IntrospectShellStatus): string;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.wasIntrospected = wasIntrospected;
|
|
4
|
+
exports.getIntrospectedPlatformNames = getIntrospectedPlatformNames;
|
|
5
|
+
exports.formatIntrospectionStatus = formatIntrospectionStatus;
|
|
6
|
+
const PLATFORM_LABELS = {
|
|
7
|
+
mac: 'Mac',
|
|
8
|
+
windows: 'Windows',
|
|
9
|
+
linux: 'Linux',
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Check if a build was introspected (has connectedAt or disconnectedAt on any platform).
|
|
13
|
+
* Builds that were introspected cannot be released.
|
|
14
|
+
*/
|
|
15
|
+
function wasIntrospected(build) {
|
|
16
|
+
const introspect = build.introspect;
|
|
17
|
+
if (!introspect)
|
|
18
|
+
return false;
|
|
19
|
+
const platforms = ['mac', 'windows', 'linux'];
|
|
20
|
+
for (const platform of platforms) {
|
|
21
|
+
const data = introspect[platform];
|
|
22
|
+
if ((data === null || data === void 0 ? void 0 : data.connectedAt) || (data === null || data === void 0 ? void 0 : data.disconnectedAt)) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get list of platform names that were introspected (have connectedAt or disconnectedAt).
|
|
30
|
+
* Returns capitalized platform names (e.g., ['Mac', 'Windows']).
|
|
31
|
+
*/
|
|
32
|
+
function getIntrospectedPlatformNames(build) {
|
|
33
|
+
const introspect = build.introspect;
|
|
34
|
+
if (!introspect)
|
|
35
|
+
return [];
|
|
36
|
+
const platforms = ['mac', 'windows', 'linux'];
|
|
37
|
+
const result = [];
|
|
38
|
+
for (const platform of platforms) {
|
|
39
|
+
const data = introspect[platform];
|
|
40
|
+
if ((data === null || data === void 0 ? void 0 : data.connectedAt) || (data === null || data === void 0 ? void 0 : data.disconnectedAt)) {
|
|
41
|
+
result.push(PLATFORM_LABELS[platform]);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Format introspection status as a human-readable string.
|
|
48
|
+
* Combines breakpoint status and shell status into a single label.
|
|
49
|
+
*/
|
|
50
|
+
function formatIntrospectionStatus(breakpointStatus, shellStatus) {
|
|
51
|
+
if (breakpointStatus === 'paused') {
|
|
52
|
+
if (shellStatus === 'connected') {
|
|
53
|
+
return 'paused (shell connected)';
|
|
54
|
+
}
|
|
55
|
+
if (shellStatus === 'disconnected') {
|
|
56
|
+
return 'paused (shell disconnected)';
|
|
57
|
+
}
|
|
58
|
+
return 'paused';
|
|
59
|
+
}
|
|
60
|
+
if (breakpointStatus === 'ready') {
|
|
61
|
+
if (shellStatus === 'connected') {
|
|
62
|
+
return 'ready (shell connected)';
|
|
63
|
+
}
|
|
64
|
+
if (shellStatus === 'disconnected') {
|
|
65
|
+
return 'ready (shell disconnected)';
|
|
66
|
+
}
|
|
67
|
+
return 'ready';
|
|
68
|
+
}
|
|
69
|
+
if (breakpointStatus === 'resuming') {
|
|
70
|
+
return 'resuming';
|
|
71
|
+
}
|
|
72
|
+
if (breakpointStatus === 'initializing') {
|
|
73
|
+
return 'initializing';
|
|
74
|
+
}
|
|
75
|
+
if (breakpointStatus === 'finished') {
|
|
76
|
+
return 'finished';
|
|
77
|
+
}
|
|
78
|
+
if (breakpointStatus === 'error' || shellStatus === 'error') {
|
|
79
|
+
return 'error';
|
|
80
|
+
}
|
|
81
|
+
return 'status unknown';
|
|
82
|
+
}
|
package/lib/esm/desktopify.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Configuration, PackagerOptions, PublishOptions } from 'app-builder-lib';
|
|
2
2
|
import { IApp2 } from './desktopify2';
|
|
3
|
+
import { BreakpointPauseLease, BreakpointQueueEntry, CurrentBreakpointState } from './introspection/breakpoints';
|
|
3
4
|
import { IApp } from './toDesktop';
|
|
4
5
|
type appBuilderLib = PackagerOptions & PublishOptions;
|
|
5
6
|
export interface IAppBuilderLib extends appBuilderLib {
|
|
@@ -92,6 +93,30 @@ export interface PlatformBuild {
|
|
|
92
93
|
status: BuildStatus;
|
|
93
94
|
}
|
|
94
95
|
export type CIRunner = 'azure' | 'circle';
|
|
96
|
+
export type IntrospectBreakpointStatus = 'error' | 'finished' | 'initializing' | 'paused' | 'ready' | 'resuming';
|
|
97
|
+
export type IntrospectShellStatus = 'connected' | 'connecting' | 'disconnected' | 'error' | 'initializing' | 'ready';
|
|
98
|
+
export interface IntrospectPlatformData {
|
|
99
|
+
breakpointQueue?: BreakpointQueueEntry[];
|
|
100
|
+
breakpointStatus: IntrospectBreakpointStatus;
|
|
101
|
+
connectedAt?: Date;
|
|
102
|
+
connectedUserId?: string;
|
|
103
|
+
createdAt: ISODate;
|
|
104
|
+
currentBreakpoint?: CurrentBreakpointState;
|
|
105
|
+
disconnectedAt?: Date;
|
|
106
|
+
enabled: boolean;
|
|
107
|
+
error?: string;
|
|
108
|
+
jtiExpiresAt?: Date;
|
|
109
|
+
pauseLease?: BreakpointPauseLease;
|
|
110
|
+
sessionJti?: string;
|
|
111
|
+
shellStatus: IntrospectShellStatus;
|
|
112
|
+
tunnelUrl?: string;
|
|
113
|
+
usedJti?: string;
|
|
114
|
+
}
|
|
115
|
+
export interface IntrospectData {
|
|
116
|
+
linux: IntrospectPlatformData;
|
|
117
|
+
mac: IntrospectPlatformData;
|
|
118
|
+
windows: IntrospectPlatformData;
|
|
119
|
+
}
|
|
95
120
|
export interface Build {
|
|
96
121
|
appCustomDomain?: string;
|
|
97
122
|
appName: string;
|
|
@@ -118,6 +143,7 @@ export interface Build {
|
|
|
118
143
|
hash?: string;
|
|
119
144
|
icon?: string;
|
|
120
145
|
id: string;
|
|
146
|
+
introspect?: IntrospectData;
|
|
121
147
|
isArtifactsPruned?: boolean;
|
|
122
148
|
isBeingCancelled?: boolean;
|
|
123
149
|
linux?: PlatformBuild;
|
package/lib/esm/index.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export * from './desktopify.js';
|
|
|
3
3
|
export * from './desktopify2.js';
|
|
4
4
|
export * from './getSiteInfo.js';
|
|
5
5
|
export * from './hsm.js';
|
|
6
|
+
export * from './introspection/breakpoints.js';
|
|
7
|
+
export * from './introspection/status.js';
|
|
6
8
|
export * from './invitePermissionLabels.js';
|
|
7
9
|
export * from './personalAccessTokens.js';
|
|
8
10
|
export * from './plans.js';
|
package/lib/esm/index.js
CHANGED
|
@@ -5,6 +5,8 @@ export * from './desktopify.js';
|
|
|
5
5
|
export * from './desktopify2.js';
|
|
6
6
|
export * from './getSiteInfo.js';
|
|
7
7
|
export * from './hsm.js';
|
|
8
|
+
export * from './introspection/breakpoints.js';
|
|
9
|
+
export * from './introspection/status.js';
|
|
8
10
|
export * from './invitePermissionLabels.js';
|
|
9
11
|
export * from './personalAccessTokens.js';
|
|
10
12
|
export * from './plans.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { cloneBreakpointQueue, isSameBreakpoint, markQueueSkipped, } from '../breakpoints';
|
|
3
|
+
describe('isSameBreakpoint', () => {
|
|
4
|
+
it('matches identical phase breakpoints', () => {
|
|
5
|
+
expect(isSameBreakpoint({ id: 'beforeInstall', type: 'phase' }, { id: 'beforeInstall', type: 'phase' })).toBe(true);
|
|
6
|
+
});
|
|
7
|
+
it('fails when breakpoint types differ', () => {
|
|
8
|
+
expect(isSameBreakpoint({ id: 'beforeInstall', type: 'phase' }, { id: 'todesktop:beforeInstall', position: 'before', type: 'hook' })).toBe(false);
|
|
9
|
+
});
|
|
10
|
+
it('considers hook positions', () => {
|
|
11
|
+
expect(isSameBreakpoint({ id: 'todesktop:beforeBuild', position: 'before', type: 'hook' }, { id: 'todesktop:beforeBuild', position: 'after', type: 'hook' })).toBe(false);
|
|
12
|
+
});
|
|
13
|
+
it('returns false when either side is undefined', () => {
|
|
14
|
+
expect(isSameBreakpoint(undefined, { id: 'afterInstall', type: 'phase' })).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
describe('cloneBreakpointQueue', () => {
|
|
18
|
+
it('produces a deep copy of the queue', () => {
|
|
19
|
+
const original = [
|
|
20
|
+
{ hitAt: undefined, id: 'beforeInstall', type: 'phase' },
|
|
21
|
+
{
|
|
22
|
+
id: 'todesktop:afterPack',
|
|
23
|
+
position: 'after',
|
|
24
|
+
skipped: true,
|
|
25
|
+
type: 'hook',
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
const copy = cloneBreakpointQueue(original);
|
|
29
|
+
expect(copy).toEqual(original);
|
|
30
|
+
expect(copy).not.toBe(original);
|
|
31
|
+
expect(copy[0]).not.toBe(original[0]);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
describe('markQueueSkipped', () => {
|
|
35
|
+
it('marks the starting entry as skipped and adds fallback timestamps', () => {
|
|
36
|
+
const queue = [
|
|
37
|
+
{ id: 'beforeInstall', type: 'phase' },
|
|
38
|
+
{ id: 'afterInstall', type: 'phase' },
|
|
39
|
+
];
|
|
40
|
+
const fallbackHitAt = '2024-01-01T00:00:00.000Z';
|
|
41
|
+
const updated = markQueueSkipped(queue, 0, { fallbackHitAt });
|
|
42
|
+
expect(updated[0]).toMatchObject({
|
|
43
|
+
hitAt: fallbackHitAt,
|
|
44
|
+
skipped: true,
|
|
45
|
+
});
|
|
46
|
+
expect(updated[1]).toMatchObject({ skipped: true });
|
|
47
|
+
expect(queue[0].skipped).toBeUndefined();
|
|
48
|
+
});
|
|
49
|
+
it('returns a cloned queue when index is out of bounds', () => {
|
|
50
|
+
const queue = [
|
|
51
|
+
{ id: 'beforeInstall', type: 'phase' },
|
|
52
|
+
];
|
|
53
|
+
const updated = markQueueSkipped(queue, 2);
|
|
54
|
+
expect(updated).toEqual(queue);
|
|
55
|
+
expect(updated).not.toBe(queue);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export declare const BREAKPOINT_DEFAULT_LEASE_MS: number;
|
|
2
|
+
export declare const BREAKPOINT_RENEW_INCREMENT_MS: number;
|
|
3
|
+
export declare const BREAKPOINT_MAX_REMAINING_MS: number;
|
|
4
|
+
export type PhaseBreakpointId = 'afterInstall' | 'afterPack' | 'afterSourcePrep' | 'afterUpload' | 'beforeFinalize' | 'beforeInstall' | 'beforePack' | 'beforeUpload';
|
|
5
|
+
export type HookBreakpointName = 'todesktop:afterPack' | 'todesktop:beforeBuild' | 'todesktop:beforeInstall';
|
|
6
|
+
export type HookBreakpointPosition = 'after' | 'before';
|
|
7
|
+
interface PhaseBreakpointConfig {
|
|
8
|
+
id: PhaseBreakpointId;
|
|
9
|
+
type: 'phase';
|
|
10
|
+
}
|
|
11
|
+
interface HookBreakpointConfig {
|
|
12
|
+
id: HookBreakpointName;
|
|
13
|
+
position: HookBreakpointPosition;
|
|
14
|
+
type: 'hook';
|
|
15
|
+
}
|
|
16
|
+
export type BreakpointConfig = HookBreakpointConfig | PhaseBreakpointConfig;
|
|
17
|
+
export type BreakpointQueueEntry = {
|
|
18
|
+
autoResumed?: boolean;
|
|
19
|
+
hitAt?: string;
|
|
20
|
+
skipped?: boolean;
|
|
21
|
+
} & BreakpointConfig;
|
|
22
|
+
export interface BreakpointRenewal {
|
|
23
|
+
renewedAt: string;
|
|
24
|
+
renewedByUserId: string;
|
|
25
|
+
}
|
|
26
|
+
export type CurrentBreakpointState = {
|
|
27
|
+
createdAt: string;
|
|
28
|
+
expiresAt: string;
|
|
29
|
+
renewals?: BreakpointRenewal[];
|
|
30
|
+
resumedAt?: string;
|
|
31
|
+
resumedByUserId?: string;
|
|
32
|
+
skipped?: boolean;
|
|
33
|
+
wasAutoResumed?: boolean;
|
|
34
|
+
} & BreakpointConfig;
|
|
35
|
+
export interface BreakpointPauseLease {
|
|
36
|
+
createdAt: string;
|
|
37
|
+
createdByUserId: string;
|
|
38
|
+
defaultDurationMs: number;
|
|
39
|
+
}
|
|
40
|
+
export declare function isSameBreakpoint(a: BreakpointConfig | BreakpointQueueEntry | CurrentBreakpointState | undefined, b: BreakpointConfig | BreakpointQueueEntry | CurrentBreakpointState | undefined): boolean;
|
|
41
|
+
export declare function cloneBreakpointQueue(queue?: null | ReadonlyArray<BreakpointQueueEntry>): BreakpointQueueEntry[];
|
|
42
|
+
export declare function markQueueSkipped(queue: ReadonlyArray<BreakpointQueueEntry>, startIndex: number, options?: {
|
|
43
|
+
fallbackHitAt?: string;
|
|
44
|
+
}): BreakpointQueueEntry[];
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const MINUTE = 60 * 1000;
|
|
2
|
+
export const BREAKPOINT_DEFAULT_LEASE_MS = 20 * MINUTE; // 20 minutes
|
|
3
|
+
export const BREAKPOINT_RENEW_INCREMENT_MS = 10 * MINUTE; // 10 minutes
|
|
4
|
+
export const BREAKPOINT_MAX_REMAINING_MS = 20 * MINUTE; // 20 minutes
|
|
5
|
+
export function isSameBreakpoint(a, b) {
|
|
6
|
+
if (!a || !b) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
if (a.type !== b.type) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
if (a.id !== b.id) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
if (isHookBreakpointConfig(a) && isHookBreakpointConfig(b)) {
|
|
16
|
+
return a.position === b.position;
|
|
17
|
+
}
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
function isHookBreakpointConfig(config) {
|
|
21
|
+
return config.type === 'hook';
|
|
22
|
+
}
|
|
23
|
+
export function cloneBreakpointQueue(queue) {
|
|
24
|
+
return Array.isArray(queue) ? queue.map((entry) => (Object.assign({}, entry))) : [];
|
|
25
|
+
}
|
|
26
|
+
export function markQueueSkipped(queue, startIndex, options = {}) {
|
|
27
|
+
var _a;
|
|
28
|
+
const updated = cloneBreakpointQueue(queue);
|
|
29
|
+
const { fallbackHitAt } = options;
|
|
30
|
+
if (startIndex < 0 || startIndex >= updated.length) {
|
|
31
|
+
return updated;
|
|
32
|
+
}
|
|
33
|
+
const current = updated[startIndex];
|
|
34
|
+
const hitAt = (_a = current.hitAt) !== null && _a !== void 0 ? _a : fallbackHitAt;
|
|
35
|
+
updated[startIndex] = Object.assign(Object.assign(Object.assign({}, current), { skipped: true }), (hitAt ? { hitAt } : {}));
|
|
36
|
+
for (let i = startIndex + 1; i < updated.length; i += 1) {
|
|
37
|
+
if (!updated[i].hitAt) {
|
|
38
|
+
updated[i] = Object.assign(Object.assign({}, updated[i]), { skipped: true });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return updated;
|
|
42
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Build } from '../desktopify';
|
|
2
|
+
import type { IntrospectBreakpointStatus, IntrospectShellStatus } from '../desktopify';
|
|
3
|
+
export type IntrospectPlatformName = 'mac' | 'windows' | 'linux';
|
|
4
|
+
/**
|
|
5
|
+
* Check if a build was introspected (has connectedAt or disconnectedAt on any platform).
|
|
6
|
+
* Builds that were introspected cannot be released.
|
|
7
|
+
*/
|
|
8
|
+
export declare function wasIntrospected(build: Build): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Get list of platform names that were introspected (have connectedAt or disconnectedAt).
|
|
11
|
+
* Returns capitalized platform names (e.g., ['Mac', 'Windows']).
|
|
12
|
+
*/
|
|
13
|
+
export declare function getIntrospectedPlatformNames(build: Build): string[];
|
|
14
|
+
/**
|
|
15
|
+
* Format introspection status as a human-readable string.
|
|
16
|
+
* Combines breakpoint status and shell status into a single label.
|
|
17
|
+
*/
|
|
18
|
+
export declare function formatIntrospectionStatus(breakpointStatus?: IntrospectBreakpointStatus, shellStatus?: IntrospectShellStatus): string;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const PLATFORM_LABELS = {
|
|
2
|
+
mac: 'Mac',
|
|
3
|
+
windows: 'Windows',
|
|
4
|
+
linux: 'Linux',
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Check if a build was introspected (has connectedAt or disconnectedAt on any platform).
|
|
8
|
+
* Builds that were introspected cannot be released.
|
|
9
|
+
*/
|
|
10
|
+
export function wasIntrospected(build) {
|
|
11
|
+
const introspect = build.introspect;
|
|
12
|
+
if (!introspect)
|
|
13
|
+
return false;
|
|
14
|
+
const platforms = ['mac', 'windows', 'linux'];
|
|
15
|
+
for (const platform of platforms) {
|
|
16
|
+
const data = introspect[platform];
|
|
17
|
+
if ((data === null || data === void 0 ? void 0 : data.connectedAt) || (data === null || data === void 0 ? void 0 : data.disconnectedAt)) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get list of platform names that were introspected (have connectedAt or disconnectedAt).
|
|
25
|
+
* Returns capitalized platform names (e.g., ['Mac', 'Windows']).
|
|
26
|
+
*/
|
|
27
|
+
export function getIntrospectedPlatformNames(build) {
|
|
28
|
+
const introspect = build.introspect;
|
|
29
|
+
if (!introspect)
|
|
30
|
+
return [];
|
|
31
|
+
const platforms = ['mac', 'windows', 'linux'];
|
|
32
|
+
const result = [];
|
|
33
|
+
for (const platform of platforms) {
|
|
34
|
+
const data = introspect[platform];
|
|
35
|
+
if ((data === null || data === void 0 ? void 0 : data.connectedAt) || (data === null || data === void 0 ? void 0 : data.disconnectedAt)) {
|
|
36
|
+
result.push(PLATFORM_LABELS[platform]);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Format introspection status as a human-readable string.
|
|
43
|
+
* Combines breakpoint status and shell status into a single label.
|
|
44
|
+
*/
|
|
45
|
+
export function formatIntrospectionStatus(breakpointStatus, shellStatus) {
|
|
46
|
+
if (breakpointStatus === 'paused') {
|
|
47
|
+
if (shellStatus === 'connected') {
|
|
48
|
+
return 'paused (shell connected)';
|
|
49
|
+
}
|
|
50
|
+
if (shellStatus === 'disconnected') {
|
|
51
|
+
return 'paused (shell disconnected)';
|
|
52
|
+
}
|
|
53
|
+
return 'paused';
|
|
54
|
+
}
|
|
55
|
+
if (breakpointStatus === 'ready') {
|
|
56
|
+
if (shellStatus === 'connected') {
|
|
57
|
+
return 'ready (shell connected)';
|
|
58
|
+
}
|
|
59
|
+
if (shellStatus === 'disconnected') {
|
|
60
|
+
return 'ready (shell disconnected)';
|
|
61
|
+
}
|
|
62
|
+
return 'ready';
|
|
63
|
+
}
|
|
64
|
+
if (breakpointStatus === 'resuming') {
|
|
65
|
+
return 'resuming';
|
|
66
|
+
}
|
|
67
|
+
if (breakpointStatus === 'initializing') {
|
|
68
|
+
return 'initializing';
|
|
69
|
+
}
|
|
70
|
+
if (breakpointStatus === 'finished') {
|
|
71
|
+
return 'finished';
|
|
72
|
+
}
|
|
73
|
+
if (breakpointStatus === 'error' || shellStatus === 'error') {
|
|
74
|
+
return 'error';
|
|
75
|
+
}
|
|
76
|
+
return 'status unknown';
|
|
77
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@todesktop/shared",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.193.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./lib/cjs/index.js",
|
|
6
6
|
"module": "./lib/esm/index.js",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"format": "prettier . --write && eslint --fix",
|
|
28
28
|
"lint": "prettier . --check && eslint",
|
|
29
29
|
"prepublishOnly": "npm run build",
|
|
30
|
+
"test": "vitest run",
|
|
30
31
|
"typecheck": "tsc --noEmit"
|
|
31
32
|
},
|
|
32
33
|
"author": "Dave Jeffery <dave@davejeffery.com>",
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
"app-builder-lib": "^25.1.8",
|
|
46
47
|
"eslint": "catalog:",
|
|
47
48
|
"prettier": "catalog:",
|
|
48
|
-
"typescript": "catalog:"
|
|
49
|
+
"typescript": "catalog:",
|
|
50
|
+
"vitest": "^1.6.1"
|
|
49
51
|
}
|
|
50
52
|
}
|
package/src/desktopify.ts
CHANGED
|
@@ -3,12 +3,13 @@ import {
|
|
|
3
3
|
Configuration,
|
|
4
4
|
PackagerOptions,
|
|
5
5
|
PublishOptions,
|
|
6
|
-
// app-builder-lib shouldn't be installed as a dependency for dependent
|
|
7
|
-
// packages since it's too large
|
|
8
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
9
|
-
// @ts-ignore
|
|
10
6
|
} from 'app-builder-lib';
|
|
11
7
|
import { IApp2 } from './desktopify2';
|
|
8
|
+
import {
|
|
9
|
+
BreakpointPauseLease,
|
|
10
|
+
BreakpointQueueEntry,
|
|
11
|
+
CurrentBreakpointState,
|
|
12
|
+
} from './introspection/breakpoints';
|
|
12
13
|
import { IApp } from './toDesktop';
|
|
13
14
|
|
|
14
15
|
type appBuilderLib = PackagerOptions & PublishOptions;
|
|
@@ -130,6 +131,46 @@ export interface PlatformBuild {
|
|
|
130
131
|
|
|
131
132
|
export type CIRunner = 'azure' | 'circle';
|
|
132
133
|
|
|
134
|
+
export type IntrospectBreakpointStatus =
|
|
135
|
+
| 'error'
|
|
136
|
+
| 'finished'
|
|
137
|
+
| 'initializing'
|
|
138
|
+
| 'paused'
|
|
139
|
+
| 'ready'
|
|
140
|
+
| 'resuming';
|
|
141
|
+
|
|
142
|
+
export type IntrospectShellStatus =
|
|
143
|
+
| 'connected'
|
|
144
|
+
| 'connecting'
|
|
145
|
+
| 'disconnected'
|
|
146
|
+
| 'error'
|
|
147
|
+
| 'initializing'
|
|
148
|
+
| 'ready';
|
|
149
|
+
|
|
150
|
+
export interface IntrospectPlatformData {
|
|
151
|
+
breakpointQueue?: BreakpointQueueEntry[];
|
|
152
|
+
breakpointStatus: IntrospectBreakpointStatus;
|
|
153
|
+
connectedAt?: Date;
|
|
154
|
+
connectedUserId?: string;
|
|
155
|
+
createdAt: ISODate;
|
|
156
|
+
currentBreakpoint?: CurrentBreakpointState;
|
|
157
|
+
disconnectedAt?: Date;
|
|
158
|
+
enabled: boolean;
|
|
159
|
+
error?: string;
|
|
160
|
+
jtiExpiresAt?: Date;
|
|
161
|
+
pauseLease?: BreakpointPauseLease;
|
|
162
|
+
sessionJti?: string;
|
|
163
|
+
shellStatus: IntrospectShellStatus;
|
|
164
|
+
tunnelUrl?: string;
|
|
165
|
+
usedJti?: string;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface IntrospectData {
|
|
169
|
+
linux: IntrospectPlatformData;
|
|
170
|
+
mac: IntrospectPlatformData;
|
|
171
|
+
windows: IntrospectPlatformData;
|
|
172
|
+
}
|
|
173
|
+
|
|
133
174
|
export interface Build {
|
|
134
175
|
appCustomDomain?: string;
|
|
135
176
|
appName: string;
|
|
@@ -161,6 +202,7 @@ export interface Build {
|
|
|
161
202
|
hash?: string;
|
|
162
203
|
icon?: string;
|
|
163
204
|
id: string;
|
|
205
|
+
introspect?: IntrospectData;
|
|
164
206
|
isArtifactsPruned?: boolean;
|
|
165
207
|
isBeingCancelled?: boolean;
|
|
166
208
|
linux?: PlatformBuild;
|
package/src/index.cjs.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
2
2
|
// CJS-specific entry point
|
|
3
3
|
// Path is relative to lib/cjs/ output directory
|
|
4
4
|
const packageJson = require('../../package.json');
|
|
@@ -8,6 +8,8 @@ export * from './desktopify';
|
|
|
8
8
|
export * from './desktopify2';
|
|
9
9
|
export * from './getSiteInfo';
|
|
10
10
|
export * from './hsm';
|
|
11
|
+
export * from './introspection/breakpoints';
|
|
12
|
+
export * from './introspection/status';
|
|
11
13
|
export * from './invitePermissionLabels';
|
|
12
14
|
export * from './personalAccessTokens';
|
|
13
15
|
export * from './plans';
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,8 @@ export * from './desktopify.js';
|
|
|
6
6
|
export * from './desktopify2.js';
|
|
7
7
|
export * from './getSiteInfo.js';
|
|
8
8
|
export * from './hsm.js';
|
|
9
|
+
export * from './introspection/breakpoints.js';
|
|
10
|
+
export * from './introspection/status.js';
|
|
9
11
|
export * from './invitePermissionLabels.js';
|
|
10
12
|
export * from './personalAccessTokens.js';
|
|
11
13
|
export * from './plans.js';
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
cloneBreakpointQueue,
|
|
4
|
+
isSameBreakpoint,
|
|
5
|
+
markQueueSkipped,
|
|
6
|
+
} from '../breakpoints';
|
|
7
|
+
import type { BreakpointQueueEntry } from '../breakpoints';
|
|
8
|
+
|
|
9
|
+
describe('isSameBreakpoint', () => {
|
|
10
|
+
it('matches identical phase breakpoints', () => {
|
|
11
|
+
expect(
|
|
12
|
+
isSameBreakpoint(
|
|
13
|
+
{ id: 'beforeInstall', type: 'phase' },
|
|
14
|
+
{ id: 'beforeInstall', type: 'phase' },
|
|
15
|
+
),
|
|
16
|
+
).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('fails when breakpoint types differ', () => {
|
|
20
|
+
expect(
|
|
21
|
+
isSameBreakpoint(
|
|
22
|
+
{ id: 'beforeInstall', type: 'phase' },
|
|
23
|
+
{ id: 'todesktop:beforeInstall', position: 'before', type: 'hook' },
|
|
24
|
+
),
|
|
25
|
+
).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('considers hook positions', () => {
|
|
29
|
+
expect(
|
|
30
|
+
isSameBreakpoint(
|
|
31
|
+
{ id: 'todesktop:beforeBuild', position: 'before', type: 'hook' },
|
|
32
|
+
{ id: 'todesktop:beforeBuild', position: 'after', type: 'hook' },
|
|
33
|
+
),
|
|
34
|
+
).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('returns false when either side is undefined', () => {
|
|
38
|
+
expect(
|
|
39
|
+
isSameBreakpoint(undefined, { id: 'afterInstall', type: 'phase' }),
|
|
40
|
+
).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('cloneBreakpointQueue', () => {
|
|
45
|
+
it('produces a deep copy of the queue', () => {
|
|
46
|
+
const original: ReadonlyArray<BreakpointQueueEntry> = [
|
|
47
|
+
{ hitAt: undefined, id: 'beforeInstall', type: 'phase' },
|
|
48
|
+
{
|
|
49
|
+
id: 'todesktop:afterPack',
|
|
50
|
+
position: 'after',
|
|
51
|
+
skipped: true,
|
|
52
|
+
type: 'hook',
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
const copy = cloneBreakpointQueue(original);
|
|
57
|
+
|
|
58
|
+
expect(copy).toEqual(original);
|
|
59
|
+
expect(copy).not.toBe(original);
|
|
60
|
+
expect(copy[0]).not.toBe(original[0]);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('markQueueSkipped', () => {
|
|
65
|
+
it('marks the starting entry as skipped and adds fallback timestamps', () => {
|
|
66
|
+
const queue: ReadonlyArray<BreakpointQueueEntry> = [
|
|
67
|
+
{ id: 'beforeInstall', type: 'phase' },
|
|
68
|
+
{ id: 'afterInstall', type: 'phase' },
|
|
69
|
+
];
|
|
70
|
+
const fallbackHitAt = '2024-01-01T00:00:00.000Z';
|
|
71
|
+
|
|
72
|
+
const updated = markQueueSkipped(queue, 0, { fallbackHitAt });
|
|
73
|
+
|
|
74
|
+
expect(updated[0]).toMatchObject({
|
|
75
|
+
hitAt: fallbackHitAt,
|
|
76
|
+
skipped: true,
|
|
77
|
+
});
|
|
78
|
+
expect(updated[1]).toMatchObject({ skipped: true });
|
|
79
|
+
expect(queue[0].skipped).toBeUndefined();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('returns a cloned queue when index is out of bounds', () => {
|
|
83
|
+
const queue: ReadonlyArray<BreakpointQueueEntry> = [
|
|
84
|
+
{ id: 'beforeInstall', type: 'phase' },
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
const updated = markQueueSkipped(queue, 2);
|
|
88
|
+
|
|
89
|
+
expect(updated).toEqual(queue);
|
|
90
|
+
expect(updated).not.toBe(queue);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
const MINUTE = 60 * 1000;
|
|
2
|
+
export const BREAKPOINT_DEFAULT_LEASE_MS = 20 * MINUTE; // 20 minutes
|
|
3
|
+
export const BREAKPOINT_RENEW_INCREMENT_MS = 10 * MINUTE; // 10 minutes
|
|
4
|
+
export const BREAKPOINT_MAX_REMAINING_MS = 20 * MINUTE; // 20 minutes
|
|
5
|
+
|
|
6
|
+
export type PhaseBreakpointId =
|
|
7
|
+
| 'afterInstall'
|
|
8
|
+
| 'afterPack'
|
|
9
|
+
| 'afterSourcePrep'
|
|
10
|
+
| 'afterUpload'
|
|
11
|
+
| 'beforeFinalize'
|
|
12
|
+
| 'beforeInstall'
|
|
13
|
+
| 'beforePack'
|
|
14
|
+
| 'beforeUpload';
|
|
15
|
+
|
|
16
|
+
export type HookBreakpointName =
|
|
17
|
+
| 'todesktop:afterPack'
|
|
18
|
+
| 'todesktop:beforeBuild'
|
|
19
|
+
| 'todesktop:beforeInstall';
|
|
20
|
+
|
|
21
|
+
export type HookBreakpointPosition = 'after' | 'before';
|
|
22
|
+
|
|
23
|
+
interface PhaseBreakpointConfig {
|
|
24
|
+
id: PhaseBreakpointId;
|
|
25
|
+
type: 'phase';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface HookBreakpointConfig {
|
|
29
|
+
id: HookBreakpointName;
|
|
30
|
+
position: HookBreakpointPosition;
|
|
31
|
+
type: 'hook';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type BreakpointConfig = HookBreakpointConfig | PhaseBreakpointConfig;
|
|
35
|
+
|
|
36
|
+
export type BreakpointQueueEntry = {
|
|
37
|
+
autoResumed?: boolean;
|
|
38
|
+
hitAt?: string;
|
|
39
|
+
skipped?: boolean;
|
|
40
|
+
} & BreakpointConfig;
|
|
41
|
+
|
|
42
|
+
export interface BreakpointRenewal {
|
|
43
|
+
renewedAt: string;
|
|
44
|
+
renewedByUserId: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type CurrentBreakpointState = {
|
|
48
|
+
createdAt: string;
|
|
49
|
+
expiresAt: string;
|
|
50
|
+
renewals?: BreakpointRenewal[];
|
|
51
|
+
resumedAt?: string;
|
|
52
|
+
resumedByUserId?: string;
|
|
53
|
+
skipped?: boolean;
|
|
54
|
+
wasAutoResumed?: boolean;
|
|
55
|
+
} & BreakpointConfig;
|
|
56
|
+
|
|
57
|
+
export interface BreakpointPauseLease {
|
|
58
|
+
createdAt: string;
|
|
59
|
+
createdByUserId: string;
|
|
60
|
+
defaultDurationMs: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function isSameBreakpoint(
|
|
64
|
+
a:
|
|
65
|
+
| BreakpointConfig
|
|
66
|
+
| BreakpointQueueEntry
|
|
67
|
+
| CurrentBreakpointState
|
|
68
|
+
| undefined,
|
|
69
|
+
b:
|
|
70
|
+
| BreakpointConfig
|
|
71
|
+
| BreakpointQueueEntry
|
|
72
|
+
| CurrentBreakpointState
|
|
73
|
+
| undefined,
|
|
74
|
+
): boolean {
|
|
75
|
+
if (!a || !b) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (a.type !== b.type) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (a.id !== b.id) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (isHookBreakpointConfig(a) && isHookBreakpointConfig(b)) {
|
|
88
|
+
return a.position === b.position;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isHookBreakpointConfig(
|
|
95
|
+
config: BreakpointConfig | BreakpointQueueEntry | CurrentBreakpointState,
|
|
96
|
+
): config is HookBreakpointConfig {
|
|
97
|
+
return config.type === 'hook';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function cloneBreakpointQueue(
|
|
101
|
+
queue?: null | ReadonlyArray<BreakpointQueueEntry>,
|
|
102
|
+
): BreakpointQueueEntry[] {
|
|
103
|
+
return Array.isArray(queue) ? queue.map((entry) => ({ ...entry })) : [];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function markQueueSkipped(
|
|
107
|
+
queue: ReadonlyArray<BreakpointQueueEntry>,
|
|
108
|
+
startIndex: number,
|
|
109
|
+
options: { fallbackHitAt?: string } = {},
|
|
110
|
+
): BreakpointQueueEntry[] {
|
|
111
|
+
const updated = cloneBreakpointQueue(queue);
|
|
112
|
+
const { fallbackHitAt } = options;
|
|
113
|
+
|
|
114
|
+
if (startIndex < 0 || startIndex >= updated.length) {
|
|
115
|
+
return updated;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const current = updated[startIndex];
|
|
119
|
+
const hitAt = current.hitAt ?? fallbackHitAt;
|
|
120
|
+
|
|
121
|
+
updated[startIndex] = {
|
|
122
|
+
...current,
|
|
123
|
+
skipped: true,
|
|
124
|
+
...(hitAt ? { hitAt } : {}),
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
for (let i = startIndex + 1; i < updated.length; i += 1) {
|
|
128
|
+
if (!updated[i].hitAt) {
|
|
129
|
+
updated[i] = {
|
|
130
|
+
...updated[i],
|
|
131
|
+
skipped: true,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return updated;
|
|
137
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { Build } from '../desktopify';
|
|
2
|
+
import type {
|
|
3
|
+
IntrospectBreakpointStatus,
|
|
4
|
+
IntrospectShellStatus,
|
|
5
|
+
} from '../desktopify';
|
|
6
|
+
|
|
7
|
+
export type IntrospectPlatformName = 'mac' | 'windows' | 'linux';
|
|
8
|
+
|
|
9
|
+
const PLATFORM_LABELS: Record<IntrospectPlatformName, string> = {
|
|
10
|
+
mac: 'Mac',
|
|
11
|
+
windows: 'Windows',
|
|
12
|
+
linux: 'Linux',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Check if a build was introspected (has connectedAt or disconnectedAt on any platform).
|
|
17
|
+
* Builds that were introspected cannot be released.
|
|
18
|
+
*/
|
|
19
|
+
export function wasIntrospected(build: Build): boolean {
|
|
20
|
+
const introspect = build.introspect;
|
|
21
|
+
if (!introspect) return false;
|
|
22
|
+
|
|
23
|
+
const platforms: IntrospectPlatformName[] = ['mac', 'windows', 'linux'];
|
|
24
|
+
|
|
25
|
+
for (const platform of platforms) {
|
|
26
|
+
const data = introspect[platform];
|
|
27
|
+
if (data?.connectedAt || data?.disconnectedAt) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get list of platform names that were introspected (have connectedAt or disconnectedAt).
|
|
37
|
+
* Returns capitalized platform names (e.g., ['Mac', 'Windows']).
|
|
38
|
+
*/
|
|
39
|
+
export function getIntrospectedPlatformNames(build: Build): string[] {
|
|
40
|
+
const introspect = build.introspect;
|
|
41
|
+
if (!introspect) return [];
|
|
42
|
+
|
|
43
|
+
const platforms: IntrospectPlatformName[] = ['mac', 'windows', 'linux'];
|
|
44
|
+
const result: string[] = [];
|
|
45
|
+
|
|
46
|
+
for (const platform of platforms) {
|
|
47
|
+
const data = introspect[platform];
|
|
48
|
+
if (data?.connectedAt || data?.disconnectedAt) {
|
|
49
|
+
result.push(PLATFORM_LABELS[platform]);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Format introspection status as a human-readable string.
|
|
58
|
+
* Combines breakpoint status and shell status into a single label.
|
|
59
|
+
*/
|
|
60
|
+
export function formatIntrospectionStatus(
|
|
61
|
+
breakpointStatus?: IntrospectBreakpointStatus,
|
|
62
|
+
shellStatus?: IntrospectShellStatus,
|
|
63
|
+
): string {
|
|
64
|
+
if (breakpointStatus === 'paused') {
|
|
65
|
+
if (shellStatus === 'connected') {
|
|
66
|
+
return 'paused (shell connected)';
|
|
67
|
+
}
|
|
68
|
+
if (shellStatus === 'disconnected') {
|
|
69
|
+
return 'paused (shell disconnected)';
|
|
70
|
+
}
|
|
71
|
+
return 'paused';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (breakpointStatus === 'ready') {
|
|
75
|
+
if (shellStatus === 'connected') {
|
|
76
|
+
return 'ready (shell connected)';
|
|
77
|
+
}
|
|
78
|
+
if (shellStatus === 'disconnected') {
|
|
79
|
+
return 'ready (shell disconnected)';
|
|
80
|
+
}
|
|
81
|
+
return 'ready';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (breakpointStatus === 'resuming') {
|
|
85
|
+
return 'resuming';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (breakpointStatus === 'initializing') {
|
|
89
|
+
return 'initializing';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (breakpointStatus === 'finished') {
|
|
93
|
+
return 'finished';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (breakpointStatus === 'error' || shellStatus === 'error') {
|
|
97
|
+
return 'error';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return 'status unknown';
|
|
101
|
+
}
|
package/vitest.config.ts
ADDED