@remixmate/cli 0.9.72 → 0.9.74

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 (179) hide show
  1. package/LICENSE +45 -21
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/dist/argv.js +1 -51
  5. package/dist/auth/auth-lock.js +1 -100
  6. package/dist/auth/auto-login.js +7 -75
  7. package/dist/auth/commands.js +40 -290
  8. package/dist/auth/credential-store.js +1 -143
  9. package/dist/auth/device-flow-runner.js +13 -110
  10. package/dist/auth/device-flow.js +1 -132
  11. package/dist/auth/ensure.js +1 -62
  12. package/dist/auth/environment.js +1 -69
  13. package/dist/auth/pending-store.js +1 -45
  14. package/dist/auth/resolve.js +1 -44
  15. package/dist/billing.js +3 -76
  16. package/dist/capabilities.js +1 -75
  17. package/dist/cli.js +39 -211
  18. package/dist/diagnostics.js +2 -70
  19. package/dist/doctor.js +9 -188
  20. package/dist/errors.js +1 -33
  21. package/dist/exec.js +4 -47
  22. package/dist/handlers/gen-digital-human.js +33 -264
  23. package/dist/handlers/gen-image.js +9 -113
  24. package/dist/handlers/gen-post.js +15 -192
  25. package/dist/handlers/gen-video.js +7 -185
  26. package/dist/handlers/gen-voice.js +19 -133
  27. package/dist/handlers/index.js +1 -20
  28. package/dist/handlers/shared.js +1 -66
  29. package/dist/handlers/template-init.js +1 -21
  30. package/dist/http.js +3 -155
  31. package/dist/index.js +1 -8
  32. package/dist/install.js +40 -448
  33. package/dist/manifest.json +44 -3
  34. package/dist/progress.js +2 -15
  35. package/dist/project/commands.js +34 -156
  36. package/dist/project/host.js +1 -151
  37. package/dist/project/resolve.js +1 -65
  38. package/dist/project/store.js +2 -97
  39. package/dist/project/take.js +1 -106
  40. package/dist/project/trace.js +5 -278
  41. package/dist/registry.js +3 -70
  42. package/dist/runner.js +7 -225
  43. package/dist/skill-schema.js +1 -194
  44. package/dist/template/args.js +1 -69
  45. package/dist/template/check.js +2 -30
  46. package/dist/template/commands.js +34 -163
  47. package/dist/template/constants.js +1 -46
  48. package/dist/template/init.js +5 -133
  49. package/dist/template/list.js +1 -46
  50. package/dist/template/preview.js +7 -318
  51. package/dist/template/proc.js +1 -43
  52. package/dist/template/project.js +6 -161
  53. package/dist/template/publish.js +5 -185
  54. package/dist/template/render-client.js +2 -79
  55. package/dist/template/upgrade.js +4 -123
  56. package/dist/text.js +1 -15
  57. package/package.json +2 -35
  58. package/skills/doc-parse/scripts/extract_via_render.py +32 -32
  59. package/skills/export-jianying/scripts/gen_jianying_draft.py +188 -188
  60. package/skills/gen-script/SKILL.md +23 -1
  61. package/skills/gen-script/scripts/gen_script.py +644 -551
  62. package/skills/gen-script/skill.json +12 -0
  63. package/skills/gen-script/version.json +1 -1
  64. package/skills/prepare-video-assets/scripts/prepare_video_assets.py +21 -21
  65. package/skills/render-video/scripts/_chrome_vendor.py +17 -17
  66. package/skills/render-video/scripts/_digital_human_segments.py +34 -34
  67. package/skills/render-video/scripts/_video_probe.py +33 -33
  68. package/skills/render-video/scripts/_vod_polling.py +13 -13
  69. package/skills/render-video/scripts/remote_renderer_client.py +42 -42
  70. package/skills/render-video/scripts/render_video.py +639 -639
  71. package/skills/render-video/scripts/required_props.py +37 -37
  72. package/skills/render-video/scripts/upload_video.py +35 -35
  73. package/skills/render-video/version.json +1 -1
  74. package/skills/template-registry/SKILL.md +16 -0
  75. package/skills/template-registry/scripts/countdown_spec.py +92 -92
  76. package/skills/template-registry/scripts/doc_extract.py +40 -40
  77. package/skills/template-registry/scripts/list_templates.py +303 -191
  78. package/skills/template-registry/scripts/log_diagnostics.py +1 -1
  79. package/skills/template-registry/scripts/match_template.py +87 -87
  80. package/skills/template-registry/scripts/payload_hydrator.py +89 -89
  81. package/skills/template-registry/scripts/registry_loader.py +159 -159
  82. package/skills/template-registry/scripts/render_job_client.py +75 -75
  83. package/skills/template-registry/scripts/showcase_loader.py +44 -44
  84. package/skills/template-registry/skill.json +4 -1
  85. package/skills/template-registry/version.json +1 -1
  86. package/skills/template-registry/video_dsl/runtime/__init__.py +8 -8
  87. package/skills/template-registry/video_dsl/runtime/dsl_validator.py +329 -329
  88. package/skills/template-registry/video_dsl/runtime/narration_budget.py +36 -36
  89. package/skills/template-registry/video_dsl/runtime/payload_contract.py +7 -7
  90. package/skills/template-registry/video_dsl/runtime/prompt_enhancer.py +98 -98
  91. package/skills/template-registry/video_dsl/runtime/timeline_compiler.py +127 -127
  92. package/skills/template-registry/video_dsl/schema/video-dsl-v1alpha1.json +14 -1
  93. package/skills/video-parser/scripts/analyze_video.py +173 -173
  94. package/skills/video-parser/scripts/deconstruct_video.py +91 -91
  95. package/skills/video-parser/scripts/parse_via_render.py +21 -21
  96. package/skills/web-screenshot/scripts/_media_screenshot/__init__.py +4 -4
  97. package/skills/web-screenshot/scripts/_media_screenshot/bootstrap.py +7 -7
  98. package/skills/web-screenshot/scripts/_media_screenshot/browser.py +4 -4
  99. package/skills/web-screenshot/scripts/_media_screenshot/cli_args.py +4 -4
  100. package/skills/web-screenshot/scripts/_media_screenshot/js_loader.py +2 -2
  101. package/skills/web-screenshot/scripts/_media_screenshot/overlay.py +1 -1
  102. package/skills/web-screenshot/scripts/_media_screenshot/reader.py +40 -40
  103. package/skills/web-screenshot/scripts/_media_screenshot/recording.py +11 -11
  104. package/skills/web-screenshot/scripts/_media_screenshot/scenes/__init__.py +8 -8
  105. package/skills/web-screenshot/scripts/_media_screenshot/scenes/arrow.py +2 -2
  106. package/skills/web-screenshot/scripts/_media_screenshot/scenes/caption.py +1 -1
  107. package/skills/web-screenshot/scripts/_media_screenshot/scenes/click.py +1 -1
  108. package/skills/web-screenshot/scripts/_media_screenshot/scenes/code_lines.py +5 -5
  109. package/skills/web-screenshot/scripts/_media_screenshot/scenes/focus.py +1 -1
  110. package/skills/web-screenshot/scripts/_media_screenshot/scenes/highlight.py +3 -3
  111. package/skills/web-screenshot/scripts/_media_screenshot/scenes/hover.py +1 -1
  112. package/skills/web-screenshot/scripts/_media_screenshot/scenes/redact.py +1 -1
  113. package/skills/web-screenshot/scripts/_media_screenshot/scenes/scroll.py +1 -1
  114. package/skills/web-screenshot/scripts/_media_screenshot/scenes/sequence.py +1 -1
  115. package/skills/web-screenshot/scripts/_media_screenshot/scenes/title_card.py +1 -1
  116. package/skills/web-screenshot/scripts/_media_screenshot/scenes/type.py +1 -1
  117. package/skills/web-screenshot/scripts/_media_screenshot/scenes/wait.py +1 -1
  118. package/skills/web-screenshot/scripts/_media_screenshot/scenes/zoom.py +2 -2
  119. package/skills/web-screenshot/scripts/_media_screenshot/screenshot.py +15 -15
  120. package/skills/web-screenshot/scripts/_media_screenshot/storyboard.py +2 -2
  121. package/skills/web-screenshot/scripts/_media_screenshot/template.py +11 -11
  122. package/skills/web-screenshot/scripts/_media_screenshot/trim.py +10 -10
  123. package/skills/web-screenshot/scripts/_media_screenshot/urlguard.py +22 -22
  124. package/skills/web-screenshot/scripts/_media_upload.py +33 -33
  125. package/skills/web-screenshot/scripts/read_page.py +15 -15
  126. package/skills/web-screenshot/scripts/record.py +26 -26
  127. package/skills/web-screenshot/scripts/screenshot.py +5 -5
  128. package/dist/argv.d.ts +0 -15
  129. package/dist/auth/auth-lock.d.ts +0 -26
  130. package/dist/auth/auto-login.d.ts +0 -20
  131. package/dist/auth/commands.d.ts +0 -8
  132. package/dist/auth/credential-store.d.ts +0 -60
  133. package/dist/auth/device-flow-runner.d.ts +0 -58
  134. package/dist/auth/device-flow.d.ts +0 -52
  135. package/dist/auth/ensure.d.ts +0 -55
  136. package/dist/auth/environment.d.ts +0 -38
  137. package/dist/auth/pending-store.d.ts +0 -33
  138. package/dist/auth/resolve.d.ts +0 -30
  139. package/dist/billing.d.ts +0 -44
  140. package/dist/capabilities.d.ts +0 -63
  141. package/dist/cli.d.ts +0 -13
  142. package/dist/diagnostics.d.ts +0 -24
  143. package/dist/doctor.d.ts +0 -22
  144. package/dist/errors.d.ts +0 -29
  145. package/dist/exec.d.ts +0 -18
  146. package/dist/handlers/gen-digital-human.d.ts +0 -17
  147. package/dist/handlers/gen-image.d.ts +0 -20
  148. package/dist/handlers/gen-post.d.ts +0 -22
  149. package/dist/handlers/gen-video.d.ts +0 -19
  150. package/dist/handlers/gen-voice.d.ts +0 -26
  151. package/dist/handlers/index.d.ts +0 -20
  152. package/dist/handlers/shared.d.ts +0 -26
  153. package/dist/handlers/template-init.d.ts +0 -10
  154. package/dist/http.d.ts +0 -72
  155. package/dist/index.d.ts +0 -9
  156. package/dist/install.d.ts +0 -71
  157. package/dist/progress.d.ts +0 -23
  158. package/dist/project/commands.d.ts +0 -19
  159. package/dist/project/host.d.ts +0 -91
  160. package/dist/project/resolve.d.ts +0 -43
  161. package/dist/project/store.d.ts +0 -31
  162. package/dist/project/take.d.ts +0 -50
  163. package/dist/project/trace.d.ts +0 -98
  164. package/dist/registry.d.ts +0 -30
  165. package/dist/runner.d.ts +0 -39
  166. package/dist/skill-schema.d.ts +0 -138
  167. package/dist/template/args.d.ts +0 -25
  168. package/dist/template/check.d.ts +0 -23
  169. package/dist/template/commands.d.ts +0 -25
  170. package/dist/template/constants.d.ts +0 -39
  171. package/dist/template/init.d.ts +0 -28
  172. package/dist/template/list.d.ts +0 -23
  173. package/dist/template/preview.d.ts +0 -60
  174. package/dist/template/proc.d.ts +0 -26
  175. package/dist/template/project.d.ts +0 -47
  176. package/dist/template/publish.d.ts +0 -43
  177. package/dist/template/render-client.d.ts +0 -39
  178. package/dist/template/upgrade.d.ts +0 -44
  179. package/dist/text.d.ts +0 -8
@@ -1,161 +1,6 @@
1
- /**
2
- * Starter-project discovery, template identity (K4) and the shared output helper.
3
- */
4
- import { randomBytes } from 'node:crypto';
5
- import { existsSync, readFileSync, writeFileSync } from 'node:fs';
6
- import path from 'node:path';
7
- import { EXIT, SkillError } from '../errors.js';
8
- import { CHECK_ENTRY, IDENTITY_FILE, RULES_VERSION_FILE, STARTER_PACKAGE, STARTER_VERSION } from './constants.js';
9
- import { usageError } from './args.js';
10
- // ── project root ─────────────────────────────────────────────────────────────
11
- /** Walk up from `start` to the directory holding `.remixmate/check/index.mjs`. */
12
- export function findProjectRoot(start = process.cwd()) {
13
- let dir = path.resolve(start);
14
- for (;;) {
15
- if (existsSync(path.join(dir, CHECK_ENTRY)))
16
- return dir;
17
- const parent = path.dirname(dir);
18
- if (parent === dir)
19
- return null;
20
- dir = parent;
21
- }
22
- }
23
- export function requireProjectRoot(start) {
24
- const root = findProjectRoot(start);
25
- if (!root) {
26
- throw new SkillError(`❌ 当前目录不在模板工程内(找不到 ${CHECK_ENTRY})。\n` +
27
- ' 先运行 remixmate template init <dir>,再进入该目录执行。', EXIT.USAGE);
28
- }
29
- return root;
30
- }
31
- /** A template name is a single directory under templates/ — never a path. */
32
- export function validateTemplateName(name) {
33
- if (!name)
34
- throw usageError('缺少模板名:remixmate template <command> <name>');
35
- if (name === '.' || name === '..' || /[\\/]/.test(name) || name.startsWith('-')) {
36
- throw usageError(`模板名不合法: ${name}(应为 templates/ 下的目录名)`);
37
- }
38
- return name;
39
- }
40
- export function requireTemplateDir(root, name) {
41
- const dir = path.join(root, 'templates', name);
42
- if (!existsSync(path.join(dir, 'template.json'))) {
43
- throw new SkillError(`❌ 找不到 templates/${name}/template.json`, EXIT.USAGE);
44
- }
45
- return dir;
46
- }
47
- export function readJsonFile(file) {
48
- try {
49
- return JSON.parse(readFileSync(file, 'utf8'));
50
- }
51
- catch (err) {
52
- throw new SkillError(`❌ ${path.basename(file)} 解析失败: ${err.message}`);
53
- }
54
- }
55
- /** kebab-case slug of a directory name; falls back to `template` when nothing survives. */
56
- export function kebabSlug(input) {
57
- const slug = input
58
- .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
59
- .toLowerCase()
60
- .replace(/[^a-z0-9]+/g, '-')
61
- .replace(/^-+|-+$/g, '');
62
- return slug || 'template';
63
- }
64
- /** First-publish key: `my-<slug>-<6 hex>`. */
65
- export function generateTemplateKey(dirName, hex = randomBytes(3).toString('hex')) {
66
- return `my-${kebabSlug(dirName)}-${hex}`;
67
- }
68
- export function readIdentity(templateDir) {
69
- const file = path.join(templateDir, IDENTITY_FILE);
70
- if (!existsSync(file))
71
- return null;
72
- const raw = readJsonFile(file);
73
- if (typeof raw.templateKey !== 'string' || !raw.templateKey) {
74
- throw new SkillError(`❌ ${IDENTITY_FILE} 缺少 templateKey,请修复或删除该文件后重试`);
75
- }
76
- return { templateKey: raw.templateKey, version: typeof raw.version === 'number' ? raw.version : 0 };
77
- }
78
- /** Stable key: the identity file wins, so renaming the directory never forks a template. */
79
- export function resolveTemplateKey(templateDir) {
80
- const id = readIdentity(templateDir);
81
- if (id)
82
- return { templateKey: id.templateKey, existing: true };
83
- return { templateKey: generateTemplateKey(path.basename(templateDir)), existing: false };
84
- }
85
- export function writeIdentity(templateDir, identity) {
86
- writeFileSync(path.join(templateDir, IDENTITY_FILE), JSON.stringify(identity, null, 2) + '\n', 'utf8');
87
- }
88
- // ── rules version (template upgrade) ─────────────────────────────────────────
89
- /** Numeric dotted compare (prerelease tags ignored): <0 / 0 / >0. */
90
- export function compareVersions(a, b) {
91
- const parse = (v) => v.replace(/^v/, '').split(/[-+]/)[0].split('.').map((n) => Number(n) || 0);
92
- const pa = parse(a);
93
- const pb = parse(b);
94
- for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
95
- const d = (pa[i] ?? 0) - (pb[i] ?? 0);
96
- if (d !== 0)
97
- return d;
98
- }
99
- return 0;
100
- }
101
- /**
102
- * The starter version whose rules this project's `.remixmate/` holds.
103
- * `.remixmate/version.json` wins; 0.1.0 projects fall back to package.json
104
- * (copied verbatim from the starter at init). null = unknown.
105
- */
106
- export function readRulesVersion(root) {
107
- const marker = path.join(root, RULES_VERSION_FILE);
108
- if (existsSync(marker)) {
109
- try {
110
- const v = JSON.parse(readFileSync(marker, 'utf8')).starterVersion;
111
- if (typeof v === 'string' && v)
112
- return v;
113
- }
114
- catch {
115
- /* fall through */
116
- }
117
- }
118
- const pkgFile = path.join(root, 'package.json');
119
- if (existsSync(pkgFile)) {
120
- try {
121
- const pkg = JSON.parse(readFileSync(pkgFile, 'utf8'));
122
- if (pkg.name === STARTER_PACKAGE && typeof pkg.version === 'string')
123
- return pkg.version;
124
- }
125
- catch {
126
- /* unknown */
127
- }
128
- }
129
- return null;
130
- }
131
- /** Hint line when the project's rules are older than what this cli ships with, else null. */
132
- export function staleRulesHint(root) {
133
- const have = readRulesVersion(root);
134
- if (have && compareVersions(have, STARTER_VERSION) >= 0)
135
- return null;
136
- return (`⚠ 本地校验规则来自 starter ${have ?? '(未知版本)'},当前 cli 对应 ${STARTER_VERSION}。` +
137
- '平台规则可能已更新,本地通过不代表发布能过。运行 remixmate template upgrade 更新规则(不会改动 templates/)。');
138
- }
139
- // ── output ───────────────────────────────────────────────────────────────────
140
- /**
141
- * Human text goes to stdout by default. With --json it moves to stderr so the
142
- * last stdout line is the JSON result (progress lines still go to stdout —
143
- * hosts parse them by their `__progress__` marker).
144
- */
145
- export class Output {
146
- json;
147
- constructor(json) {
148
- this.json = json;
149
- }
150
- info(line = '') {
151
- ;
152
- (this.json ? process.stderr : process.stdout).write(line + '\n');
153
- }
154
- warn(line) {
155
- process.stderr.write(line + '\n');
156
- }
157
- result(obj) {
158
- if (this.json)
159
- process.stdout.write(JSON.stringify(obj) + '\n');
160
- }
161
- }
1
+ var x=Object.defineProperty;var n=(e,t)=>x(e,"name",{value:t,configurable:!0});import{randomBytes as h}from"node:crypto";import{existsSync as a,readFileSync as u,writeFileSync as w}from"node:fs";import o from"node:path";import{EXIT as m,SkillError as l}from"../errors.js";import{CHECK_ENTRY as y,IDENTITY_FILE as c,RULES_VERSION_FILE as S,STARTER_PACKAGE as j,STARTER_VERSION as g}from"./constants.js";import{usageError as d}from"./args.js";function E(e=process.cwd()){let t=o.resolve(e);for(;;){if(a(o.join(t,y)))return t;const r=o.dirname(t);if(r===t)return null;t=r}}n(E,"findProjectRoot");function _(e){const t=E(e);if(!t)throw new l(`❌ 当前目录不在模板工程内(找不到 ${y})。
2
+ 先运行 remixmate template init <dir>,再进入该目录执行。`,m.USAGE);return t}n(_,"requireProjectRoot");function V(e){if(!e)throw d("缺少模板名:remixmate template <command> <name>");if(e==="."||e===".."||/[\\/]/.test(e)||e.startsWith("-"))throw d(`模板名不合法: ${e}(应为 templates/ 下的目录名)`);return e}n(V,"validateTemplateName");function C(e,t){const r=o.join(e,"templates",t);if(!a(o.join(r,"template.json")))throw new l(`❌ 找不到 templates/${t}/template.json`,m.USAGE);return r}n(C,"requireTemplateDir");function $(e){try{return JSON.parse(u(e,"utf8"))}catch(t){throw new l(`❌ ${o.basename(e)} 解析失败: ${t.message}`)}}n($,"readJsonFile");function K(e){return e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"template"}n(K,"kebabSlug");function R(e,t=h(3).toString("hex")){return`my-${K(e)}-${t}`}n(R,"generateTemplateKey");function T(e){const t=o.join(e,c);if(!a(t))return null;const r=$(t);if(typeof r.templateKey!="string"||!r.templateKey)throw new l(`❌ ${c} 缺少 templateKey,请修复或删除该文件后重试`);return{templateKey:r.templateKey,version:typeof r.version=="number"?r.version:0}}n(T,"readIdentity");function L(e){const t=T(e);return t?{templateKey:t.templateKey,existing:!0}:{templateKey:R(o.basename(e)),existing:!1}}n(L,"resolveTemplateKey");function G(e,t){w(o.join(e,c),JSON.stringify(t,null,2)+`
3
+ `,"utf8")}n(G,"writeIdentity");function v(e,t){const r=n(s=>s.replace(/^v/,"").split(/[-+]/)[0].split(".").map(p=>Number(p)||0),"parse"),i=r(e),f=r(t);for(let s=0;s<Math.max(i.length,f.length);s++){const p=(i[s]??0)-(f[s]??0);if(p!==0)return p}return 0}n(v,"compareVersions");function N(e){const t=o.join(e,S);if(a(t))try{const i=JSON.parse(u(t,"utf8")).starterVersion;if(typeof i=="string"&&i)return i}catch{}const r=o.join(e,"package.json");if(a(r))try{const i=JSON.parse(u(r,"utf8"));if(i.name===j&&typeof i.version=="string")return i.version}catch{}return null}n(N,"readRulesVersion");function P(e){const t=N(e);return t&&v(t,g)>=0?null:`⚠ 本地校验规则来自 starter ${t??"(未知版本)"},当前 cli 对应 ${g}。平台规则可能已更新,本地通过不代表发布能过。运行 remixmate template upgrade 更新规则(不会改动 templates/)。`}n(P,"staleRulesHint");class U{static{n(this,"Output")}json;constructor(t){this.json=t}info(t=""){(this.json?process.stderr:process.stdout).write(t+`
4
+ `)}warn(t){process.stderr.write(t+`
5
+ `)}result(t){this.json&&process.stdout.write(JSON.stringify(t)+`
6
+ `)}}export{U as Output,v as compareVersions,E as findProjectRoot,R as generateTemplateKey,K as kebabSlug,T as readIdentity,$ as readJsonFile,N as readRulesVersion,_ as requireProjectRoot,C as requireTemplateDir,L as resolveTemplateKey,P as staleRulesHint,V as validateTemplateName,G as writeIdentity};
@@ -1,185 +1,5 @@
1
- /**
2
- * `remixmate template publish <name> [--name <displayName>] [--dry-run]`
3
- *
4
- * Ported from template-library `scripts/publish-private.mjs` — same ab-api
5
- * contract (prepareSave → presignUpload → PUT → save, isBuiltin=false), same
6
- * tar excludes. Differences, all from the spec:
7
- * - the key is stable per template directory (K4 `.remixmate.json`), so a
8
- * re-publish updates instead of forking, and renaming the dir is harmless;
9
- * - the starter's own `check` runs first (requirement 6.2);
10
- * - the project files stay read-only except for `.remixmate.json` — server
11
- * normalization only touches a staging copy.
12
- *
13
- * Never printed: the token and the presigned upload URL.
14
- */
15
- import { cpSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
16
- import os from 'node:os';
17
- import path from 'node:path';
18
- import { EXIT, SkillError } from '../errors.js';
19
- import { mmPost, resolveHttpContext } from '../http.js';
20
- import { emitProgress } from '../progress.js';
21
- import { IDENTITY_FILE, TAR_EXCLUDES } from './constants.js';
22
- import { runCheck } from './check.js';
23
- import { Output, readJsonFile, requireProjectRoot, requireTemplateDir, resolveTemplateKey, validateTemplateName, writeIdentity, } from './project.js';
24
- import { runProcess } from './proc.js';
25
- import { fetchCoreSurface, resolveRenderBaseUrl } from './render-client.js';
26
- /** tar argv with the shared exclude list — exported for tests. */
27
- export function tarArgs(srcDir, outFile) {
28
- const args = ['-czf', outFile];
29
- for (const ex of TAR_EXCLUDES)
30
- args.push(`--exclude=${ex}`);
31
- args.push('-C', srcDir, '.');
32
- return args;
33
- }
34
- /**
35
- * Core preflight: the template's declared coreExports must all exist on the
36
- * target ab-render (the tar never carries core). Returns a block reason or null.
37
- * "Couldn't check" is a block, not a pass.
38
- */
39
- export function evaluateCoreSurface(manifest, surface, warn = () => { }) {
40
- if (!surface)
41
- return 'ab-render /health 未返回 coreSurface —— 该实例版本过旧,无法校验 core 契约';
42
- if (!surface.available)
43
- return `ab-render 无法读取 template-core:${surface.error ?? '未知原因'}`;
44
- if (!Array.isArray(manifest.coreExports)) {
45
- return 'template.json 缺少 coreExports —— 先运行 remixmate template check 生成';
46
- }
47
- const have = new Set(surface.exports ?? []);
48
- const missing = manifest.coreExports.filter((n) => !have.has(n));
49
- const declared = typeof manifest.coreVersion === 'string' ? manifest.coreVersion : null;
50
- // `versions` is a K5 addition; older instances only expose packageVersion.
51
- const offered = Array.isArray(surface.versions) && surface.versions.length > 0
52
- ? surface.versions
53
- : surface.packageVersion ? [surface.packageVersion] : [];
54
- if (missing.length > 0) {
55
- return (`目标 ab-render 的 template-core 缺少导出:${missing.join(', ')}。` +
56
- `模板声明需要 core@${declared ?? '(未声明)'},平台当前提供 ${offered.length ? offered.map((v) => `core@${v}`).join(', ') : '(未知版本)'}。` +
57
- '请改用平台已有的导出,或等平台升级 core 后再发布。');
58
- }
59
- if (declared && offered.length > 0 && !offered.includes(declared)) {
60
- warn(` ⚠ 模板声明 core@${declared},平台提供 ${offered.join(', ')} —— 导出齐全可以渲染,但效果未必与本地完全一致`);
61
- }
62
- return null;
63
- }
64
- function tarball(srcDir, outFile) {
65
- return runProcess('tar', tarArgs(srcDir, outFile), { stdio: 'pipe' }).then((r) => {
66
- if (r.code !== 0)
67
- throw new SkillError(`❌ 打包失败(tar 退出码 ${r.code}): ${r.stderr.trim().slice(0, 300)}`);
68
- });
69
- }
70
- async function putToOss(uploadUrl, bytes) {
71
- let res;
72
- try {
73
- // Content-Type must equal what ab-api signed, or OSS answers SignatureDoesNotMatch.
74
- res = await fetch(uploadUrl, { method: 'PUT', headers: { 'Content-Type': 'application/gzip' }, body: new Uint8Array(bytes) });
75
- }
76
- catch (err) {
77
- throw new SkillError(`❌ 上传模板包失败(网络错误): ${err.message}`, EXIT.BACKEND_UNREACHABLE);
78
- }
79
- if (res.status >= 300) {
80
- // Body/URL intentionally omitted: the URL is presigned.
81
- throw new SkillError(`❌ 上传模板包失败(OSS HTTP ${res.status})`, res.status >= 500 ? EXIT.BACKEND_UNREACHABLE : EXIT.ERROR);
82
- }
83
- }
84
- export async function runPublish(opts) {
85
- const out = new Output(Boolean(opts.json));
86
- const name = validateTemplateName(opts.name);
87
- const root = requireProjectRoot(opts.cwd);
88
- const tplDir = requireTemplateDir(root, name);
89
- emitProgress({ phase: 'template-publish:check', progress: 0.05 });
90
- const checkCode = await runCheck({ name, cwd: root, stdio: opts.json ? 'stderr' : 'inherit' });
91
- if (checkCode !== 0) {
92
- throw new SkillError(`❌ 本地校验未通过(check 退出码 ${checkCode}),已中止发布。`, checkCode === 2 ? EXIT.USAGE : EXIT.ERROR);
93
- }
94
- // Re-read after check: it refreshes coreVersion / coreExports.
95
- const templateJson = readJsonFile(path.join(tplDir, 'template.json'));
96
- const { templateKey, existing } = resolveTemplateKey(tplDir);
97
- const displayName = opts.displayName ?? (typeof templateJson.name === 'string' && templateJson.name ? templateJson.name : name);
98
- const coreVersion = typeof templateJson.coreVersion === 'string' ? templateJson.coreVersion : '';
99
- // Credentials before any network call, so a missing login is exit 4 rather
100
- // than a confusing preflight failure. Dry-run needs none.
101
- let ctx = null;
102
- if (!opts.dryRun) {
103
- ctx = await resolveHttpContext('template-publish', { privateToken: opts.token, apiBaseUrl: opts.apiBaseUrl });
104
- }
105
- const renderBase = resolveRenderBaseUrl();
106
- out.info(`模板 ${name}`);
107
- out.info(`templateKey ${templateKey}${existing ? '' : '(首次发布,新生成)'}`);
108
- out.info(`名称 ${displayName}`);
109
- out.info(`coreVersion ${coreVersion || '(未声明)'}`);
110
- emitProgress({ phase: 'template-publish:preflight', progress: 0.15 });
111
- const surface = await fetchCoreSurface(renderBase);
112
- const blocked = evaluateCoreSurface(templateJson, surface, (m) => out.warn(m));
113
- if (blocked)
114
- throw new SkillError(`❌ core 预检未通过:${blocked}`);
115
- out.info(`core 预检 通过(${surface?.exports.length ?? 0} 个导出)`);
116
- if (opts.dryRun) {
117
- out.info('\n[dry-run] 将依次调用 prepareSave → presignUpload → 上传 → save(isBuiltin=false)');
118
- out.info(`[dry-run] 打包排除:${TAR_EXCLUDES.join(', ')}(保留 dsl-example.json)`);
119
- const result = { ok: true, name, templateKey, version: null, dryRun: true, created: !existing };
120
- out.result({ ...result });
121
- return result;
122
- }
123
- const http = ctx;
124
- let staging;
125
- try {
126
- emitProgress({ phase: 'template-publish:prepare', progress: 0.3 });
127
- const prepared = await mmPost(http, '/remotionTemplate/prepareSave', {
128
- templateJson,
129
- name: displayName,
130
- templateKey,
131
- ...(coreVersion ? { coreVersion } : {}),
132
- });
133
- const resolvedKey = prepared?.templateKey || templateKey;
134
- // ab-api save 要求制品里 template.json 的 templateId === 登记的 templateKey
135
- // (templateregistry.ValidateManifestInvariants)。工程里的 templateId 是本地目录级标识
136
- // (Studio 注册用),平台身份是 templateKey —— 只改暂存副本,工程文件不动。
137
- const normalized = { ...(prepared?.templateJson || templateJson), templateId: resolvedKey };
138
- // Staging copy: server normalization never touches the project files.
139
- staging = mkdtempSync(path.join(os.tmpdir(), 'remixmate-publish-'));
140
- const pkgDir = path.join(staging, 'pkg');
141
- cpSync(tplDir, pkgDir, { recursive: true });
142
- rmSync(path.join(pkgDir, IDENTITY_FILE), { force: true });
143
- writeFileSync(path.join(pkgDir, 'template.json'), JSON.stringify(normalized, null, 2) + '\n', 'utf8');
144
- if (JSON.stringify(normalized) !== JSON.stringify(templateJson)) {
145
- out.warn(' ⚠ 服务端规范化后的 template.json 与本地不同;本次上传使用规范化版本,本地文件未改动。');
146
- }
147
- emitProgress({ phase: 'template-publish:pack', progress: 0.45 });
148
- const tarPath = path.join(staging, `${resolvedKey}.tar.gz`);
149
- await tarball(pkgDir, tarPath);
150
- const bytes = readFileSync(tarPath);
151
- out.info(`打包 ${(bytes.length / 1024).toFixed(0)} KB`);
152
- emitProgress({ phase: 'template-publish:presign', progress: 0.55 });
153
- const grant = await mmPost(http, '/remotionTemplate/presignUpload', { templateKey: resolvedKey });
154
- if (!grant?.uploadUrl)
155
- throw new SkillError('❌ presignUpload 未返回 uploadUrl');
156
- emitProgress({ phase: 'template-publish:upload', progress: 0.7 });
157
- await putToOss(String(grant.uploadUrl), bytes);
158
- emitProgress({ phase: 'template-publish:save', progress: 0.85 });
159
- await mmPost(http, '/remotionTemplate/save', {
160
- templateKey: resolvedKey,
161
- name: displayName,
162
- isBuiltin: false,
163
- sourceOssKey: grant.sourceOssKey,
164
- version: grant.version,
165
- templateJson: normalized,
166
- ...(coreVersion ? { coreVersion } : {}),
167
- });
168
- const version = typeof grant.version === 'number' ? grant.version : Number(grant.version) || 0;
169
- writeIdentity(tplDir, { templateKey: resolvedKey, version });
170
- emitProgress({ phase: 'template-publish:done', progress: 1, templateKey: resolvedKey });
171
- out.info(`\n✅ 已发布为你的私有模板`);
172
- out.info(` templateKey ${resolvedKey}`);
173
- out.info(` version ${version}`);
174
- out.info(` (已写入 templates/${name}/${IDENTITY_FILE},再次发布会更新同一个模板)`);
175
- out.info('\n出片:');
176
- out.info(` remixmate render-video --template-id ${resolvedKey} …`);
177
- const result = { ok: true, name, templateKey: resolvedKey, version, dryRun: false, created: !existing };
178
- out.result({ ...result });
179
- return result;
180
- }
181
- finally {
182
- if (staging)
183
- rmSync(staging, { recursive: true, force: true });
184
- }
185
- }
1
+ var O=Object.defineProperty;var u=(r,e)=>O(r,"name",{value:e,configurable:!0});import{cpSync as P,mkdtempSync as V,readFileSync as D,rmSync as A,writeFileSync as J}from"node:fs";import I from"node:os";import f from"node:path";import{EXIT as g,SkillError as d}from"../errors.js";import{mmPost as x,resolveHttpContext as F}from"../http.js";import{emitProgress as p}from"../progress.js";import{IDENTITY_FILE as B,TAR_EXCLUDES as C}from"./constants.js";import{runCheck as z}from"./check.js";import{Output as H,readJsonFile as L,requireProjectRoot as _,requireTemplateDir as q,resolveTemplateKey as X,validateTemplateName as G,writeIdentity as Y}from"./project.js";import{runProcess as M}from"./proc.js";import{fetchCoreSurface as Q,resolveRenderBaseUrl as W}from"./render-client.js";function Z(r,e){const t=["-czf",e];for(const i of C)t.push(`--exclude=${i}`);return t.push("-C",r,"."),t}u(Z,"tarArgs");function ee(r,e,t=()=>{}){if(!e)return"ab-render /health 未返回 coreSurface —— 该实例版本过旧,无法校验 core 契约";if(!e.available)return`ab-render 无法读取 template-core:${e.error??"未知原因"}`;if(!Array.isArray(r.coreExports))return"template.json 缺少 coreExports —— 先运行 remixmate template check 生成";const i=new Set(e.exports??[]),l=r.coreExports.filter(a=>!i.has(a)),s=typeof r.coreVersion=="string"?r.coreVersion:null,o=Array.isArray(e.versions)&&e.versions.length>0?e.versions:e.packageVersion?[e.packageVersion]:[];return l.length>0?`目标 ab-render 的 template-core 缺少导出:${l.join(", ")}。模板声明需要 core@${s??"(未声明)"},平台当前提供 ${o.length?o.map(a=>`core@${a}`).join(", "):"(未知版本)"}。请改用平台已有的导出,或等平台升级 core 后再发布。`:(s&&o.length>0&&!o.includes(s)&&t(` ⚠ 模板声明 core@${s},平台提供 ${o.join(", ")} —— 导出齐全可以渲染,但效果未必与本地完全一致`),null)}u(ee,"evaluateCoreSurface");function re(r,e){return M("tar",Z(r,e),{stdio:"pipe"}).then(t=>{if(t.code!==0)throw new d(`❌ 打包失败(tar 退出码 ${t.code}): ${t.stderr.trim().slice(0,300)}`)})}u(re,"tarball");async function te(r,e){let t;try{t=await fetch(r,{method:"PUT",headers:{"Content-Type":"application/gzip"},body:new Uint8Array(e)})}catch(i){throw new d(`❌ 上传模板包失败(网络错误): ${i.message}`,g.BACKEND_UNREACHABLE)}if(t.status>=300)throw new d(`❌ 上传模板包失败(OSS HTTP ${t.status})`,t.status>=500?g.BACKEND_UNREACHABLE:g.ERROR)}u(te,"putToOss");async function he(r){const e=new H(!!r.json),t=G(r.name),i=_(r.cwd),l=q(i,t);p({phase:"template-publish:check",progress:.05});const s=await z({name:t,cwd:i,stdio:r.json?"stderr":"inherit"});if(s!==0)throw new d(`❌ 本地校验未通过(check 退出码 ${s}),已中止发布。`,s===2?g.USAGE:g.ERROR);const o=L(f.join(l,"template.json")),{templateKey:a,existing:v}=X(l),$=r.displayName??(typeof o.name=="string"&&o.name?o.name:t),h=typeof o.coreVersion=="string"?o.coreVersion:"";let E=null;r.dryRun||(E=await F("template-publish",{privateToken:r.token,apiBaseUrl:r.apiBaseUrl}));const N=W();e.info(`模板 ${t}`),e.info(`templateKey ${a}${v?"":"(首次发布,新生成)"}`),e.info(`名称 ${$}`),e.info(`coreVersion ${h||"(未声明)"}`),p({phase:"template-publish:preflight",progress:.15});const k=await Q(N),K=ee(o,k,c=>e.warn(c));if(K)throw new d(`❌ core 预检未通过:${K}`);if(e.info(`core 预检 通过(${k?.exports.length??0} 个导出)`),r.dryRun){e.info(`
2
+ [dry-run] 将依次调用 prepareSave → presignUpload → 上传 → save(isBuiltin=false)`),e.info(`[dry-run] 打包排除:${C.join(", ")}(保留 dsl-example.json)`);const c={ok:!0,name:t,templateKey:a,version:null,dryRun:!0,created:!v};return e.result({...c}),c}const b=E;let y;try{p({phase:"template-publish:prepare",progress:.3});const c=await x(b,"/remotionTemplate/prepareSave",{templateJson:o,name:$,templateKey:a,...h?{coreVersion:h}:{}}),n=c?.templateKey||a,S={...c?.templateJson||o,templateId:n};y=V(f.join(I.tmpdir(),"remixmate-publish-"));const w=f.join(y,"pkg");P(l,w,{recursive:!0}),A(f.join(w,B),{force:!0}),J(f.join(w,"template.json"),JSON.stringify(S,null,2)+`
3
+ `,"utf8"),JSON.stringify(S)!==JSON.stringify(o)&&e.warn(" ⚠ 服务端规范化后的 template.json 与本地不同;本次上传使用规范化版本,本地文件未改动。"),p({phase:"template-publish:pack",progress:.45});const T=f.join(y,`${n}.tar.gz`);await re(w,T);const R=D(T);e.info(`打包 ${(R.length/1024).toFixed(0)} KB`),p({phase:"template-publish:presign",progress:.55});const m=await x(b,"/remotionTemplate/presignUpload",{templateKey:n});if(!m?.uploadUrl)throw new d("❌ presignUpload 未返回 uploadUrl");p({phase:"template-publish:upload",progress:.7}),await te(String(m.uploadUrl),R),p({phase:"template-publish:save",progress:.85}),await x(b,"/remotionTemplate/save",{templateKey:n,name:$,isBuiltin:!1,sourceOssKey:m.sourceOssKey,version:m.version,templateJson:S,...h?{coreVersion:h}:{}});const j=typeof m.version=="number"?m.version:Number(m.version)||0;Y(l,{templateKey:n,version:j}),p({phase:"template-publish:done",progress:1,templateKey:n}),e.info(`
4
+ ✅ 已发布为你的私有模板`),e.info(` templateKey ${n}`),e.info(` version ${j}`),e.info(` (已写入 templates/${t}/${B},再次发布会更新同一个模板)`),e.info(`
5
+ 出片:`),e.info(` remixmate render-video --template-id ${n} …`);const U={ok:!0,name:t,templateKey:n,version:j,dryRun:!1,created:!v};return e.result({...U}),U}finally{y&&A(y,{recursive:!0,force:!0})}}u(he,"runPublish");export{ee as evaluateCoreSurface,he as runPublish,Z as tarArgs};
@@ -1,79 +1,2 @@
1
- /**
2
- * Minimal ab-render client for preview (/renderDraft) and publish preflight (/health).
3
- *
4
- * ab-render is a different origin from ab-api but verifies the same PrivToken,
5
- * so the token is resolved against the ab-api base (where `remixmate login`
6
- * stored it) and sent to ab-render as X-Priv-Token. All routes are POST.
7
- */
8
- import { traceHeaders } from '../diagnostics.js';
9
- import { NOT_AUTHENTICATED_HINT } from '../auth/resolve.js';
10
- import { EXIT, SkillError } from '../errors.js';
11
- import { DEFAULT_RENDER_BASE_URL } from './constants.js';
12
- export function resolveRenderBaseUrl() {
13
- return ((process.env.AB_RENDER_URL ?? '').trim() || DEFAULT_RENDER_BASE_URL).replace(/\/+$/, '');
14
- }
15
- /** Business error from ab-render (`code != 0`), payload kept for issue rendering. */
16
- export class RenderBusinessError extends SkillError {
17
- code;
18
- payload;
19
- constructor(message, code, payload) {
20
- super(message, EXIT.ERROR);
21
- this.code = code;
22
- this.payload = payload;
23
- }
24
- }
25
- export async function renderPost(base, route, body, opts) {
26
- const controller = new AbortController();
27
- const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 60_000);
28
- const headers = {
29
- 'Content-Type': 'application/json',
30
- 'x-invoke-skill': opts.skillName,
31
- ...traceHeaders(),
32
- };
33
- if (opts.token)
34
- headers['X-Priv-Token'] = opts.token;
35
- let res;
36
- try {
37
- res = await fetch(`${base}${route}`, {
38
- method: 'POST',
39
- headers,
40
- body: JSON.stringify(body ?? {}),
41
- signal: controller.signal,
42
- });
43
- }
44
- catch (err) {
45
- const why = err.name === 'AbortError' ? '请求超时' : err.message;
46
- throw new SkillError(`❌ ab-render 不可达(${base}${route}):${why}`, EXIT.BACKEND_UNREACHABLE);
47
- }
48
- finally {
49
- clearTimeout(timer);
50
- }
51
- const text = await res.text();
52
- if (res.status === 401)
53
- throw new SkillError(NOT_AUTHENTICATED_HINT, EXIT.NOT_AUTHENTICATED);
54
- if (res.status >= 500) {
55
- throw new SkillError(`❌ ab-render ${route} HTTP ${res.status}`, EXIT.BACKEND_UNREACHABLE);
56
- }
57
- let json;
58
- try {
59
- json = JSON.parse(text);
60
- }
61
- catch {
62
- throw new SkillError(`❌ ab-render ${route} 返回非 JSON(HTTP ${res.status})`);
63
- }
64
- if (json.code !== undefined && json.code !== 0) {
65
- if (json.code === 401) {
66
- throw new SkillError(`${NOT_AUTHENTICATED_HINT}\n (ab-render: ${json.msg ?? 'code=401'})`, EXIT.NOT_AUTHENTICATED);
67
- }
68
- throw new RenderBusinessError(`❌ ab-render ${route} code=${json.code}: ${json.msg ?? 'unknown'}`, json.code, json);
69
- }
70
- return (json.data ?? json);
71
- }
72
- /** /health → coreSurface, or null when the instance predates the core contract. */
73
- export async function fetchCoreSurface(base) {
74
- const data = await renderPost(base, '/health', {}, {
75
- skillName: 'template-publish',
76
- timeoutMs: 15_000,
77
- });
78
- return data?.coreSurface ?? null;
79
- }
1
+ var h=Object.defineProperty;var s=(r,t)=>h(r,"name",{value:t,configurable:!0});import{traceHeaders as w}from"../diagnostics.js";import{NOT_AUTHENTICATED_HINT as E}from"../auth/resolve.js";import{EXIT as i,SkillError as a}from"../errors.js";import{DEFAULT_RENDER_BASE_URL as N}from"./constants.js";function b(){return((process.env.AB_RENDER_URL??"").trim()||N).replace(/\/+$/,"")}s(b,"resolveRenderBaseUrl");class A extends a{static{s(this,"RenderBusinessError")}code;payload;constructor(t,c,o){super(t,i.ERROR),this.code=c,this.payload=o}}async function p(r,t,c,o){const l=new AbortController,m=setTimeout(()=>l.abort(),o.timeoutMs??6e4),d={"Content-Type":"application/json","x-invoke-skill":o.skillName,...w()};o.token&&(d["X-Priv-Token"]=o.token);let n;try{n=await fetch(`${r}${t}`,{method:"POST",headers:d,body:JSON.stringify(c??{}),signal:l.signal})}catch(T){const u=T.name==="AbortError"?"请求超时":T.message;throw new a(`❌ ab-render 不可达(${r}${t}):${u}`,i.BACKEND_UNREACHABLE)}finally{clearTimeout(m)}const f=await n.text();if(n.status===401)throw new a(E,i.NOT_AUTHENTICATED);if(n.status>=500)throw new a(`❌ ab-render ${t} HTTP ${n.status}`,i.BACKEND_UNREACHABLE);let e;try{e=JSON.parse(f)}catch{throw new a(`❌ ab-render ${t} 返回非 JSON(HTTP ${n.status})`)}if(e.code!==void 0&&e.code!==0)throw e.code===401?new a(`${E}
2
+ (ab-render: ${e.msg??"code=401"})`,i.NOT_AUTHENTICATED):new A(`❌ ab-render ${t} code=${e.code}: ${e.msg??"unknown"}`,e.code,e);return e.data??e}s(p,"renderPost");async function k(r){return(await p(r,"/health",{},{skillName:"template-publish",timeoutMs:15e3}))?.coreSurface??null}s(k,"fetchCoreSurface");export{A as RenderBusinessError,k as fetchCoreSurface,p as renderPost,b as resolveRenderBaseUrl};
@@ -1,123 +1,4 @@
1
- /**
2
- * `remixmate template upgrade [--dry-run] [--with-docs] [--force] [--json]`
3
- *
4
- * 只更新工程里的本地校验规则(`.remixmate/`:check 脚本、template.schema.json、
5
- * template-security-policy.json),来源是本 cli 钉死的 starter 版本(STARTER_VERSION)。
6
- *
7
- * 为什么需要:starter 在 init 时整份复制进用户工程,之后平台收紧规则,老工程的
8
- * 本地 check 还是旧规则 —— 本地通过、服务端拒绝。一个 starter 版本 = 一套规则版本,
9
- * 所以升级 cli 后跑一次 upgrade 就能对齐。
10
- *
11
- * 刻意不动的东西:templates/(用户的模板)、src/、package.json 与依赖、examples/。
12
- * core 版本变化只提示不自动改 —— 升 core 可能影响模板渲染,必须是用户的决定。
13
- * `--with-docs` 额外刷新 AGENTS.md / CLAUDE.md(内容不同时旧文件备份为 *.orig)。
14
- */
15
- import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
16
- import os from 'node:os';
17
- import path from 'node:path';
18
- import { EXIT, SkillError } from '../errors.js';
19
- import { emitProgress } from '../progress.js';
20
- import { DOC_FILES, RULES_DIR, RULES_VERSION_FILE, STARTER_VERSION } from './constants.js';
21
- import { obtainStarterTarball } from './init.js';
22
- import { Output, compareVersions, readRulesVersion, requireProjectRoot } from './project.js';
23
- import { runProcess } from './proc.js';
24
- /** Dependencies whose pinned version the starter changed — reported, never auto-applied. */
25
- export function diffDependencies(project, starter) {
26
- const mine = { ...project.devDependencies, ...project.dependencies };
27
- const theirs = { ...starter.devDependencies, ...starter.dependencies };
28
- return Object.entries(theirs)
29
- .filter(([name, v]) => mine[name] !== v)
30
- .map(([name, v]) => ({ name, project: mine[name] ?? null, starter: v }))
31
- .sort((a, b) => a.name.localeCompare(b.name));
32
- }
33
- function readPkg(file) {
34
- try {
35
- return JSON.parse(readFileSync(file, 'utf8'));
36
- }
37
- catch {
38
- return {};
39
- }
40
- }
41
- export async function runUpgrade(opts = {}) {
42
- const out = new Output(Boolean(opts.json));
43
- const root = requireProjectRoot(opts.cwd);
44
- const from = readRulesVersion(root);
45
- const to = STARTER_VERSION;
46
- if (from && compareVersions(from, to) >= 0 && !opts.force) {
47
- out.info(`✅ 本地校验规则已是最新(starter ${from},当前 cli 对应 ${to})。`);
48
- const result = { ok: true, from, to, upgraded: false, dryRun: Boolean(opts.dryRun), docs: [], dependencyHints: [] };
49
- out.result({ ...result });
50
- return result;
51
- }
52
- const tmp = mkdtempSync(path.join(os.tmpdir(), 'remixmate-upgrade-'));
53
- try {
54
- emitProgress({ phase: 'template-upgrade:fetch', progress: 0.2 });
55
- const { file, label } = await obtainStarterTarball(tmp, out);
56
- const unpacked = path.join(tmp, 'starter');
57
- mkdirSync(unpacked, { recursive: true });
58
- const tar = await runProcess('tar', ['-xzf', file, '-C', unpacked, '--strip-components=1'], { stdio: 'pipe' });
59
- if (tar.code !== 0) {
60
- throw new SkillError(`❌ 解压 starter 失败(tar 退出码 ${tar.code}): ${tar.stderr.trim().slice(0, 300)}`);
61
- }
62
- const newRules = path.join(unpacked, RULES_DIR);
63
- if (!existsSync(path.join(newRules, 'check', 'index.mjs'))) {
64
- throw new SkillError(`❌ ${label} 里没有 ${RULES_DIR}/check/index.mjs,不是有效的 starter 包`);
65
- }
66
- const docs = opts.withDocs
67
- ? DOC_FILES.filter((f) => existsSync(path.join(unpacked, f)))
68
- : [];
69
- const dependencyHints = diffDependencies(readPkg(path.join(root, 'package.json')), readPkg(path.join(unpacked, 'package.json')));
70
- out.info(`升级本地校验规则:starter ${from ?? '(未知版本)'} → ${to}(${label})`);
71
- out.info(` 替换 ${RULES_DIR}/(check 脚本、schema、安全策略)`);
72
- for (const d of docs)
73
- out.info(` 刷新 ${d}(内容不同时旧文件备份为 ${d}.orig)`);
74
- out.info(' 不改动 templates/、src/、examples/、package.json');
75
- if (!opts.dryRun) {
76
- emitProgress({ phase: 'template-upgrade:apply', progress: 0.6 });
77
- // 先放到旁边再整体换名,避免复制到一半中断留下半新半旧的规则目录。
78
- const target = path.join(root, RULES_DIR);
79
- const staged = path.join(root, `${RULES_DIR}.upgrading`);
80
- const old = path.join(root, `${RULES_DIR}.previous`);
81
- rmSync(staged, { recursive: true, force: true });
82
- rmSync(old, { recursive: true, force: true });
83
- cpSync(newRules, staged, { recursive: true });
84
- // 老 starter 包(0.1.0)没有 version.json:补写,之后 cli 才认得出版本。
85
- const marker = path.join(staged, path.relative(RULES_DIR, RULES_VERSION_FILE));
86
- if (!existsSync(marker)) {
87
- writeFileSync(marker, JSON.stringify({ starterVersion: to }, null, 2) + '\n', 'utf8');
88
- }
89
- if (existsSync(target))
90
- renameSync(target, old);
91
- renameSync(staged, target);
92
- rmSync(old, { recursive: true, force: true });
93
- for (const d of docs) {
94
- const dest = path.join(root, d);
95
- const next = readFileSync(path.join(unpacked, d), 'utf8');
96
- if (existsSync(dest)) {
97
- const prev = readFileSync(dest, 'utf8');
98
- if (prev === next)
99
- continue;
100
- writeFileSync(`${dest}.orig`, prev, 'utf8');
101
- }
102
- writeFileSync(dest, next, 'utf8');
103
- }
104
- }
105
- for (const h of dependencyHints) {
106
- out.warn(` ⚠ 新 starter 钉的 ${h.name}@${h.starter},你的工程是 ${h.project ?? '(未安装)'}。` +
107
- `需要时手动执行 npm i ${h.name}@${h.starter}(升级 core 可能影响模板效果,先 check + preview 再发布)。`);
108
- }
109
- emitProgress({ phase: 'template-upgrade:done', progress: 1 });
110
- out.info(opts.dryRun ? '\n[dry-run] 未写入任何文件。' : '\n✅ 已更新。接着运行 remixmate template check 看新规则下是否还有问题。');
111
- const result = { ok: true, from, to, upgraded: !opts.dryRun, dryRun: Boolean(opts.dryRun), docs, dependencyHints };
112
- out.result({ ...result });
113
- return result;
114
- }
115
- catch (err) {
116
- if (err instanceof SkillError)
117
- throw err;
118
- throw new SkillError(`❌ 升级失败: ${err.message}`, EXIT.ERROR);
119
- }
120
- finally {
121
- rmSync(tmp, { recursive: true, force: true });
122
- }
123
- }
1
+ var F=Object.defineProperty;var l=(e,r)=>F(e,"name",{value:r,configurable:!0});import{cpSync as L,existsSync as p,mkdirSync as N,mkdtempSync as P,readFileSync as R,renameSync as _,rmSync as y,writeFileSync as S}from"node:fs";import T from"node:os";import n from"node:path";import{EXIT as B,SkillError as g}from"../errors.js";import{emitProgress as k}from"../progress.js";import{DOC_FILES as C,RULES_DIR as u,RULES_VERSION_FILE as U,STARTER_VERSION as H}from"./constants.js";import{obtainStarterTarball as J}from"./init.js";import{Output as q,compareVersions as z,readRulesVersion as A,requireProjectRoot as X}from"./project.js";import{runProcess as G}from"./proc.js";function K(e,r){const i={...e.devDependencies,...e.dependencies},c={...r.devDependencies,...r.dependencies};return Object.entries(c).filter(([o,s])=>i[o]!==s).map(([o,s])=>({name:o,project:i[o]??null,starter:s})).sort((o,s)=>o.name.localeCompare(s.name))}l(K,"diffDependencies");function b(e){try{return JSON.parse(R(e,"utf8"))}catch{return{}}}l(b,"readPkg");async function ie(e={}){const r=new q(!!e.json),i=X(e.cwd),c=A(i),o=H;if(c&&z(c,o)>=0&&!e.force){r.info(`✅ 本地校验规则已是最新(starter ${c},当前 cli 对应 ${o})。`);const a={ok:!0,from:c,to:o,upgraded:!1,dryRun:!!e.dryRun,docs:[],dependencyHints:[]};return r.result({...a}),a}const s=P(n.join(T.tmpdir(),"remixmate-upgrade-"));try{k({phase:"template-upgrade:fetch",progress:.2});const{file:a,label:w}=await J(s,r),f=n.join(s,"starter");N(f,{recursive:!0});const j=await G("tar",["-xzf",a,"-C",f,"--strip-components=1"],{stdio:"pipe"});if(j.code!==0)throw new g(`❌ 解压 starter 失败(tar 退出码 ${j.code}): ${j.stderr.trim().slice(0,300)}`);const v=n.join(f,u);if(!p(n.join(v,"check","index.mjs")))throw new g(`❌ ${w} 里没有 ${u}/check/index.mjs,不是有效的 starter 包`);const $=e.withDocs?C.filter(t=>p(n.join(f,t))):[],x=K(b(n.join(i,"package.json")),b(n.join(f,"package.json")));r.info(`升级本地校验规则:starter ${c??"(未知版本)"} → ${o}(${w})`),r.info(` 替换 ${u}/(check 脚本、schema、安全策略)`);for(const t of $)r.info(` 刷新 ${t}(内容不同时旧文件备份为 ${t}.orig)`);if(r.info(" 不改动 templates/、src/、examples/、package.json"),!e.dryRun){k({phase:"template-upgrade:apply",progress:.6});const t=n.join(i,u),d=n.join(i,`${u}.upgrading`),h=n.join(i,`${u}.previous`);y(d,{recursive:!0,force:!0}),y(h,{recursive:!0,force:!0}),L(v,d,{recursive:!0});const O=n.join(d,n.relative(u,U));p(O)||S(O,JSON.stringify({starterVersion:o},null,2)+`
2
+ `,"utf8"),p(t)&&_(t,h),_(d,t),y(h,{recursive:!0,force:!0});for(const D of $){const m=n.join(i,D),I=R(n.join(f,D),"utf8");if(p(m)){const V=R(m,"utf8");if(V===I)continue;S(`${m}.orig`,V,"utf8")}S(m,I,"utf8")}}for(const t of x)r.warn(` ⚠ 新 starter 钉的 ${t.name}@${t.starter},你的工程是 ${t.project??"(未安装)"}。需要时手动执行 npm i ${t.name}@${t.starter}(升级 core 可能影响模板效果,先 check + preview 再发布)。`);k({phase:"template-upgrade:done",progress:1}),r.info(e.dryRun?`
3
+ [dry-run] 未写入任何文件。`:`
4
+ ✅ 已更新。接着运行 remixmate template check 看新规则下是否还有问题。`);const E={ok:!0,from:c,to:o,upgraded:!e.dryRun,dryRun:!!e.dryRun,docs:$,dependencyHints:x};return r.result({...E}),E}catch(a){throw a instanceof g?a:new g(`❌ 升级失败: ${a.message}`,B.ERROR)}finally{y(s,{recursive:!0,force:!0})}}l(ie,"runUpgrade");export{K as diffDependencies,ie as runUpgrade};
package/dist/text.js CHANGED
@@ -1,15 +1 @@
1
- /**
2
- * Terminal text helpers shared by the human-facing commands.
3
- */
4
- // Ranges covering the CJK / fullwidth blocks the CLI's Chinese output uses.
5
- const WIDE = /[ᄀ-ᅟ⺀-䳿一-鿿가-힣豈-﫿︰-﹏＀-⦆¢-₩]/;
6
- /**
7
- * Pad to a target terminal width. CJK characters occupy two columns but count as
8
- * one code unit, so String.padEnd misaligns any column that contains them.
9
- */
10
- export function padDisplay(text, width) {
11
- let cols = 0;
12
- for (const ch of text)
13
- cols += WIDE.test(ch) ? 2 : 1;
14
- return text + ' '.repeat(Math.max(1, width - cols));
15
- }
1
+ var n=Object.defineProperty;var c=(t,o)=>n(t,"name",{value:o,configurable:!0});const r=/[ᄀ-ᅟ⺀-䳿一-鿿가-힣豈-﫿︰-﹏＀-⦆¢-₩]/;function p(t,o){let a=0;for(const e of t)a+=r.test(e)?2:1;return t+" ".repeat(Math.max(1,o-a))}c(p,"padDisplay");export{p as padDisplay};