xshell 1.1.20 → 1.1.22

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/apps.d.ts CHANGED
@@ -1,5 +1,3 @@
1
- import type { Readable } from 'stream';
2
- import type OSS from 'ali-oss';
3
1
  import { type CallOptions } from './process.ts';
4
2
  export declare let npm: {
5
3
  bin: string;
@@ -17,42 +15,3 @@ export declare let npm: {
17
15
  break?: boolean;
18
16
  }): Promise<void>;
19
17
  };
20
- /** 使用 tsc 编译项目
21
- - fpd: 项目目录
22
- - options?:
23
- - tsconfig?: `'<dir>/tsconfig.json'`
24
- - typecheck?: `false` 是否只进行 typecheck, (使用 call_node 执行而非 term) */
25
- export declare function tsc(fpd: string, { tsconfig, typecheck, }?: {
26
- tsconfig?: string;
27
- typecheck?: boolean;
28
- }): Promise<import("./process.ts").CallResult<string>>;
29
- export declare let oss: {
30
- client: OSS;
31
- bucket: string;
32
- region: string;
33
- fpd_cdn: string;
34
- fpd_oss: string;
35
- init({ bucket, region, fpd_cdn, fpd_oss, access_id, access_key }: {
36
- bucket: string;
37
- region: string;
38
- fpd_cdn: string;
39
- fpd_oss: string;
40
- access_id: string;
41
- access_key: string;
42
- }): Promise<void>;
43
- /** 上传文件到 oss,返回文件 url
44
- - fp: 上传到 oss 的路径, 如 assets/web.latest.zip
45
- - data: 文件 (Uint8Array) | 本地文件完整路径 (string) | ReadableStream
46
- - options?:
47
- - private?: `false` 设置为私有,需要通过 URL 签名参数访问
48
- - print?: `true` 打印消息
49
- - cdn?: `false` false 时返回 oss 链接,true 时返回 cdn 链接
50
- - copy?: `false` 是否复制到剪贴板 */
51
- upload(fp_remote: string, data: Uint8Array | string | Readable, { private: _private, print, cdn, }?: {
52
- private?: boolean;
53
- print?: boolean;
54
- cdn?: boolean;
55
- }): Promise<string>;
56
- /** 获取经过签名后可访问的 url */
57
- get_url(fp: string): Promise<string>;
58
- };
package/apps.js CHANGED
@@ -1,7 +1,5 @@
1
1
  import util from 'util';
2
2
  import { call_nodejs, platform, username } from "./process.js";
3
- import { check } from "./utils.js";
4
- import { path } from "./path.js";
5
3
  export let npm = {
6
4
  bin: platform == 'win32' ? `C:/Users/${username}/AppData/Roaming/npm/node_modules/pnpm/bin/pnpm.cjs` : '/usr/bin/pnpm',
7
5
  async call(cwd, bin, args, options) {
@@ -23,76 +21,4 @@ export let npm = {
23
21
  await this.call(cwd, this.bin, args);
24
22
  },
25
23
  };
26
- /** 使用 tsc 编译项目
27
- - fpd: 项目目录
28
- - options?:
29
- - tsconfig?: `'<dir>/tsconfig.json'`
30
- - typecheck?: `false` 是否只进行 typecheck, (使用 call_node 执行而非 term) */
31
- export async function tsc(fpd, { tsconfig = './tsconfig.json', typecheck = false, } = {}) {
32
- return call_nodejs(`${fpd}/node_modules/typescript/bin/tsc`, [
33
- '--project', tsconfig,
34
- ...typecheck ? ['--noEmit'] : []
35
- ], {
36
- cwd: fpd,
37
- print: { command: false, code: false, stdout: true, stderr: true }
38
- });
39
- }
40
- export let oss = {
41
- client: null,
42
- bucket: null,
43
- region: null,
44
- fpd_cdn: null,
45
- fpd_oss: null,
46
- async init({ bucket, region, fpd_cdn, fpd_oss, access_id, access_key }) {
47
- if (this.client)
48
- return;
49
- const OSS = (await import('ali-oss')).default;
50
- this.bucket = bucket;
51
- this.region = region;
52
- this.fpd_cdn = fpd_cdn;
53
- this.fpd_oss = fpd_oss;
54
- this.client = new OSS({
55
- accessKeyId: access_id,
56
- accessKeySecret: access_key,
57
- region: this.region,
58
- bucket: this.bucket,
59
- secure: true
60
- });
61
- },
62
- /** 上传文件到 oss,返回文件 url
63
- - fp: 上传到 oss 的路径, 如 assets/web.latest.zip
64
- - data: 文件 (Uint8Array) | 本地文件完整路径 (string) | ReadableStream
65
- - options?:
66
- - private?: `false` 设置为私有,需要通过 URL 签名参数访问
67
- - print?: `true` 打印消息
68
- - cdn?: `false` false 时返回 oss 链接,true 时返回 cdn 链接
69
- - copy?: `false` 是否复制到剪贴板 */
70
- async upload(fp_remote, data, { private: _private = false, print = true, cdn = false, } = {}) {
71
- check(this.client, 'OSS 应该已经初始化了');
72
- check(!fp_remote.startsWith('/'), 'fp 不能以 / 开头');
73
- check(!fp_remote.isdir, '不能使用 oss.upload 上传文件夹,请使用 oss.upload_dir');
74
- if (typeof data === 'string')
75
- check(path.isAbsolute(data), 'oss.upload 传入 data 参数类型为 string 时,必须为本地文件完整路径');
76
- if (data instanceof Uint8Array && !Buffer.isBuffer(data))
77
- data = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
78
- await this.client.put(fp_remote, data);
79
- let url;
80
- if (_private) {
81
- await this.client.putACL(fp_remote, 'private');
82
- url = await this.get_url(fp_remote);
83
- }
84
- else
85
- url = new URL(`${cdn ? this.fpd_cdn : this.fpd_oss}${fp_remote}`).toString();
86
- if (print)
87
- console.log(`已上传到 oss${_private ? ' (私有)' : ''}:`, url);
88
- return url;
89
- },
90
- /** 获取经过签名后可访问的 url */
91
- async get_url(fp) {
92
- check(this.client, 'OSS 应该已经初始化了');
93
- const url = new URL(this.client.signatureUrl(fp, { expires: /* 十年 */ 3600 * 24 * 365 * 10 }));
94
- const url_ = `${this.fpd_cdn}${url.pathname.slice(1)}${url.search}`;
95
- return url_;
96
- }
97
- };
98
24
  //# sourceMappingURL=apps.js.map
package/file.d.ts CHANGED
@@ -84,6 +84,7 @@ export interface FListOptions {
84
84
  absolute?: boolean;
85
85
  print?: boolean;
86
86
  stats?: boolean;
87
+ best_effort?: boolean;
87
88
  }
88
89
  /**
89
90
  - fpd: 文件夹完整路径 absolute path of directory
@@ -94,7 +95,7 @@ export interface FListOptions {
94
95
  - filter?: `() => true` RegExp | (fp: string) => any
95
96
  注意当 deep = true 时被 filter 过滤掉的目录及目录中的文件不会包含在结果中
96
97
  - stats?: `false` 启用后返回 FStats 列表
97
- Returns the FStats list when enabled */
98
+ - best_effort?: `false` 启用后,当遇到 stat 软链接文件不存在等错误时,会返回一个 fake_time FStats 文件,而不是抛出错误 */
98
99
  export declare function flist(fpd: string): Promise<string[]>;
99
100
  export declare function flist(fpd: string, options?: FListOptions & {
100
101
  stats: true;
package/file.js CHANGED
@@ -119,8 +119,25 @@ export async function fappend(fp, data, { print = true } = {}) {
119
119
  throw error;
120
120
  }
121
121
  }
122
+ const fake_time = new Date(0);
123
+ const fake_stats = {
124
+ fp: '',
125
+ size: 0n,
126
+ atimeNs: 0n,
127
+ mtimeNs: 0n,
128
+ ctimeNs: 0n,
129
+ birthtimeNs: 0n,
130
+ atimeMs: 0n,
131
+ mtimeMs: 0n,
132
+ ctimeMs: 0n,
133
+ birthtimeMs: 0n,
134
+ mtime: fake_time,
135
+ atime: fake_time,
136
+ ctime: fake_time,
137
+ birthtime: fake_time,
138
+ };
122
139
  export async function flist(fpd, options = {}) {
123
- const { filter, deep = false, absolute = false, print = true, stats = false } = options;
140
+ const { filter, deep = false, absolute = false, print = true, stats = false, best_effort = false } = options;
124
141
  check(path.isAbsolute(fpd), t("flist: 参数 fpd: '{{fpd}}' 必须是绝对路径", { fpd }));
125
142
  check(fpd.isdir, t("flist: 参数 fpd: '{{fpd}}' 必须以 / 结尾", { fpd }));
126
143
  if (!path.isAbsolute(fpd))
@@ -148,7 +165,16 @@ export async function flist(fpd, options = {}) {
148
165
  fps.push(fp);
149
166
  }
150
167
  async function _fstat(fp) {
151
- let _stats = await fstat(absolute ? fp : fpd + fp);
168
+ let _stats;
169
+ try {
170
+ _stats = await fstat(absolute ? fp : fpd + fp);
171
+ }
172
+ catch (error) {
173
+ if (best_effort)
174
+ _stats = { ...fake_stats };
175
+ else
176
+ throw error;
177
+ }
152
178
  if (!absolute)
153
179
  _stats.fp = fp;
154
180
  return _stats;
package/git.d.ts CHANGED
@@ -5,10 +5,10 @@ export declare class Git {
5
5
  constructor(cwd: string);
6
6
  static init(cwd: string, print?: boolean): Promise<Git>;
7
7
  static clone(repo: string, fpd: string, shallow?: boolean | string): Promise<Git>;
8
- call(args?: any[], { color, print, printers, throw_code, }?: {
8
+ call(args?: any[], { color, print, on_stderr, throw_code, }?: {
9
9
  color?: boolean;
10
10
  print?: CallOptions['print'];
11
- printers?: CallOptions['printers'];
11
+ on_stderr?: CallOptions['on_stderr'];
12
12
  throw_code?: CallOptions['throw_code'];
13
13
  }): Promise<import("./process.ts").CallResult<string>>;
14
14
  get_branch(): Promise<string>;
package/git.js CHANGED
@@ -23,7 +23,7 @@ export class Git {
23
23
  ]);
24
24
  return git;
25
25
  }
26
- async call(args = [], { color = true, print, printers, throw_code, } = {}) {
26
+ async call(args = [], { color = true, print, on_stderr, throw_code, } = {}) {
27
27
  return call(Git.exe, [
28
28
  ...color ? [] : [
29
29
  '-c', 'color.status=false',
@@ -35,8 +35,8 @@ export class Git {
35
35
  ], {
36
36
  cwd: this.cwd,
37
37
  print,
38
- printers,
39
- throw_code
38
+ throw_code,
39
+ on_stderr
40
40
  });
41
41
  }
42
42
  async get_branch() {
package/net.browser.js CHANGED
@@ -345,7 +345,7 @@ export class Remote {
345
345
  if (url)
346
346
  this.url = url;
347
347
  check(!funcs?.echo);
348
- this.funcs = this.initiator ? funcs : {
348
+ this.funcs = {
349
349
  ...funcs,
350
350
  echo({ data }) {
351
351
  return data;
package/net.js CHANGED
@@ -530,7 +530,7 @@ export class Remote {
530
530
  if (url)
531
531
  this.url = url;
532
532
  check(!funcs?.echo);
533
- this.funcs = this.initiator ? funcs : {
533
+ this.funcs = {
534
534
  ...funcs,
535
535
  echo({ data }) {
536
536
  return data;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xshell",
3
- "version": "1.1.20",
3
+ "version": "1.1.22",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "bin": {
@@ -64,7 +64,6 @@
64
64
  "@xterm/addon-web-links": "^0.11.0",
65
65
  "@xterm/addon-webgl": "^0.18.0",
66
66
  "@xterm/xterm": "^5.5.0",
67
- "ali-oss": "^6.22.0",
68
67
  "archiver": "^7.0.1",
69
68
  "chalk": "^5.4.1",
70
69
  "chardet": "^2.0.0",
@@ -90,7 +89,7 @@
90
89
  "mime-types": "^2.1.35",
91
90
  "ora": "^8.1.1",
92
91
  "react": "^19.0.0",
93
- "react-i18next": "^15.2.0",
92
+ "react-i18next": "^15.3.0",
94
93
  "react-object-model": "^1.2.21",
95
94
  "resolve-path": "^1.4.0",
96
95
  "sass": "^1.83.0",
@@ -113,7 +112,6 @@
113
112
  },
114
113
  "devDependencies": {
115
114
  "@babel/types": "^7.26.3",
116
- "@types/ali-oss": "^6.16.11",
117
115
  "@types/archiver": "^6.0.3",
118
116
  "@types/babel__traverse": "^7.20.6",
119
117
  "@types/chardet": "^0.8.3",
package/process.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { InspectOptions } from 'util';
2
2
  import { type ChildProcess } from 'child_process';
3
3
  import './prototype.ts';
4
- import type { Encoding } from './file.ts';
4
+ import { type Encoding } from './file.ts';
5
5
  import type { MyProxy } from './net.ts';
6
6
  export declare const sea: boolean;
7
7
  export declare const exe_nodejs: string;
@@ -18,7 +18,6 @@ export declare const print_no_command: {
18
18
  readonly stderr: true;
19
19
  };
20
20
  };
21
- type StdioType = 'pipe' | 'ignore' | 'inherit' | number;
22
21
  export interface FullPrintOptions {
23
22
  stdout: boolean;
24
23
  stderr: boolean;
@@ -32,13 +31,25 @@ interface BaseOptions {
32
31
  envs?: Record<string, string>;
33
32
  /** 创建子进程时添加 http_proxy, https_proxy, no_proxy 环境变量以启用代理 */
34
33
  proxy?: MyProxy | true;
35
- /** 使用文件作为标准输入 */
36
- fp_stdin?: string;
37
- /** 使用文件作为标准输出 */
38
- fp_stdout?: string;
39
- /** 使用文件作为标准错误 */
40
- fp_stderr?: string;
41
- /** true 时会设置 UV_PROCESS_WINDOWS_HIDE
34
+ /** 控制子进程 stdin,默认为 `false` (读空设备),除非传入了 {@link CallOptions.input} 属性
35
+ - 默认值:
36
+ - start(): false ('ignore', 空设备)
37
+ - (call|launch)(): true (pipe, 作为 child.stdout 可读流)
38
+ - `true` 设置子进程 stdin 为 'pipe'
39
+ - `false` 设置子进程 stdin 为 'ignore' (空设备)
40
+ - `string` 设置子进程 stdin 为某个文件路径 (打开文件,将句柄设置为子进程 stdin) */
41
+ stdin?: boolean | string;
42
+ /** 控制子进程 stdout
43
+ - 默认值:
44
+ - start(): false ('ignore', 空设备)
45
+ - (call|launch)(): true (pipe, 作为 child.stdout 可读流)
46
+ - `true` 设置子进程 stdout 为 'pipe' (作为 child.stdout 可读流)
47
+ - `false` 设置子进程 stdout 为 'ignore' (空设备)
48
+ - `string` 设置子进程 stdout 某个文件路径 (打开文件,将句柄设置为子进程 stdout) */
49
+ stdout?: boolean | string;
50
+ /** 控制子进程 stderr,默认为 `stdout` 的值,用法同 {@link BaseOptions.stdout} */
51
+ stderr?: boolean | string;
52
+ /** 默认为 false,不显示窗口,会设置 UV_PROCESS_WINDOWS_HIDE
42
53
  然后设置启动进程的参数 CREATE_NO_WINDOW, 和 SW_HIDE
43
54
  D:/0/libuv/src/win/process.c
44
55
  CREATE_NO_WINDOW:
@@ -55,12 +66,6 @@ interface BaseOptions {
55
66
  export interface StartOptions extends BaseOptions {
56
67
  /** `true` 是否打印启动命令行 */
57
68
  print?: boolean;
58
- /** 使用文件作为标准输入 */
59
- fp_stdin?: string;
60
- /** 使用文件作为标准输出 */
61
- fp_stdout?: string;
62
- /** 使用文件作为标准错误 */
63
- fp_stderr?: string;
64
69
  }
65
70
  export declare function get_command(exe: string, args?: string[]): string;
66
71
  export interface SubProcess<TOutput extends string | Buffer = string> extends ChildProcess {
@@ -72,48 +77,44 @@ export interface SubProcess<TOutput extends string | Buffer = string> extends Ch
72
77
  args: string[];
73
78
  /** 启动命令 */
74
79
  command: string;
75
- /** 调用 call 的 Promise 结果,call, launch 都有,start 创建的进程没有 */
76
- presult: Promise<CallResult<TOutput>>;
80
+ /** 调用 call 的 Promise 结果,只有通过 launch 启动的进程有 */
81
+ presult?: Promise<CallResult<TOutput>>;
77
82
  /** 是否还在运行 */
78
83
  get running(): boolean;
79
84
  /** 是否已结束,等价于 !running */
80
85
  get finished(): boolean;
81
86
  }
82
- /** 启动独立 (detached) 的进程,不受当前 node.js 进程退出的影响,重定向 stdio 到文件或直接忽略
83
- 使用 windowsHide 选项避免创建命令行窗口
87
+ /** 使用 exe 启动独立 (detached) 的进程,不受当前 node.js 进程退出的影响,可重定向 stdio 到文件(默认直接忽略)
84
88
  - exe: .exe 路径或文件名 (建议使用完整路径,跳过 path 搜索,性能更高)
85
89
  - args?: `[ ]` 参数列表
86
- - options?:
87
- - cwd?
90
+ - options?: {@link StartOptions} 继承自 {@link BaseOptions}
91
+ - cwd?: `'T:/'`
88
92
  - envs?: `process.env` 覆盖/添加到 process.env 的环境变量,传 null 时可以取消设置该变量
89
- - encoding?: `'utf-8'` 子进程输出编码
93
+ - proxy?: 创建子进程时添加 http_proxy, https_proxy, no_proxy 环境变量以启用代理
90
94
  - print?: `true` 是否打印启动命令行
91
- - input?: string, 启动子进程之后写入到子进程 stdin 中的内容,写完后关闭子进程 stdin (pty 不关闭)
92
- - fp_stdin?: 使用文件作为标准输入,设置 stdio 中对应的值
93
- - fp_stdout?: 使用文件作为标准输出,设置 stdio 中对应的值
94
- - fp_stderr?: 使用文件作为标准错误,设置 stdio 中对应的值 */
95
+ - stdin?: 控制子进程 stdin,默认为 `false` ('ignore', 读空设备)
96
+ 可传入 string,设置子进程 stdin 为某个文件路径 (打开文件,将句柄设置为子进程 stdin)
97
+ - stdout?: 控制子进程 stdout,默认为 `false` ('ignore', 写空设备)
98
+ 可传入 string,设置子进程 stdout 某个文件路径 (打开文件,将句柄设置为子进程 stdout)
99
+ - stderr?: 控制子进程 stderr,默认为 `stdout` 的值,用法同 stdout
100
+ - window?: 默认为 false,不显示窗口,设置启动进程的参数 CREATE_NO_WINDOW, 和 SW_HIDE
101
+ - title?: 由创建进程的调用者设置的,用于区分、识别、记忆的可选名称 */
95
102
  export declare function start(exe: string, args?: string[], options?: StartOptions): Promise<SubProcess>;
96
103
  export interface CallOptions extends BaseOptions {
97
104
  /** `true` print 选项,支持设置细项 */
98
- print?: boolean | FullPrintOptions;
105
+ print?: boolean | Partial<FullPrintOptions>;
99
106
  /** `true` code 不为 0 时是否抛出异常 */
100
107
  throw_code?: boolean;
101
- /** `'utf-8'` 子进程输出编码 */
108
+ /** `'utf-8'` 子进程输出编码,不传 'binary' 则 stdout, stderr 为 string 流 */
102
109
  encoding?: 'binary' | Encoding;
103
- /** `[stdin ? 'pipe' : 'ignore', 'pipe', 'pipe']` 设置为 'ignore' 时忽略 stdio 处理 */
104
- stdio?: 'pipe' | 'ignore' | [StdioType, StdioType, StdioType];
105
- /** `false` 子进程是否可能会读取 stdin
106
- 设置为 true 时会设置子进程 stdin 为 'pipe', 否则为 'ignore' */
107
- stdin?: boolean;
108
- /** 启动子进程之后写入到子进程 stdin 中的内容,写完后关闭子进程 stdin */
110
+ /** 启动子进程之后写入到子进程 stdin 中的内容,写完后就关闭子进程 stdin,通知子进程处理 */
109
111
  input?: string;
110
- /** 实时处理 stdout 和 stderr 的每个 chunk,启用后子进程输出不会自动 print */
111
- printers?: {
112
- stdout?: (chunk: string) => void;
113
- stderr?: (chunk: string) => void;
114
- };
115
112
  /** 可以传入回调函数及时获取通过 start 创建的子进程,便于执行 kill、获得 pid 等操作 */
116
- on_child?: (child: SubProcess) => void;
113
+ on_child?(child: SubProcess): void;
114
+ /** 实时处理 stdout 的每个 chunk,设置后子进程输出不会自动 print,且 CallResult 中 stdout 属性为 '' */
115
+ on_stdout?(chunk: string): void;
116
+ /** 实时处理 stderr 的每个 chunk,设置后子进程输出不会自动 print,且 CallResult 中 stderr 属性为 '' */
117
+ on_stderr?(chunk: string): void;
117
118
  }
118
119
  export interface CallResult<TOutput extends string | Buffer = string> {
119
120
  pid: number;
@@ -142,18 +143,34 @@ export declare class CallError<TOutput extends string | Buffer = string> extends
142
143
  [inspect.custom](depth: number, options: InspectOptions, inspect: Function): string;
143
144
  }
144
145
  /** 调用 exe 启动子进程,等待并获取返回结果,错误时抛出 CallError
145
- - exe: .exe 路径或文件名 (建议使用路径,跳过 path 搜索,性能更高)
146
- - args: `[ ]` 参数列表
147
- - options?:
148
- - cwd?
149
- - print?: `true` print 选项,支持设置细项
146
+ - exe: exe 路径或文件名 (建议使用路径,跳过 path 搜索,性能更高)
147
+ - args?: `[ ]` 参数列表
148
+ - options?: {@link CallOptions} 继承自 {@link BaseOptions}
149
+ - cwd?: `'T:/'`
150
+ - print?: `true` print 选项,支持设置细项 {@link FullPrintOptions}
151
+ - command?: `true` 打印启动命令行
152
+ - code?: `true` 打印进程执行成功的消息 (进程 ... 正常结束)
153
+ - stdout?: `true` 打印 stdout
154
+ - stderr?: `true` 打印 stderr
150
155
  - throw_code?: `true` code 不为 0 时是否抛出异常
151
156
  - envs?: `process.env` 覆盖/添加到 process.env 的环境变量,传 null 时可以取消设置该变量
152
- - encoding?: `'utf-8'` 子进程输出编码, 设置为 binary 时返回 stdout, stderr 类型为 Buffer; 否则是 string
153
- - printers?: 实时处理 stdout 和 stderr 的每个 chunk,启用后子进程输出不会自动 print,且对应的 stdout, stderr 为空
154
- - stdio?: `'pipe'` 设置为 'ignore' 时忽略 stdio 处理
157
+ - proxy?: 创建子进程时添加 http_proxy, https_proxy, no_proxy 环境变量以启用代理
155
158
  - input?: string, 启动子进程之后写入到子进程 stdin 中的内容,写完后关闭子进程 stdin
156
- - on_child?: 可以传入回调函数及时获取通过 start 创建的子进程,便于执行 kill、获得 pid 等操作 */
159
+ - stdin?: 控制子进程 stdin,默认为 `false` (读空设备),除非传入了 {@link CallOptions.input} 属性
160
+ - `true` 设置子进程 stdin 为 'pipe'
161
+ - `false` 设置子进程 stdin 为 'ignore' (空设备)
162
+ - `string` 设置子进程 stdin 为某个文件路径 (打开文件,将句柄设置为子进程 stdin)
163
+ - stdout?: 控制子进程 stdout,默认为 `true` (pipe, 作为 child.stdout 可读流)
164
+ - `true` 设置子进程 stdout 为 'pipe' (作为 child.stdout 可读流)
165
+ - `false` 设置子进程 stdout 为 'ignore' (空设备)
166
+ - `string` 设置子进程 stdout 某个文件路径 (打开文件,将句柄设置为子进程 stdout)
167
+ - stderr?: 控制子进程 stderr,用法同 {@link BaseOptions.stdout}
168
+ - window?: 默认为 false,不显示窗口,设置启动进程的参数 CREATE_NO_WINDOW, 和 SW_HIDE
169
+ - title?: 由创建进程的调用者设置的,用于区分、识别、记忆的可选名称
170
+ - encoding?: `'utf-8'` 子进程输出编码, 设置为 binary 时返回 stdout, stderr 类型为 Buffer; 否则是 string
171
+ - on_child?: 可以传入回调函数及时获取通过 spawn 创建的子进程,便于执行 kill、获得 pid 等操作
172
+ - on_stdout?: 实时处理 stdout 的每个 chunk,设置后子进程输出不会自动 print,且 CallResult 中 stdout 属性为 ''
173
+ - on_stderr?: 实时处理 stderr 的每个 chunk,设置后子进程输出不会自动 print,且 CallResult 中 stderr 属性为 '' */
157
174
  export declare function call(exe: string, args: string[], options: CallOptions & {
158
175
  encoding: 'binary';
159
176
  }): Promise<CallResult<Buffer>>;
package/process.js CHANGED
@@ -2,6 +2,7 @@ import { spawn } from 'child_process';
2
2
  import os from 'os';
3
3
  import node_sea from 'node:sea';
4
4
  import "./prototype.js";
5
+ import { fopen } from "./file.js";
5
6
  import { inspect, DecoderStream, filter_values, check, colored, timeout } from "./utils.js";
6
7
  export const sea = node_sea.isSea();
7
8
  export const exe_nodejs = process.execPath.fp;
@@ -15,13 +16,9 @@ export function get_command(exe, args) {
15
16
  ? ` ${args.map(arg => arg.quote_if_space()).join(' ')}`
16
17
  : '');
17
18
  }
18
- async function prepare_spawn(detached, exe, args, { cwd, window: _window, envs,
19
+ async function prepare_spawn(detached, exe, args, { cwd, window: _window = false, envs,
19
20
  // @ts-ignore
20
- input,
21
- // @ts-ignore
22
- stdin, fp_stdin, fp_stdout, fp_stderr, print = true, proxy,
23
- // @ts-ignore
24
- stdio }) {
21
+ input, stdin = Boolean(input), stdout = !detached, stderr = stdout, print = true, proxy, }) {
25
22
  // --- 处理 proxy, envs
26
23
  if (proxy === true) {
27
24
  const { MyProxy } = await import("./net.js");
@@ -38,53 +35,41 @@ stdio }) {
38
35
  } : {},
39
36
  ...envs
40
37
  });
41
- // --- 处理 stdio
42
- if (input && fp_stdin)
43
- throw new Error('input fp_stdin 不能同时设置');
38
+ // --- 处理 stdio: 将 stdio 中的 true, false, string 转换为对应的 node.js stdio 值
39
+ if (detached)
40
+ check([stdin, stdout, stderr].every(io => io !== true), '调用 start 启动 detached 进程时 stdio 不能为 true (pipe)');
44
41
  let opened_handles = [];
45
42
  async function close_all_handles() {
46
43
  await Promise.all(opened_handles.map(async (handle) => handle.close()));
47
44
  }
48
- if (Array.isArray(stdio)) {
49
- if (input)
50
- check(stdio[0] === 'pipe', '传入 input 时 stdio[0] 应该是 pipe');
51
- }
52
- else if (typeof stdio === 'string')
53
- stdio = [stdio, stdio, stdio];
54
- else if (detached)
55
- stdio = ['ignore', 'ignore', 'ignore'];
56
- else
57
- stdio = [
58
- // 当子进程 stdin 为 'ignore' 时,python 会报错 [WinError 6] 句柄无效,cmd / powershell 都会立即结束退出
59
- (stdin || input) ? 'pipe' : 'ignore',
60
- 'pipe',
61
- 'pipe'
62
- ];
63
- if (fp_stdin || fp_stdout || fp_stderr) {
64
- const { fopen } = await import("./file.js");
65
- async function open(fp, flags) {
66
- try {
67
- const handle = await fopen(fp, flags);
68
- opened_handles.push(handle);
69
- return handle.fd;
70
- }
71
- catch (error) {
72
- await close_all_handles();
73
- throw error;
74
- }
45
+ async function resolve_stdio(io, flags) {
46
+ if (io === true)
47
+ return 'pipe';
48
+ else if (typeof io === 'string') {
49
+ const handle = await fopen(io, flags);
50
+ opened_handles.push(handle);
51
+ return handle.fd;
75
52
  }
76
- if (fp_stdin)
77
- stdio[0] = await open(fp_stdin, 'r');
78
- if (fp_stdout)
79
- stdio[1] = await open(fp_stdout, 'w');
80
- if (fp_stderr)
81
- stdio[2] = fp_stderr === fp_stdout
82
- ? stdio[1]
83
- : await open(fp_stderr, 'w');
53
+ else
54
+ return 'ignore';
84
55
  }
85
- if (detached)
86
- check(stdio.every((x) => x === 'ignore' || typeof x === 'number'), '调用 start 时 stdio 只能是 fd 或者 ignore');
87
- // --- 处理 stdio 结束
56
+ let pstdout;
57
+ let stdio;
58
+ try {
59
+ stdio = await Promise.all([
60
+ resolve_stdio(stdin, 'r'),
61
+ (pstdout = resolve_stdio(stdout, 'w')),
62
+ // 若 stderr 和 stdout 相同,则复用其 fd
63
+ stderr === stdout && typeof stderr === 'string'
64
+ ? pstdout
65
+ : resolve_stdio(stderr, 'w')
66
+ ]);
67
+ }
68
+ catch (error) {
69
+ await close_all_handles();
70
+ throw error;
71
+ }
72
+ // --- 处理 print
88
73
  if (typeof print === 'boolean')
89
74
  print = {
90
75
  command: print,
@@ -92,17 +77,23 @@ stdio }) {
92
77
  stdout: print,
93
78
  stderr: print
94
79
  };
80
+ print = {
81
+ command: print.command ?? true,
82
+ code: print.code ?? true,
83
+ stdout: stdout && (print.stdout ?? true),
84
+ stderr: stderr && (print.stderr ?? true)
85
+ };
95
86
  const command = get_command(exe, args);
96
87
  if (print.command)
97
88
  console.log(command.blue);
98
89
  return {
99
- print,
90
+ // 已经转为了完整的 FullPrintOptions
91
+ print: print,
100
92
  command,
101
93
  spawn_options: {
102
94
  cwd,
103
95
  shell: false,
104
- // processhacker 在 windowsHide 为 true 时不显示窗口
105
- windowsHide: _window ?? Boolean(!detached || fp_stdout),
96
+ windowsHide: !_window,
106
97
  detached,
107
98
  stdio,
108
99
  ...envs_ ? { env: envs_ } : {}
@@ -142,19 +133,21 @@ function to_subprocess(child, { title, exe, args, command, }) {
142
133
  });
143
134
  return child;
144
135
  }
145
- /** 启动独立 (detached) 的进程,不受当前 node.js 进程退出的影响,重定向 stdio 到文件或直接忽略
146
- 使用 windowsHide 选项避免创建命令行窗口
136
+ /** 使用 exe 启动独立 (detached) 的进程,不受当前 node.js 进程退出的影响,可重定向 stdio 到文件(默认直接忽略)
147
137
  - exe: .exe 路径或文件名 (建议使用完整路径,跳过 path 搜索,性能更高)
148
138
  - args?: `[ ]` 参数列表
149
- - options?:
150
- - cwd?
139
+ - options?: {@link StartOptions} 继承自 {@link BaseOptions}
140
+ - cwd?: `'T:/'`
151
141
  - envs?: `process.env` 覆盖/添加到 process.env 的环境变量,传 null 时可以取消设置该变量
152
- - encoding?: `'utf-8'` 子进程输出编码
142
+ - proxy?: 创建子进程时添加 http_proxy, https_proxy, no_proxy 环境变量以启用代理
153
143
  - print?: `true` 是否打印启动命令行
154
- - input?: string, 启动子进程之后写入到子进程 stdin 中的内容,写完后关闭子进程 stdin (pty 不关闭)
155
- - fp_stdin?: 使用文件作为标准输入,设置 stdio 中对应的值
156
- - fp_stdout?: 使用文件作为标准输出,设置 stdio 中对应的值
157
- - fp_stderr?: 使用文件作为标准错误,设置 stdio 中对应的值 */
144
+ - stdin?: 控制子进程 stdin,默认为 `false` ('ignore', 读空设备)
145
+ 可传入 string,设置子进程 stdin 为某个文件路径 (打开文件,将句柄设置为子进程 stdin)
146
+ - stdout?: 控制子进程 stdout,默认为 `false` ('ignore', 写空设备)
147
+ 可传入 string,设置子进程 stdout 某个文件路径 (打开文件,将句柄设置为子进程 stdout)
148
+ - stderr?: 控制子进程 stderr,默认为 `stdout` 的值,用法同 stdout
149
+ - window?: 默认为 false,不显示窗口,设置启动进程的参数 CREATE_NO_WINDOW, 和 SW_HIDE
150
+ - title?: 由创建进程的调用者设置的,用于区分、识别、记忆的可选名称 */
158
151
  export async function start(exe, args = [], options = {}) {
159
152
  const { spawn_options, close_all_handles, command } = await prepare_spawn(true, exe, args, options);
160
153
  try {
@@ -205,7 +198,7 @@ export async function call(exe, args = [], options = {}) {
205
198
  await close_all_handles();
206
199
  }
207
200
  const { stdio } = spawn_options;
208
- const { encoding = 'utf-8', throw_code = true, input, printers, on_child } = options;
201
+ const { encoding = 'utf-8', throw_code = true, input, on_stdout, on_stderr, on_child } = options;
209
202
  // 防止 child spawn 失败时 crash nodejs 进程
210
203
  child.on('error', error => {
211
204
  console.error(error);
@@ -216,39 +209,37 @@ export async function call(exe, args = [], options = {}) {
216
209
  child.stdin.end(input);
217
210
  }
218
211
  // --- 收集进程输出
212
+ const piped_stdout = stdio[1] === 'pipe';
213
+ const piped_stderr = stdio[2] === 'pipe';
219
214
  let stdouts = [];
220
215
  let stderrs = [];
221
- if (stdio[1] === 'pipe') {
222
- if (encoding === 'utf-8')
223
- child.stdout.setEncoding('utf-8');
216
+ if (piped_stdout) {
217
+ if (encoding === 'utf-8' || encoding === 'utf-16le')
218
+ child.stdout.setEncoding(encoding);
224
219
  else if (encoding !== 'binary')
225
220
  child.stdout = child.stdout.pipe(new DecoderStream(encoding));
226
- child.stdout.on('data', printers?.stdout
227
- ? printers.stdout
228
- : print.stdout
229
- ? (chunk) => {
230
- stdouts.push(chunk);
231
- process.stdout.write(chunk);
232
- }
233
- : (chunk) => {
234
- stdouts.push(chunk);
235
- });
221
+ child.stdout.on('data', on_stdout || (print.stdout
222
+ ? (chunk) => {
223
+ stdouts.push(chunk);
224
+ process.stdout.write(chunk);
225
+ }
226
+ : (chunk) => {
227
+ stdouts.push(chunk);
228
+ }));
236
229
  }
237
- if (stdio[2] === 'pipe') {
238
- if (encoding === 'utf-8')
239
- child.stderr.setEncoding('utf-8');
230
+ if (piped_stderr) {
231
+ if (encoding === 'utf-8' || encoding === 'utf-16le')
232
+ child.stderr.setEncoding(encoding);
240
233
  else if (encoding !== 'binary')
241
234
  child.stderr = child.stderr.pipe(new DecoderStream(encoding));
242
- child.stderr.on('data', printers?.stderr
243
- ? printers.stderr
244
- : print.stderr
245
- ? (chunk) => {
246
- stderrs.push(chunk);
247
- process.stderr.write(chunk);
248
- }
249
- : (chunk) => {
250
- stderrs.push(chunk);
251
- });
235
+ child.stderr.on('data', on_stderr || (print.stderr
236
+ ? (chunk) => {
237
+ stderrs.push(chunk);
238
+ process.stderr.write(chunk);
239
+ }
240
+ : (chunk) => {
241
+ stderrs.push(chunk);
242
+ }));
252
243
  }
253
244
  on_child?.(child);
254
245
  let code, signal;
@@ -279,19 +270,14 @@ export async function call(exe, args = [], options = {}) {
279
270
  child,
280
271
  print,
281
272
  [inspect.custom]() {
282
- return inspect(this, {
283
- omit: [
284
- 'child',
285
- 'message',
286
- 'command',
287
- 'code',
288
- 'signal',
289
- 'pid',
290
- 'print',
291
- ...print.stdout ? ['stdout'] : [],
292
- ...print.stderr ? ['stderr'] : [],
293
- ]
294
- });
273
+ const { stdout, stderr, command, print, code, signal } = this;
274
+ return {
275
+ ...!print.command ? { command } : {},
276
+ ...piped_stdout && !print.stdout ? { stdout } : {},
277
+ ...piped_stderr && !print.stderr ? { stderr } : {},
278
+ ...code ? { code } : {},
279
+ ...signal ? { signal } : {},
280
+ };
295
281
  }
296
282
  };
297
283
  if (throw_code && code)
@@ -299,20 +285,15 @@ export async function call(exe, args = [], options = {}) {
299
285
  return result;
300
286
  }
301
287
  export async function launch(exe, args, options) {
302
- let presult;
303
- let ps = await new Promise((resolve, reject) => {
304
- try {
305
- presult = call(exe, args, {
306
- on_child: resolve,
307
- ...options,
308
- });
309
- }
310
- catch (error) {
311
- reject(error);
312
- }
288
+ return new Promise(resolve => {
289
+ const presult = call(exe, args, {
290
+ on_child(child) {
291
+ child.presult = presult;
292
+ resolve(child);
293
+ },
294
+ ...options,
295
+ });
313
296
  });
314
- ps.presult = presult;
315
- return ps;
316
297
  }
317
298
  /** 调用 node <js> 并等待结果
318
299
  - js: .js 路径 (相对路径根据 cwd 解析)
package/server.js CHANGED
@@ -14,7 +14,7 @@ import { contentType as get_content_type } from 'mime-types';
14
14
  // --- my libs
15
15
  import { t } from "./i18n/instance.js";
16
16
  import { request as _request, Remote } from "./net.js";
17
- import { inspect, check, range_to_numbers, encode, filter_keys, filter_values, consume_stream } from "./utils.js";
17
+ import { inspect, check, range_to_numbers, encode, filter_keys, filter_values, consume_stream, colored } from "./utils.js";
18
18
  import { flist, fread, fstat } from "./file.js";
19
19
  import { exe_nodejs, sea } from "./process.js";
20
20
  // ------------ my server
@@ -36,8 +36,8 @@ export class Server {
36
36
  };
37
37
  /** sea 下最后修改时间,用于 http 资源缓存 */
38
38
  last_modified_str;
39
- static js_exts = new Set(['.js', '.mjs', '.cjs']);
40
- static logger_ignore_fexts = new Set(['.js', '.css', '.map', '.png', '.jpg', '.svg', '.ico', '.json', '.woff2', '.ttf', '.php']);
39
+ static js_exts = new Set(['js', 'mjs', 'cjs']);
40
+ static logger_ignore_fexts = new Set(['js', 'css', 'map', 'png', 'jpg', 'svg', 'ico', 'json', 'woff2', 'ttf', 'php']);
41
41
  static empty_body_statuses = new Set([304, 204, 205]);
42
42
  static empty_body_methods = new Set(['HEAD', 'OPTIONS']);
43
43
  app;
@@ -281,7 +281,7 @@ export class Server {
281
281
  // 时间
282
282
  `${this.log_date ? new Date().to_str() : new Date().to_time_str()} ` +
283
283
  // wss://
284
- `${`ws${socket.encrypted ? 's' : ''}`.yellow}://` +
284
+ `ws${socket.encrypted ? 's' : ''}://`.yellow +
285
285
  // host
286
286
  host +
287
287
  // path
@@ -386,15 +386,14 @@ export class Server {
386
386
  let { method } = request;
387
387
  if (Server.logger_ignore_fexts.has(path.fext))
388
388
  return;
389
- let s = '';
390
389
  // 时间
391
- s += `${this.log_date ? new Date().to_str() : new Date().to_time_str()} `;
390
+ let s = `${this.log_date ? new Date().to_str() : new Date().to_time_str()} `;
392
391
  // method
393
392
  method = method.toLowerCase();
394
393
  if (method !== 'get')
395
394
  s += `${method.yellow} `;
396
395
  // http, https, http2, websocket
397
- s += `${http_version === '2.0' ? 'https'.magenta : protocol}://`;
396
+ s += colored(`${protocol}://`, 'magenta', http_version === '2.0');
398
397
  // host
399
398
  s += host;
400
399
  // path
@@ -24,7 +24,8 @@ export declare function delay(milliseconds: number, { signal }?: {
24
24
  export declare class TimeoutError extends Error {
25
25
  name: "TimeoutError";
26
26
  }
27
- /** 在指定的时间 (milliseconds) 内运行某个任务,超时之后
27
+ /** 在指定的时间 (milliseconds) 内运行某个任务,超时之后抛出错误或调用 on_timeout
28
+ - milliseconds: 限时毫秒数
28
29
  - action?: 要等待运行的任务, async function 或 promise
29
30
  - on_timeout?: 超时后调用的函数
30
31
  - 如果传入了 on_timeout 参数: 调用 on_timeout,然后 timeout 函数正常返回 null
package/utils.browser.js CHANGED
@@ -76,7 +76,8 @@ export async function delay(milliseconds, { signal } = {}) {
76
76
  export class TimeoutError extends Error {
77
77
  name = 'TimeoutError';
78
78
  }
79
- /** 在指定的时间 (milliseconds) 内运行某个任务,超时之后
79
+ /** 在指定的时间 (milliseconds) 内运行某个任务,超时之后抛出错误或调用 on_timeout
80
+ - milliseconds: 限时毫秒数
80
81
  - action?: 要等待运行的任务, async function 或 promise
81
82
  - on_timeout?: 超时后调用的函数
82
83
  - 如果传入了 on_timeout 参数: 调用 on_timeout,然后 timeout 函数正常返回 null
package/utils.d.ts CHANGED
@@ -90,7 +90,8 @@ export declare function delay(milliseconds: number, options?: TimerOptions): Pro
90
90
  export declare class TimeoutError extends Error {
91
91
  name: "TimeoutError";
92
92
  }
93
- /** 在指定的时间 (milliseconds) 内运行某个任务,超时之后
93
+ /** 在指定的时间 (milliseconds) 内运行某个任务,超时之后抛出错误或调用 on_timeout
94
+ - milliseconds: 限时毫秒数
94
95
  - action?: 要等待运行的任务, async function 或 promise
95
96
  - on_timeout?: 超时后调用的函数
96
97
  - 如果传入了 on_timeout 参数: 调用 on_timeout,然后 timeout 函数正常返回 null
package/utils.js CHANGED
@@ -280,7 +280,8 @@ export async function delay(milliseconds, options) {
280
280
  export class TimeoutError extends Error {
281
281
  name = 'TimeoutError';
282
282
  }
283
- /** 在指定的时间 (milliseconds) 内运行某个任务,超时之后
283
+ /** 在指定的时间 (milliseconds) 内运行某个任务,超时之后抛出错误或调用 on_timeout
284
+ - milliseconds: 限时毫秒数
284
285
  - action?: 要等待运行的任务, async function 或 promise
285
286
  - on_timeout?: 超时后调用的函数
286
287
  - 如果传入了 on_timeout 参数: 调用 on_timeout,然后 timeout 函数正常返回 null