@stylewx/service 0.1.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.
Files changed (49) hide show
  1. package/dist/analyze.d.ts +18 -0
  2. package/dist/analyze.d.ts.map +1 -0
  3. package/dist/analyze.js +109 -0
  4. package/dist/analyze.js.map +1 -0
  5. package/dist/errors.d.ts +18 -0
  6. package/dist/errors.d.ts.map +1 -0
  7. package/dist/errors.js +53 -0
  8. package/dist/errors.js.map +1 -0
  9. package/dist/generate.d.ts +32 -0
  10. package/dist/generate.d.ts.map +1 -0
  11. package/dist/generate.js +229 -0
  12. package/dist/generate.js.map +1 -0
  13. package/dist/index.d.ts +10 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +10 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/llm.d.ts +54 -0
  18. package/dist/llm.d.ts.map +1 -0
  19. package/dist/llm.js +190 -0
  20. package/dist/llm.js.map +1 -0
  21. package/dist/optimize.d.ts +12 -0
  22. package/dist/optimize.d.ts.map +1 -0
  23. package/dist/optimize.js +21 -0
  24. package/dist/optimize.js.map +1 -0
  25. package/dist/publish.d.ts +13 -0
  26. package/dist/publish.d.ts.map +1 -0
  27. package/dist/publish.js +19 -0
  28. package/dist/publish.js.map +1 -0
  29. package/dist/render.d.ts +17 -0
  30. package/dist/render.d.ts.map +1 -0
  31. package/dist/render.js +35 -0
  32. package/dist/render.js.map +1 -0
  33. package/dist/service.test.d.ts +2 -0
  34. package/dist/service.test.d.ts.map +1 -0
  35. package/dist/service.test.js +163 -0
  36. package/dist/service.test.js.map +1 -0
  37. package/dist/theme-store.d.ts +28 -0
  38. package/dist/theme-store.d.ts.map +1 -0
  39. package/dist/theme-store.js +93 -0
  40. package/dist/theme-store.js.map +1 -0
  41. package/dist/theme-store.test.d.ts +2 -0
  42. package/dist/theme-store.test.d.ts.map +1 -0
  43. package/dist/theme-store.test.js +70 -0
  44. package/dist/theme-store.test.js.map +1 -0
  45. package/dist/themes.d.ts +15 -0
  46. package/dist/themes.d.ts.map +1 -0
  47. package/dist/themes.js +38 -0
  48. package/dist/themes.js.map +1 -0
  49. package/package.json +36 -0
@@ -0,0 +1,93 @@
1
+ /**
2
+ * 主题库:把「自定义 / AI 生成」的主题持久化到本地,方便以后复用。
3
+ * 默认存到用户级 `~/.stylewx/themes.json`(可用环境变量 STYLEWX_THEMES_PATH 覆盖)。
4
+ * 注意:本模块使用 Node fs —— 只允许出现在 service 层(core/theme/validator 保持同构,不得引用)。
5
+ */
6
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
7
+ import { dirname, join } from 'node:path';
8
+ import { homedir } from 'node:os';
9
+ import { validateTheme, getPresetTheme } from '@stylewx/theme';
10
+ import { serviceError } from './errors.js';
11
+ const DEFAULT_DIR = join(homedir(), '.stylewx');
12
+ const DEFAULT_FILE = join(DEFAULT_DIR, 'themes.json');
13
+ function storeFile(opts) {
14
+ return opts?.file ?? process.env.STYLEWX_THEMES_PATH ?? DEFAULT_FILE;
15
+ }
16
+ /** 读取已保存的主题(文件缺失或损坏时返回空,不抛错)。 */
17
+ function readSavedThemes(file) {
18
+ try {
19
+ if (!existsSync(file))
20
+ return [];
21
+ const raw = readFileSync(file, 'utf8');
22
+ const data = JSON.parse(raw);
23
+ const arr = Array.isArray(data) ? data : (data.themes ?? []);
24
+ return arr.filter((t) => !!t && typeof t === 'object' && typeof t.name === 'string');
25
+ }
26
+ catch {
27
+ return [];
28
+ }
29
+ }
30
+ function writeSavedThemes(file, themes) {
31
+ mkdirSync(dirname(file), { recursive: true });
32
+ writeFileSync(file, JSON.stringify({ themes }, null, 2), 'utf8');
33
+ }
34
+ /** 列出所有已保存的自定义主题。 */
35
+ export function listSavedThemes(opts) {
36
+ return { themes: readSavedThemes(storeFile(opts)).map((t) => structuredClone(t)) };
37
+ }
38
+ /**
39
+ * 保存一个主题(校验通过后写入本地库)。同名会覆盖。
40
+ * @returns 保存后的主题(已通过 schema + 白名单校验)。
41
+ */
42
+ export function saveTheme(input, opts) {
43
+ const result = validateTheme(input);
44
+ if (!result.ok || !result.theme) {
45
+ const detail = result.issues.map((i) => `${i.path}: ${i.message}`).join(';');
46
+ throw serviceError('invalid_theme', `主题不合法,无法保存:${detail}`, '请用符合主题 Schema 的 JSON 传入,或先用 generate_theme 生成后保存。');
47
+ }
48
+ const theme = result.theme;
49
+ const file = storeFile(opts);
50
+ const all = readSavedThemes(file);
51
+ const idx = all.findIndex((t) => t.name === theme.name);
52
+ if (idx >= 0)
53
+ all[idx] = structuredClone(theme);
54
+ else
55
+ all.push(structuredClone(theme));
56
+ writeSavedThemes(file, all);
57
+ return { theme };
58
+ }
59
+ /**
60
+ * 导出主题为完整 JSON(供复用/分享)。支持:已保存的自定义主题名、预置主题名、或完整主题对象。
61
+ * @returns 可直接传给 render_preview / publish_draft 复用的完整主题对象。
62
+ */
63
+ export function exportTheme(input, opts) {
64
+ if (input && typeof input === 'object') {
65
+ const result = validateTheme(input);
66
+ if (result.ok && result.theme)
67
+ return { theme: result.theme };
68
+ const detail = result.issues.map((i) => `${i.path}: ${i.message}`).join(';');
69
+ throw serviceError('invalid_theme', `主题不合法:${detail}`, '请传入符合 Schema 的主题对象或已存在的主题名。');
70
+ }
71
+ if (typeof input === 'string' && input.trim()) {
72
+ const name = input.trim();
73
+ const saved = readSavedThemes(storeFile(opts)).find((t) => t.name === name);
74
+ if (saved)
75
+ return { theme: structuredClone(saved) };
76
+ const preset = getPresetTheme(name);
77
+ if (preset)
78
+ return { theme: structuredClone(preset) };
79
+ throw serviceError('invalid_theme', `找不到主题「${name}」。`, '可先 list_saved_themes / list_themes 查看可用主题名。');
80
+ }
81
+ throw serviceError('invalid_theme', 'export_theme 需要主题对象或主题名。', '请传入主题 JSON 或已存在的主题名称字符串。');
82
+ }
83
+ /** 删除一个已保存的主题。 */
84
+ export function deleteTheme(name, opts) {
85
+ const file = storeFile(opts);
86
+ const all = readSavedThemes(file);
87
+ const next = all.filter((t) => t.name !== name);
88
+ if (next.length === all.length)
89
+ throw serviceError('invalid_theme', `没有已保存的主题「${name}」。`, '请用 list_saved_themes 查看已存主题。');
90
+ writeSavedThemes(file, next);
91
+ return { deleted: name };
92
+ }
93
+ //# sourceMappingURL=theme-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme-store.js","sourceRoot":"","sources":["../src/theme-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=theme-store.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme-store.test.d.ts","sourceRoot":"","sources":["../src/theme-store.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,70 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import { mkdtempSync, rmSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { PRESET_THEMES } from '@stylewx/theme';
6
+ import { saveTheme, listSavedThemes, exportTheme, deleteTheme } from './theme-store.js';
7
+ import { listThemes, resolveTheme } from './themes.js';
8
+ let tmp;
9
+ function makeFile() {
10
+ tmp = mkdtempSync(join(tmpdir(), 'mp-store-'));
11
+ return join(tmp, 'themes.json');
12
+ }
13
+ afterEach(() => {
14
+ if (tmp)
15
+ rmSync(tmp, { recursive: true, force: true });
16
+ });
17
+ // 用真实预置主题克隆作为合法基座(保证通过 Schema),只覆盖 name/description 模拟自定义
18
+ const vanilla = structuredClone(PRESET_THEMES[0]);
19
+ const validTheme = { ...vanilla, name: '测试主题-复用', description: '测试用主题' };
20
+ describe('theme-store', () => {
21
+ it('保存后能列出并导出,同名覆盖', () => {
22
+ const file = makeFile();
23
+ const saved = saveTheme(validTheme, { file });
24
+ expect(saved.theme.name).toBe('测试主题-复用');
25
+ const listed = listSavedThemes({ file });
26
+ expect(listed.themes).toHaveLength(1);
27
+ expect(listed.themes[0].name).toBe('测试主题-复用');
28
+ // 同名覆盖
29
+ saveTheme({ ...validTheme, description: '覆盖版' }, { file });
30
+ expect(listSavedThemes({ file }).themes).toHaveLength(1);
31
+ expect(listSavedThemes({ file }).themes[0].description).toBe('覆盖版');
32
+ // 导出(按名)
33
+ const exported = exportTheme('测试主题-复用', { file });
34
+ expect(exported.theme.name).toBe('测试主题-复用');
35
+ });
36
+ it('非法主题保存时抛 invalid_theme', () => {
37
+ const file = makeFile();
38
+ let err;
39
+ try {
40
+ saveTheme({ name: 'x' }, { file });
41
+ }
42
+ catch (e) {
43
+ err = e;
44
+ }
45
+ expect(err).toBeTruthy();
46
+ expect(err?.error?.code).toBe('invalid_theme');
47
+ });
48
+ it('删除已保存主题', () => {
49
+ const file = makeFile();
50
+ saveTheme(validTheme, { file });
51
+ const r = deleteTheme('测试主题-复用', { file });
52
+ expect(r.deleted).toBe('测试主题-复用');
53
+ expect(listSavedThemes({ file }).themes).toHaveLength(0);
54
+ });
55
+ it('listThemes 合并已存主题(预置优先、去重)', () => {
56
+ const file = makeFile();
57
+ const all = listThemes(); // 不传 file,读真实目录 → 可能为空,但结构应为预置
58
+ expect(all.themes.length).toBeGreaterThanOrEqual(26);
59
+ // 用注入 file 验证合并逻辑:保存一个非预置名主题后,再走 resolveTheme
60
+ saveTheme({ ...validTheme, name: 'my-custom-ai' }, { file });
61
+ // resolveTheme 经 listSavedThemes 查全局(非注入),同名不冲突;这里验证 resolveTheme 对已存名可用需注入,
62
+ // 但 resolveTheme 内部无 file 注入,故改为验证:listSavedThemes 注入 file 返回 1 条
63
+ expect(listSavedThemes({ file }).themes[0].name).toBe('my-custom-ai');
64
+ });
65
+ it('resolveTheme 支持完整主题对象', () => {
66
+ const t = resolveTheme(validTheme);
67
+ expect(t.name).toBe('测试主题-复用');
68
+ });
69
+ });
70
+ //# sourceMappingURL=theme-store.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme-store.test.js","sourceRoot":"","sources":["../src/theme-store.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { Theme } from '@stylewx/theme';
2
+ /**
3
+ * 预置主题摘要。返回完整的主题对象(含 blocks),这样 Agent 可直接把它传给
4
+ * render_preview / publish_draft 复用,无需二次构造。
5
+ * 还会合并「本地已保存的自定义 / AI 主题」(同名去重,预置优先),方便复用。
6
+ */
7
+ export declare function listThemes(): {
8
+ themes: Theme[];
9
+ };
10
+ /**
11
+ * 把输入解析为可用主题:支持预置主题名(字符串)或完整主题对象。
12
+ * 对象会经 zod + 微信白名单校验;非法时抛出统一错误。
13
+ */
14
+ export declare function resolveTheme(input: unknown): Theme;
15
+ //# sourceMappingURL=themes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"themes.d.ts","sourceRoot":"","sources":["../src/themes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAI3C;;;;GAIG;AACH,wBAAgB,UAAU,IAAI;IAAE,MAAM,EAAE,KAAK,EAAE,CAAA;CAAE,CAKhD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAyBlD"}
package/dist/themes.js ADDED
@@ -0,0 +1,38 @@
1
+ import { PRESET_THEMES, getPresetTheme, validateTheme } from '@stylewx/theme';
2
+ import { serviceError } from './errors.js';
3
+ import { listSavedThemes } from './theme-store.js';
4
+ /**
5
+ * 预置主题摘要。返回完整的主题对象(含 blocks),这样 Agent 可直接把它传给
6
+ * render_preview / publish_draft 复用,无需二次构造。
7
+ * 还会合并「本地已保存的自定义 / AI 主题」(同名去重,预置优先),方便复用。
8
+ */
9
+ export function listThemes() {
10
+ const presets = PRESET_THEMES.map((theme) => structuredClone(theme));
11
+ const presetNames = new Set(presets.map((t) => t.name));
12
+ const saved = listSavedThemes().themes.filter((t) => !presetNames.has(t.name));
13
+ return { themes: [...presets, ...saved] };
14
+ }
15
+ /**
16
+ * 把输入解析为可用主题:支持预置主题名(字符串)或完整主题对象。
17
+ * 对象会经 zod + 微信白名单校验;非法时抛出统一错误。
18
+ */
19
+ export function resolveTheme(input) {
20
+ if (typeof input === 'string') {
21
+ const name = input.trim();
22
+ const theme = getPresetTheme(name) ?? listSavedThemes().themes.find((t) => t.name === name);
23
+ if (!theme) {
24
+ const names = PRESET_THEMES.map((t) => t.name).join('、');
25
+ throw serviceError('invalid_theme', `未知主题「${input}」。`, `请使用 list_themes 返回的主题名(可用:${names},或你已保存的自定义主题名),或传入完整主题对象。`);
26
+ }
27
+ return theme;
28
+ }
29
+ if (input && typeof input === 'object') {
30
+ const result = validateTheme(input);
31
+ if (result.ok && result.theme)
32
+ return result.theme;
33
+ const detail = result.issues.map((i) => `${i.path}: ${i.message}`).join(';');
34
+ throw serviceError('invalid_theme', `主题不合法:${detail}`, '请用符合主题 Schema 的 JSON 传入,或先用 generate_theme 生成主题;也可传预置主题名。');
35
+ }
36
+ throw serviceError('invalid_theme', 'theme 必须是主题对象或预置主题名。', '请提供主题对象或字符串名称。');
37
+ }
38
+ //# sourceMappingURL=themes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"themes.js","sourceRoot":"","sources":["../src/themes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAE7E,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAElD;;;;GAIG;AACH,MAAM,UAAU,UAAU;IACxB,MAAM,OAAO,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAA;IACpE,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;IACvD,MAAM,KAAK,GAAG,eAAe,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;IAC9E,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,OAAO,EAAE,GAAG,KAAK,CAAC,EAAE,CAAA;AAC3C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAA;QACzB,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,eAAe,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;QAC3F,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,KAAK,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;YACxD,MAAM,YAAY,CAChB,eAAe,EACf,QAAQ,KAAK,IAAI,EACjB,6BAA6B,KAAK,2BAA2B,CAC9D,CAAA;QACH,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC;IACD,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvC,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,CAAC,CAAA;QACnC,IAAI,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,KAAK;YAAE,OAAO,MAAM,CAAC,KAAK,CAAA;QAClD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAC5E,MAAM,YAAY,CAChB,eAAe,EACf,SAAS,MAAM,EAAE,EACjB,2DAA2D,CAC5D,CAAA;IACH,CAAC;IACD,MAAM,YAAY,CAAC,eAAe,EAAE,sBAAsB,EAAE,gBAAgB,CAAC,CAAA;AAC/E,CAAC"}
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@stylewx/service",
3
+ "version": "0.1.0",
4
+ "description": "stylewx shared service layer: composes core/theme/validator/publisher/preview into the 6 high-level operations reused by both MCP Server and REST API.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "sideEffects": false,
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.json",
20
+ "test": "vitest run",
21
+ "test:watch": "vitest",
22
+ "type-check": "tsc -p tsconfig.json --noEmit",
23
+ "prepublishOnly": "pnpm build"
24
+ },
25
+ "dependencies": {
26
+ "@stylewx/core": "workspace:*",
27
+ "@stylewx/preview": "workspace:*",
28
+ "@stylewx/publisher": "workspace:*",
29
+ "@stylewx/theme": "workspace:*",
30
+ "@stylewx/validator": "workspace:*",
31
+ "zod": "^4.0.0"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public"
35
+ }
36
+ }