SnowLuma v2.4.2 source snapshot
Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
Publish MCP to npm / Build, test, and publish MCP (push) Waiting to run
Publish SDK to npm / Build, test, and publish SDK (push) Waiting to run
Sync action catalog to docs / sync (push) Waiting to run
Publish UI to npm / Build and publish UI (push) Waiting to run
Build and Release / preflight (push) Waiting to run

This commit is contained in:
TanTan committed 2026-11-13 09:02:00 +08:00
commit fa9c73363d
1367 files changed
+355735

No files matched your search

+223
View File
@@ -0,0 +1,223 @@
import protobufVitePlugin from '@snowluma/proton/vite';
import fs from 'fs';
import { builtinModules } from 'module';
import path from 'path';
import { fileURLToPath } from 'url';
import { defineConfig, PluginOption, UserConfig } from 'vite';
import cp from 'vite-plugin-cp';
import { sqliteBuildPlugin } from '@snowluma/sqlite/vite';
import { APP_VERSION } from '@snowluma/common/version';
import runtimePackage from '../runtime/package.json' with { type: 'json' };
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(__dirname, '..', '..');
const distDir = path.resolve(repoRoot, 'dist');
const runtimeDir = path.resolve(repoRoot, 'packages', 'runtime');
const nativeDir = path.resolve(runtimeDir, 'native');
// vite-plugin-cp consumes globs through globby; on Windows we must use POSIX-style separators.
const toPosix = (p: string) => p.replace(/\\/g, '/');
// `@snowluma/websocket` is bundled — it's an
// in-tree TS workspace wrapper that routes its native `.node`
// addon through `dist/native/`. Only Node builtins stay external.
//
const nodeModules = [...builtinModules, ...builtinModules.map((m) => `node:${m}`)].flat();
const runtimeSrc = toPosix(runtimeDir);
const nativeSrc = toPosix(nativeDir);
// Target selection: `SNOWLUMA_TARGET=<platform>-<arch>` overrides the host
// detection, enabling cross-target packaging on CI.
const targetTriple = process.env.SNOWLUMA_TARGET ?? `${process.platform}-${process.arch}`;
const [targetPlatform, targetArch] = targetTriple.split('-');
// Runtime scaffolding files copied into dist/. Windows and Linux use different
// launchers, while both targets ship their matching QQ integration binaries.
const runtimeDistFiles = [
'check-node-version.cjs',
targetPlatform === 'win32' ? 'launcher.bat' : 'launcher.sh',
];
const agreementFiles = ['EULA.md', 'PRIVACY.md'];
// Native binaries shipped for the selected target:
// * `snowluma-*.{dll,node,so}` – NTQQ hook (Windows + Linux).
// * `websocket-*.node` – RFC 6455 framing/mask addon (all platforms).
const nativeFiles = [
`websocket-${targetTriple}.node`,
...(targetPlatform === 'win32'
? [`snowluma-${targetTriple}.dll`, `snowluma-${targetTriple}.node`]
: []),
...(targetPlatform === 'linux'
? [`snowluma-${targetTriple}.node`, `snowluma-${targetTriple}.so`]
: []),
];
// FFmpeg native addon ported from NapCat. Uses NapCat's
// `<platform>.<arch>` naming so the prebuilt binaries can be vendored
// as-is without renaming. Lives under `native/ffmpeg/` to keep it
// separate from the SnowLuma flat-naming addons above.
const ffmpegAddonFile = `ffmpegAddon.${targetPlatform}.${targetArch}.node`;
const ffmpegSrcDir = path.resolve(nativeDir, 'ffmpeg');
// Fail fast if any expected native binary is missing from packages/runtime/native/.
// vite-plugin-cp only emits a warning on missing source files; we want a hard
// error so CI can't accidentally ship an incomplete archive.
const missingNatives = nativeFiles.filter((f) => !fs.existsSync(path.join(nativeDir, f)));
if (!fs.existsSync(path.join(ffmpegSrcDir, ffmpegAddonFile))) {
missingNatives.push(`ffmpeg/${ffmpegAddonFile}`);
}
if (missingNatives.length > 0) {
throw new Error(
`Missing native binaries for target ${targetTriple}:\n` +
missingNatives.map((f) => ` - ${path.join(nativeDir, f)}`).join('\n'),
);
}
// Legal docs are served by the WebUI consent gate; a missing one would silently
// disable consent in the shipped build, so fail the build instead (mirrors the
// native-binary guard above).
const missingAgreements = agreementFiles.filter((f) => !fs.existsSync(path.resolve(repoRoot, f)));
if (missingAgreements.length > 0) {
throw new Error(
`Missing legal documents required by the WebUI consent gate:\n` +
missingAgreements.map((f) => ` - ${path.resolve(repoRoot, f)}`).join('\n'),
);
}
const BaseConfigPlugin: PluginOption[] = [
{
name: 'release-metadata',
generateBundle(_options, bundle) {
const entry = bundle['index.mjs'];
if (entry?.type !== 'chunk' || entry.imports.some((id) => !nodeModules.includes(id))) {
this.error('The update entry must only import Node builtins.');
}
this.emitFile({
type: 'asset',
fileName: 'package.json',
source: JSON.stringify({ ...runtimePackage, version: APP_VERSION }, null, 2) + '\n',
});
if (process.env.BUILD_WEBUI === 'true') {
const entries = [
...new Set([
...Object.keys(bundle).map((file) => file.split('/')[0]),
...runtimeDistFiles,
...agreementFiles,
'native',
'client',
'package.json',
'release.json',
]),
].sort();
this.emitFile({
type: 'asset',
fileName: 'release.json',
source:
JSON.stringify(
{ version: APP_VERSION, target: targetTriple, entries },
null,
2,
) + '\n',
});
}
},
},
// Proton substitutes every `protobuf_encode<T>` / `protobuf_decode<T>`
// call site with a monomorphized codec at build time. WITHOUT this
// plugin in the plugins array the runtime fallback in
// `@snowluma/proton/runtime.ts` throws on the first call (and the
// packaged Win/Linux release does exactly that). It MUST appear
// before `cp` so the transform runs on every source file Vite asks
// for, regardless of where `cp` later copies emitted assets.
protobufVitePlugin(),
sqliteBuildPlugin(targetTriple),
cp({
targets: [
...runtimeDistFiles.map((file) => ({
src: `${runtimeSrc}/${file}`,
dest: distDir,
flatten: true,
})),
...nativeFiles.map((f) => ({
src: `${nativeSrc}/${f}`,
dest: path.join(distDir, 'native'),
flatten: true,
})),
{
src: `${toPosix(ffmpegSrcDir)}/${ffmpegAddonFile}`,
dest: path.join(distDir, 'native', 'ffmpeg'),
flatten: true,
},
// Legal docs served verbatim by the WebUI consent gate. Copied next to
// the bundle so consent.ts's path probe finds them in a packaged deploy.
...agreementFiles.map((file) => ({
src: toPosix(path.resolve(repoRoot, file)),
dest: distDir,
flatten: true,
})),
],
}),
];
const BaseConfig = (source_map: boolean = false) =>
defineConfig({
resolve: {
conditions: ['node', 'default'],
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
build: {
sourcemap: source_map,
target: 'node24',
minify: false,
lib: {
entry: {
index: path.resolve(__dirname, 'src/index.ts'),
},
formats: ['es'],
fileName: (_, entryName) => `${entryName}.mjs`,
},
rollupOptions: {
external: nodeModules,
output: {
// Keep recovery runnable while an interrupted update is being restored.
codeSplitting: false,
// Bundled CommonJS dependencies still need Node's native require.
banner: [
"import { createRequire as __snowlumaCreateRequire } from 'node:module';",
'const require = __snowlumaCreateRequire(import.meta.url);',
].join('\n'),
},
},
// Emit to monorepo root dist/ so the existing release pipeline keeps working.
outDir: distDir,
// Required since outDir is outside the vite project root.
emptyOutDir: true,
},
define: {
__BUILD_WEBUI__: process.env.BUILD_WEBUI === 'true',
__PACKAGED__: true,
},
});
export default defineConfig(({ mode }): UserConfig => {
if (mode === 'development') {
return {
...BaseConfig(true),
plugins: [...BaseConfigPlugin],
};
}
return {
...BaseConfig(),
plugins: [...BaseConfigPlugin],
};
});