@skillbuilder/minitool-core 0.0.0-beta-20261027053041051

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.
Files changed (61) hide show
  1. package/README.md +74 -0
  2. package/lib/di/index.js +21 -0
  3. package/lib/di/modules.js +23 -0
  4. package/lib/di/tokens.js +12 -0
  5. package/lib/index.js +29 -0
  6. package/lib/runtime.js +42 -0
  7. package/lib/services/index.js +19 -0
  8. package/lib/services/minitool-api.js +125 -0
  9. package/lib/services/minitool-contract.js +76 -0
  10. package/lib/services/minitool-service.js +161 -0
  11. package/lib/services/upload-service.js +183 -0
  12. package/lib/utils/config-merger.js +63 -0
  13. package/lib/utils/cos-uploader.js +120 -0
  14. package/lib/utils/env-helper.js +4 -0
  15. package/lib/utils/helper.js +46 -0
  16. package/lib/utils/loading.js +52 -0
  17. package/lib/utils/logger.js +83 -0
  18. package/lib/utils/open-browser.js +32 -0
  19. package/lib/utils/openChrome.applescript +78 -0
  20. package/lib/utils/output.js +19 -0
  21. package/lib/utils/port-finder.js +43 -0
  22. package/lib/utils/project-validator.js +233 -0
  23. package/lib/utils/request.js +73 -0
  24. package/lib/utils/table.js +29 -0
  25. package/lib/utils/to-hump.js +5 -0
  26. package/lib/utils/tracker/ai-apm.js +119 -0
  27. package/lib/utils/tracker/apm.js +73 -0
  28. package/lib/utils/tracker/cli-apm.js +188 -0
  29. package/lib/utils/tracker/device.js +30 -0
  30. package/lib/utils/tracker/index.js +21 -0
  31. package/lib/utils/tracker/tracker.js +2 -0
  32. package/package.json +32 -0
  33. package/types/di/index.d.ts +3 -0
  34. package/types/di/modules.d.ts +9 -0
  35. package/types/di/tokens.d.ts +9 -0
  36. package/types/index.d.ts +14 -0
  37. package/types/runtime.d.ts +24 -0
  38. package/types/services/index.d.ts +3 -0
  39. package/types/services/minitool-api.d.ts +30 -0
  40. package/types/services/minitool-contract.d.ts +103 -0
  41. package/types/services/minitool-service.d.ts +29 -0
  42. package/types/services/upload-service.d.ts +48 -0
  43. package/types/utils/config-merger.d.ts +6 -0
  44. package/types/utils/cos-uploader.d.ts +45 -0
  45. package/types/utils/env-helper.d.ts +1 -0
  46. package/types/utils/helper.d.ts +8 -0
  47. package/types/utils/loading.d.ts +15 -0
  48. package/types/utils/logger.d.ts +27 -0
  49. package/types/utils/open-browser.d.ts +2 -0
  50. package/types/utils/output.d.ts +7 -0
  51. package/types/utils/port-finder.d.ts +2 -0
  52. package/types/utils/project-validator.d.ts +25 -0
  53. package/types/utils/request.d.ts +26 -0
  54. package/types/utils/table.d.ts +9 -0
  55. package/types/utils/to-hump.d.ts +1 -0
  56. package/types/utils/tracker/ai-apm.d.ts +68 -0
  57. package/types/utils/tracker/apm.d.ts +21 -0
  58. package/types/utils/tracker/cli-apm.d.ts +47 -0
  59. package/types/utils/tracker/device.d.ts +8 -0
  60. package/types/utils/tracker/index.d.ts +5 -0
  61. package/types/utils/tracker/tracker.d.ts +22 -0
package/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # @skillbuilder/minitool-core
2
+
3
+ 小工具 CLI / MCP 共用业务核心,通过 `createMinitoolCore()` 的私有 Inversify 容器组装。
4
+ 接口依据 `skill-cli` 的 `origin/feat-pipline-auth` 分支实现;不再要求宿主填写占位接口路径。
5
+
6
+ ## 服务与接口
7
+
8
+ | 服务方法 | 平台接口 | 关键参数 |
9
+ | --- | --- | --- |
10
+ | `getConfig` | GET `/api/sns/v2/creator/red_app/third/config` | `source` |
11
+ | `list` | GET `…/list` | `source`、`page_num`、`page_size` |
12
+ | `getStatus` | GET `…/detail` | `source`、`appId` |
13
+ | `getHistory` | GET `…/history_version` | `source`、`appId` |
14
+ | `createPreview` | POST `…/preview_code` | `source`、`pkgFileId`、`md5`;可选名称、图标、版本 |
15
+ | `submitReview` | POST `…/save` | `source`、名称、简介、图标/封面/代码包 fileId、md5、版本、类目、权限 |
16
+ | `edit` | POST `…/base/save` | `source`、`appId`、`appName`、`slogan`、`iconFileId` 必填且非空白;`coverFileId` 可选 |
17
+ | `upload` | GET `/api/sns/v2/red_skill/upload/permit` + COS/ROS | `spectrum/red_app` 或 `spectrum/red_skill_tool_image` |
18
+
19
+ `source` 必须来自平台分配,不设置默认来源。请求只使用 Bearer 请求头,身份由网关决定。
20
+ 列表保留平台的 `apps`,详情保留 `appId` 等 camelCase 字段;展示状态与历史审核状态分别补充中文标签。
21
+ 提审前查询当前配置,校验类目和权限编码;本地校验不代表服务端审核通过。
22
+
23
+ 上传接受 ZIP / 图片,返回 `fileId`、`md5`、`sha256`、`size`、`type`,不创建小工具。
24
+ ZIP 根目录必须含 `index.html`,文件不超过 10 MiB,解压不超过 30 MiB,拒绝越界、重复路径、符号链接和 CRC 错误。
25
+ 图片接受 PNG、JPEG、GIF、WebP。上传使用经过校验的临时快照,最终清理;可传 `expectedSha256` 拒绝确认后变更的文件。
26
+ 通用上传服务只提供安全的 HTTPS 展示地址,忽略 ROS 返回的 HTTP、临时签名或无效地址;上传成功仍返回 `fileId`。
27
+ 预览和提审消费上传返回的文件标识;首次提审不传 `appId`,更新版本传已有 `appId`。
28
+
29
+ ## DI 与鉴权边界
30
+
31
+ `MiniToolService` 注入 `IMiniToolApi`、`IUploadService`;HTTP 适配器注入 `AccessTokenProvider` 和网关配置。
32
+ 凭据存储、扫码、PKCE、刷新和 Secret 登录在 `minitool-cli-auth` 中实现,core 不读取凭据文件。
33
+ 域名由 `@skillbuilder/endpoints` 集中维护,宿主可显式传入网关和请求函数供集成测试。
34
+
35
+ ```ts
36
+ const runtime = createMinitoolCore({
37
+ accessTokens,
38
+ api: { apiBaseUrl },
39
+ });
40
+ try {
41
+ const page = await runtime.miniTools.list({ source, pageNum: 1, pageSize: 20 });
42
+ } finally {
43
+ await runtime.dispose();
44
+ }
45
+ ```
46
+
47
+ 公共 Token 集中在 `src/di/tokens.ts`;测试可通过后加载的 `ContainerModule` + `rebindSync` 替换 API、上传或业务服务。
48
+ 上传还提供 `MINITOOL_TOKENS.cosClientFactory`:可注入 `CosClientFactory` 替换文件存储传输,保留正式 `UploadService`、授权策略和文件校验。
49
+ 每个 Runtime 使用独立单例容器,`dispose()` 幂等释放资源。禁止目录扫描和生产假数据。
50
+ 写接口不自动重试;网络中断、5xx/408、不可解析响应或缺少成功字段返回 `OUTCOME_UNKNOWN`,应先查询列表、状态或历史。
51
+ 服务端拒绝、HTTP 权限错误和本地参数错误有独立错误码,错误信息不回显 Token 或上传授权。
52
+
53
+ 通用工具继续提供 Logger、Loading、浏览器启动、端口发现、HTTP 和表格输出。
54
+ CLI APM 默认关闭,仅显式设置 `REDMINITOOL_APM_ENDPOINT` 后上报脱敏事件。
55
+
56
+ ## 可视化工作台与测试
57
+
58
+ 在仓库根目录执行:
59
+
60
+ ```sh
61
+ pnpm --filter @skillbuilder/minitool-core playground
62
+ ```
63
+
64
+ 打开 `http://127.0.0.1:5189`,点击「运行完整验证」。默认使用独立本地演示平台,22 项检查覆盖全部 8 个业务服务,运行结束会登出演示账号。
65
+ 工作台提供参数表单 / JSON 编辑、ZIP 和图片上传、模拟扫码确认 / 拒绝、自动续期、7 类故障注入和脱敏 HTTP 记录。
66
+ 服务端通过真实 HTTP 调用本地平台,仅通过 DI 替换云存储传输;本地上传会实际传送字节并校验 MD5。详细结构、真实环境用法与测试说明见 [playground/README.md](playground/README.md)。
67
+
68
+ ```sh
69
+ # 自动构建依赖与 core,执行单元、契约和 HTTP 集成测试
70
+ pnpm --filter @skillbuilder/minitool-core test
71
+
72
+ # 同一套测试,附 Node.js 覆盖率报告
73
+ pnpm --filter @skillbuilder/minitool-core test:coverage
74
+ ```
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.ContainerModule = void 0;
18
+ var inversify_1 = require("inversify");
19
+ Object.defineProperty(exports, "ContainerModule", { enumerable: true, get: function () { return inversify_1.ContainerModule; } });
20
+ __exportStar(require("./modules"), exports);
21
+ __exportStar(require("./tokens"), exports);
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createMinitoolModule = createMinitoolModule;
7
+ const cos_nodejs_sdk_v5_1 = __importDefault(require("cos-nodejs-sdk-v5"));
8
+ const inversify_1 = require("inversify");
9
+ const minitool_api_1 = require("../services/minitool-api");
10
+ const minitool_service_1 = require("../services/minitool-service");
11
+ const upload_service_1 = require("../services/upload-service");
12
+ const tokens_1 = require("./tokens");
13
+ /** MiniTool Core 显式注册业务、传输及其窄依赖。 */
14
+ function createMinitoolModule(options) {
15
+ return new inversify_1.ContainerModule(({ bind }) => {
16
+ bind(tokens_1.MINITOOL_TOKENS.auth).toConstantValue(options.auth);
17
+ bind(tokens_1.MINITOOL_TOKENS.apiOptions).toConstantValue(options.api);
18
+ bind(tokens_1.MINITOOL_TOKENS.api).to(minitool_api_1.MiniToolHttpClient).inSingletonScope();
19
+ bind(tokens_1.MINITOOL_TOKENS.cosClientFactory).toConstantValue((options) => new cos_nodejs_sdk_v5_1.default(options));
20
+ bind(tokens_1.MINITOOL_TOKENS.upload).to(upload_service_1.UploadService).inSingletonScope();
21
+ bind(tokens_1.MINITOOL_TOKENS.miniTool).to(minitool_service_1.MiniToolService).inSingletonScope();
22
+ });
23
+ }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MINITOOL_TOKENS = void 0;
4
+ /** MiniTool Runtime 服务与依赖标识。 */
5
+ exports.MINITOOL_TOKENS = Object.freeze({
6
+ api: Symbol.for("@skillbuilder/minitool-core/api"),
7
+ apiOptions: Symbol.for("@skillbuilder/minitool-core/api-options"),
8
+ auth: Symbol.for("@skillbuilder/minitool-core/auth"),
9
+ miniTool: Symbol.for("@skillbuilder/minitool-core/minitool"),
10
+ cosClientFactory: Symbol.for("@skillbuilder/minitool-core/cos-client-factory"),
11
+ upload: Symbol.for("@skillbuilder/minitool-core/upload"),
12
+ });
package/lib/index.js ADDED
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./di"), exports);
18
+ __exportStar(require("./runtime"), exports);
19
+ __exportStar(require("./services"), exports);
20
+ __exportStar(require("./utils/tracker"), exports);
21
+ __exportStar(require("./utils/helper"), exports);
22
+ __exportStar(require("./utils/loading"), exports);
23
+ __exportStar(require("./utils/logger"), exports);
24
+ __exportStar(require("./utils/open-browser"), exports);
25
+ __exportStar(require("./utils/output"), exports);
26
+ __exportStar(require("./utils/port-finder"), exports);
27
+ __exportStar(require("./utils/project-validator"), exports);
28
+ __exportStar(require("./utils/request"), exports);
29
+ __exportStar(require("./utils/table"), exports);
package/lib/runtime.js ADDED
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createMinitoolCore = createMinitoolCore;
4
+ const endpoints_1 = require("@skillbuilder/endpoints");
5
+ const inversify_1 = require("inversify");
6
+ const modules_1 = require("./di/modules");
7
+ const tokens_1 = require("./di/tokens");
8
+ class DefaultMinitoolRuntime {
9
+ container;
10
+ upload;
11
+ miniTools;
12
+ disposed = false;
13
+ disposePromise;
14
+ constructor(container) {
15
+ this.container = container;
16
+ this.upload = container.get(tokens_1.MINITOOL_TOKENS.upload);
17
+ this.miniTools = container.get(tokens_1.MINITOOL_TOKENS.miniTool);
18
+ }
19
+ get isDisposed() {
20
+ return this.disposed;
21
+ }
22
+ dispose() {
23
+ if (!this.disposePromise) {
24
+ this.disposed = true;
25
+ this.disposePromise = this.container.unbindAll();
26
+ }
27
+ return this.disposePromise;
28
+ }
29
+ }
30
+ /** 组装相互隔离、可释放的业务 Runtime;Container 不向调用方暴露。 */
31
+ function createMinitoolCore(options) {
32
+ const container = new inversify_1.Container({ defaultScope: "Singleton" });
33
+ const modules = [
34
+ (0, modules_1.createMinitoolModule)({
35
+ auth: options.accessTokens,
36
+ api: options.api ?? { apiBaseUrl: endpoints_1.EDITH_BASE_URL },
37
+ }),
38
+ ];
39
+ modules.push(...(options.modules ?? []));
40
+ container.loadSync(...modules);
41
+ return new DefaultMinitoolRuntime(container);
42
+ }
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./minitool-service"), exports);
18
+ __exportStar(require("./upload-service"), exports);
19
+ __exportStar(require("./minitool-api"), exports);
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.MiniToolHttpClient = exports.MINITOOL_ROUTES = void 0;
16
+ exports.isRecord = isRecord;
17
+ exports.validateApiBaseUrl = validateApiBaseUrl;
18
+ require("reflect-metadata");
19
+ const inversify_1 = require("inversify");
20
+ const tokens_1 = require("../di/tokens");
21
+ const cli_apm_1 = require("../utils/tracker/cli-apm");
22
+ const minitool_contract_1 = require("./minitool-contract");
23
+ const PREFIX = "/api/sns/v2/creator/red_app/third";
24
+ exports.MINITOOL_ROUTES = Object.freeze({
25
+ config: ["GET", `${PREFIX}/config`],
26
+ list: ["GET", `${PREFIX}/list`],
27
+ status: ["GET", `${PREFIX}/detail`],
28
+ history: ["GET", `${PREFIX}/history_version`],
29
+ submit: ["POST", `${PREFIX}/save`],
30
+ edit: ["POST", `${PREFIX}/base/save`],
31
+ preview: ["POST", `${PREFIX}/preview_code`],
32
+ permit: ["GET", "/api/sns/v2/red_skill/upload/permit"],
33
+ });
34
+ function isRecord(value) {
35
+ return value !== null && typeof value === "object" && !Array.isArray(value);
36
+ }
37
+ /** 仅允许 HTTPS 或本机测试地址,且禁止凭据重定向到其他 host。 */
38
+ function validateApiBaseUrl(value) {
39
+ const url = new URL(value);
40
+ if ((url.protocol !== "https:" &&
41
+ !(url.protocol === "http:" && ["localhost", "127.0.0.1"].includes(url.hostname))) ||
42
+ url.username ||
43
+ url.password ||
44
+ url.search ||
45
+ url.hash ||
46
+ url.pathname !== "/")
47
+ throw new Error("API 地址须为 HTTPS origin(本机测试服务除外)");
48
+ return url.origin;
49
+ }
50
+ let MiniToolHttpClient = class MiniToolHttpClient {
51
+ accessTokens;
52
+ baseUrl;
53
+ request;
54
+ constructor(accessTokens, options) {
55
+ this.accessTokens = accessTokens;
56
+ this.baseUrl = validateApiBaseUrl(options.apiBaseUrl);
57
+ this.request = options.request ?? fetch;
58
+ }
59
+ async call(operation, payload) {
60
+ const [method, route] = exports.MINITOOL_ROUTES[operation];
61
+ const url = new URL(route, this.baseUrl);
62
+ const mutation = method === "POST";
63
+ if (!mutation) {
64
+ for (const [key, value] of Object.entries(payload)) {
65
+ if (value !== undefined)
66
+ url.searchParams.set(key, String(value));
67
+ }
68
+ }
69
+ const token = await this.accessTokens.getAccessToken();
70
+ const unknown = () => new minitool_contract_1.MiniToolError("OUTCOME_UNKNOWN", "请求结果未知,请先查询列表、状态或历史版本确认结果,再决定是否重试。");
71
+ let response;
72
+ try {
73
+ response = await this.request(url, {
74
+ method,
75
+ headers: {
76
+ authorization: `Bearer ${token}`,
77
+ ...(mutation ? { "content-type": "application/json" } : {}),
78
+ },
79
+ body: mutation ? JSON.stringify(payload) : undefined,
80
+ redirect: "error",
81
+ signal: AbortSignal.timeout(30_000),
82
+ });
83
+ }
84
+ catch {
85
+ (0, cli_apm_1.reportCliHttpError)({ method, url: `${url.origin}${url.pathname}` });
86
+ throw mutation
87
+ ? unknown()
88
+ : new minitool_contract_1.MiniToolError("REQUEST_FAILED", "小工具接口请求失败,请稍后重试");
89
+ }
90
+ if (response.status === 401 || response.status === 403) {
91
+ throw new minitool_contract_1.MiniToolError("AUTH_REQUIRED", "登录凭据或接口权限无效,请重新扫码登录并检查来源权限");
92
+ }
93
+ if (!response.ok) {
94
+ if (mutation && (response.status >= 500 || response.status === 408))
95
+ throw unknown();
96
+ throw new minitool_contract_1.MiniToolError("REQUEST_FAILED", `小工具接口请求失败(HTTP ${response.status})`);
97
+ }
98
+ let body;
99
+ try {
100
+ body = await response.json();
101
+ }
102
+ catch {
103
+ throw mutation ? unknown() : new minitool_contract_1.MiniToolError("INVALID_RESPONSE", "服务端未返回有效 JSON");
104
+ }
105
+ if (isRecord(body) &&
106
+ (body.success === false ||
107
+ (body.code !== undefined && ![0, "0", "OK"].includes(body.code)))) {
108
+ // 不拼接服务端原文,避免网关回显 Token / 临时上传凭据。
109
+ throw new minitool_contract_1.MiniToolError("API_REJECTED", "服务端拒绝请求,请检查参数与账号权限");
110
+ }
111
+ if (!isRecord(body) || !isRecord(body.data)) {
112
+ throw mutation
113
+ ? unknown()
114
+ : new minitool_contract_1.MiniToolError("INVALID_RESPONSE", "服务端响应缺少 data 对象");
115
+ }
116
+ return body.data;
117
+ }
118
+ };
119
+ exports.MiniToolHttpClient = MiniToolHttpClient;
120
+ exports.MiniToolHttpClient = MiniToolHttpClient = __decorate([
121
+ (0, inversify_1.injectable)(),
122
+ __param(0, (0, inversify_1.inject)(tokens_1.MINITOOL_TOKENS.auth)),
123
+ __param(1, (0, inversify_1.inject)(tokens_1.MINITOOL_TOKENS.apiOptions)),
124
+ __metadata("design:paramtypes", [Object, Object])
125
+ ], MiniToolHttpClient);
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MiniToolError = exports.miniToolUploadSchema = exports.miniToolEditSchema = exports.miniToolReviewSchema = exports.miniToolPreviewSchema = exports.miniToolDetailSchema = exports.miniToolListSchema = exports.miniToolSourceSchema = void 0;
4
+ exports.parseMiniToolInput = parseMiniToolInput;
5
+ const zod_1 = require("zod");
6
+ const text = zod_1.z.string().trim().min(1);
7
+ const version = text.regex(/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/, "版本须为 x.y.z");
8
+ const md5 = text.regex(/^[a-f\d]{32}$/i, "md5 须为 32 位十六进制字符串").toLowerCase();
9
+ const codes = zod_1.z
10
+ .array(text)
11
+ .refine((values) => new Set(values).size === values.length, "编码不能重复");
12
+ const source = { source: text };
13
+ exports.miniToolSourceSchema = zod_1.z.strictObject(source);
14
+ exports.miniToolListSchema = zod_1.z.strictObject({
15
+ ...source,
16
+ pageNum: zod_1.z.number().int().min(1).max(2147483647).default(1),
17
+ pageSize: zod_1.z.number().int().min(1).max(100).default(20),
18
+ });
19
+ exports.miniToolDetailSchema = zod_1.z.strictObject({ ...source, appId: text });
20
+ exports.miniToolPreviewSchema = zod_1.z.strictObject({
21
+ ...source,
22
+ pkgFileId: text,
23
+ md5,
24
+ appName: text.optional(),
25
+ iconFileId: text.optional(),
26
+ version: version.optional(),
27
+ });
28
+ exports.miniToolReviewSchema = zod_1.z.strictObject({
29
+ ...source,
30
+ appId: text.optional(),
31
+ appName: text,
32
+ slogan: text,
33
+ iconFileId: text,
34
+ coverFileId: text,
35
+ version,
36
+ pkgFileId: text,
37
+ md5,
38
+ categories: codes.length(1),
39
+ authorizations: codes.default([]),
40
+ });
41
+ // 平台 base/save 要求完整基础资料;只改一项时,其余必填项仍需传回现有值。
42
+ exports.miniToolEditSchema = zod_1.z.strictObject({
43
+ ...source,
44
+ appId: text,
45
+ appName: text,
46
+ slogan: text,
47
+ iconFileId: text,
48
+ coverFileId: text.optional(),
49
+ });
50
+ exports.miniToolUploadSchema = zod_1.z.strictObject({
51
+ filePath: text,
52
+ type: zod_1.z.enum(["pkg", "image"]),
53
+ expectedSha256: text
54
+ .regex(/^[a-f\d]{64}$/i)
55
+ .toLowerCase()
56
+ .optional(),
57
+ });
58
+ function parseMiniToolInput(schema, value) {
59
+ const result = schema.safeParse(value);
60
+ if (!result.success) {
61
+ const detail = result.error.issues
62
+ .map((issue) => `${issue.path.join(".") || "参数"}: ${issue.message}`)
63
+ .join(";");
64
+ throw new MiniToolError("INVALID_ARGS", detail);
65
+ }
66
+ return result.data;
67
+ }
68
+ class MiniToolError extends Error {
69
+ code;
70
+ constructor(code, message) {
71
+ super(message);
72
+ this.code = code;
73
+ this.name = "MiniToolError";
74
+ }
75
+ }
76
+ exports.MiniToolError = MiniToolError;
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
14
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
15
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
16
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
17
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
18
+ };
19
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
20
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
21
+ };
22
+ var __metadata = (this && this.__metadata) || function (k, v) {
23
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
24
+ };
25
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
26
+ return function (target, key) { decorator(target, key, paramIndex); }
27
+ };
28
+ var __importDefault = (this && this.__importDefault) || function (mod) {
29
+ return (mod && mod.__esModule) ? mod : { "default": mod };
30
+ };
31
+ Object.defineProperty(exports, "__esModule", { value: true });
32
+ exports.MiniToolService = void 0;
33
+ require("reflect-metadata");
34
+ const promises_1 = __importDefault(require("node:fs/promises"));
35
+ const node_os_1 = __importDefault(require("node:os"));
36
+ const node_path_1 = __importDefault(require("node:path"));
37
+ const inversify_1 = require("inversify");
38
+ const zod_1 = require("zod");
39
+ const tokens_1 = require("../di/tokens");
40
+ const project_validator_1 = require("../utils/project-validator");
41
+ const minitool_contract_1 = require("./minitool-contract");
42
+ const upload_service_1 = require("./upload-service");
43
+ __exportStar(require("./minitool-contract"), exports);
44
+ const appSchema = zod_1.z
45
+ .object({
46
+ appId: zod_1.z.string().min(1),
47
+ appName: zod_1.z.string().optional(),
48
+ version: zod_1.z.string().optional(),
49
+ status: zod_1.z.number().optional(),
50
+ auditStatus: zod_1.z.number().optional(),
51
+ })
52
+ .passthrough();
53
+ const optionSchema = zod_1.z.object({ code: zod_1.z.string(), name: zod_1.z.string().optional() }).passthrough();
54
+ const configSchema = zod_1.z
55
+ .object({ categories: zod_1.z.array(optionSchema), authorizations: zod_1.z.array(optionSchema) })
56
+ .passthrough();
57
+ function response(schema, value, mutation = false) {
58
+ const result = schema.safeParse(value);
59
+ if (!result.success)
60
+ throw new minitool_contract_1.MiniToolError(mutation ? "OUTCOME_UNKNOWN" : "INVALID_RESPONSE", mutation ? "请求结果未知,请先查询状态确认结果,再决定是否重试" : "小工具接口响应格式无效");
61
+ return result.data;
62
+ }
63
+ function statusText(value) {
64
+ const output = { ...value };
65
+ if (Number.isInteger(value.status))
66
+ output.statusText =
67
+ ["未发布", "审核中", "已发布", "未通过", "已下架"][value.status] ?? "未知";
68
+ if (Number.isInteger(value.auditStatus))
69
+ output.auditStatusText =
70
+ ["未知", "审核中", "审核通过", "审核驳回", "创作者撤回"][value.auditStatus] ??
71
+ "未知";
72
+ return output;
73
+ }
74
+ /** 编排业务校验、平台接口与文件传输;不读取凭据文件。 */
75
+ let MiniToolService = class MiniToolService {
76
+ api;
77
+ uploadService;
78
+ constructor(api, uploadService) {
79
+ this.api = api;
80
+ this.uploadService = uploadService;
81
+ }
82
+ async getConfig(input) {
83
+ return response(configSchema, await this.api.call("config", (0, minitool_contract_1.parseMiniToolInput)(minitool_contract_1.miniToolSourceSchema, input)));
84
+ }
85
+ async list(input) {
86
+ const { source, pageNum, pageSize } = (0, minitool_contract_1.parseMiniToolInput)(minitool_contract_1.miniToolListSchema, input);
87
+ const data = response(zod_1.z.object({ apps: zod_1.z.array(appSchema), total: zod_1.z.number().int().nonnegative() }), await this.api.call("list", { source, page_num: pageNum, page_size: pageSize }));
88
+ return { apps: data.apps.map(statusText), total: data.total, pageNum, pageSize };
89
+ }
90
+ async getStatus(input) {
91
+ return statusText(response(appSchema, await this.api.call("status", (0, minitool_contract_1.parseMiniToolInput)(minitool_contract_1.miniToolDetailSchema, input))));
92
+ }
93
+ async getHistory(input) {
94
+ const data = response(zod_1.z.object({ versions: zod_1.z.array(zod_1.z.record(zod_1.z.string(), zod_1.z.unknown())) }).passthrough(), await this.api.call("history", (0, minitool_contract_1.parseMiniToolInput)(minitool_contract_1.miniToolDetailSchema, input)));
95
+ return { ...data, versions: data.versions.map(statusText) };
96
+ }
97
+ async upload(input) {
98
+ const valid = (0, minitool_contract_1.parseMiniToolInput)(minitool_contract_1.miniToolUploadSchema, input);
99
+ const { bytes, ...file } = await (0, project_validator_1.inspectMiniToolFile)(valid.filePath, valid.type);
100
+ if (valid.expectedSha256 && valid.expectedSha256 !== file.sha256)
101
+ throw new minitool_contract_1.MiniToolError("LOCAL_FILE_CHANGED", "文件在确认后发生变化,请重新检查并确认上传");
102
+ const scene = file.type === "pkg" ? "red_app" : "red_skill_tool_image";
103
+ // 上传校验过的快照,避免 COS 按路径读取期间本地产物被改写。
104
+ const temporary = await promises_1.default.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), "redminitool-upload-"));
105
+ try {
106
+ const snapshot = node_path_1.default.join(temporary, `upload.${file.format}`);
107
+ await promises_1.default.writeFile(snapshot, bytes, { mode: 0o600 });
108
+ const uploaded = await this.uploadService.upload({
109
+ filePath: snapshot,
110
+ bizName: "spectrum",
111
+ scene,
112
+ contentType: file.type === "pkg"
113
+ ? "application/zip"
114
+ : `image/${file.format === "jpg" ? "jpeg" : file.format}`,
115
+ getPermit: async () => (0, upload_service_1.normalizeUploadPermit)(await this.api.call("permit", {
116
+ biz_name: "spectrum",
117
+ scene,
118
+ file_count: 1,
119
+ file_format: file.format,
120
+ version: 1,
121
+ source: "web",
122
+ })),
123
+ });
124
+ return {
125
+ fileId: uploaded.fileId,
126
+ md5: file.md5,
127
+ sha256: file.sha256,
128
+ size: file.size,
129
+ type: file.type,
130
+ };
131
+ }
132
+ finally {
133
+ await promises_1.default.rm(temporary, { recursive: true, force: true });
134
+ }
135
+ }
136
+ async createPreview(input) {
137
+ const valid = (0, minitool_contract_1.parseMiniToolInput)(minitool_contract_1.miniToolPreviewSchema, input);
138
+ return response(zod_1.z.object({ previewUrl: zod_1.z.url() }).passthrough(), await this.api.call("preview", valid), true);
139
+ }
140
+ async submitReview(input) {
141
+ const valid = (0, minitool_contract_1.parseMiniToolInput)(minitool_contract_1.miniToolReviewSchema, input);
142
+ const config = await this.getConfig({ source: valid.source });
143
+ for (const field of ["categories", "authorizations"]) {
144
+ const allowed = new Set(config[field].map((item) => item.code));
145
+ if (valid[field].some((code) => !allowed.has(code)))
146
+ throw new minitool_contract_1.MiniToolError("INVALID_ARGS", `${field} 包含平台配置中不存在的编码`);
147
+ }
148
+ return statusText(response(zod_1.z.object({ appId: zod_1.z.string().min(1) }).passthrough(), await this.api.call("submit", valid), true));
149
+ }
150
+ async edit(input) {
151
+ const valid = (0, minitool_contract_1.parseMiniToolInput)(minitool_contract_1.miniToolEditSchema, input);
152
+ return response(zod_1.z.object({ appId: zod_1.z.string().min(1) }).passthrough(), await this.api.call("edit", valid), true);
153
+ }
154
+ };
155
+ exports.MiniToolService = MiniToolService;
156
+ exports.MiniToolService = MiniToolService = __decorate([
157
+ (0, inversify_1.injectable)(),
158
+ __param(0, (0, inversify_1.inject)(tokens_1.MINITOOL_TOKENS.api)),
159
+ __param(1, (0, inversify_1.inject)(tokens_1.MINITOOL_TOKENS.upload)),
160
+ __metadata("design:paramtypes", [Object, Object])
161
+ ], MiniToolService);