@tikagent/video-editor-cli 0.0.0-stage → 1.5.0-preview.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +82 -0
- package/THIRD-PARTY-NOTICES.md +21 -0
- package/dist/bin/tik-video-editor-cli.js +2 -0
- package/dist/lib/asr.js +1 -0
- package/dist/lib/audio.js +1 -0
- package/dist/lib/auth.js +1 -0
- package/dist/lib/browser-zip.js +1 -0
- package/dist/lib/browser.js +1 -0
- package/dist/lib/cli.js +1 -0
- package/dist/lib/doctor.js +1 -0
- package/dist/lib/download.js +1 -0
- package/dist/lib/export-progress.js +1 -0
- package/dist/lib/import-video.js +1 -0
- package/dist/lib/local.js +1 -0
- package/dist/lib/media.js +1 -0
- package/dist/lib/operation.js +1 -0
- package/dist/lib/preset.js +1 -0
- package/dist/lib/processes.js +1 -0
- package/dist/lib/self.js +1 -0
- package/dist/lib/service-urls.js +1 -0
- package/dist/lib/storage.js +1 -0
- package/dist/runtime/LICENSE +21 -0
- package/dist/runtime/analyze.js +1 -0
- package/dist/runtime/app.js +1 -0
- package/dist/runtime/cli/lib/browser-zip.js +1 -0
- package/dist/runtime/cli/lib/browser.js +1 -0
- package/dist/runtime/cli/lib/processes.js +1 -0
- package/dist/runtime/export-audio.js +1 -0
- package/dist/runtime/export-cache.js +1 -0
- package/dist/runtime/export-encoder.js +1 -0
- package/dist/runtime/export-save.js +1 -0
- package/dist/runtime/export-worker.js +1 -0
- package/dist/runtime/favicon.svg +14 -0
- package/dist/runtime/i18n.js +1 -0
- package/dist/runtime/icons/favicon-32.png +0 -0
- package/dist/runtime/icons/tik-logo.png +0 -0
- package/dist/runtime/index.html +320 -0
- package/dist/runtime/library/README.md +12 -0
- package/dist/runtime/library/elements/bokeh-drift.svg +19 -0
- package/dist/runtime/library/elements/light-leak-cool.svg +16 -0
- package/dist/runtime/library/elements/light-leak-warm.svg +22 -0
- package/dist/runtime/library/elements/vhs-scanlines.svg +18 -0
- package/dist/runtime/library/elements/vignette-frame.svg +14 -0
- package/dist/runtime/library/fonts//351/230/277/351/207/214/345/267/264/345/267/264/346/231/256/346/203/240/344/275/223-/344/270/255/351/273/221.woff2 +0 -0
- package/dist/runtime/library/fonts//351/230/277/351/207/214/345/267/264/345/267/264/346/231/256/346/203/240/344/275/223-/345/270/270/350/247/204.woff2 +0 -0
- package/dist/runtime/library/fonts//351/230/277/351/207/214/345/267/264/345/267/264/346/231/256/346/203/240/344/275/223-/347/262/227/344/275/223.woff2 +0 -0
- package/dist/runtime/library/sfx/README.md +13 -0
- package/dist/runtime/library/svg/arrow-bounce.svg +18 -0
- package/dist/runtime/library/svg/arrow-scribble.svg +13 -0
- package/dist/runtime/library/svg/burst-lines.svg +30 -0
- package/dist/runtime/library/svg/checkmark-success.svg +19 -0
- package/dist/runtime/library/svg/confetti-burst.svg +23 -0
- package/dist/runtime/library/svg/countdown-ring.svg +92 -0
- package/dist/runtime/library/svg/equalizer.svg +18 -0
- package/dist/runtime/library/svg/glow-orbs.svg +29 -0
- package/dist/runtime/library/svg/heart-pop.svg +26 -0
- package/dist/runtime/library/svg/loading-dots.svg +16 -0
- package/dist/runtime/library/svg/location-pin.svg +25 -0
- package/dist/runtime/library/svg/lower-third.svg +17 -0
- package/dist/runtime/library/svg/new-badge.svg +18 -0
- package/dist/runtime/library/svg/progress-bar.svg +16 -0
- package/dist/runtime/library/svg/radial-pulse.svg +19 -0
- package/dist/runtime/library/svg/rating-stars.svg +25 -0
- package/dist/runtime/library/svg/rec-indicator.svg +41 -0
- package/dist/runtime/library/svg/sparkles.svg +17 -0
- package/dist/runtime/library/svg/speech-bubble.svg +23 -0
- package/dist/runtime/library/svg/subscribe-bell.svg +29 -0
- package/dist/runtime/library/svg/subscribe-pill.svg +22 -0
- package/dist/runtime/library/svg/swipe-up.svg +46 -0
- package/dist/runtime/library/svg/underline-swoosh.svg +15 -0
- package/dist/runtime/manifest.json +40 -0
- package/dist/runtime/meter-worklet.js +1 -0
- package/dist/runtime/optimized-encoder-worker.js +1 -0
- package/dist/runtime/optimized-export.js +1 -0
- package/dist/runtime/paths.js +1 -0
- package/dist/runtime/project-store.js +1 -0
- package/dist/runtime/ruler-worker.js +1 -0
- package/dist/runtime/server.js +1 -0
- package/dist/runtime/style.css +2126 -0
- package/dist/runtime/timeline.js +1 -0
- package/dist/runtime/waveform.js +1 -0
- package/package.json +37 -4
- package/scripts/build-package.js +69 -0
- package/scripts/next-version.js +56 -0
- package/scripts/pack-runtime.js +26 -0
- package/scripts/release-channel.js +115 -0
- package/README.md +0 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(t){const r=function(){"use strict";const t=1e-9,r=t=>"audio"===t.kind?"audio":"video",i=t=>t.start+t.duration,e=(r,e)=>r.start<i(e)-t&&e.start<i(r)-t,s=t=>"string"==typeof t&&/^[VA][1-9]\d*$/.test(t)&&Number.isSafeInteger(+t.slice(1)),n=t=>"A"===t[0]?"audio":"video";function a(t,r){const i="audio"===r?"A":"V",e=t.reduce((t,i)=>i.kind===r?Math.max(t,+i.id.slice(1)):t,0)+1;if(!Number.isSafeInteger(e))throw new Error("Track identifier exhausted");return i+e}function o(t,r,i){t.tracks||=[];const e={id:a(t.tracks,r),kind:r};let s=t.tracks.findIndex(t=>t.id===i);return s<0&&(s="video"===r?t.tracks.findIndex(t=>"audio"===t.kind):t.tracks.length),s<0&&(s=t.tracks.length),t.tracks.splice(s,0,e),e.id}return{EPS:t,kindOf:r,overlaps:e,normalize:function(a){if(!a||!Array.isArray(a.clips))return a;const d=[],c=new Set;if(void 0!==a.tracks&&!Array.isArray(a.tracks))throw new Error("tracks must be an array");for(const t of a.tracks||[]){if(!t||!s(t.id)||t.kind!==n(t.id)||c.has(t.id))throw new Error("Invalid or duplicate track");if(null!=t.name&&"string"!=typeof t.name)throw new Error("Track name must be a string");c.add(t.id);const r={id:t.id,kind:t.kind};"string"==typeof t.name&&t.name.trim()&&(r.name=t.name.trim()),d.push(r)}const l=new Set;for(const t of a.clips){if(!s(t.track)||n(t.track)!==r(t))throw new Error(`Invalid track for clip ${t.id||"?"}`);if(!Number.isFinite(t.start)||t.start<0||!Number.isFinite(t.duration)||t.duration<=0)throw new Error(`Invalid timing for clip ${t.id||"?"}`);c.has(t.track)||l.add(t.track)}for(const t of a.disabledTracks||[])s(t)&&!c.has(t)&&l.add(t);const f=[...l].sort((t,r)=>t[0]===r[0]?"V"===t[0]?+r.slice(1)-+t.slice(1):+t.slice(1)-+r.slice(1):"V"===t[0]?-1:1);for(const t of f){const r=n(t);let i=d.findIndex(i=>"video"===r&&("audio"===i.kind||+i.id.slice(1)<+t.slice(1)));i<0&&(i=d.length),d.splice(i,0,{id:t,kind:r}),c.add(t)}if(d.some((t,r)=>"video"===t.kind&&d.slice(0,r).some(t=>"audio"===t.kind)))throw new Error("Video tracks must precede audio tracks");a.tracks=d;const k=new Set(a.disabledTracks||[]),u=new Map;for(const t of a.clips)u.has(t.track)||u.set(t.track,[]),u.get(t.track).push(t);for(const r of[...d]){const s=(u.get(r.id)||[]).sort((t,r)=>t.start-r.start);let n=[];const d=[r.id];for(const c of s){n=n.filter(r=>i(r.clip)>c.start+t);let s=0;for(const t of n)e(c,t.clip)&&(s=Math.max(s,t.level+1));for(;d.length<=s;){const t=o(a,r.kind,"video"===r.kind?d[d.length-1]:void 0);d.push(t),k.has(r.id)&&k.add(t)}c.track=d[s],n.push({clip:c,level:s})}}return(void 0!==a.disabledTracks||k.size)&&(a.disabledTracks=[...k].filter(t=>c.has(t)||d.some(r=>r.id===t))),a},insertTrack:o,firstGap:function(r,e,s,n,a=new Set){let o=Math.max(0,s);const d=r.filter(t=>t.track===e&&!a.has(t.id)).sort((t,r)=>t.start-r.start);for(const r of d){if(o+n<=r.start+t)break;o<i(r)-t&&(o=i(r))}return o},freeTrack:function(t,r,i,s,n,a=new Set){const d=r=>!t.clips.some(t=>t.track===r&&!a.has(t.id)&&e(t,{start:i,duration:s}));if(t.tracks.some(t=>t.id===n&&t.kind===r)&&d(n))return n;const c=t.tracks.find(i=>i.kind===r&&!(t.disabledTracks||[]).includes(i.id)&&d(i.id));return c?c.id:o(t,r)}}}();"object"==typeof module&&module.exports?module.exports=r:t.FableCutTimeline=r}("object"==typeof globalThis?globalThis:this);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";const e=require("node:fs").promises,r=require("node:path"),s=require("node:crypto"),{spawn:t}=require("node:child_process"),{localMedia:a,command:o}=require("./export-cache");function i(e,r){return Object.assign(new Error(e),{status:r})}async function n(e){const r=JSON.parse(await o("ffprobe",["-v","error","-select_streams","a:0","-show_entries","stream=channels","-of","json",e])),s=r.streams?.[0]?.channels;if(!Number.isInteger(s)||s<1)throw i("source has no audio stream",422);return s}module.exports={Waveforms:class{constructor({libraryDir:e}){this.libraryDir=e,this.jobs=new Map,this.queue=Promise.resolve()}async channels(e,r){return n(await a(e,this.libraryDir,r))}async peaks(o,c){const u=await a(o,this.libraryDir,c),l=await e.stat(u),f=s.createHash("sha256").update(`1|${u}|${l.size}|${l.mtimeMs}`).digest("hex"),h=r.join(o.analysisDir,"waveforms",f+".json");try{return JSON.parse(await e.readFile(h,"utf8"))}catch{}if(!this.jobs.has(h)){const s=this.queue.then(async()=>{const s=await function(e,r){return new Promise((s,a)=>{const o=t("ffmpeg",["-v","error","-nostdin","-i",e,"-map","0:a:0","-af","aresample=8000:async=1:first_pts=0","-f","s16le","-acodec","pcm_s16le","-"],{stdio:["ignore","pipe","pipe"],windowsHide:!0}),n=2*r,c=Array.from({length:r},()=>[]),u=new Array(r).fill(0);let l,f=Buffer.alloc(0),h=0,m="";const d=()=>{for(let e=0;e<r;e++)c[e].push(Math.min(255,Math.round(u[e]/32768*255))),u[e]=0},p=setTimeout(()=>{l=new Error("ffmpeg waveform timed out"),o.kill("SIGKILL")},6e5);o.stdout.on("data",e=>{const s=f.length?Buffer.concat([f,e]):e,t=s.length-s.length%n;for(let e=0;e<t;e+=n){for(let t=0;t<r;t++){const r=Math.abs(s.readInt16LE(e+2*t));r>u[t]&&(u[t]=r)}++h%160==0&&d()}f=Buffer.from(s.subarray(t))}),o.stderr.on("data",e=>{m=(m+e).slice(-4e3)}),o.on("error",e=>{l=e}),o.on("close",e=>(clearTimeout(p),l||0!==e?a(l||new Error(m||"ffmpeg waveform failed")):h?(h%160&&d(),void s({version:1,peaksPerSecond:50,duration:h/8e3,channels:r,peaks:c.map(e=>Buffer.from(Uint8Array.from(e)).toString("base64"))})):a(i("source has no decodable audio",422))))})}(u,await n(u));await e.mkdir(r.dirname(h),{recursive:!0});const a=`${h}.${process.pid}.tmp`;return await e.writeFile(a,JSON.stringify(s)),await e.rename(a,h),s}).finally(()=>this.jobs.delete(h));this.queue=s.catch(()=>{}),this.jobs.set(h,s)}return this.jobs.get(h)}},PEAKS_PER_SECOND:50,SAMPLE_RATE:8e3};
|
package/package.json
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tikagent/video-editor-cli",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.5.0-preview.1",
|
|
4
|
+
"description": "Local video editing, on-demand preview, and headless",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"bin": {
|
|
8
|
+
"tik-video-editor-cli": "dist/bin/tik-video-editor-cli.js"
|
|
9
|
+
},
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"registry": "https://registry.npmjs.org/",
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist/",
|
|
16
|
+
"scripts/",
|
|
17
|
+
"LICENSE",
|
|
18
|
+
"THIRD-PARTY-NOTICES.md"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"next": "node scripts/next-version.js",
|
|
22
|
+
"sync-runtime": "node scripts/pack-runtime.js",
|
|
23
|
+
"build": "node scripts/build-package.js",
|
|
24
|
+
"prepare": "node scripts/build-package.js"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"tik",
|
|
31
|
+
"video-editor",
|
|
32
|
+
"cli",
|
|
33
|
+
"headless-export"
|
|
34
|
+
],
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"javascript-obfuscator": "5.8.1",
|
|
37
|
+
"terser": "5.51.2"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
const { execFileSync } = require("node:child_process");
|
|
6
|
+
const cliDir = path.resolve(__dirname, "..");
|
|
7
|
+
const dist = path.join(cliDir, "dist");
|
|
8
|
+
|
|
9
|
+
async function build() {
|
|
10
|
+
// Registry installs contain only dist/. Rebuilds need no development tools.
|
|
11
|
+
if (!fs.existsSync(path.join(cliDir, "lib/cli.js"))) {
|
|
12
|
+
for (const file of ["bin/tik-video-editor-cli.js", "runtime/server.js"]) {
|
|
13
|
+
if (!fs.existsSync(path.join(dist, file))) throw new Error("Incomplete CLI distribution: " + file);
|
|
14
|
+
}
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const { minify } = require("terser");
|
|
18
|
+
const obfuscator = require("javascript-obfuscator");
|
|
19
|
+
execFileSync(process.execPath, [path.join(__dirname, "pack-runtime.js")], { stdio: "inherit" });
|
|
20
|
+
const staging = fs.mkdtempSync(path.join(cliDir, ".dist-"));
|
|
21
|
+
let before = 0, after = 0;
|
|
22
|
+
try {
|
|
23
|
+
for (const dir of ["bin", "lib", "runtime"]) fs.cpSync(path.join(cliDir, dir), path.join(staging, dir), { recursive: true });
|
|
24
|
+
// These scripts share browser globals with inline HTML and other scripts.
|
|
25
|
+
const browserScripts = new Set(["timeline.js", "app.js", "i18n.js", "optimized-export.js", "meter-worklet.js", "ruler-worker.js"]);
|
|
26
|
+
async function transform(dir) {
|
|
27
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name, "en"))) {
|
|
28
|
+
const file = path.join(dir, entry.name);
|
|
29
|
+
if (entry.isDirectory()) { await transform(file); continue; }
|
|
30
|
+
if (!entry.name.endsWith(".js")) continue;
|
|
31
|
+
const relative = path.relative(staging, file);
|
|
32
|
+
const isCLI = relative.startsWith("bin" + path.sep) || relative.startsWith("lib" + path.sep);
|
|
33
|
+
const browser = relative.startsWith("runtime" + path.sep) && browserScripts.has(entry.name);
|
|
34
|
+
const source = fs.readFileSync(file, "utf8");
|
|
35
|
+
const result = await minify(source, {
|
|
36
|
+
ecma: 2022,
|
|
37
|
+
compress: { passes: 2, toplevel: !browser },
|
|
38
|
+
mangle: { toplevel: !browser },
|
|
39
|
+
format: { comments: /^!|@license|@preserve/i, shebang: true },
|
|
40
|
+
sourceMap: false,
|
|
41
|
+
});
|
|
42
|
+
let code = result.code;
|
|
43
|
+
if (isCLI) {
|
|
44
|
+
const shebang = code.startsWith("#!") ? code.slice(0, code.indexOf("\n") + 1) : "";
|
|
45
|
+
code = shebang + obfuscator.obfuscate(code.slice(shebang.length), {
|
|
46
|
+
target: "node", seed: 1701, compact: true,
|
|
47
|
+
identifierNamesGenerator: "hexadecimal", renameGlobals: false,
|
|
48
|
+
stringArray: true, stringArrayEncoding: ["base64"], stringArrayThreshold: 0.75,
|
|
49
|
+
controlFlowFlattening: false, deadCodeInjection: false,
|
|
50
|
+
debugProtection: false, selfDefending: false,
|
|
51
|
+
sourceMap: false,
|
|
52
|
+
}).getObfuscatedCode();
|
|
53
|
+
}
|
|
54
|
+
fs.writeFileSync(file, code + "\n");
|
|
55
|
+
execFileSync(process.execPath, ["--check", file], { stdio: "pipe" });
|
|
56
|
+
before += Buffer.byteLength(source); after += Buffer.byteLength(code + "\n");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
await transform(staging);
|
|
60
|
+
fs.chmodSync(path.join(staging, "bin/tik-video-editor-cli.js"), 0o755);
|
|
61
|
+
// Preserve upstream MIT separately; never overwrite the CLI's own license.
|
|
62
|
+
fs.copyFileSync(path.join(cliDir, "../LICENSE"), path.join(staging, "runtime/LICENSE"));
|
|
63
|
+
fs.rmSync(dist, { recursive: true, force: true });
|
|
64
|
+
fs.renameSync(staging, dist);
|
|
65
|
+
console.error(`CLI distribution: JavaScript ${before} → ${after} bytes; no source maps`);
|
|
66
|
+
} finally { fs.rmSync(staging, { recursive: true, force: true }); }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
build().catch(error => { console.error(error.message); process.exitCode = 1; });
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// 把 cli/package.json 的 version 推进一号。
|
|
4
|
+
// 不带参数:1.5.0-alpha.1 → 1.5.0-alpha.2,1.5.0 → 1.5.1。
|
|
5
|
+
// 带轨道:换成该轨道。已是同一轨道则序号加 1,否则从 .1 开始。stable 去掉后缀。
|
|
6
|
+
// latest 是 stable 的旧说法。用法:pnpm next [alpha|insider|preview|stable]
|
|
7
|
+
const fs = require("fs");
|
|
8
|
+
const path = require("path");
|
|
9
|
+
|
|
10
|
+
const LEVELS = new Set(["alpha", "insider", "preview", "stable", "latest"]);
|
|
11
|
+
const VERSION_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-(alpha|insider|preview|test|rc)\.(0|[1-9]\d*))?$/;
|
|
12
|
+
const VERSION_FIELD_RE = /"version":\s*"[^"]*"/;
|
|
13
|
+
|
|
14
|
+
function nextVersion(current, level) {
|
|
15
|
+
const matched = VERSION_RE.exec(String(current ?? "").trim());
|
|
16
|
+
if (!matched) {
|
|
17
|
+
throw new Error(`无法识别的版本 "${current}"。需要 x.y.z 或 x.y.z-<级别>.N。`);
|
|
18
|
+
}
|
|
19
|
+
const core = `${matched[1]}.${matched[2]}.${matched[3]}`;
|
|
20
|
+
const currentLevel = matched[4];
|
|
21
|
+
const currentN = matched[5] === undefined ? undefined : Number(matched[5]);
|
|
22
|
+
if (!level) {
|
|
23
|
+
if (currentLevel) return `${core}-${currentLevel}.${currentN + 1}`;
|
|
24
|
+
return `${matched[1]}.${matched[2]}.${Number(matched[3]) + 1}`;
|
|
25
|
+
}
|
|
26
|
+
if (!LEVELS.has(level)) {
|
|
27
|
+
throw new Error(`轨道只能是 alpha、insider、preview 或 stable,当前是 ${level}。`);
|
|
28
|
+
}
|
|
29
|
+
if (level === "latest" || level === "stable") return core;
|
|
30
|
+
if (currentLevel === level) return `${core}-${level}.${currentN + 1}`;
|
|
31
|
+
return `${core}-${level}.1`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function writePackageVersion(pkgPath, version) {
|
|
35
|
+
const raw = fs.readFileSync(pkgPath, "utf8");
|
|
36
|
+
if (!VERSION_FIELD_RE.test(raw)) {
|
|
37
|
+
throw new Error(`${pkgPath} 没有 version 字段。`);
|
|
38
|
+
}
|
|
39
|
+
fs.writeFileSync(pkgPath, raw.replace(VERSION_FIELD_RE, `"version": "${version}"`), "utf8");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (require.main === module) {
|
|
43
|
+
const level = process.argv[2];
|
|
44
|
+
const pkgPath = path.join(__dirname, "..", "package.json");
|
|
45
|
+
try {
|
|
46
|
+
const current = JSON.parse(fs.readFileSync(pkgPath, "utf8")).version;
|
|
47
|
+
const next = nextVersion(current, level);
|
|
48
|
+
writePackageVersion(pkgPath, next);
|
|
49
|
+
console.log(`${current} -> ${next}`);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error(error instanceof Error ? error.message : error);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = { nextVersion };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
const cliDir = path.resolve(__dirname, "..");
|
|
7
|
+
const root = path.resolve(cliDir, "..");
|
|
8
|
+
const target = path.join(cliDir, "runtime");
|
|
9
|
+
const entries = [
|
|
10
|
+
"server.js", "export-worker.js", "export-encoder.js", "export-audio.js", "export-save.js", "cli/lib/browser.js", "cli/lib/browser-zip.js", "cli/lib/processes.js", "export-cache.js", "waveform.js", "optimized-export.js", "optimized-encoder-worker.js", "paths.js", "project-store.js", "timeline.js", "analyze.js", "app.js", "i18n.js", "index.html", "style.css",
|
|
11
|
+
"meter-worklet.js", "ruler-worker.js", "manifest.json", "favicon.svg", "icons", "library",
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const missing = entries.filter((entry) => !fs.existsSync(path.join(root, entry)));
|
|
15
|
+
if (missing.length) {
|
|
16
|
+
/* Registry installs already contain runtime/. `npm rebuild` may execute the
|
|
17
|
+
prepare hook again, but the repository sources are intentionally absent. */
|
|
18
|
+
if (fs.existsSync(path.join(target, "server.js")) || fs.existsSync(path.join(cliDir, "dist/runtime/server.js"))) process.exit(0);
|
|
19
|
+
throw new Error("Missing FableCut source entries: " + missing.join(", "));
|
|
20
|
+
}
|
|
21
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
22
|
+
fs.mkdirSync(target, { recursive: true });
|
|
23
|
+
for (const entry of entries) {
|
|
24
|
+
const source = path.join(root, entry);
|
|
25
|
+
fs.cpSync(source, path.join(target, entry), { recursive: true });
|
|
26
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const RELEASE_VERSION_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
|
4
|
+
const LEVEL_VERSION_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)-(alpha|insider|preview)\.(0|[1-9]\d*)$/;
|
|
5
|
+
|
|
6
|
+
function fail(message) {
|
|
7
|
+
const error = new Error(message);
|
|
8
|
+
error.exitCode = 1;
|
|
9
|
+
throw error;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const TRACKS = new Set(["alpha", "insider", "preview", "stable", "latest"]);
|
|
13
|
+
|
|
14
|
+
/** 留空版本号时按轨道取下一号。stable 返回基线,npm dist-tag 仍由 distTag() 写成 latest。 */
|
|
15
|
+
function resolveChannelRelease({ declared, channel = "alpha", knownVersions = [] }) {
|
|
16
|
+
const track = channel === "latest" ? "stable" : channel;
|
|
17
|
+
if (!TRACKS.has(channel)) {
|
|
18
|
+
fail(`轨道只能是 alpha、insider、preview 或 stable,当前是 ${channel}`);
|
|
19
|
+
}
|
|
20
|
+
if (!RELEASE_VERSION_RE.test(releaseBaseline(declared))) {
|
|
21
|
+
fail(`基线必须是 x.y.z,当前是 ${declared}`);
|
|
22
|
+
}
|
|
23
|
+
const baseline = releaseBaseline(declared);
|
|
24
|
+
if (track === "stable") return baseline;
|
|
25
|
+
const escaped = baseline.replace(/[.]/g, "\\.");
|
|
26
|
+
const re = new RegExp(`(?<![0-9])${escaped}-${track}\\.([1-9]\\d*|0)(?![0-9])`);
|
|
27
|
+
let max = 0;
|
|
28
|
+
for (const version of knownVersions) {
|
|
29
|
+
const matched = re.exec(String(version));
|
|
30
|
+
if (matched) max = Math.max(max, Number(matched[1]));
|
|
31
|
+
}
|
|
32
|
+
return `${baseline}-${track}.${max + 1}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function releaseBaseline(version) {
|
|
36
|
+
return String(version).replace(/-(?:alpha|insider|preview)\.(?:0|[1-9]\d*)$/, "");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** 本地 tag 和 package.json 近期版本。构建机通常没有远端 tag,流水线要另外传入。 */
|
|
40
|
+
function knownVersionsFromGit() {
|
|
41
|
+
const { execFileSync } = require("node:child_process");
|
|
42
|
+
const versions = [];
|
|
43
|
+
try {
|
|
44
|
+
versions.push(...execFileSync("git", ["tag", "-l"], { encoding: "utf8" }).split(/\r?\n/));
|
|
45
|
+
} catch {
|
|
46
|
+
// 没有 git 时从 .1 开始。
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const log = execFileSync("git", ["log", "-n", "40", "-p", "--", "cli/package.json"], { encoding: "utf8" });
|
|
50
|
+
for (const match of log.matchAll(/"version":\s*"([^"]+)"/g)) versions.push(match[1]);
|
|
51
|
+
} catch {
|
|
52
|
+
// 历史读不到时从 .1 开始。
|
|
53
|
+
}
|
|
54
|
+
return versions;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** 后缀和 dist-tag 同名。正式版是 latest。 */
|
|
58
|
+
function distTag(version) {
|
|
59
|
+
const level = LEVEL_VERSION_RE.exec(version);
|
|
60
|
+
if (level) return level[4];
|
|
61
|
+
if (RELEASE_VERSION_RE.test(version)) return "latest";
|
|
62
|
+
fail(`版本必须是 x.y.z、x.y.z-alpha.N、x.y.z-insider.N 或 x.y.z-preview.N,当前是 ${version}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function gitTag(version) {
|
|
66
|
+
distTag(version);
|
|
67
|
+
return `video-editor-cli@${version}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** alpha 和 insider 发到 CNB,preview 和正式版发到 npmjs。 */
|
|
71
|
+
function registry(version) {
|
|
72
|
+
const tag = distTag(version);
|
|
73
|
+
return tag === "alpha" || tag === "insider" ? "cnb" : "npmjs";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** CNB 保持私有。npmjs 上的 preview 和正式版必须公开,否则作用域包会要求付费私有套餐。 */
|
|
77
|
+
function publishAccess(version) {
|
|
78
|
+
return registry(version) === "cnb" ? "restricted" : "public";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isDirectRun() {
|
|
82
|
+
return require.main === module;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (isDirectRun()) {
|
|
86
|
+
const version = process.argv[3];
|
|
87
|
+
try {
|
|
88
|
+
if (process.argv[2] === "--resolve-version") {
|
|
89
|
+
const declared = process.argv[3];
|
|
90
|
+
const channel = process.argv[4] || "alpha";
|
|
91
|
+
const knownFile = process.argv[5];
|
|
92
|
+
const knownVersions = knownVersionsFromGit();
|
|
93
|
+
if (knownFile) {
|
|
94
|
+
const { readFileSync } = require("node:fs");
|
|
95
|
+
knownVersions.push(...readFileSync(knownFile, "utf8").split(/\r?\n/));
|
|
96
|
+
}
|
|
97
|
+
process.stdout.write(`${resolveChannelRelease({ declared, channel, knownVersions })}\n`);
|
|
98
|
+
} else if (process.argv[2] === "--print-dist-tag") {
|
|
99
|
+
process.stdout.write(`${distTag(version)}\n`);
|
|
100
|
+
} else if (process.argv[2] === "--print-git-tag") {
|
|
101
|
+
process.stdout.write(`${gitTag(version)}\n`);
|
|
102
|
+
} else if (process.argv[2] === "--print-registry") {
|
|
103
|
+
process.stdout.write(`${registry(version)}\n`);
|
|
104
|
+
} else if (process.argv[2] === "--print-access") {
|
|
105
|
+
process.stdout.write(`${publishAccess(version)}\n`);
|
|
106
|
+
} else {
|
|
107
|
+
fail("用法:node scripts/release-channel.js --print-dist-tag|--print-git-tag|--print-registry|--print-access <版本>");
|
|
108
|
+
}
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.error(error instanceof Error ? error.message : error);
|
|
111
|
+
process.exit(error.exitCode || 1);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = { distTag, gitTag, publishAccess, registry, resolveChannelRelease };
|
package/README.md
DELETED