@xcanwin/manyoyo 8.6.12 → 8.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.
package/bin/manyoyo.js CHANGED
@@ -61,18 +61,14 @@ const {
61
61
  formatServeLogValue,
62
62
  getServeProcessSnapshot
63
63
  } = require('../lib/serve-log');
64
+ const { formatNow, resolveQuickChatPath, expandTemplate } = require('../lib/name-template');
64
65
  const { version: BIN_VERSION, imageVersion: IMAGE_VERSION_DEFAULT } = require('../package.json');
65
66
  const IMAGE_VERSION_BASE = String(IMAGE_VERSION_DEFAULT || '1.0.0').split('-')[0];
66
67
  const IMAGE_VERSION_HELP_EXAMPLE = IMAGE_VERSION_DEFAULT || `${IMAGE_VERSION_BASE}-common`;
67
68
 
68
- // Helper function to format date like bash $(date +%m%d-%H%M)
69
+ // {now} 与默认容器名用的时间:年月日-时分秒(命令行与网页共用 lib/name-template.js)
69
70
  function formatDate() {
70
- const now = new Date();
71
- const month = String(now.getMonth() + 1).padStart(2, '0');
72
- const day = String(now.getDate()).padStart(2, '0');
73
- const hour = String(now.getHours()).padStart(2, '0');
74
- const minute = String(now.getMinutes()).padStart(2, '0');
75
- return `${month}${day}-${hour}${minute}`;
71
+ return formatNow();
76
72
  }
77
73
 
78
74
  function detectCommandName() {
@@ -95,6 +91,7 @@ const CONFIG = {
95
91
  // Default configuration
96
92
  let CONTAINER_NAME = `my-${formatDate()}`;
97
93
  let HOST_PATH = process.cwd();
94
+ let HOST_PATH_TEMPLATED = false;
98
95
  let CONTAINER_PATH = HOST_PATH;
99
96
  let IMAGE_NAME = "ghcr.io/xcanwin/manyoyo";
100
97
  let IMAGE_VERSION = IMAGE_VERSION_DEFAULT || `${IMAGE_VERSION_BASE}-common`;
@@ -159,11 +156,7 @@ function normalizeCommandSuffix(suffix) {
159
156
  }
160
157
 
161
158
  function resolveContainerNameTemplate(name) {
162
- if (typeof name !== 'string') {
163
- return name;
164
- }
165
- const nowValue = formatDate();
166
- return name.replace(/\{now\}|\$\{now\}/g, nowValue);
159
+ return expandTemplate(name, { now: formatDate() }).value;
167
160
  }
168
161
 
169
162
  function pickConfigValue(...values) {
@@ -1598,6 +1591,7 @@ https://github.com/xcanwin/manyoyo
1598
1591
  isServerStopMode,
1599
1592
  pickConfigValue,
1600
1593
  resolveContainerNameTemplate,
1594
+ templateVars: { now: formatDate(), quickChatPath: resolveQuickChatPath(config) },
1601
1595
  normalizeCommandSuffix,
1602
1596
  normalizeJsonEnvMap,
1603
1597
  normalizeCliEnvMap,
@@ -1608,6 +1602,7 @@ https://github.com/xcanwin/manyoyo
1608
1602
  });
1609
1603
 
1610
1604
  HOST_PATH = resolvedRuntime.hostPath;
1605
+ HOST_PATH_TEMPLATED = resolvedRuntime.hostPathTemplated === true;
1611
1606
  CONTAINER_NAME = resolvedRuntime.containerName;
1612
1607
  CONTAINER_PATH = resolvedRuntime.containerPath;
1613
1608
  IMAGE_NAME = resolvedRuntime.imageName;
@@ -1827,6 +1822,7 @@ function createRuntimeContext(modeState = {}) {
1827
1822
  return {
1828
1823
  containerName: CONTAINER_NAME,
1829
1824
  hostPath: HOST_PATH,
1825
+ hostPathTemplated: HOST_PATH_TEMPLATED,
1830
1826
  containerPath: CONTAINER_PATH,
1831
1827
  imageName: IMAGE_NAME,
1832
1828
  imageVersion: IMAGE_VERSION,
@@ -1877,6 +1873,10 @@ function handleRemoveContainer(runtime) {
1877
1873
  }
1878
1874
 
1879
1875
  function validateHostPath(runtime) {
1876
+ // 目录是按模板({now} / {quickChatPath})生成的:此刻才建,只在真的要用它的时候建
1877
+ if (runtime.hostPathTemplated && !fs.existsSync(runtime.hostPath)) {
1878
+ fs.mkdirSync(runtime.hostPath, { recursive: true });
1879
+ }
1880
1880
  if (!fs.existsSync(runtime.hostPath)) {
1881
1881
  console.log(`${RED}⚠️ 错误: 宿主机路径不存在: ${runtime.hostPath}${NC}`);
1882
1882
  process.exit(1);
@@ -701,5 +701,6 @@ module.exports = {
701
701
  parseHostsAndResolv,
702
702
  parseUrlHostPort,
703
703
  isHostTarget,
704
+ isMissingImageError,
704
705
  createNetworkManager
705
706
  };
@@ -0,0 +1,52 @@
1
+ 'use strict';
2
+
3
+ const os = require('os');
4
+ const path = require('path');
5
+
6
+ // 配置里 containerName / hostPath / containerPath 的占位符,命令行与网页共用这一个展开函数:
7
+ // {now}(也认 ${now})→ 年月日-时分秒,如 20261024-230915
8
+ // {quickChatPath} → 快捷对话目录(serve.quickChat.path),没配置就是默认工作目录 ~/.manyoyo/work
9
+ const NOW_TOKEN = /\{now\}|\$\{now\}/g;
10
+ const QUICK_CHAT_PATH_TOKEN = /\{quickChatPath\}/g;
11
+
12
+ function formatNow(date = new Date()) {
13
+ const pad = value => String(value).padStart(2, '0');
14
+ return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}-${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;
15
+ }
16
+
17
+ function expandHome(value, homeDir = os.homedir()) {
18
+ const text = String(value || '').trim();
19
+ if (text === '~') return homeDir;
20
+ if (text.startsWith('~/')) return path.join(homeDir, text.slice(2));
21
+ return text;
22
+ }
23
+
24
+ /** 快捷对话目录的绝对路径(去掉末尾斜杠);没配置返回 defaultDir */
25
+ function resolveQuickChatPath(config, { homeDir = os.homedir(), defaultDir = path.join(os.homedir(), '.manyoyo', 'work') } = {}) {
26
+ const quickChat = config && config.serve && typeof config.serve === 'object' ? config.serve.quickChat : null;
27
+ const raw = quickChat && typeof quickChat.path === 'string' ? quickChat.path.trim() : '';
28
+ if (!raw) return defaultDir;
29
+ const resolved = path.resolve(expandHome(raw, homeDir));
30
+ return resolved;
31
+ }
32
+
33
+ /**
34
+ * 展开占位符。返回 { value, templated }:templated 表示原文里确实有占位符
35
+ * (调用方据此决定要不要替用户建目录:只有按模板生成的目录才自动建)。
36
+ */
37
+ function expandTemplate(value, vars = {}) {
38
+ if (typeof value !== 'string') return { value, templated: false };
39
+ const now = vars.now === undefined ? formatNow() : vars.now;
40
+ let templated = false;
41
+ let result = value.replace(NOW_TOKEN, () => {
42
+ templated = true;
43
+ return now;
44
+ });
45
+ result = result.replace(QUICK_CHAT_PATH_TOKEN, () => {
46
+ templated = true;
47
+ return String(vars.quickChatPath || '').replace(/\/+$/, '');
48
+ });
49
+ return { value: result, templated };
50
+ }
51
+
52
+ module.exports = { formatNow, expandHome, resolveQuickChatPath, expandTemplate };
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const crypto = require('crypto');
4
+ const { expandTemplate } = require('./name-template');
4
5
 
5
6
  function toArray(value) {
6
7
  return Array.isArray(value) ? value : (value ? [value] : []);
@@ -17,6 +18,8 @@ function resolveRuntimeConfig(options = {}) {
17
18
  const envVars = params.envVars || process.env;
18
19
  const pickConfigValue = params.pickConfigValue;
19
20
  const resolveContainerNameTemplate = params.resolveContainerNameTemplate;
21
+ // 占位符变量({now} / {quickChatPath}):一次解析里只取一个时间,容器名和目录用同一个时间戳
22
+ const templateVars = params.templateVars || null;
20
23
  const normalizeCommandSuffix = params.normalizeCommandSuffix;
21
24
  const normalizeJsonEnvMap = params.normalizeJsonEnvMap;
22
25
  const normalizeCliEnvMap = params.normalizeCliEnvMap;
@@ -29,18 +32,25 @@ function resolveRuntimeConfig(options = {}) {
29
32
  const isServerStopMode = params.isServerStopMode === true;
30
33
 
31
34
  let hostPath = pickConfigValue(cliOptions.hostPath, runConfig.hostPath, globalConfig.hostPath, defaults.hostPath) || defaults.hostPath;
35
+ let hostPathTemplated = false;
36
+ if (templateVars) {
37
+ const expanded = expandTemplate(hostPath, templateVars);
38
+ hostPath = expanded.value;
39
+ hostPathTemplated = expanded.templated;
40
+ }
32
41
  let containerName = defaults.containerName;
33
42
  const mergedContainerName = pickConfigValue(cliOptions.contName, runConfig.containerName, globalConfig.containerName);
34
43
  if (mergedContainerName) {
35
44
  containerName = mergedContainerName;
36
45
  }
37
- containerName = resolveContainerNameTemplate(containerName);
46
+ containerName = templateVars ? expandTemplate(containerName, templateVars).value : resolveContainerNameTemplate(containerName);
38
47
 
39
48
  let containerPath = defaults.containerPath;
40
49
  const mergedContainerPath = pickConfigValue(cliOptions.contPath, runConfig.containerPath, globalConfig.containerPath);
41
50
  if (mergedContainerPath) {
42
51
  containerPath = mergedContainerPath;
43
52
  }
53
+ if (templateVars) containerPath = expandTemplate(containerPath, templateVars).value;
44
54
 
45
55
  const imageName = pickConfigValue(cliOptions.imageName, runConfig.imageName, globalConfig.imageName, defaults.imageName) || defaults.imageName;
46
56
  const imageVersion = pickConfigValue(cliOptions.imageVer, runConfig.imageVersion, globalConfig.imageVersion, defaults.imageVersion) || defaults.imageVersion;
@@ -182,6 +192,8 @@ function resolveRuntimeConfig(options = {}) {
182
192
 
183
193
  return {
184
194
  hostPath,
195
+ // 宿主机目录是按模板({now} / {quickChatPath})生成的:真正创建容器时才替用户建目录
196
+ hostPathTemplated,
185
197
  containerName,
186
198
  containerPath,
187
199
  imageName,