mock-service-cli 4.5.0 → 4.6.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/README.md CHANGED
@@ -76,8 +76,9 @@ npx mock-service-cli [options] [path]
76
76
  | `-D` 或 `--web-dir` | 启用 Web 服务器,指定 web 目录(SPA 应用) | - |
77
77
  | `-P` 或 `--web-port` | Web 服务器端口 | 9090 |
78
78
  | `-b` 或 `--web-baseurl` | 指定 SPA Web 服务器的公共路径 | - |
79
- | `-R` 或 `--static-server` | 启用静态服务器,指定静态资源目录 | - |
80
- | `--static-config <file>` | 静态服务器的 JSON 配置:热更新、HTTPS、挂载、代理与 SPA 回退 | - |
79
+ | `-R` 或 `--static-server` | 启用单目录静态服务器,指定静态资源目录 | - |
80
+ | `--spa-fallback <path>` | 单目录模式显式启用 SPA 回退 | - |
81
+ | `--static-config <file>` | 使用自包含 JSON 配置启动静态服务器 | - |
81
82
  | `-w` 或 `--open` | 自动打开 API 概览页、文件浏览器页 | false |
82
83
  | `-e` 或 `--explorer` | 启用文件浏览器服务器,指定要浏览的目录 | ./ |
83
84
  | `--edit` | 启用文件浏览器的新建、重命名、删除和上传操作 | false |
@@ -108,16 +109,21 @@ mock-service-cli -f ./mock.js
108
109
  # 启动静态服务器
109
110
  mock-service-cli -R ./public
110
111
 
111
- # 使用 Live Server 风格的热更新配置
112
- mock-service-cli -R ./public --static-config ./static-server.json
112
+ # 单目录模式启用 SPA 回退
113
+ mock-service-cli -R ./public --spa-fallback /index.html
114
+
115
+ # 使用自包含配置(不需要目录参数)
116
+ mock-service-cli -R --static-config ./static-server.json
117
+ mock-service-cli --static-config ./static-server.json
113
118
  ```
114
119
 
115
120
  静态服务器会监听资源变化并向 HTML 页面注入热更新客户端:CSS 文件更新时替换样式表,其余变更刷新页面。
116
121
  默认不打开浏览器;在配置中设置 `"open": true` 或提供页面路径即可打开。完整配置字段和示例见
117
122
  [`docs/static-server.config.example.json`](./docs/static-server.config.example.json):支持忽略规则、SPA 回退、挂载目录、代理、HTTPS、CORS、响应头和自定义浏览器命令。
118
- `-p`、`--host`、`-A` `--proxy-options` 仍可使用,且同名 CLI 配置优先。
119
- 未设置 `spaFallback` 时,根目录和子目录会显示可点击的目录索引,不会自动加载 `index.html`;用户可直接访问该文件。
120
- 只有显式设置 `"spaFallback": "/index.html"`(或其他入口)时,未命中的路由才会返回 SPA 入口页。
123
+ 配置文件只在顶层保留服务器级字段。所有应用都放在 `mounts` 中,并可独立设置 `directory`、`spaFallback`、`proxy`、`cors`、`headers` `secure`。mount 未设置 `path` 时默认挂载到 `/`;`directory` 可省略,但该应用必须配置至少一条代理。代理键使用完整公开路径,例如 `/api/app`。
124
+ 代理规则使用 `{ "target": "http://...", "rewrite": true }`;`rewrite` true 时只移除匹配的完整代理前缀并保留剩余路径和 query string。应用级 `secure` 控制其代理 HTTPS 目标的证书校验,默认 `false`。配置模式不接受 `--proxy-options`、`--rewrite` 或 `--spa-fallback`。
125
+ 未设置 `spaFallback` 时,应用目录会显示可点击的目录索引;可直接访问其中的 HTML 文件。mount 未命中的路由不会继续进入其他应用的 SPA fallback。
126
+ 设置了 `spaFallback` 的应用可选配置 `accessLog.success` 与 `accessLog.failure` 文件路径。服务将把每次访问以 JSON Lines 写入对应文件:2xx/3xx 写入 success,4xx/5xx 写入 failure;路径相对配置文件解析。启动后控制台会突出显示每条代理所属的应用、公开前缀、目标和 rewrite 状态。
121
127
 
122
128
  ### Web 服务器 (SPA)
123
129
 
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>文件浏览器登录</title>
7
- <link rel="icon" type="image/svg+xml" href="/favicon-file-explorer-login.svg" />
7
+ <link rel="icon" type="image/svg+xml" href="__FILE_EXPLORER_FAVICON_URL__" />
8
8
  <style>
9
9
  * { box-sizing: border-box; }
10
10
  body { margin: 0; min-height: 100vh; display: grid; place-items: center; background: #f4f6f8; color: #202124; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>文件浏览器 - File Explorer</title>
7
- <link rel="icon" type="image/svg+xml" href="/favicon-file-explorer.svg" />
7
+ <link rel="icon" type="image/svg+xml" href="__FILE_EXPLORER_FAVICON_URL__" />
8
8
  <style>
9
9
  * {
10
10
  margin: 0;
@@ -1158,6 +1158,7 @@
1158
1158
  let explorerLifecycleVersion = 0;
1159
1159
  let currentArchiveJobId = null;
1160
1160
  let currentArchiveJobOperation = null;
1161
+ let explorerRootId = null;
1161
1162
  let archiveCapabilities = { createFormats: [], readExtensions: [] };
1162
1163
 
1163
1164
  const EXPLORER_AUTH_STORAGE_KEY = 'mock-service-cli.file-explorer.password';
@@ -1281,6 +1282,7 @@
1281
1282
  const config = await response.json();
1282
1283
  if (!response.ok || config.error) throw new Error(config.error || 'Failed to load configuration');
1283
1284
  editMode = Boolean(config.editMode);
1285
+ explorerRootId = typeof config.rootId === 'string' ? config.rootId : null;
1284
1286
  } catch (error) {
1285
1287
  console.warn('Failed to load file explorer configuration:', error);
1286
1288
  editMode = false;
@@ -1413,7 +1415,25 @@
1413
1415
  }
1414
1416
  }
1415
1417
 
1416
- async function initializeExplorer(isPageRestore = false) {
1418
+ function getStoredExplorerPath() {
1419
+ try {
1420
+ const stored = JSON.parse(sessionStorage.getItem(EXPLORER_PATH_STORAGE_KEY));
1421
+ if (stored && stored.rootId === explorerRootId && typeof stored.path === 'string' && stored.path.startsWith('/')) {
1422
+ return stored.path;
1423
+ }
1424
+ } catch (error) {
1425
+ // Earlier releases stored the path directly; discard that unscoped value.
1426
+ }
1427
+ sessionStorage.removeItem(EXPLORER_PATH_STORAGE_KEY);
1428
+ return '/';
1429
+ }
1430
+
1431
+ function storeExplorerPath(path) {
1432
+ if (!explorerRootId) return;
1433
+ sessionStorage.setItem(EXPLORER_PATH_STORAGE_KEY, JSON.stringify({ rootId: explorerRootId, path }));
1434
+ }
1435
+
1436
+ async function initializeExplorer() {
1417
1437
  if (explorerInitializationPromise) return explorerInitializationPromise;
1418
1438
 
1419
1439
  const lifecycleVersion = explorerLifecycleVersion;
@@ -1443,7 +1463,7 @@
1443
1463
  await loadArchiveCapabilities();
1444
1464
  if (lifecycleVersion !== explorerLifecycleVersion) return;
1445
1465
  startHealthCheck();
1446
- await loadDirectory(isPageRestore ? currentPath : sessionStorage.getItem(EXPLORER_PATH_STORAGE_KEY) || '/');
1466
+ await loadDirectory(getStoredExplorerPath(), { fallbackToRoot: true });
1447
1467
  })();
1448
1468
  explorerInitializationPromise = initializationPromise;
1449
1469
 
@@ -1460,7 +1480,7 @@
1460
1480
 
1461
1481
  window.addEventListener('pagehide', handlePageHide);
1462
1482
  window.addEventListener('pageshow', event => {
1463
- if (event.persisted) initializeExplorer(true);
1483
+ if (event.persisted) initializeExplorer();
1464
1484
  });
1465
1485
 
1466
1486
  function showContextMenu(x, y, filePath, isDirectory) {
@@ -1538,7 +1558,7 @@
1538
1558
  hideContextMenu();
1539
1559
  }
1540
1560
 
1541
- async function loadDirectory(path) {
1561
+ async function loadDirectory(path, { fallbackToRoot = false } = {}) {
1542
1562
  const fileGrid = document.getElementById('fileGrid');
1543
1563
  const oldCurrentPath = currentPath;
1544
1564
  const requestId = ++directoryRequestId;
@@ -1562,12 +1582,14 @@
1562
1582
  const data = await response.json();
1563
1583
 
1564
1584
  if (!response.ok || data.error) {
1565
- throw new Error(data.error);
1585
+ const error = new Error(data.error || 'Failed to load directory');
1586
+ error.status = response.status;
1587
+ throw error;
1566
1588
  }
1567
1589
  if (requestId !== directoryRequestId) return;
1568
1590
 
1569
1591
  currentPath = data.currentPath;
1570
- sessionStorage.setItem(EXPLORER_PATH_STORAGE_KEY, currentPath);
1592
+ storeExplorerPath(currentPath);
1571
1593
  allFiles = data.files || [];
1572
1594
 
1573
1595
  if (document.getElementById('searchInput')) {
@@ -1580,6 +1602,10 @@
1580
1602
  updateBackButton(data.parentPath);
1581
1603
  } catch (error) {
1582
1604
  if (error.name === 'AbortError' || requestId !== directoryRequestId) return;
1605
+ if (fallbackToRoot && path !== '/' && error.status === 404) {
1606
+ sessionStorage.removeItem(EXPLORER_PATH_STORAGE_KEY);
1607
+ return loadDirectory('/', { fallbackToRoot: false });
1608
+ }
1583
1609
  console.error('Error loading directory:', error);
1584
1610
  currentPath = oldCurrentPath;
1585
1611
  const parentPath = currentPath === '/' ? null : currentPath.substring(0, currentPath.lastIndexOf('/')) || '/';
package/dist/meta.json CHANGED
@@ -1088,7 +1088,7 @@
1088
1088
  "format": "cjs"
1089
1089
  },
1090
1090
  "src/bin/mock-service-cli": {
1091
- "bytes": 12219,
1091
+ "bytes": 13264,
1092
1092
  "imports": [
1093
1093
  {
1094
1094
  "path": "node_modules/colors/safe.js",
@@ -6704,7 +6704,7 @@
6704
6704
  "format": "cjs"
6705
6705
  },
6706
6706
  "src/lib/staticServer.js": {
6707
- "bytes": 23163,
6707
+ "bytes": 33699,
6708
6708
  "imports": [
6709
6709
  {
6710
6710
  "path": "node_modules/express/index.js",
@@ -7484,7 +7484,7 @@
7484
7484
  "format": "cjs"
7485
7485
  },
7486
7486
  "src/lib/fileExplorerServer.js": {
7487
- "bytes": 25074,
7487
+ "bytes": 25537,
7488
7488
  "imports": [
7489
7489
  {
7490
7490
  "path": "node_modules/express/index.js",
@@ -8872,7 +8872,7 @@
8872
8872
  "bytesInOutput": 429
8873
8873
  },
8874
8874
  "src/bin/mock-service-cli": {
8875
- "bytesInOutput": 9280
8875
+ "bytesInOutput": 10204
8876
8876
  },
8877
8877
  "node_modules/depd/index.js": {
8878
8878
  "bytesInOutput": 6779
@@ -9856,7 +9856,7 @@
9856
9856
  "bytesInOutput": 10974
9857
9857
  },
9858
9858
  "src/lib/staticServer.js": {
9859
- "bytesInOutput": 18312
9859
+ "bytesInOutput": 26622
9860
9860
  },
9861
9861
  "node_modules/busboy/lib/utils.js": {
9862
9862
  "bytesInOutput": 8707
@@ -9979,13 +9979,13 @@
9979
9979
  "bytesInOutput": 7774
9980
9980
  },
9981
9981
  "src/lib/fileExplorerServer.js": {
9982
- "bytesInOutput": 18585
9982
+ "bytesInOutput": 19066
9983
9983
  },
9984
9984
  "src/runtime.js": {
9985
9985
  "bytesInOutput": 507
9986
9986
  }
9987
9987
  },
9988
- "bytes": 1621515
9988
+ "bytes": 1631230
9989
9989
  }
9990
9990
  }
9991
9991
  }