@guanghechen/commander 4.6.0 → 4.7.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.
@@ -1,9 +1,96 @@
1
+ import { stat, readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
1
3
  import { parse } from '@guanghechen/env';
2
4
  import { Reporter } from '@guanghechen/reporter';
3
- import { stat, readFile } from 'node:fs/promises';
4
- import * as path from 'node:path';
5
- import path__default from 'node:path';
6
- import * as fs from 'node:fs';
5
+ import fs from 'node:fs';
6
+
7
+ const WINDOWS_DRIVE_ABSOLUTE_REGEX = /^[a-zA-Z]:[\\/]/;
8
+ function isAbsolutePath(filepath) {
9
+ return (filepath.startsWith('/') ||
10
+ filepath.startsWith('\\\\') ||
11
+ WINDOWS_DRIVE_ABSOLUTE_REGEX.test(filepath));
12
+ }
13
+ function resolvePathFrom(base, fragment) {
14
+ const useWindowsStyle = WINDOWS_DRIVE_ABSOLUTE_REGEX.test(base);
15
+ const normalizedBase = base.replace(/\\/g, '/');
16
+ const normalizedFragment = fragment.replace(/\\/g, '/');
17
+ const source = isAbsolutePath(normalizedFragment)
18
+ ? normalizedFragment
19
+ : `${normalizedBase.replace(/\/$/, '')}/${normalizedFragment}`;
20
+ const prefix = useWindowsStyle ? source.slice(0, 2) : '';
21
+ const body = useWindowsStyle ? source.slice(2) : source;
22
+ const stack = [];
23
+ for (const token of body.split('/')) {
24
+ if (token === '' || token === '.') {
25
+ continue;
26
+ }
27
+ if (token === '..') {
28
+ if (stack.length > 0) {
29
+ stack.pop();
30
+ }
31
+ continue;
32
+ }
33
+ stack.push(token);
34
+ }
35
+ if (useWindowsStyle) {
36
+ const resolved = `${prefix}/${stack.join('/')}`;
37
+ return resolved.endsWith('/') ? resolved.slice(0, -1) : resolved;
38
+ }
39
+ return `/${stack.join('/')}`;
40
+ }
41
+ function createUnsupportedFsError(operation) {
42
+ return new Error(`runtime does not support file-system operation: ${operation}`);
43
+ }
44
+ function getFallbackCwd() {
45
+ const proc = globalThis.process;
46
+ if (proc && typeof proc.cwd === 'function') {
47
+ return proc.cwd();
48
+ }
49
+ return '/';
50
+ }
51
+ function createBrowserCommandRuntime() {
52
+ return {
53
+ cwd: () => getFallbackCwd(),
54
+ isAbsolute: filepath => isAbsolutePath(filepath),
55
+ resolve: (...paths) => {
56
+ if (paths.length === 0) {
57
+ return getFallbackCwd();
58
+ }
59
+ let resolved = getFallbackCwd();
60
+ for (const path of paths) {
61
+ if (path.length === 0) {
62
+ continue;
63
+ }
64
+ resolved = resolvePathFrom(resolved, path);
65
+ }
66
+ return resolved;
67
+ },
68
+ readFile: async () => {
69
+ throw createUnsupportedFsError('readFile');
70
+ },
71
+ stat: async () => {
72
+ throw createUnsupportedFsError('stat');
73
+ },
74
+ };
75
+ }
76
+
77
+ let defaultRuntime = createBrowserCommandRuntime();
78
+ function getDefaultCommandRuntime() {
79
+ return defaultRuntime;
80
+ }
81
+ function setDefaultCommandRuntime(runtime) {
82
+ defaultRuntime = runtime;
83
+ }
84
+
85
+ function createNodeCommandRuntime() {
86
+ return {
87
+ cwd: () => process.cwd(),
88
+ isAbsolute: filepath => path.isAbsolute(filepath),
89
+ resolve: (...paths) => path.resolve(...paths),
90
+ readFile: filepath => readFile(filepath, 'utf8'),
91
+ stat: filepath => stat(filepath),
92
+ };
93
+ }
7
94
 
8
95
  const TERMINAL_STYLE = {
9
96
  bold: '\x1b[1m',
@@ -270,6 +357,7 @@ class Command {
270
357
  #builtin;
271
358
  #presetConfig;
272
359
  #reporter;
360
+ #runtime;
273
361
  #parent;
274
362
  #options = [];
275
363
  #arguments = [];
@@ -285,6 +373,7 @@ class Command {
285
373
  this.#builtin = normalizeBuiltinConfig(config.builtin);
286
374
  this.#presetConfig = config.preset;
287
375
  this.#reporter = config.reporter;
376
+ this.#runtime = config.runtime ?? getDefaultCommandRuntime();
288
377
  }
289
378
  get name() {
290
379
  return this.#name || undefined;
@@ -803,12 +892,12 @@ class Command {
803
892
  return await this.#assertPresetRoot(commandPreset.root, 'command.preset.root', commandPath);
804
893
  }
805
894
  async #assertPresetRoot(root, sourceName, commandPath) {
806
- if (!path__default.isAbsolute(root)) {
895
+ if (!this.#runtime.isAbsolute(root)) {
807
896
  throw new CommanderError('ConfigurationError', `invalid preset root from "${sourceName}": "${root}" is not an absolute directory`, commandPath);
808
897
  }
809
898
  let stats;
810
899
  try {
811
- stats = await stat(root);
900
+ stats = await this.#runtime.stat(root);
812
901
  }
813
902
  catch (error) {
814
903
  throw new CommanderError('ConfigurationError', `invalid preset root from "${sourceName}": "${root}" cannot be accessed (${error.message})`, commandPath);
@@ -848,7 +937,7 @@ class Command {
848
937
  },
849
938
  ];
850
939
  }
851
- const absolutePath = path__default.resolve(presetRoot, defaultFilename);
940
+ const absolutePath = this.#runtime.resolve(presetRoot, defaultFilename);
852
941
  return [
853
942
  {
854
943
  displayPath: absolutePath,
@@ -858,13 +947,13 @@ class Command {
858
947
  ];
859
948
  }
860
949
  #resolvePresetFileAbsolutePath(filepath, presetRoot) {
861
- if (path__default.isAbsolute(filepath)) {
950
+ if (this.#runtime.isAbsolute(filepath)) {
862
951
  return filepath;
863
952
  }
864
953
  if (presetRoot !== undefined) {
865
- return path__default.resolve(presetRoot, filepath);
954
+ return this.#runtime.resolve(presetRoot, filepath);
866
955
  }
867
- return path__default.resolve(process.cwd(), filepath);
956
+ return this.#runtime.resolve(this.#runtime.cwd(), filepath);
868
957
  }
869
958
  #assertPresetOptionFragments(tokens, filepath, chain, optionPolicyMap) {
870
959
  if (tokens.length === 0) {
@@ -977,14 +1066,14 @@ class Command {
977
1066
  }
978
1067
  async #readPresetFile(file, commandPath) {
979
1068
  try {
980
- return await readFile(file.absolutePath, 'utf8');
1069
+ return await this.#runtime.readFile(file.absolutePath);
981
1070
  }
982
1071
  catch (error) {
983
1072
  const ioError = error;
984
1073
  if (!file.explicit && ioError.code === 'ENOENT') {
985
1074
  return undefined;
986
1075
  }
987
- throw new CommanderError('ConfigurationError', `failed to read preset file "${file.displayPath}": ${ioError.message}`, commandPath);
1076
+ throw new CommanderError('ConfigurationError', `failed to read preset file "${file.displayPath}": ${error.message}`, commandPath);
988
1077
  }
989
1078
  }
990
1079
  #tokenizePresetOptions(content) {
@@ -2051,4 +2140,6 @@ class PwshCompletion {
2051
2140
  }
2052
2141
  }
2053
2142
 
2054
- export { BashCompletion, Coerce, Command, CommanderError, CompletionCommand, FishCompletion, PwshCompletion, isDomain, isIp, isIpv4, isIpv6, logColorfulOption, logDateOption, logLevelOption, silentOption };
2143
+ setDefaultCommandRuntime(createNodeCommandRuntime());
2144
+
2145
+ export { BashCompletion, Coerce, Command, CommanderError, CompletionCommand, FishCompletion, PwshCompletion, createBrowserCommandRuntime, createNodeCommandRuntime, getDefaultCommandRuntime, isDomain, isIp, isIpv4, isIpv6, logColorfulOption, logDateOption, logLevelOption, setDefaultCommandRuntime, silentOption };