@flighthq/shell 0.4.0-next.1898.266773a → 0.4.0-next.1908.751021c

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { isShellUrlAllowed, moveItemToTrash, moveItemsToTrash, openShellExternalUrl, openShellPath, openShellPathResult, readShellShortcutLink, setShellUrlSchemeAllowlist, shellBeep, showItemInFolder, writeShellShortcutLink, } from './contract';
1
+ export { explainShellBackend, isShellUrlAllowed, moveItemToTrash, moveItemsToTrash, openShellExternalUrl, openShellPath, openShellPathResult, readShellShortcutLink, setShellUrlSchemeAllowlist, shellBeep, showItemInFolder, writeShellShortcutLink, } from './contract';
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,aAAa,EACb,mBAAmB,EACnB,qBAAqB,EACrB,0BAA0B,EAC1B,SAAS,EACT,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,aAAa,EACb,mBAAmB,EACnB,qBAAqB,EACrB,0BAA0B,EAC1B,SAAS,EACT,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { isShellUrlAllowed, moveItemToTrash, moveItemsToTrash, openShellExternalUrl, openShellPath, openShellPathResult, readShellShortcutLink, setShellUrlSchemeAllowlist, shellBeep, showItemInFolder, writeShellShortcutLink, } from './contract';
1
+ export { explainShellBackend, isShellUrlAllowed, moveItemToTrash, moveItemsToTrash, openShellExternalUrl, openShellPath, openShellPathResult, readShellShortcutLink, setShellUrlSchemeAllowlist, shellBeep, showItemInFolder, writeShellShortcutLink, } from './contract';
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,aAAa,EACb,mBAAmB,EACnB,qBAAqB,EACrB,0BAA0B,EAC1B,SAAS,EACT,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,aAAa,EACb,mBAAmB,EACnB,qBAAqB,EACrB,0BAA0B,EAC1B,SAAS,EACT,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,YAAY,CAAC"}
package/dist/shell.d.ts CHANGED
@@ -1,13 +1,17 @@
1
+ import type { BackendExplanation } from '@flighthq/types/contract';
1
2
  import type { ShellBackend, ShellOpenExternalOptions, ShellOpenPathOptions, ShellShortcutLink, ShellShortcutWriteOperation } from '@flighthq/types/contract';
2
- export declare function createWebShellBackend(): ShellBackend;
3
+ export declare function explainShellBackend(): BackendExplanation;
3
4
  export declare function getShellBackend(): ShellBackend;
5
+ export declare function installShellHostBackend(backend: ShellBackend): void;
4
6
  export declare function isShellUrlAllowed(url: string): boolean;
5
7
  export declare function moveItemsToTrash(paths: readonly string[]): Promise<readonly boolean[]>;
6
8
  export declare function moveItemToTrash(path: string): Promise<boolean>;
9
+ export declare function observeShellHostResult(operation: string, succeeded: boolean): void;
7
10
  export declare function openShellExternalUrl(url: string, options?: Readonly<ShellOpenExternalOptions>): Promise<boolean>;
8
11
  export declare function openShellPath(path: string, options?: Readonly<ShellOpenPathOptions>): Promise<boolean>;
9
12
  export declare function openShellPathResult(path: string, options?: Readonly<ShellOpenPathOptions>): Promise<string>;
10
13
  export declare function readShellShortcutLink(shortcutPath: string): Promise<ShellShortcutLink | null>;
14
+ export declare function resetShellBackendForTest(): void;
11
15
  export declare function setShellBackend(backend: ShellBackend | null): void;
12
16
  export declare function setShellUrlSchemeAllowlist(schemes: readonly string[] | null): void;
13
17
  export declare function shellBeep(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../src/shell.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,wBAAwB,EACxB,oBAAoB,EACpB,iBAAiB,EACjB,2BAA2B,EAC5B,MAAM,0BAA0B,CAAC;AAKlC,wBAAgB,qBAAqB,IAAI,YAAY,CA4CpD;AAGD,wBAAgB,eAAe,IAAI,YAAY,CAG9C;AAID,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAQtD;AAID,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,OAAO,EAAE,CAAC,CAEtF;AAGD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAE9D;AAQD,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,wBAAwB,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAGhH;AAGD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAEtG;AAKD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAE3G;AAID,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAE7F;AAGD,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,GAAG,IAAI,CAElE;AAMD,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,GAAG,IAAI,CAElF;AAGD,wBAAgB,SAAS,IAAI,IAAI,CAEhC;AAGD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAE/D;AAMD,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,QAAQ,CAAC,iBAAiB,CAAC,EACjC,SAAS,CAAC,EAAE,2BAA2B,GACtC,OAAO,CAAC,OAAO,CAAC,CAElB"}
1
+ {"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../src/shell.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EACV,YAAY,EACZ,wBAAwB,EACxB,oBAAoB,EACpB,iBAAiB,EACjB,2BAA2B,EAC5B,MAAM,0BAA0B,CAAC;AAElC,wBAAgB,mBAAmB,IAAI,kBAAkB,CAaxD;AAGD,wBAAgB,eAAe,IAAI,YAAY,CAE9C;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAMnE;AAID,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAQtD;AAID,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,OAAO,EAAE,CAAC,CAEtF;AAGD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAE9D;AAED,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI,CAKlF;AAQD,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,wBAAwB,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAGhH;AAGD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAEtG;AAKD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAE3G;AAID,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAE7F;AAED,wBAAgB,wBAAwB,IAAI,IAAI,CAK/C;AAGD,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,GAAG,IAAI,CAElE;AAMD,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,GAAG,IAAI,CAElF;AAGD,wBAAgB,SAAS,IAAI,IAAI,CAEhC;AAGD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAE/D;AAMD,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,QAAQ,CAAC,iBAAiB,CAAC,EACjC,SAAS,CAAC,EAAE,2BAA2B,GACtC,OAAO,CAAC,OAAO,CAAC,CAElB"}
package/dist/shell.js CHANGED
@@ -1,58 +1,28 @@
1
- // Builds the default web backend. Only openExternal is achievable on the web (window.open); revealing
2
- // items, opening arbitrary local paths, trashing, shortcut links, and batch operations require a native
3
- // host, so they return false / null / error-string sentinels here.
4
- export function createWebShellBackend() {
5
- return {
6
- beep() {
7
- // No portable web equivalent of a system beep; no-op until a native host provides one.
8
- },
9
- async moveItemsToTrash() {
10
- // The web cannot move local paths to the OS trash; native-host only.
11
- return [];
12
- },
13
- async moveToTrash() {
14
- // The web cannot move a local path to the OS trash; native-host only.
15
- return false;
16
- },
17
- async openExternal(url) {
18
- // window.open returns a Window handle on success, or null when blocked (popup blocker, no window).
19
- // The activate option (macOS foreground raise) has no web equivalent and is silently ignored.
20
- if (typeof window === 'undefined' || typeof window.open !== 'function')
21
- return false;
22
- try {
23
- return window.open(url, '_blank', 'noopener') !== null;
24
- }
25
- catch {
26
- return false;
27
- }
28
- },
29
- async openPath() {
30
- // The web sandbox cannot open arbitrary local paths; only a native host can.
31
- return false;
32
- },
33
- async openPathResult() {
34
- // The web cannot open arbitrary local paths; native-host only.
35
- return 'unavailable on web';
36
- },
37
- async readShortcutLink() {
38
- // Windows .lnk shortcut links are not accessible on the web.
39
- return null;
40
- },
41
- async showItemInFolder() {
42
- // The web has no OS file manager to reveal an item in; native-host only.
43
- return false;
44
- },
45
- async writeShortcutLink() {
46
- // Windows .lnk shortcut links cannot be written on the web.
47
- return false;
48
- },
49
- };
1
+ export function explainShellBackend() {
2
+ if (_custom !== null) {
3
+ return { conflict: _hostConflict, layer: 'custom', operation: null, viability: 'unobserved' };
4
+ }
5
+ if (_host !== null) {
6
+ return {
7
+ conflict: _hostConflict,
8
+ layer: 'host',
9
+ operation: _hostObservation !== null ? _hostObservation.operation : null,
10
+ viability: _hostObservation !== null ? _hostObservation.viability : 'unobserved',
11
+ };
12
+ }
13
+ return { conflict: false, layer: 'host-not-enabled', operation: null, viability: 'unobserved' };
50
14
  }
51
- // The active shell backend, or a lazily-created web default. There is always a backend.
15
+ // The active shell backend. Precedence: custom > host > sentinel.
52
16
  export function getShellBackend() {
53
- if (_backend === null)
54
- _backend = createWebShellBackend();
55
- return _backend;
17
+ return _custom ?? _host ?? _sentinel;
18
+ }
19
+ export function installShellHostBackend(backend) {
20
+ if (_host !== null) {
21
+ if (_host !== backend)
22
+ _hostConflict = true;
23
+ return;
24
+ }
25
+ _host = backend;
56
26
  }
57
27
  // True when url is allowed by the active URL-scheme allowlist. When no allowlist is set, all URLs
58
28
  // are allowed. Used internally by openShellExternalUrl; also exported for callers that need the check.
@@ -76,6 +46,12 @@ export function moveItemsToTrash(paths) {
76
46
  export function moveItemToTrash(path) {
77
47
  return getShellBackend().moveToTrash(path);
78
48
  }
49
+ export function observeShellHostResult(operation, succeeded) {
50
+ _hostObservation = {
51
+ operation,
52
+ viability: succeeded ? 'available' : 'runtime-api-unavailable',
53
+ };
54
+ }
79
55
  // Opens a URL in the user's default browser / external handler. Returns false when blocked, popup-
80
56
  // blocked, or the URL scheme is not in the active allowlist (see setShellUrlSchemeAllowlist).
81
57
  // SECURITY: handing an attacker-controlled URL to the OS default handler is the classic openExternal
@@ -102,9 +78,15 @@ export function openShellPathResult(path, options) {
102
78
  export function readShellShortcutLink(shortcutPath) {
103
79
  return getShellBackend().readShortcutLink(shortcutPath);
104
80
  }
105
- // Installs a native host shell backend; pass null to fall back to the web default.
81
+ export function resetShellBackendForTest() {
82
+ _custom = null;
83
+ _host = null;
84
+ _hostConflict = false;
85
+ _hostObservation = null;
86
+ }
87
+ // Installs a custom shell backend; pass null to clear the custom override.
106
88
  export function setShellBackend(backend) {
107
- _backend = backend;
89
+ _custom = backend;
108
90
  }
109
91
  // Sets the URL-scheme allowlist consulted by openShellExternalUrl. Pass null to allow all schemes
110
92
  // (default behavior). When a non-null list is set, openShellExternalUrl returns false for any URL whose
@@ -128,6 +110,36 @@ export function showItemInFolder(path) {
128
110
  export function writeShellShortcutLink(shortcutPath, link, operation) {
129
111
  return getShellBackend().writeShortcutLink(shortcutPath, link, operation);
130
112
  }
131
- let _backend = null;
113
+ const _sentinel = {
114
+ beep() { },
115
+ async moveItemsToTrash() {
116
+ return [];
117
+ },
118
+ async moveToTrash() {
119
+ return false;
120
+ },
121
+ async openExternal() {
122
+ return false;
123
+ },
124
+ async openPath() {
125
+ return false;
126
+ },
127
+ async openPathResult() {
128
+ return 'unavailable on web';
129
+ },
130
+ async readShortcutLink() {
131
+ return null;
132
+ },
133
+ async showItemInFolder() {
134
+ return false;
135
+ },
136
+ async writeShortcutLink() {
137
+ return false;
138
+ },
139
+ };
140
+ let _custom = null;
141
+ let _host = null;
142
+ let _hostConflict = false;
143
+ let _hostObservation = null;
132
144
  let _urlSchemeAllowlist = null;
133
145
  //# sourceMappingURL=shell.js.map
package/dist/shell.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"shell.js","sourceRoot":"","sources":["../src/shell.ts"],"names":[],"mappings":"AAQA,sGAAsG;AACtG,wGAAwG;AACxG,mEAAmE;AACnE,MAAM,UAAU,qBAAqB;IACnC,OAAO;QACL,IAAI;YACF,uFAAuF;QACzF,CAAC;QACD,KAAK,CAAC,gBAAgB;YACpB,qEAAqE;YACrE,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,KAAK,CAAC,WAAW;YACf,sEAAsE;YACtE,OAAO,KAAK,CAAC;QACf,CAAC;QACD,KAAK,CAAC,YAAY,CAAC,GAAG;YACpB,mGAAmG;YACnG,8FAA8F;YAC9F,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,UAAU;gBAAE,OAAO,KAAK,CAAC;YACrF,IAAI,CAAC;gBACH,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,UAAU,CAAC,KAAK,IAAI,CAAC;YACzD,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;QACD,KAAK,CAAC,QAAQ;YACZ,6EAA6E;YAC7E,OAAO,KAAK,CAAC;QACf,CAAC;QACD,KAAK,CAAC,cAAc;YAClB,+DAA+D;YAC/D,OAAO,oBAAoB,CAAC;QAC9B,CAAC;QACD,KAAK,CAAC,gBAAgB;YACpB,6DAA6D;YAC7D,OAAO,IAAI,CAAC;QACd,CAAC;QACD,KAAK,CAAC,gBAAgB;YACpB,yEAAyE;YACzE,OAAO,KAAK,CAAC;QACf,CAAC;QACD,KAAK,CAAC,iBAAiB;YACrB,4DAA4D;YAC5D,OAAO,KAAK,CAAC;QACf,CAAC;KACF,CAAC;AACJ,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,eAAe;IAC7B,IAAI,QAAQ,KAAK,IAAI;QAAE,QAAQ,GAAG,qBAAqB,EAAE,CAAC;IAC1D,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,kGAAkG;AAClG,uGAAuG;AACvG,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,IAAI,mBAAmB,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC9C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACvD,OAAO,mBAAmB,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,qGAAqG;AACrG,wEAAwE;AACxE,MAAM,UAAU,gBAAgB,CAAC,KAAwB;IACvD,OAAO,eAAe,EAAE,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;AACnD,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,eAAe,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;AAC7C,CAAC;AAED,mGAAmG;AACnG,8FAA8F;AAC9F,qGAAqG;AACrG,sGAAsG;AACtG,+FAA+F;AAC/F,uGAAuG;AACvG,MAAM,UAAU,oBAAoB,CAAC,GAAW,EAAE,OAA4C;IAC5F,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC3D,OAAO,eAAe,EAAE,CAAC,YAAY,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACtD,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,OAAwC;IAClF,OAAO,eAAe,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AACnD,CAAC;AAED,4FAA4F;AAC5F,iGAAiG;AACjG,8EAA8E;AAC9E,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAE,OAAwC;IACxF,OAAO,eAAe,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AACzD,CAAC;AAED,kGAAkG;AAClG,+BAA+B;AAC/B,MAAM,UAAU,qBAAqB,CAAC,YAAoB;IACxD,OAAO,eAAe,EAAE,CAAC,gBAAgB,CAAC,YAAY,CAAC,CAAC;AAC1D,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,eAAe,CAAC,OAA4B;IAC1D,QAAQ,GAAG,OAAO,CAAC;AACrB,CAAC;AAED,kGAAkG;AAClG,wGAAwG;AACxG,uFAAuF;AACvF,uFAAuF;AACvF,MAAM,UAAU,0BAA0B,CAAC,OAAiC;IAC1E,mBAAmB,GAAG,OAAO,CAAC;AAChC,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,SAAS;IACvB,eAAe,EAAE,CAAC,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED,iGAAiG;AACjG,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO,eAAe,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;AAClD,CAAC;AAED,2FAA2F;AAC3F,wEAAwE;AACxE,kGAAkG;AAClG,0EAA0E;AAC1E,MAAM,UAAU,sBAAsB,CACpC,YAAoB,EACpB,IAAiC,EACjC,SAAuC;IAEvC,OAAO,eAAe,EAAE,CAAC,iBAAiB,CAAC,YAAY,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;AAC5E,CAAC;AAED,IAAI,QAAQ,GAAwB,IAAI,CAAC;AACzC,IAAI,mBAAmB,GAA6B,IAAI,CAAC"}
1
+ {"version":3,"file":"shell.js","sourceRoot":"","sources":["../src/shell.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flighthq/shell",
3
- "version": "0.4.0-next.1898.266773a",
3
+ "version": "0.4.0-next.1908.751021c",
4
4
  "author": "Joshua Granick and other contributors",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -38,7 +38,7 @@
38
38
  "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
39
39
  },
40
40
  "dependencies": {
41
- "@flighthq/types": "0.4.0-next.1898.266773a"
41
+ "@flighthq/types": "0.4.0-next.1908.751021c"
42
42
  },
43
43
  "devDependencies": {
44
44
  "typescript": "^5.3.0"
package/src/shell.test.ts CHANGED
@@ -1,15 +1,18 @@
1
1
  import type { ShellBackend, ShellOpenExternalOptions, ShellOpenPathOptions } from '@flighthq/types/contract';
2
2
 
3
3
  import {
4
- createWebShellBackend,
4
+ explainShellBackend,
5
5
  getShellBackend,
6
+ installShellHostBackend,
6
7
  isShellUrlAllowed,
7
8
  moveItemsToTrash,
8
9
  moveItemToTrash,
10
+ observeShellHostResult,
9
11
  openShellExternalUrl,
10
12
  openShellPath,
11
13
  openShellPathResult,
12
14
  readShellShortcutLink,
15
+ resetShellBackendForTest,
13
16
  setShellBackend,
14
17
  setShellUrlSchemeAllowlist,
15
18
  shellBeep,
@@ -72,40 +75,33 @@ function fakeBackend(): ShellBackend & {
72
75
  }
73
76
 
74
77
  afterEach(() => {
75
- setShellBackend(null);
78
+ resetShellBackendForTest();
76
79
  setShellUrlSchemeAllowlist(null);
77
80
  });
78
81
 
79
- describe('createWebShellBackend', () => {
80
- it('returns a backend whose native-only operations resolve to sentinels without throwing', async () => {
81
- const backend = createWebShellBackend();
82
- expect(await backend.openPath('/tmp/x')).toBe(false);
83
- expect(await backend.showItemInFolder('/tmp/x')).toBe(false);
84
- expect(await backend.moveToTrash('/tmp/x')).toBe(false);
85
- expect(await backend.writeShortcutLink('/tmp/x.lnk', { target: '/tmp/x' })).toBe(false);
86
- expect(await backend.readShortcutLink('/tmp/x.lnk')).toBeNull();
87
- expect(() => backend.beep()).not.toThrow();
82
+ describe('explainShellBackend', () => {
83
+ it('returns host-not-enabled when no backend is installed', () => {
84
+ expect(explainShellBackend()).toEqual({
85
+ conflict: false,
86
+ layer: 'host-not-enabled',
87
+ operation: null,
88
+ viability: 'unobserved',
89
+ });
88
90
  });
89
91
 
90
- it('batch trash resolves to an empty array on the web', async () => {
91
- const backend = createWebShellBackend();
92
- expect(await backend.moveItemsToTrash(['/tmp/a', '/tmp/b'])).toEqual([]);
93
- });
94
-
95
- it('openPathResult returns the unavailable-on-web sentinel string', async () => {
96
- const backend = createWebShellBackend();
97
- expect(await backend.openPathResult('/tmp/x')).toBe('unavailable on web');
92
+ it('returns custom layer when a custom backend is set', () => {
93
+ setShellBackend(fakeBackend());
94
+ expect(explainShellBackend().layer).toBe('custom');
98
95
  });
99
96
 
100
- it('openExternal type-checks to boolean regardless of options', async () => {
101
- const backend = createWebShellBackend();
102
- expect(typeof (await backend.openExternal('https://example.com'))).toBe('boolean');
103
- expect(typeof (await backend.openExternal('https://example.com', { activate: true }))).toBe('boolean');
97
+ it('returns host layer when a host backend is installed', () => {
98
+ installShellHostBackend(fakeBackend());
99
+ expect(explainShellBackend().layer).toBe('host');
104
100
  });
105
101
  });
106
102
 
107
103
  describe('getShellBackend', () => {
108
- it('falls back to a web backend', () => {
104
+ it('falls back to sentinel', () => {
109
105
  expect(getShellBackend()).not.toBeNull();
110
106
  });
111
107
 
@@ -116,6 +112,23 @@ describe('getShellBackend', () => {
116
112
  });
117
113
  });
118
114
 
115
+ describe('installShellHostBackend', () => {
116
+ it('first-host-wins and sets conflict on second different backend', () => {
117
+ const first = fakeBackend();
118
+ const second = fakeBackend();
119
+ installShellHostBackend(first);
120
+ installShellHostBackend(second);
121
+ expect(explainShellBackend().conflict).toBe(true);
122
+ });
123
+
124
+ it('does not set conflict when the same backend is installed twice', () => {
125
+ const backend = fakeBackend();
126
+ installShellHostBackend(backend);
127
+ installShellHostBackend(backend);
128
+ expect(explainShellBackend().conflict).toBe(false);
129
+ });
130
+ });
131
+
119
132
  describe('isShellUrlAllowed', () => {
120
133
  it('returns true when no allowlist is set', () => {
121
134
  expect(isShellUrlAllowed('https://example.com')).toBe(true);
@@ -159,6 +172,24 @@ describe('moveItemToTrash', () => {
159
172
  });
160
173
  });
161
174
 
175
+ describe('observeShellHostResult', () => {
176
+ it('records a successful observation into the explanation', () => {
177
+ installShellHostBackend(fakeBackend());
178
+ observeShellHostResult('openExternal', true);
179
+ const explanation = explainShellBackend();
180
+ expect(explanation.operation).toBe('openExternal');
181
+ expect(explanation.viability).toBe('available');
182
+ });
183
+
184
+ it('records a failed observation into the explanation', () => {
185
+ installShellHostBackend(fakeBackend());
186
+ observeShellHostResult('openExternal', false);
187
+ const explanation = explainShellBackend();
188
+ expect(explanation.operation).toBe('openExternal');
189
+ expect(explanation.viability).toBe('runtime-api-unavailable');
190
+ });
191
+ });
192
+
162
193
  describe('openShellExternalUrl', () => {
163
194
  it('opens via the active backend', async () => {
164
195
  const backend = fakeBackend();
@@ -246,7 +277,7 @@ describe('openShellPathResult', () => {
246
277
  });
247
278
 
248
279
  describe('readShellShortcutLink', () => {
249
- it('returns null on the web (no backend set)', async () => {
280
+ it('returns null with sentinel (no backend set)', async () => {
250
281
  const result = await readShellShortcutLink('/tmp/x.lnk');
251
282
  expect(result).toBeNull();
252
283
  });
@@ -259,8 +290,17 @@ describe('readShellShortcutLink', () => {
259
290
  });
260
291
  });
261
292
 
293
+ describe('resetShellBackendForTest', () => {
294
+ it('clears all backend slots', () => {
295
+ setShellBackend(fakeBackend());
296
+ installShellHostBackend(fakeBackend());
297
+ resetShellBackendForTest();
298
+ expect(explainShellBackend().layer).toBe('host-not-enabled');
299
+ });
300
+ });
301
+
262
302
  describe('setShellBackend', () => {
263
- it('clears back to the web fallback when passed null', () => {
303
+ it('clears back to sentinel when passed null', () => {
264
304
  setShellBackend(fakeBackend());
265
305
  setShellBackend(null);
266
306
  expect(getShellBackend()).not.toBeNull();
@@ -299,7 +339,7 @@ describe('showItemInFolder', () => {
299
339
  });
300
340
 
301
341
  describe('writeShellShortcutLink', () => {
302
- it('returns false on the web (no backend set)', async () => {
342
+ it('returns false with sentinel (no backend set)', async () => {
303
343
  const result = await writeShellShortcutLink('/tmp/x.lnk', { target: '/tmp/target' });
304
344
  expect(result).toBe(false);
305
345
  });