feat: support commerical plugin server inject

This commit is contained in:
Jian Lu 2025-04-03 20:05:49 +08:00
parent a7301b5e47
commit 1a3d11f94e
4 changed files with 183 additions and 4 deletions

View File

@ -26,12 +26,14 @@
"@vercel/ncc": "0.36.1",
"babel-loader": "^9.2.1",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"bundle-require": "^5.1.0",
"chalk": "2.4.2",
"css-loader": "^6.8.1",
"esbuild-register": "^3.4.2",
"fast-glob": "^3.3.1",
"gulp": "4.0.2",
"gulp-typescript": "6.0.0-alpha.1",
"javascript-obfuscator": "^4.1.1",
"less": "^4.2.0",
"less-loader": "^12.2.0",
"postcss": "^8.4.29",
@ -40,7 +42,7 @@
"react-imported-component": "^6.5.4",
"style-loader": "^3.3.3",
"tar": "^6.2.0",
"tsup": "8.2.4",
"tsup": "^8.4.0",
"typescript": "5.1.3",
"update-notifier": "3.0.0",
"vite-plugin-css-injected-by-js": "^3.2.1",
@ -49,7 +51,8 @@
},
"license": "AGPL-3.0",
"scripts": {
"build": "tsup"
"build": "tsup",
"build:watch": "tsup --watch"
},
"gitHead": "d0b4efe4be55f8c79a98a331d99d9f8cf99021a1"
}

View File

@ -14,7 +14,7 @@ import fg from 'fast-glob';
import fs from 'fs-extra';
import path from 'path';
import { build as tsupBuild } from 'tsup';
import * as bundleRequire from 'bundle-require';
import { EsbuildSupportExts, globExcludeFiles } from './constant';
import { PkgLog, UserConfig, getPackageJson } from './utils';
import {
@ -27,6 +27,8 @@ import {
} from './utils/buildPluginUtils';
import { getDepPkgPath, getDepsConfig } from './utils/getDepsConfig';
import { RsdoctorRspackPlugin } from '@rsdoctor/rspack-plugin';
import { obfuscate } from './utils/obfuscationResult';
import pluginEsbuildCommercialInject from './plugins/pluginEsbuildCommercialInject';
const validExts = ['.ts', '.tsx', '.js', '.jsx', '.mjs'];
const serverGlobalFiles: string[] = ['src/**', '!src/client/**', ...globExcludeFiles];
@ -307,6 +309,83 @@ export async function buildPluginServer(cwd: string, userConfig: UserConfig, sou
await buildServerDeps(cwd, serverFiles, log);
}
export async function buildProPluginServer(cwd: string, userConfig: UserConfig, sourcemap: boolean, log: PkgLog) {
log('build pro plugin server source');
const packageJson = getPackageJson(cwd);
const serverFiles = fg.globSync(serverGlobalFiles, { cwd, absolute: true });
buildCheck({ cwd, packageJson, entry: 'server', files: serverFiles, log });
const otherExts = Array.from(
new Set(serverFiles.map((item) => path.extname(item)).filter((item) => !EsbuildSupportExts.includes(item))),
);
if (otherExts.length) {
log('%s will not be processed, only be copied to the dist directory.', chalk.yellow(otherExts.join(',')));
}
deleteServerFiles(cwd, log);
// remove compilerOptions.paths in tsconfig.json
let tsconfig = bundleRequire.loadTsConfig(path.join(cwd, 'tsconfig.json'));
fs.writeFileSync(path.join(cwd, 'tsconfig.json'), JSON.stringify({
...tsconfig.data,
compilerOptions: { ...tsconfig.data.compilerOptions, paths: [] }
}, null, 2));
tsconfig = bundleRequire.loadTsConfig(path.join(cwd, 'tsconfig.json'));
// convert all ts to js, some files may not be referenced by the entry file
await tsupBuild(
userConfig.modifyTsupConfig({
entry: serverFiles,
splitting: false,
clean: false,
bundle: false,
silent: true,
treeshake: false,
target: 'node16',
sourcemap,
outDir: path.join(cwd, target_dir),
format: 'cjs',
skipNodeModulesBundle: true,
loader: {
...otherExts.reduce((prev, cur) => ({ ...prev, [cur]: 'copy' }), {}),
'.json': 'copy',
},
}),
);
const entryFile = path.join(cwd, 'src/index.ts');
// bundle all files、inject commercial code and obfuscate
await tsupBuild(
userConfig.modifyTsupConfig({
entry: [entryFile],
splitting: false,
clean: false,
bundle: true,
silent: true,
treeshake: false,
target: 'node16',
sourcemap,
outDir: path.join(cwd, target_dir),
format: 'cjs',
skipNodeModulesBundle: true,
tsconfig: tsconfig.path,
loader: {
...otherExts.reduce((prev, cur) => ({ ...prev, [cur]: 'copy' }), {}),
'.json': 'copy',
},
esbuildPlugins: [pluginEsbuildCommercialInject],
onSuccess: async () => {
const serverFiles = [path.join(cwd, target_dir, 'index.js')];
serverFiles.forEach((file) => {
obfuscate(file);
});
},
}),
);
fs.removeSync(tsconfig.path);
await buildServerDeps(cwd, serverFiles, log);
}
export async function buildPluginClient(cwd: string, userConfig: UserConfig, sourcemap: boolean, log: PkgLog) {
log('build plugin client');
const packageJson = getPackageJson(cwd);
@ -567,6 +646,10 @@ __webpack_require__.p = (function() {
export async function buildPlugin(cwd: string, userConfig: UserConfig, sourcemap: boolean, log: PkgLog) {
await buildPluginClient(cwd, userConfig, sourcemap, log);
await buildPluginServer(cwd, userConfig, sourcemap, log);
if (cwd.includes('/pro-plugins') && !cwd.includes('plugin-commercial')) {
await buildProPluginServer(cwd, userConfig, sourcemap, log);
} else {
await buildPluginServer(cwd, userConfig, sourcemap, log);
}
writeExternalPackageVersion(cwd, log);
}

View File

@ -0,0 +1,45 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { transform } from 'esbuild';
import * as path from 'path';
import fs from 'node:fs'
const pluginEsbuildCommercialInject = {
name: 'plugin-esbuild-commercial-inject',
setup(build) {
build.onLoad({ filter: /src\/index\.ts$/ }, async (args) => {
let text = fs.readFileSync(args.path, 'utf8');
const regex = /export\s*\{\s*default\s*\}\s*from\s*(?:'([^']*)'|"([^"]*)");?/; // match: export { default } from './plugin';
const match = text.match(regex);
if (match) {
text = text.replace(regex, ``);
const moduleName = match[1] || match[2];
text =
`
import { withCommercial } from '@nocobase/plugin-for-commercial/server';
import _plugin from '${moduleName}';
export default withCommercial(_plugin);
${text}
`;
console.log(`${args.path} insert commercial server code`);
} else {
console.error(`${args.path} can't insert commercial code`);
}
return {
contents: text,
loader: 'ts',
}
})
},
};
export default pluginEsbuildCommercialInject

View File

@ -0,0 +1,48 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import fs from 'fs-extra';
import * as JavaScriptObfuscator from 'javascript-obfuscator';
export const obfuscate = (filePath: string) => {
const fileContent = fs.readFileSync(filePath, 'utf8');
const obfuscationResult = JavaScriptObfuscator.obfuscate(fileContent, {
compact: true,
controlFlowFlattening: true,
controlFlowFlatteningThreshold: 0.75,
deadCodeInjection: true,
deadCodeInjectionThreshold: 0.4,
debugProtection: false,
debugProtectionInterval: 0,
disableConsoleOutput: true,
identifierNamesGenerator: 'hexadecimal',
log: false,
numbersToExpressions: true,
renameGlobals: false,
selfDefending: true,
simplify: true,
splitStrings: true,
splitStringsChunkLength: 10,
stringArray: true,
stringArrayCallsTransform: true,
stringArrayCallsTransformThreshold: 0.75,
stringArrayEncoding: ['base64'],
stringArrayIndexShift: true,
stringArrayRotate: true,
stringArrayShuffle: true,
stringArrayWrappersCount: 2,
stringArrayWrappersChainedCalls: true,
stringArrayWrappersParametersMaxCount: 4,
stringArrayWrappersType: 'function',
stringArrayThreshold: 0.75,
transformObjectKeys: true,
unicodeEscapeSequence: false
});
fs.writeFileSync(filePath, obfuscationResult.getObfuscatedCode(), 'utf8');
};