Refine desktop setup and remove bundled app center apps
This commit is contained in:
@@ -19,8 +19,9 @@
|
||||
* @mariozechner/clipboard).
|
||||
*/
|
||||
|
||||
const { cpSync, existsSync, readdirSync, rmSync, statSync, mkdirSync, realpathSync } = require('fs');
|
||||
const { cpSync, existsSync, readdirSync, rmSync, statSync, mkdirSync, realpathSync, readFileSync, writeFileSync } = require('fs');
|
||||
const { join, dirname, basename, relative } = require('path');
|
||||
const ts = require('typescript');
|
||||
|
||||
// On Windows, paths in pnpm's virtual store can exceed the default MAX_PATH
|
||||
// limit (260 chars). Node.js 18.17+ respects the system LongPathsEnabled
|
||||
@@ -280,30 +281,6 @@ function removeOptionalNativeClipboard(nodeModulesDir) {
|
||||
return removed;
|
||||
}
|
||||
|
||||
function copyNianxxPlayNodeModules(resourcesDir, platform, arch) {
|
||||
const src = join(__dirname, '..', 'build', 'apps', 'nianxx-play', 'node_modules');
|
||||
const nianxxPlayRoot = join(resourcesDir, 'resources', 'nianxx-play');
|
||||
const dest = join(nianxxPlayRoot, 'node_modules');
|
||||
|
||||
if (!existsSync(nianxxPlayRoot)) return;
|
||||
if (!existsSync(src)) {
|
||||
console.warn('[after-pack] ⚠️ build/apps/nianxx-play/node_modules not found. Run prepare:nianxx-play first.');
|
||||
return;
|
||||
}
|
||||
|
||||
const depCount = readdirSync(src, { withFileTypes: true })
|
||||
.filter(d => d.isDirectory() && d.name !== '.bin')
|
||||
.length;
|
||||
|
||||
console.log(`[after-pack] Copying ${depCount} NianxxPlay dependencies to ${dest} ...`);
|
||||
rmSync(dest, { recursive: true, force: true });
|
||||
cpSync(src, dest, { recursive: true, dereference: true });
|
||||
cleanupUnnecessaryFiles(dest);
|
||||
cleanupKoffi(dest, platform, arch);
|
||||
cleanupNativePlatformPackages(dest, platform, arch);
|
||||
console.log('[after-pack] ✅ NianxxPlay node_modules copied.');
|
||||
}
|
||||
|
||||
// ── Broken module patcher ─────────────────────────────────────────────────────
|
||||
// Some bundled packages have transpiled CJS that sets `module.exports = exports.default`
|
||||
// without ever assigning `exports.default`, leaving module.exports === undefined.
|
||||
@@ -488,7 +465,8 @@ function patchPluginIds(pluginDir, expectedId) {
|
||||
if (!existsSync(pkgJsonPath)) return;
|
||||
|
||||
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8'));
|
||||
const entryFiles = [pkg.main, pkg.module].filter(Boolean);
|
||||
const extensionEntries = Array.isArray(pkg.openclaw?.extensions) ? pkg.openclaw.extensions : [];
|
||||
const entryFiles = [...new Set([pkg.main, pkg.module, ...extensionEntries].filter(Boolean))];
|
||||
|
||||
for (const entry of entryFiles) {
|
||||
const entryPath = join(pluginDir, entry);
|
||||
@@ -520,6 +498,194 @@ function patchPluginIds(pluginDir, expectedId) {
|
||||
// bundle-openclaw-plugins.mjs so the packaged app is self-contained even when
|
||||
// build/openclaw-plugins/ was not pre-generated.
|
||||
|
||||
function readJsonFile(filePath) {
|
||||
return JSON.parse(readFileSync(filePath, 'utf8'));
|
||||
}
|
||||
|
||||
function writeJsonFile(filePath, value) {
|
||||
writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
||||
}
|
||||
|
||||
function normalizeEntryPath(entry) {
|
||||
if (typeof entry !== 'string') return null;
|
||||
const trimmed = entry.trim();
|
||||
if (!trimmed || require('path').isAbsolute(trimmed)) return null;
|
||||
return trimmed.replace(/^\.\//, '');
|
||||
}
|
||||
|
||||
function toPackageEntry(entry) {
|
||||
return entry.startsWith('.') ? entry : `./${entry}`;
|
||||
}
|
||||
|
||||
function isJavaScriptEntry(entry) {
|
||||
return /\.(?:cjs|mjs|js)$/i.test(entry);
|
||||
}
|
||||
|
||||
function entryExists(pluginDir, entry) {
|
||||
const normalized = normalizeEntryPath(entry);
|
||||
return Boolean(normalized) && existsSync(join(pluginDir, normalized));
|
||||
}
|
||||
|
||||
function collectRuntimeEntryHints(pkg) {
|
||||
const hints = [];
|
||||
const extensions = pkg.openclaw?.extensions;
|
||||
if (Array.isArray(extensions)) hints.push(...extensions);
|
||||
if (typeof pkg.main === 'string') hints.push(pkg.main);
|
||||
if (typeof pkg.module === 'string') hints.push(pkg.module);
|
||||
hints.push('./dist/index.js', './index.js');
|
||||
return [...new Set(hints.map(normalizeEntryPath).filter(Boolean))];
|
||||
}
|
||||
|
||||
function findExistingRuntimeEntry(pluginDir, pkg) {
|
||||
for (const hint of collectRuntimeEntryHints(pkg)) {
|
||||
if (isJavaScriptEntry(hint) && existsSync(join(pluginDir, hint))) {
|
||||
return hint;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function patchRuntimeEntryHints(pluginDir) {
|
||||
const pkgJsonPath = join(pluginDir, 'package.json');
|
||||
if (!existsSync(pkgJsonPath)) return null;
|
||||
|
||||
const pkg = readJsonFile(pkgJsonPath);
|
||||
let modified = false;
|
||||
|
||||
const extensions = pkg.openclaw?.extensions;
|
||||
if (Array.isArray(extensions)) {
|
||||
const patchedExtensions = extensions.map((entry) => {
|
||||
const normalized = normalizeEntryPath(entry);
|
||||
if (!normalized?.endsWith('.ts')) return entry;
|
||||
const jsEntry = `dist/${normalized.replace(/\.ts$/i, '.js')}`;
|
||||
return existsSync(join(pluginDir, jsEntry)) ? toPackageEntry(jsEntry) : entry;
|
||||
});
|
||||
if (JSON.stringify(patchedExtensions) !== JSON.stringify(extensions)) {
|
||||
pkg.openclaw.extensions = patchedExtensions;
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
const existingRuntimeEntry = findExistingRuntimeEntry(pluginDir, pkg);
|
||||
if (existingRuntimeEntry) {
|
||||
if (typeof pkg.main !== 'string' || !entryExists(pluginDir, pkg.main)) {
|
||||
pkg.main = toPackageEntry(existingRuntimeEntry);
|
||||
modified = true;
|
||||
}
|
||||
if (typeof pkg.module === 'string' && !entryExists(pluginDir, pkg.module)) {
|
||||
pkg.module = toPackageEntry(existingRuntimeEntry);
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
writeJsonFile(pkgJsonPath, pkg);
|
||||
}
|
||||
|
||||
return existingRuntimeEntry;
|
||||
}
|
||||
|
||||
function patchManifestChannelConfigs(pluginDir) {
|
||||
const manifestPath = join(pluginDir, 'openclaw.plugin.json');
|
||||
if (!existsSync(manifestPath)) return;
|
||||
|
||||
const manifest = readJsonFile(manifestPath);
|
||||
if (manifest.channelConfigs || !Array.isArray(manifest.channels)) return;
|
||||
|
||||
const schema = { type: 'object' };
|
||||
manifest.channelConfigs = Object.fromEntries(
|
||||
manifest.channels
|
||||
.filter((channelId) => typeof channelId === 'string' && channelId.trim().length > 0)
|
||||
.map((channelId) => [channelId, { schema }]),
|
||||
);
|
||||
writeJsonFile(manifestPath, manifest);
|
||||
}
|
||||
|
||||
function collectTypeScriptFiles(pluginDir) {
|
||||
const result = [];
|
||||
const skipDirs = new Set(['node_modules', 'dist', '.git']);
|
||||
|
||||
function walk(currentDir) {
|
||||
for (const entry of readdirSync(currentDir, { withFileTypes: true })) {
|
||||
if (skipDirs.has(entry.name)) continue;
|
||||
const fullPath = join(currentDir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
walk(fullPath);
|
||||
continue;
|
||||
}
|
||||
if (!entry.isFile()) continue;
|
||||
if (!entry.name.endsWith('.ts')) continue;
|
||||
if (entry.name.endsWith('.d.ts') || entry.name.endsWith('.test.ts')) continue;
|
||||
result.push(fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
walk(pluginDir);
|
||||
return result;
|
||||
}
|
||||
|
||||
function compileTypeScriptPluginIfNeeded(pluginDir, pluginId) {
|
||||
const pkgJsonPath = join(pluginDir, 'package.json');
|
||||
if (!existsSync(pkgJsonPath)) return;
|
||||
|
||||
const pkg = readJsonFile(pkgJsonPath);
|
||||
const extensionEntries = Array.isArray(pkg.openclaw?.extensions) ? pkg.openclaw.extensions : [];
|
||||
const hasTypeScriptEntry = extensionEntries.some((entry) => normalizeEntryPath(entry)?.endsWith('.ts'));
|
||||
if (!hasTypeScriptEntry) {
|
||||
const runtimeEntry = patchRuntimeEntryHints(pluginDir);
|
||||
if (runtimeEntry) {
|
||||
console.log(`[after-pack] Runtime entry: ${runtimeEntry}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const tsFiles = collectTypeScriptFiles(pluginDir);
|
||||
if (tsFiles.length === 0) {
|
||||
throw new Error(`Plugin ${pluginId} declares TypeScript entries but no .ts source files were found.`);
|
||||
}
|
||||
|
||||
const distDir = join(pluginDir, 'dist');
|
||||
rmSync(distDir, { recursive: true, force: true });
|
||||
|
||||
for (const sourcePath of tsFiles) {
|
||||
const source = readFileSync(sourcePath, 'utf8');
|
||||
const output = ts.transpileModule(source, {
|
||||
compilerOptions: {
|
||||
target: ts.ScriptTarget.ES2022,
|
||||
module: ts.ModuleKind.ES2022,
|
||||
esModuleInterop: true,
|
||||
importsNotUsedAsValues: ts.ImportsNotUsedAsValues.Remove,
|
||||
sourceMap: false,
|
||||
inlineSources: false,
|
||||
},
|
||||
fileName: sourcePath,
|
||||
reportDiagnostics: true,
|
||||
});
|
||||
|
||||
const diagnostics = output.diagnostics ?? [];
|
||||
const blocking = diagnostics.filter((diag) => diag.category === ts.DiagnosticCategory.Error);
|
||||
if (blocking.length > 0) {
|
||||
const message = blocking
|
||||
.map((diag) => ts.flattenDiagnosticMessageText(diag.messageText, '\n'))
|
||||
.join('\n');
|
||||
throw new Error(`Failed to transpile ${relative(pluginDir, sourcePath)}:\n${message}`);
|
||||
}
|
||||
|
||||
const rel = relative(pluginDir, sourcePath).replace(/\.ts$/i, '.js');
|
||||
const outputPath = join(distDir, rel);
|
||||
mkdirSync(dirname(outputPath), { recursive: true });
|
||||
writeFileSync(outputPath, output.outputText, 'utf8');
|
||||
}
|
||||
|
||||
patchRuntimeEntryHints(pluginDir);
|
||||
const runtimeEntry = findExistingRuntimeEntry(pluginDir, readJsonFile(pkgJsonPath));
|
||||
if (!runtimeEntry) {
|
||||
throw new Error(`Plugin ${pluginId} did not produce a loadable JavaScript runtime entry.`);
|
||||
}
|
||||
|
||||
console.log(`[after-pack] Compiled ${tsFiles.length} TypeScript files -> dist/ (${runtimeEntry})`);
|
||||
}
|
||||
|
||||
function getVirtualStoreNodeModules(realPkgPath) {
|
||||
let dir = realPkgPath;
|
||||
while (dir !== dirname(dir)) {
|
||||
@@ -670,10 +836,6 @@ exports.default = async function afterPack(context) {
|
||||
console.log(`[after-pack] ✅ Removed optional native clipboard packages (${clipboardRemoved}) to avoid macOS Gatekeeper prompts.`);
|
||||
}
|
||||
|
||||
// 1.0 Copy bundled large-app runtime deps that electron-builder skips because
|
||||
// node_modules/ is ignored globally.
|
||||
copyNianxxPlayNodeModules(resourcesDir, platform, arch);
|
||||
|
||||
// Patch broken modules whose CJS transpiled output sets module.exports = undefined,
|
||||
// causing TypeError in Node.js 22+ ESM interop.
|
||||
patchBrokenModules(dest);
|
||||
@@ -685,6 +847,7 @@ exports.default = async function afterPack(context) {
|
||||
// - node_modules/ is excluded by .gitignore so the deps copy must be manual
|
||||
const BUNDLED_PLUGINS = [
|
||||
{ npmName: '@tencent-weixin/openclaw-weixin', pluginId: 'openclaw-weixin' },
|
||||
{ npmName: '@larksuite/openclaw-lark', pluginId: 'openclaw-lark' },
|
||||
];
|
||||
|
||||
mkdirSync(pluginsDestRoot, { recursive: true });
|
||||
@@ -700,6 +863,8 @@ exports.default = async function afterPack(context) {
|
||||
cleanupNativePlatformPackages(pluginNM, platform, arch);
|
||||
}
|
||||
// Fix hardcoded plugin ID mismatches in compiled JS
|
||||
patchManifestChannelConfigs(pluginDestDir);
|
||||
compileTypeScriptPluginIfNeeded(pluginDestDir, pluginId);
|
||||
patchPluginIds(pluginDestDir, pluginId);
|
||||
}
|
||||
}
|
||||
@@ -715,6 +880,7 @@ exports.default = async function afterPack(context) {
|
||||
rmSync(pluginDestDir, { recursive: true, force: true });
|
||||
cpSync(sourceDir, pluginDestDir, { recursive: true, dereference: true });
|
||||
cleanupUnnecessaryFiles(pluginDestDir);
|
||||
patchManifestChannelConfigs(pluginDestDir);
|
||||
patchPluginIds(pluginDestDir, entry.name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
* Build a self-contained mirror of OpenClaw third-party plugins for packaging.
|
||||
* Current plugins:
|
||||
* - @tencent-weixin/openclaw-weixin -> build/openclaw-plugins/openclaw-weixin
|
||||
* - @larksuite/openclaw-lark -> build/openclaw-plugins/openclaw-lark
|
||||
*
|
||||
* The output plugin directory contains:
|
||||
* - plugin source files (index.ts, openclaw.plugin.json, package.json, ...)
|
||||
* - plugin runtime files (dist/index.js or index.js, openclaw.plugin.json, package.json, ...)
|
||||
* - plugin runtime node_modules/ (flattened direct + transitive deps)
|
||||
*/
|
||||
|
||||
@@ -16,6 +17,7 @@ import 'zx/globals';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import ts from 'typescript';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = path.resolve(__dirname, '..');
|
||||
@@ -36,8 +38,197 @@ function normWin(p) {
|
||||
|
||||
const PLUGINS = [
|
||||
{ npmName: '@tencent-weixin/openclaw-weixin', pluginId: 'openclaw-weixin' },
|
||||
{ npmName: '@larksuite/openclaw-lark', pluginId: 'openclaw-lark' },
|
||||
];
|
||||
|
||||
function readJsonFile(filePath) {
|
||||
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
}
|
||||
|
||||
function writeJsonFile(filePath, value) {
|
||||
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
||||
}
|
||||
|
||||
function normalizeEntryPath(entry) {
|
||||
if (typeof entry !== 'string') return null;
|
||||
const trimmed = entry.trim();
|
||||
if (!trimmed || path.isAbsolute(trimmed)) return null;
|
||||
return trimmed.replace(/^\.\//, '');
|
||||
}
|
||||
|
||||
function toPackageEntry(entry) {
|
||||
return entry.startsWith('.') ? entry : `./${entry}`;
|
||||
}
|
||||
|
||||
function isJavaScriptEntry(entry) {
|
||||
return /\.(?:cjs|mjs|js)$/i.test(entry);
|
||||
}
|
||||
|
||||
function entryExists(pluginDir, entry) {
|
||||
const normalized = normalizeEntryPath(entry);
|
||||
return Boolean(normalized) && fs.existsSync(path.join(pluginDir, normalized));
|
||||
}
|
||||
|
||||
function collectRuntimeEntryHints(pkg) {
|
||||
const hints = [];
|
||||
const extensions = pkg.openclaw?.extensions;
|
||||
if (Array.isArray(extensions)) hints.push(...extensions);
|
||||
if (typeof pkg.main === 'string') hints.push(pkg.main);
|
||||
if (typeof pkg.module === 'string') hints.push(pkg.module);
|
||||
hints.push('./dist/index.js', './index.js');
|
||||
return [...new Set(hints.map(normalizeEntryPath).filter(Boolean))];
|
||||
}
|
||||
|
||||
function findExistingRuntimeEntry(pluginDir, pkg) {
|
||||
for (const hint of collectRuntimeEntryHints(pkg)) {
|
||||
if (isJavaScriptEntry(hint) && fs.existsSync(path.join(pluginDir, hint))) {
|
||||
return hint;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function patchRuntimeEntryHints(pluginDir) {
|
||||
const pkgJsonPath = path.join(pluginDir, 'package.json');
|
||||
if (!fs.existsSync(pkgJsonPath)) return null;
|
||||
|
||||
const pkg = readJsonFile(pkgJsonPath);
|
||||
let modified = false;
|
||||
|
||||
const extensions = pkg.openclaw?.extensions;
|
||||
if (Array.isArray(extensions)) {
|
||||
const patchedExtensions = extensions.map((entry) => {
|
||||
const normalized = normalizeEntryPath(entry);
|
||||
if (!normalized?.endsWith('.ts')) return entry;
|
||||
const jsEntry = `dist/${normalized.replace(/\.ts$/i, '.js')}`;
|
||||
return fs.existsSync(path.join(pluginDir, jsEntry)) ? toPackageEntry(jsEntry) : entry;
|
||||
});
|
||||
if (JSON.stringify(patchedExtensions) !== JSON.stringify(extensions)) {
|
||||
pkg.openclaw.extensions = patchedExtensions;
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
const existingRuntimeEntry = findExistingRuntimeEntry(pluginDir, pkg);
|
||||
if (existingRuntimeEntry) {
|
||||
if (typeof pkg.main !== 'string' || !entryExists(pluginDir, pkg.main)) {
|
||||
pkg.main = toPackageEntry(existingRuntimeEntry);
|
||||
modified = true;
|
||||
}
|
||||
if (typeof pkg.module === 'string' && !entryExists(pluginDir, pkg.module)) {
|
||||
pkg.module = toPackageEntry(existingRuntimeEntry);
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
writeJsonFile(pkgJsonPath, pkg);
|
||||
}
|
||||
|
||||
return existingRuntimeEntry;
|
||||
}
|
||||
|
||||
function patchManifestChannelConfigs(pluginDir) {
|
||||
const manifestPath = path.join(pluginDir, 'openclaw.plugin.json');
|
||||
if (!fs.existsSync(manifestPath)) return;
|
||||
|
||||
const manifest = readJsonFile(manifestPath);
|
||||
if (manifest.channelConfigs || !Array.isArray(manifest.channels)) return;
|
||||
|
||||
const schema = { type: 'object' };
|
||||
manifest.channelConfigs = Object.fromEntries(
|
||||
manifest.channels
|
||||
.filter((channelId) => typeof channelId === 'string' && channelId.trim().length > 0)
|
||||
.map((channelId) => [channelId, { schema }]),
|
||||
);
|
||||
writeJsonFile(manifestPath, manifest);
|
||||
}
|
||||
|
||||
function collectTypeScriptFiles(pluginDir) {
|
||||
const result = [];
|
||||
const skipDirs = new Set(['node_modules', 'dist', '.git']);
|
||||
|
||||
function walk(currentDir) {
|
||||
for (const entry of fs.readdirSync(currentDir, { withFileTypes: true })) {
|
||||
if (skipDirs.has(entry.name)) continue;
|
||||
const fullPath = path.join(currentDir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
walk(fullPath);
|
||||
continue;
|
||||
}
|
||||
if (!entry.isFile()) continue;
|
||||
if (!entry.name.endsWith('.ts')) continue;
|
||||
if (entry.name.endsWith('.d.ts') || entry.name.endsWith('.test.ts')) continue;
|
||||
result.push(fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
walk(pluginDir);
|
||||
return result;
|
||||
}
|
||||
|
||||
function compileTypeScriptPluginIfNeeded(pluginDir, pluginId) {
|
||||
const pkgJsonPath = path.join(pluginDir, 'package.json');
|
||||
if (!fs.existsSync(pkgJsonPath)) return;
|
||||
|
||||
const pkg = readJsonFile(pkgJsonPath);
|
||||
const extensionEntries = Array.isArray(pkg.openclaw?.extensions) ? pkg.openclaw.extensions : [];
|
||||
const hasTypeScriptEntry = extensionEntries.some((entry) => normalizeEntryPath(entry)?.endsWith('.ts'));
|
||||
if (!hasTypeScriptEntry) {
|
||||
const runtimeEntry = patchRuntimeEntryHints(pluginDir);
|
||||
if (runtimeEntry) {
|
||||
echo` 🔗 Runtime entry: ${runtimeEntry}`;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const tsFiles = collectTypeScriptFiles(pluginDir);
|
||||
if (tsFiles.length === 0) {
|
||||
throw new Error(`Plugin ${pluginId} declares TypeScript entries but no .ts source files were found.`);
|
||||
}
|
||||
|
||||
const distDir = path.join(pluginDir, 'dist');
|
||||
fs.rmSync(distDir, { recursive: true, force: true });
|
||||
|
||||
for (const sourcePath of tsFiles) {
|
||||
const source = fs.readFileSync(sourcePath, 'utf8');
|
||||
const output = ts.transpileModule(source, {
|
||||
compilerOptions: {
|
||||
target: ts.ScriptTarget.ES2022,
|
||||
module: ts.ModuleKind.ES2022,
|
||||
esModuleInterop: true,
|
||||
importsNotUsedAsValues: ts.ImportsNotUsedAsValues.Remove,
|
||||
sourceMap: false,
|
||||
inlineSources: false,
|
||||
},
|
||||
fileName: sourcePath,
|
||||
reportDiagnostics: true,
|
||||
});
|
||||
|
||||
const diagnostics = output.diagnostics ?? [];
|
||||
const blocking = diagnostics.filter((diag) => diag.category === ts.DiagnosticCategory.Error);
|
||||
if (blocking.length > 0) {
|
||||
const message = blocking
|
||||
.map((diag) => ts.flattenDiagnosticMessageText(diag.messageText, '\n'))
|
||||
.join('\n');
|
||||
throw new Error(`Failed to transpile ${path.relative(pluginDir, sourcePath)}:\n${message}`);
|
||||
}
|
||||
|
||||
const rel = path.relative(pluginDir, sourcePath).replace(/\.ts$/i, '.js');
|
||||
const outputPath = path.join(distDir, rel);
|
||||
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
||||
fs.writeFileSync(outputPath, output.outputText, 'utf8');
|
||||
}
|
||||
|
||||
patchRuntimeEntryHints(pluginDir);
|
||||
const runtimeEntry = findExistingRuntimeEntry(pluginDir, readJsonFile(pkgJsonPath));
|
||||
if (!runtimeEntry) {
|
||||
throw new Error(`Plugin ${pluginId} did not produce a loadable JavaScript runtime entry.`);
|
||||
}
|
||||
|
||||
echo` 🛠️ Compiled ${tsFiles.length} TypeScript files -> dist/ (${runtimeEntry})`;
|
||||
}
|
||||
|
||||
function getVirtualStoreNodeModules(realPkgPath) {
|
||||
let dir = realPkgPath;
|
||||
while (dir !== path.dirname(dir)) {
|
||||
@@ -171,6 +362,8 @@ function bundleOnePlugin({ npmName, pluginId }) {
|
||||
// 4) Patch plugin ID mismatch: some npm packages hardcode a different ID in
|
||||
// their JS output than what openclaw.plugin.json declares. The Gateway
|
||||
// validates that these match, so we fix it post-copy.
|
||||
patchManifestChannelConfigs(outputDir);
|
||||
compileTypeScriptPluginIfNeeded(outputDir, pluginId);
|
||||
patchPluginId(outputDir, pluginId);
|
||||
|
||||
echo` ✅ ${pluginId}: copied ${copiedCount} deps (skipped dupes: ${skippedDupes})`;
|
||||
@@ -196,7 +389,8 @@ function patchPluginId(pluginDir, expectedId) {
|
||||
if (!fs.existsSync(pkgJsonPath)) return;
|
||||
|
||||
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
|
||||
const entryFiles = [pkg.main, pkg.module].filter(Boolean);
|
||||
const extensionEntries = Array.isArray(pkg.openclaw?.extensions) ? pkg.openclaw.extensions : [];
|
||||
const entryFiles = [...new Set([pkg.main, pkg.module, ...extensionEntries].filter(Boolean))];
|
||||
|
||||
// Known ID mismatches to patch. Keys are the wrong ID found in compiled JS,
|
||||
// values are the correct ID (must match openclaw.plugin.json).
|
||||
|
||||
@@ -1315,7 +1315,7 @@ echo` 🧭 Wrote Yinian runtime patch marker ${YINIAN_RUNTIME_PATCH_MARKER.ver
|
||||
// 8. Verify the bundle
|
||||
const entryExists = fs.existsSync(path.join(OUTPUT, 'openclaw.mjs'));
|
||||
const distExists = fs.existsSync(path.join(OUTPUT, 'dist', 'entry.js'));
|
||||
const requiredTemplateFiles = ['AGENTS.md', 'TOOLS.md', 'HEARTBEAT.md'];
|
||||
const requiredTemplateFiles = ['SOUL.md', 'IDENTITY.md', 'USER.md', 'AGENTS.md', 'TOOLS.md', 'HEARTBEAT.md', 'BOOT.md'];
|
||||
const missingTemplateFiles = requiredTemplateFiles.filter((fileName) => (
|
||||
!fs.existsSync(path.join(OUTPUT, 'docs', 'reference', 'templates', fileName))
|
||||
));
|
||||
|
||||
@@ -13,7 +13,18 @@ const SHOULD_BUNDLE = process.env.YINIAN_BUNDLE_MODEL_AUTH === '1';
|
||||
const SOURCE_AUTH_PROFILES = process.env.YINIAN_MODEL_AUTH_SOURCE
|
||||
? resolve(process.env.YINIAN_MODEL_AUTH_SOURCE)
|
||||
: join(homedir(), '.openclaw', 'agents', 'main', 'agent', 'auth-profiles.json');
|
||||
const PROFILE_IDS = ['minimax:default', 'minimax:cn'];
|
||||
const MODEL_PROVIDER_KEY = (process.env.YINIAN_MODEL_PROVIDER_KEY || 'yinian-model').trim();
|
||||
const MODEL_ID = (process.env.YINIAN_MODEL_ID || '').trim();
|
||||
const MODEL_NAME = (process.env.YINIAN_MODEL_NAME || MODEL_ID).trim();
|
||||
const MODEL_BASE_URL = (process.env.YINIAN_MODEL_BASE_URL || '').trim();
|
||||
const MODEL_API = (process.env.YINIAN_MODEL_API || 'openai-completions').trim();
|
||||
const MODEL_AUTH_PROFILE_ID = (process.env.YINIAN_MODEL_AUTH_PROFILE_ID || `${MODEL_PROVIDER_KEY}:default`).trim();
|
||||
const SOURCE_PROFILE_ID = (process.env.YINIAN_MODEL_AUTH_SOURCE_PROFILE_ID || '').trim();
|
||||
const MODEL_FALLBACKS = (process.env.YINIAN_MODEL_FALLBACKS || '')
|
||||
.split(',')
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean);
|
||||
const LEGACY_SOURCE_PROFILE_IDS = ['minimax:default', 'minimax:cn'];
|
||||
|
||||
function log(message) {
|
||||
console.log(`[yinian-model-auth] ${message}`);
|
||||
@@ -44,7 +55,7 @@ if (!SHOULD_BUNDLE) {
|
||||
bundled: false,
|
||||
reason: 'YINIAN_BUNDLE_MODEL_AUTH is not enabled',
|
||||
});
|
||||
log('pilot model auth bundling disabled.');
|
||||
log('pilot model auth bundling disabled. Customer pilot installers must use package:pilot or set YINIAN_BUNDLE_MODEL_AUTH=1.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
@@ -52,26 +63,54 @@ if (!existsSync(SOURCE_AUTH_PROFILES)) {
|
||||
fail(`source auth profiles not found: ${SOURCE_AUTH_PROFILES}`);
|
||||
}
|
||||
|
||||
if (!MODEL_PROVIDER_KEY) {
|
||||
fail('YINIAN_MODEL_PROVIDER_KEY is required for model auth bundling.');
|
||||
}
|
||||
|
||||
if (!MODEL_ID) {
|
||||
fail('YINIAN_MODEL_ID is required for model auth bundling.');
|
||||
}
|
||||
|
||||
if (!MODEL_BASE_URL) {
|
||||
fail('YINIAN_MODEL_BASE_URL is required for model auth bundling.');
|
||||
}
|
||||
|
||||
const source = readJson(SOURCE_AUTH_PROFILES);
|
||||
const sourceProfiles = source && typeof source === 'object' && source.profiles && typeof source.profiles === 'object'
|
||||
? source.profiles
|
||||
: {};
|
||||
const profiles = {};
|
||||
const sourceProfileIds = [
|
||||
SOURCE_PROFILE_ID,
|
||||
MODEL_AUTH_PROFILE_ID,
|
||||
`${MODEL_PROVIDER_KEY}:default`,
|
||||
...LEGACY_SOURCE_PROFILE_IDS,
|
||||
].filter(Boolean);
|
||||
const candidateProfileIds = Array.from(new Set([
|
||||
...sourceProfileIds,
|
||||
...Object.keys(sourceProfiles),
|
||||
]));
|
||||
let bundledKey = '';
|
||||
|
||||
for (const profileId of PROFILE_IDS) {
|
||||
for (const profileId of candidateProfileIds) {
|
||||
const profile = sourceProfiles[profileId];
|
||||
if (!profile || typeof profile !== 'object') continue;
|
||||
if (profile.type !== 'api_key' || profile.provider !== 'minimax') continue;
|
||||
if (profile.type !== 'api_key') continue;
|
||||
if (typeof profile.key !== 'string' || profile.key.trim().length < 8) continue;
|
||||
profiles[profileId] = {
|
||||
bundledKey = profile.key.trim();
|
||||
break;
|
||||
}
|
||||
|
||||
if (bundledKey) {
|
||||
profiles[MODEL_AUTH_PROFILE_ID] = {
|
||||
type: 'api_key',
|
||||
provider: 'minimax',
|
||||
key: profile.key,
|
||||
provider: MODEL_PROVIDER_KEY,
|
||||
key: bundledKey,
|
||||
};
|
||||
}
|
||||
|
||||
if (!profiles['minimax:default']) {
|
||||
fail('minimax:default API key profile is required for pilot model auth bundling.');
|
||||
if (!profiles[MODEL_AUTH_PROFILE_ID]) {
|
||||
fail(`API key profile is required for pilot model auth bundling. Set YINIAN_MODEL_AUTH_SOURCE_PROFILE_ID or create ${MODEL_AUTH_PROFILE_ID}.`);
|
||||
}
|
||||
|
||||
writeManifest({
|
||||
@@ -79,14 +118,23 @@ writeManifest({
|
||||
purpose: 'internal-pilot-only',
|
||||
source: 'local-openclaw-auth-profiles',
|
||||
profileIds: Object.keys(profiles),
|
||||
model: {
|
||||
providerKey: MODEL_PROVIDER_KEY,
|
||||
modelId: MODEL_ID,
|
||||
modelName: MODEL_NAME || MODEL_ID,
|
||||
baseUrl: MODEL_BASE_URL,
|
||||
api: MODEL_API,
|
||||
authProfileId: MODEL_AUTH_PROFILE_ID,
|
||||
fallbackModelRefs: MODEL_FALLBACKS,
|
||||
},
|
||||
store: {
|
||||
version: 1,
|
||||
profiles,
|
||||
order: {
|
||||
minimax: Object.keys(profiles),
|
||||
[MODEL_PROVIDER_KEY]: Object.keys(profiles),
|
||||
},
|
||||
lastGood: {
|
||||
minimax: 'minimax:default',
|
||||
[MODEL_PROVIDER_KEY]: MODEL_AUTH_PROFILE_ID,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,341 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import {
|
||||
chmodSync,
|
||||
cpSync,
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
readdirSync,
|
||||
readFileSync,
|
||||
rmSync,
|
||||
statSync,
|
||||
writeFileSync,
|
||||
} from 'node:fs';
|
||||
import { basename, dirname, join, resolve, relative } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = resolve(__dirname, '..');
|
||||
const DEFAULT_SOURCE = resolve(ROOT, '..', '..', 'NianxxPlay');
|
||||
const SOURCE_DIR = resolve(process.env.NIANXX_PLAY_DIR || DEFAULT_SOURCE);
|
||||
const OUTPUT_DIR = resolve(ROOT, 'build', 'apps', 'nianxx-play');
|
||||
const BUNDLE_RUNTIME_ENV = process.env.NIANXX_PLAY_BUNDLE_ENV === '1';
|
||||
const RUNTIME_ENV_FILE_NAME = '.env.runtime';
|
||||
|
||||
function log(message) {
|
||||
console.log(`[nianxx-play-bundle] ${message}`);
|
||||
}
|
||||
|
||||
function fail(message) {
|
||||
console.error(`[nianxx-play-bundle] ${message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function run(command, args, cwd) {
|
||||
const result = spawnSync(command, args, {
|
||||
cwd,
|
||||
env: {
|
||||
...process.env,
|
||||
NEXT_TELEMETRY_DISABLED: '1',
|
||||
},
|
||||
stdio: 'inherit',
|
||||
shell: process.platform === 'win32',
|
||||
});
|
||||
if (result.status !== 0) {
|
||||
fail(`${command} ${args.join(' ')} failed with exit code ${result.status ?? 'unknown'}`);
|
||||
}
|
||||
}
|
||||
|
||||
function readPackageVersion(packageJsonPath) {
|
||||
try {
|
||||
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
||||
return typeof pkg.version === 'string' ? pkg.version : '0.0.0';
|
||||
} catch {
|
||||
return '0.0.0';
|
||||
}
|
||||
}
|
||||
|
||||
function parseEnvFile(envPath) {
|
||||
if (!existsSync(envPath)) return [];
|
||||
const entries = [];
|
||||
const raw = readFileSync(envPath, 'utf8');
|
||||
for (const line of raw.split(/\r?\n/)) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith('#')) continue;
|
||||
const match = trimmed.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
|
||||
if (!match) continue;
|
||||
const key = match[1];
|
||||
let value = match[2].trim();
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
entries.push({ key, value });
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
function selectRuntimeEnvFile(sourceDir) {
|
||||
const explicit = process.env.NIANXX_PLAY_ENV_FILE?.trim();
|
||||
if (explicit) {
|
||||
const resolved = resolve(explicit);
|
||||
return existsSync(resolved) ? resolved : undefined;
|
||||
}
|
||||
for (const fileName of ['.env.local', '.env.production.local', '.env.production', '.env']) {
|
||||
const candidate = join(sourceDir, fileName);
|
||||
if (existsSync(candidate)) return candidate;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function encodeEnvValue(value) {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
function writeRuntimeEnvFile(sourceDir, outputDir) {
|
||||
if (!BUNDLE_RUNTIME_ENV) return { bundled: false, values: 0 };
|
||||
const envFile = selectRuntimeEnvFile(sourceDir);
|
||||
if (!envFile) {
|
||||
fail('NIANXX_PLAY_BUNDLE_ENV=1 set, but no NianxxPlay env file was found.');
|
||||
}
|
||||
const entries = parseEnvFile(envFile);
|
||||
if (!entries.length) {
|
||||
fail(`NIANXX_PLAY_BUNDLE_ENV=1 set, but env file has no usable entries: ${envFile}`);
|
||||
}
|
||||
const runtimeEnvPath = join(outputDir, RUNTIME_ENV_FILE_NAME);
|
||||
const text = [
|
||||
'# Bundled only for internal testing. Do not use in production builds.',
|
||||
...entries.map((entry) => `${entry.key}=${encodeEnvValue(entry.value)}`),
|
||||
'',
|
||||
].join('\n');
|
||||
writeFileSync(runtimeEnvPath, text, 'utf8');
|
||||
return { bundled: true, values: entries.length };
|
||||
}
|
||||
|
||||
function collectSecretLikeEnvValues(sourceDir) {
|
||||
const envFileNames = [
|
||||
'.env',
|
||||
'.env.local',
|
||||
'.env.production',
|
||||
'.env.production.local',
|
||||
];
|
||||
const sensitiveKeyPattern = /(SECRET|TOKEN|PASSWORD|PRIVATE|AUTH|API_KEY|ACCESS_KEY|KEY_ID|KEY_SECRET|SERVICE_ROLE)/i;
|
||||
const ignoredValues = new Set(['true', 'false', 'null', 'undefined', 'development', 'production']);
|
||||
const values = [];
|
||||
for (const fileName of envFileNames) {
|
||||
for (const entry of parseEnvFile(join(sourceDir, fileName))) {
|
||||
if (!sensitiveKeyPattern.test(entry.key)) continue;
|
||||
if (!entry.value || entry.value.length < 8) continue;
|
||||
if (ignoredValues.has(entry.value.toLowerCase())) continue;
|
||||
values.push(entry);
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
function shouldCopyPublic(src) {
|
||||
const name = basename(src);
|
||||
if (name === 'uploads') return false;
|
||||
const publicRelative = relative(publicDir, src).split('\\').join('/');
|
||||
if (publicRelative === 'generated-results' || publicRelative.startsWith('generated-results/')) return false;
|
||||
if (name.startsWith('.env')) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function shouldCopyRuntime(src) {
|
||||
const name = basename(src);
|
||||
if (name.startsWith('.env')) return false;
|
||||
if (name === '.data' || name === '.git' || name === '.next-cache') return false;
|
||||
const runtimeRelative = relative(standaloneDir, src).split('\\').join('/');
|
||||
if (runtimeRelative === 'public/uploads' || runtimeRelative.startsWith('public/uploads/')) return false;
|
||||
if (runtimeRelative === 'public/generated-results' || runtimeRelative.startsWith('public/generated-results/')) return false;
|
||||
if (runtimeRelative === 'uploads' || runtimeRelative.startsWith('uploads/')) return false;
|
||||
if (runtimeRelative === 'generated-results' || runtimeRelative.startsWith('generated-results/')) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function copyDir(from, to, filter = () => true) {
|
||||
if (!existsSync(from)) return false;
|
||||
mkdirSync(dirname(to), { recursive: true });
|
||||
cpSync(from, to, {
|
||||
recursive: true,
|
||||
dereference: true,
|
||||
filter,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
function normalizeBundlePermissions(dir) {
|
||||
if (!existsSync(dir)) return;
|
||||
const stats = statSync(dir);
|
||||
try {
|
||||
if (stats.isDirectory()) {
|
||||
chmodSync(dir, 0o755);
|
||||
for (const entry of readdirSync(dir)) {
|
||||
normalizeBundlePermissions(join(dir, entry));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (stats.isFile()) {
|
||||
const executable = (stats.mode & 0o111) !== 0;
|
||||
chmodSync(dir, executable ? 0o755 : 0o644);
|
||||
}
|
||||
} catch (error) {
|
||||
fail(`Unable to normalize bundle permissions for ${relative(ROOT, dir)}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function dirSizeBytes(dir) {
|
||||
if (!existsSync(dir)) return 0;
|
||||
const stats = statSync(dir);
|
||||
if (stats.isFile()) return stats.size;
|
||||
let size = 0;
|
||||
for (const entry of readdirSync(dir)) {
|
||||
size += dirSizeBytes(join(dir, entry));
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
function assertNoEnvFiles(dir) {
|
||||
const allowedRuntimeEnvPath = BUNDLE_RUNTIME_ENV
|
||||
? join(OUTPUT_DIR, RUNTIME_ENV_FILE_NAME)
|
||||
: undefined;
|
||||
const stack = [dir];
|
||||
while (stack.length) {
|
||||
const current = stack.pop();
|
||||
for (const entry of readdirSync(current)) {
|
||||
const fullPath = join(current, entry);
|
||||
if (allowedRuntimeEnvPath && fullPath === allowedRuntimeEnvPath) {
|
||||
continue;
|
||||
}
|
||||
if (basename(entry).startsWith('.env')) {
|
||||
fail(`Refusing to ship env file: ${relative(ROOT, fullPath)}`);
|
||||
}
|
||||
if (statSync(fullPath).isDirectory()) stack.push(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function assertNoForbiddenBundlePaths(outputDir) {
|
||||
const forbidden = [
|
||||
join(outputDir, 'public', 'uploads'),
|
||||
join(outputDir, 'public', 'generated-results'),
|
||||
join(outputDir, '.next', 'cache'),
|
||||
];
|
||||
for (const target of forbidden) {
|
||||
if (existsSync(target)) {
|
||||
fail(`Refusing to ship development/user data path: ${relative(ROOT, target)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function shouldScanForSecrets(filePath) {
|
||||
const stats = statSync(filePath);
|
||||
if (!stats.isFile()) return false;
|
||||
if (stats.size > 5 * 1024 * 1024) return false;
|
||||
return /\.(?:js|json|html|css|txt|mjs|cjs|map)$/i.test(filePath);
|
||||
}
|
||||
|
||||
function assertNoSecretValues(dir, secretEntries) {
|
||||
if (!secretEntries.length) return;
|
||||
const stack = [dir];
|
||||
while (stack.length) {
|
||||
const current = stack.pop();
|
||||
for (const entry of readdirSync(current)) {
|
||||
const fullPath = join(current, entry);
|
||||
const stats = statSync(fullPath);
|
||||
if (stats.isDirectory()) {
|
||||
stack.push(fullPath);
|
||||
continue;
|
||||
}
|
||||
if (BUNDLE_RUNTIME_ENV && fullPath === join(OUTPUT_DIR, RUNTIME_ENV_FILE_NAME)) continue;
|
||||
if (!shouldScanForSecrets(fullPath)) continue;
|
||||
const content = readFileSync(fullPath, 'utf8');
|
||||
for (const secret of secretEntries) {
|
||||
if (content.includes(secret.value)) {
|
||||
fail(`Refusing to ship bundle: secret-like env value "${secret.key}" appears in ${relative(ROOT, fullPath)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.SKIP_NIANXX_PLAY_BUNDLE === '1') {
|
||||
log('SKIP_NIANXX_PLAY_BUNDLE=1 set, skipping.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (!existsSync(join(SOURCE_DIR, 'package.json'))) {
|
||||
fail(`NianxxPlay source not found: ${SOURCE_DIR}`);
|
||||
}
|
||||
|
||||
const sourceVersion = readPackageVersion(join(SOURCE_DIR, 'package.json'));
|
||||
const secretLikeEnvValues = collectSecretLikeEnvValues(SOURCE_DIR);
|
||||
log(`source: ${SOURCE_DIR}`);
|
||||
log(`version: ${sourceVersion}`);
|
||||
|
||||
if (process.env.NIANXX_PLAY_SKIP_BUILD !== '1') {
|
||||
log('building Next.js standalone runtime...');
|
||||
run(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['run', 'build'], SOURCE_DIR);
|
||||
} else {
|
||||
log('NIANXX_PLAY_SKIP_BUILD=1 set, reusing existing .next output.');
|
||||
}
|
||||
|
||||
const standaloneDir = join(SOURCE_DIR, '.next', 'standalone');
|
||||
const staticDir = join(SOURCE_DIR, '.next', 'static');
|
||||
const publicDir = join(SOURCE_DIR, 'public');
|
||||
const contentDir = join(SOURCE_DIR, 'content');
|
||||
|
||||
if (!existsSync(join(standaloneDir, 'server.js'))) {
|
||||
fail(`Missing Next.js standalone server: ${join(standaloneDir, 'server.js')}`);
|
||||
}
|
||||
if (!existsSync(staticDir)) {
|
||||
fail(`Missing Next.js static output: ${staticDir}`);
|
||||
}
|
||||
|
||||
rmSync(OUTPUT_DIR, { recursive: true, force: true });
|
||||
mkdirSync(OUTPUT_DIR, { recursive: true });
|
||||
|
||||
log(`copying standalone runtime -> ${OUTPUT_DIR}`);
|
||||
copyDir(standaloneDir, OUTPUT_DIR, shouldCopyRuntime);
|
||||
copyDir(staticDir, join(OUTPUT_DIR, '.next', 'static'), shouldCopyRuntime);
|
||||
copyDir(publicDir, join(OUTPUT_DIR, 'public'), shouldCopyPublic);
|
||||
copyDir(contentDir, join(OUTPUT_DIR, 'content'), shouldCopyRuntime);
|
||||
const runtimeEnv = writeRuntimeEnvFile(SOURCE_DIR, OUTPUT_DIR);
|
||||
|
||||
normalizeBundlePermissions(OUTPUT_DIR);
|
||||
assertNoEnvFiles(OUTPUT_DIR);
|
||||
assertNoForbiddenBundlePaths(OUTPUT_DIR);
|
||||
assertNoSecretValues(OUTPUT_DIR, secretLikeEnvValues);
|
||||
|
||||
const manifest = {
|
||||
appId: 'nianxx-play',
|
||||
name: 'NianxxPlay',
|
||||
version: sourceVersion,
|
||||
bundledAt: new Date().toISOString(),
|
||||
runtime: 'next-standalone',
|
||||
entry: 'server.js',
|
||||
excludes: ['.env*', '.data', 'public/uploads', 'public/generated-results', 'development caches'],
|
||||
secretScan: {
|
||||
checked: true,
|
||||
sourceEnvValues: secretLikeEnvValues.length,
|
||||
},
|
||||
runtimeEnv: runtimeEnv.bundled
|
||||
? {
|
||||
bundled: true,
|
||||
file: RUNTIME_ENV_FILE_NAME,
|
||||
values: runtimeEnv.values,
|
||||
purpose: 'internal-testing-only',
|
||||
}
|
||||
: {
|
||||
bundled: false,
|
||||
},
|
||||
sizeBytes: dirSizeBytes(OUTPUT_DIR),
|
||||
};
|
||||
|
||||
writeFileSync(join(OUTPUT_DIR, 'bundle-manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
|
||||
log(`ready: ${OUTPUT_DIR}`);
|
||||
log(`size: ${(manifest.sizeBytes / 1024 / 1024).toFixed(1)} MB`);
|
||||
Reference in New Issue
Block a user