dsh-folder-path 0.1.0 → 0.2.1
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 +5 -2
- package/README_EN.md +2 -2
- package/client/globals.d.ts +30 -0
- package/client/{index.js → index.ts} +41 -24
- package/lib/client.js +196 -197
- package/lib/index.js +234 -258
- package/lib/types/index.d.ts +110 -0
- package/package.json +11 -4
- package/scripts/{build-client.mjs → build-client.mts} +9 -8
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/** Cordis plugin name; the profile row's `id` is independent of it. */
|
|
2
|
+
export declare const name = "folder-path";
|
|
3
|
+
/**
|
|
4
|
+
* The single endpoint the browser half calls. Hand-mirrored in
|
|
5
|
+
* `client/index.js`: the two halves ship as one package but cannot import each
|
|
6
|
+
* other (the browser bundle is a hand-written classic script).
|
|
7
|
+
*/
|
|
8
|
+
export declare const ROUTE_PATH = "/dsh-folder-path/api/v1/clipboard-paths";
|
|
9
|
+
/**
|
|
10
|
+
* One clipboard item as the route reports it.
|
|
11
|
+
*
|
|
12
|
+
* `isDirectory` is always set by the host probes; `matchDirectories` only looks
|
|
13
|
+
* at `name`, so the test suite may hand it path/name pairs without the flag.
|
|
14
|
+
*/
|
|
15
|
+
export interface ClipboardEntry {
|
|
16
|
+
path: string;
|
|
17
|
+
name: string;
|
|
18
|
+
isDirectory: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The part of the profile row's `config` block this plugin reads.
|
|
22
|
+
*/
|
|
23
|
+
export interface FolderPathConfig {
|
|
24
|
+
/** Set `false` to mount the host half inert. */
|
|
25
|
+
enabled?: boolean;
|
|
26
|
+
/** Clipboard probe budget in milliseconds. */
|
|
27
|
+
timeoutMs?: number | string;
|
|
28
|
+
/** PowerShell executable used for the Windows probe. */
|
|
29
|
+
powershell?: string;
|
|
30
|
+
/** `osascript` executable used for the macOS probe. */
|
|
31
|
+
osascript?: string;
|
|
32
|
+
/** Allow a browser on another machine to read this host's clipboard. */
|
|
33
|
+
allowNonLoopback?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The request shape the route handler reads: a Node HTTP request, narrowed to
|
|
37
|
+
* the fields this plugin touches.
|
|
38
|
+
*/
|
|
39
|
+
interface IncomingRequest {
|
|
40
|
+
method?: string;
|
|
41
|
+
headers: Record<string, string | undefined>;
|
|
42
|
+
socket?: {
|
|
43
|
+
remoteAddress?: string;
|
|
44
|
+
};
|
|
45
|
+
[Symbol.asyncIterator](): AsyncIterator<Uint8Array>;
|
|
46
|
+
}
|
|
47
|
+
/** The response shape the route handler writes: a Node HTTP response. */
|
|
48
|
+
interface OutgoingResponse {
|
|
49
|
+
writeHead(status: number, headers: Record<string, string | number>): void;
|
|
50
|
+
end(body?: string): void;
|
|
51
|
+
}
|
|
52
|
+
/** An exact-route handler as the DSH web server invokes it. */
|
|
53
|
+
export type RouteHandler = (request: IncomingRequest, response: OutgoingResponse) => Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* The host context surface this plugin uses. DSH's real Cordis context is
|
|
56
|
+
* richer; only these members are touched, so the local interface is kept
|
|
57
|
+
* minimal instead of importing the host's types.
|
|
58
|
+
*/
|
|
59
|
+
export interface HostContext {
|
|
60
|
+
logger?: {
|
|
61
|
+
warn?: (message: string, ...args: unknown[]) => void;
|
|
62
|
+
};
|
|
63
|
+
inject(names: readonly string[], callback: (host: HostFiber) => void): void;
|
|
64
|
+
}
|
|
65
|
+
/** The fiber `ctx.inject` hands back once the web server service appears. */
|
|
66
|
+
export interface HostFiber {
|
|
67
|
+
effect(factory: () => (() => void) | void, label: string): void;
|
|
68
|
+
webServer: {
|
|
69
|
+
register(route: {
|
|
70
|
+
kind: 'exact';
|
|
71
|
+
path: string;
|
|
72
|
+
handler: RouteHandler;
|
|
73
|
+
}): () => void;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Install the clipboard route.
|
|
78
|
+
*
|
|
79
|
+
* `webServer` is acquired through `ctx.inject` rather than read directly: the
|
|
80
|
+
* service appears asynchronously, and a plain read inside `apply` would silently
|
|
81
|
+
* see nothing and never register the route.
|
|
82
|
+
*
|
|
83
|
+
* @param ctx - host plugin context.
|
|
84
|
+
* @param config - the profile row's `config` block.
|
|
85
|
+
* @returns nothing; the route is registered once `webServer` appears.
|
|
86
|
+
*/
|
|
87
|
+
export declare function apply(ctx: HostContext, config?: FolderPathConfig): void;
|
|
88
|
+
/**
|
|
89
|
+
* Filter clipboard directories down to the ones the browser is pasting.
|
|
90
|
+
*
|
|
91
|
+
* An empty `names` list means the caller wants every copied directory; a
|
|
92
|
+
* non-empty list is matched by basename, case-insensitively, because that is
|
|
93
|
+
* all the browser can see of the path.
|
|
94
|
+
*
|
|
95
|
+
* @param directories - directory entries from the clipboard.
|
|
96
|
+
* @param names - basenames the browser reported.
|
|
97
|
+
* @returns the matching entries, in clipboard order.
|
|
98
|
+
* @remarks Exported for the host test suite; the route is its only runtime caller.
|
|
99
|
+
*/
|
|
100
|
+
export declare function matchDirectories<T extends {
|
|
101
|
+
name: string;
|
|
102
|
+
}>(directories: T[], names: string[]): T[];
|
|
103
|
+
/**
|
|
104
|
+
* Parse the macOS probe's stdout into absolute paths.
|
|
105
|
+
* @param stdout - the probe's stdout.
|
|
106
|
+
* @returns the paths it reported, in clipboard order.
|
|
107
|
+
* @remarks Exported for the host test suite; the probe is its only runtime caller.
|
|
108
|
+
*/
|
|
109
|
+
export declare function parseProbePaths(stdout: unknown): string[];
|
|
110
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-folder-path",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "DSH 插件:粘贴(复制来的)文件夹时不再尝试上传,而是把它的完整路径作为文本插入输入框(Windows / macOS)。Paste a copied folder and get its full path as text instead of a failed upload (Windows and macOS).",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
10
|
"main": "lib/index.js",
|
|
11
|
+
"types": "lib/types/index.d.ts",
|
|
11
12
|
"exports": {
|
|
12
13
|
".": {
|
|
13
14
|
"default": "./lib/index.js"
|
|
@@ -19,6 +20,7 @@
|
|
|
19
20
|
},
|
|
20
21
|
"files": [
|
|
21
22
|
"lib/index.js",
|
|
23
|
+
"lib/types",
|
|
22
24
|
"lib/client.js",
|
|
23
25
|
"client",
|
|
24
26
|
"scripts",
|
|
@@ -31,9 +33,10 @@
|
|
|
31
33
|
"docs"
|
|
32
34
|
],
|
|
33
35
|
"scripts": {
|
|
34
|
-
"build": "node scripts/build-client.
|
|
35
|
-
"
|
|
36
|
-
"
|
|
36
|
+
"build": "tsc -p tsconfig.json && tsc -p tsconfig.client.json && node scripts/build-client.mts",
|
|
37
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json --noEmit",
|
|
38
|
+
"prepare": "npm run build",
|
|
39
|
+
"test": "npm run build && node --test test/*.test.ts"
|
|
37
40
|
},
|
|
38
41
|
"engines": {
|
|
39
42
|
"node": ">=24",
|
|
@@ -71,5 +74,9 @@
|
|
|
71
74
|
"client": {
|
|
72
75
|
"platform": "web"
|
|
73
76
|
}
|
|
77
|
+
},
|
|
78
|
+
"devDependencies": {
|
|
79
|
+
"@types/node": "^26.6.5",
|
|
80
|
+
"typescript": "^7.0.2"
|
|
74
81
|
}
|
|
75
82
|
}
|
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Wrap the
|
|
2
|
+
* Wrap the compiled browser half into the DSH client bundle contract.
|
|
3
3
|
*
|
|
4
4
|
* The client module system executes a plugin bundle as a plain classic script
|
|
5
5
|
* that REGISTERS a lazy CJS factory:
|
|
6
6
|
*
|
|
7
7
|
* window.__ModuleLoader__.load({ id, factory: (require) => exports })
|
|
8
8
|
*
|
|
9
|
-
* so the bundle needs no bundler. `client/index.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* so the bundle needs no bundler. `client/index.ts` defines `createPlugin`;
|
|
10
|
+
* `tsc -p tsconfig.client.json` compiles it to `.client-build/index.js`, and
|
|
11
|
+
* this script wraps that compiled output into `lib/client.js`, which is what
|
|
12
|
+
* `exports['./client']` points at. Run by `npm run build` (and by `prepare`, so
|
|
13
|
+
* a `dsh plugin add` always ships a bundle).
|
|
13
14
|
*/
|
|
14
15
|
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
15
16
|
import { dirname, join, resolve } from 'node:path'
|
|
16
17
|
import { fileURLToPath } from 'node:url'
|
|
17
18
|
|
|
18
19
|
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
19
|
-
const source = readFileSync(join(root, 'client', 'index.js'), 'utf8')
|
|
20
|
+
const source = readFileSync(join(root, '.client-build', 'index.js'), 'utf8')
|
|
20
21
|
const target = join(root, 'lib', 'client.js')
|
|
21
22
|
|
|
22
|
-
const indent = (text) =>
|
|
23
|
+
const indent = (text: string): string =>
|
|
23
24
|
text
|
|
24
25
|
.split('\n')
|
|
25
26
|
.map((line) => (line.length > 0 ? ` ${line}` : line))
|
|
@@ -31,7 +32,7 @@ const bundle = `window.__ModuleLoader__.load({
|
|
|
31
32
|
var module = { exports: {} };
|
|
32
33
|
var exports = module.exports;
|
|
33
34
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
34
|
-
// ---- client/index.js ----
|
|
35
|
+
// ---- .client-build/index.js ----
|
|
35
36
|
${indent(source)}
|
|
36
37
|
const plugin = createPlugin(require);
|
|
37
38
|
exports.apply = plugin.apply;
|