@module-federation/treeshake-server 0.0.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/README.md +71 -0
- package/dist/adapters/createAdapterDeps.d.ts +10 -0
- package/dist/adapters/local/adapter.d.ts +10 -0
- package/dist/adapters/local/index.d.ts +3 -0
- package/dist/adapters/local/localObjectStore.d.ts +12 -0
- package/dist/adapters/registry.d.ts +7 -0
- package/dist/adapters/types.d.ts +70 -0
- package/dist/app.d.ts +18 -0
- package/dist/cli/ossEnv.d.ts +18 -0
- package/dist/domain/build/constant.d.ts +1 -0
- package/dist/domain/build/normalize-config.d.ts +21 -0
- package/dist/domain/build/retrieve-global-name.d.ts +1 -0
- package/dist/domain/build/schema.d.ts +31 -0
- package/dist/domain/upload/constant.d.ts +2 -0
- package/dist/domain/upload/retrieve-cdn-path.d.ts +4 -0
- package/dist/frontend/adapter/index.d.ts +13 -0
- package/dist/frontend/adapter/index.js +128 -0
- package/dist/frontend/adapter/index.mjs +83 -0
- package/dist/frontend/favicon.ico +0 -0
- package/dist/frontend/index.html +1 -0
- package/dist/frontend/static/css/index.16175e0f.css +1 -0
- package/dist/frontend/static/js/954.dfe166a3.js +2 -0
- package/dist/frontend/static/js/954.dfe166a3.js.LICENSE.txt +16 -0
- package/dist/frontend/static/js/async/873.6ccd5409.js +2 -0
- package/dist/frontend/static/js/async/951.ec9191e2.js +12 -0
- package/dist/frontend/static/js/async/951.ec9191e2.js.LICENSE.txt +6 -0
- package/dist/frontend/static/js/async/987.6bf8e9b0.js +2 -0
- package/dist/frontend/static/js/async/987.6bf8e9b0.js.LICENSE.txt +6 -0
- package/dist/frontend/static/js/index.db4e73c6.js +88 -0
- package/dist/frontend/static/js/lib-react.c59642e3.js +2 -0
- package/dist/frontend/static/js/lib-react.c59642e3.js.LICENSE.txt +39 -0
- package/dist/frontend/static/js/lib-router.75e1e689.js +4 -0
- package/dist/frontend/static/js/lib-router.75e1e689.js.LICENSE.txt +10 -0
- package/dist/http/env.d.ts +10 -0
- package/dist/http/middlewares/di.middleware.d.ts +4 -0
- package/dist/http/middlewares/logger.middleware.d.ts +3 -0
- package/dist/http/routes/build.d.ts +3 -0
- package/dist/http/routes/index.d.ts +2 -0
- package/dist/http/routes/maintenance.d.ts +3 -0
- package/dist/http/routes/static.d.ts +5 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +958 -0
- package/dist/index.mjs +889 -0
- package/dist/infra/logger.d.ts +6 -0
- package/dist/nodeServer.d.ts +7 -0
- package/dist/ports/objectStore.d.ts +1 -0
- package/dist/ports/projectPublisher.d.ts +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +1090 -0
- package/dist/server.mjs +1058 -0
- package/dist/services/buildService.d.ts +8 -0
- package/dist/services/cacheService.d.ts +15 -0
- package/dist/services/pnpmMaintenance.d.ts +4 -0
- package/dist/services/uploadService.d.ts +36 -0
- package/dist/template/re-shake-share/Collect.js +115 -0
- package/dist/template/re-shake-share/EmitManifest.js +49 -0
- package/dist/template/re-shake-share/index.ts +1 -0
- package/dist/template/re-shake-share/package.json +23 -0
- package/dist/template/re-shake-share/rspack.config.ts +33 -0
- package/dist/utils/runCommand.d.ts +20 -0
- package/dist/utils/runtimeEnv.d.ts +3 -0
- package/dist/utils/uploadSdk.d.ts +10 -0
- package/package.json +61 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,889 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { cors } from "hono/cors";
|
|
3
|
+
import pino from "pino";
|
|
4
|
+
import { zValidator } from "@hono/zod-validator";
|
|
5
|
+
import { nanoid } from "nanoid";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
import node_fs from "node:fs";
|
|
9
|
+
import node_os from "node:os";
|
|
10
|
+
import node_path from "node:path";
|
|
11
|
+
import { spawn } from "node:child_process";
|
|
12
|
+
import json_stable_stringify from "json-stable-stringify";
|
|
13
|
+
import { serve } from "@hono/node-server";
|
|
14
|
+
function createDiMiddleware(deps) {
|
|
15
|
+
return async (c, next)=>{
|
|
16
|
+
c.set('objectStore', deps.objectStore);
|
|
17
|
+
if (deps.projectPublisher) c.set('projectPublisher', deps.projectPublisher);
|
|
18
|
+
await next();
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const createLogger = (opts)=>pino({
|
|
22
|
+
level: (null == opts ? void 0 : opts.level) ?? 'info',
|
|
23
|
+
formatters: {
|
|
24
|
+
level (label) {
|
|
25
|
+
return {
|
|
26
|
+
level: label
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
let logger_logger = createLogger();
|
|
32
|
+
const setLogger = (next)=>{
|
|
33
|
+
logger_logger = next;
|
|
34
|
+
};
|
|
35
|
+
const loggerMiddleware = async (c, next)=>{
|
|
36
|
+
const category = c.req.path.split('/')[1] || 'root';
|
|
37
|
+
const child = logger_logger.child({
|
|
38
|
+
category,
|
|
39
|
+
path: c.req.path,
|
|
40
|
+
method: c.req.method
|
|
41
|
+
});
|
|
42
|
+
c.set('logger', child);
|
|
43
|
+
await next();
|
|
44
|
+
};
|
|
45
|
+
const parseNormalizedKey = (key)=>{
|
|
46
|
+
const res = key.split('@');
|
|
47
|
+
return {
|
|
48
|
+
name: res.slice(0, -1).join('@'),
|
|
49
|
+
version: res[res.length - 1]
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
const normalizedKey = (name, v)=>`${name}@${v}`;
|
|
53
|
+
function normalizeConfig(config) {
|
|
54
|
+
const { shared, plugins, target, libraryType, hostName, uploadOptions } = config;
|
|
55
|
+
const commonNormalizedConfig = {
|
|
56
|
+
plugins: (null == plugins ? void 0 : plugins.sort(([a], [b])=>a.localeCompare(b))) ?? [],
|
|
57
|
+
target: Array.isArray(target) ? [
|
|
58
|
+
...target
|
|
59
|
+
].sort() : [
|
|
60
|
+
target
|
|
61
|
+
],
|
|
62
|
+
uploadOptions,
|
|
63
|
+
libraryType,
|
|
64
|
+
hostName
|
|
65
|
+
};
|
|
66
|
+
const normalizedConfig = {};
|
|
67
|
+
shared.forEach(([sharedName, version, usedExports], index)=>{
|
|
68
|
+
const key = normalizedKey(sharedName, version);
|
|
69
|
+
normalizedConfig[key] = {
|
|
70
|
+
...commonNormalizedConfig,
|
|
71
|
+
shared: shared.slice(0, index).concat(shared.slice(index + 1)).sort(([s, v, u], [b, v2, u2])=>`${s}${v}${u.sort().join('')}`.localeCompare(`${b}${v2}${u2.sort().join('')}`)),
|
|
72
|
+
usedExports
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
return normalizedConfig;
|
|
76
|
+
}
|
|
77
|
+
function extractBuildConfig(config, type) {
|
|
78
|
+
const { shared, plugins, target, libraryType, usedExports } = config;
|
|
79
|
+
return {
|
|
80
|
+
shared,
|
|
81
|
+
plugins,
|
|
82
|
+
target,
|
|
83
|
+
libraryType,
|
|
84
|
+
usedExports,
|
|
85
|
+
type
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const UploadOptionsSchema = z.object({
|
|
89
|
+
scmName: z.string(),
|
|
90
|
+
bucketName: z.string(),
|
|
91
|
+
publicFilePath: z.string(),
|
|
92
|
+
publicPath: z.string(),
|
|
93
|
+
cdnRegion: z.string()
|
|
94
|
+
});
|
|
95
|
+
const BasicSchema = z.object({
|
|
96
|
+
shared: z.array(z.tuple([
|
|
97
|
+
z.string(),
|
|
98
|
+
z.string(),
|
|
99
|
+
z.array(z.string())
|
|
100
|
+
])).describe('List of plugins: [name, version, usedExports]'),
|
|
101
|
+
plugins: z.array(z.tuple([
|
|
102
|
+
z.string(),
|
|
103
|
+
z.string().optional()
|
|
104
|
+
])).describe('Specify extra build plugin names, support specify version in second item').optional(),
|
|
105
|
+
target: z.union([
|
|
106
|
+
z.string(),
|
|
107
|
+
z.array(z.string())
|
|
108
|
+
]).describe('Used to configure the target environment of Rspack output and the ECMAScript version of Rspack runtime code, the same with rspack#target'),
|
|
109
|
+
libraryType: z.string(),
|
|
110
|
+
hostName: z.string().describe('The name of the host app / mf')
|
|
111
|
+
});
|
|
112
|
+
const ConfigSchema = BasicSchema.extend({
|
|
113
|
+
uploadOptions: UploadOptionsSchema.optional()
|
|
114
|
+
});
|
|
115
|
+
const CheckTreeShakingSchema = ConfigSchema.extend({
|
|
116
|
+
uploadOptions: UploadOptionsSchema.optional()
|
|
117
|
+
});
|
|
118
|
+
const STATS_NAME = 'mf-stats.json';
|
|
119
|
+
let runtimeEnv = {};
|
|
120
|
+
const setRuntimeEnv = (env)=>{
|
|
121
|
+
runtimeEnv = env;
|
|
122
|
+
};
|
|
123
|
+
const getRuntimeEnv = ()=>runtimeEnv;
|
|
124
|
+
function _define_property(obj, key, value) {
|
|
125
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
126
|
+
value: value,
|
|
127
|
+
enumerable: true,
|
|
128
|
+
configurable: true,
|
|
129
|
+
writable: true
|
|
130
|
+
});
|
|
131
|
+
else obj[key] = value;
|
|
132
|
+
return obj;
|
|
133
|
+
}
|
|
134
|
+
class CommandExecutionError extends Error {
|
|
135
|
+
constructor(command, exitCode, stdout, stderr){
|
|
136
|
+
super(`Command "${command}" exited with code ${exitCode}`), _define_property(this, "command", void 0), _define_property(this, "exitCode", void 0), _define_property(this, "stdout", void 0), _define_property(this, "stderr", void 0);
|
|
137
|
+
this.name = 'CommandExecutionError';
|
|
138
|
+
this.command = command;
|
|
139
|
+
this.exitCode = exitCode;
|
|
140
|
+
this.stdout = stdout;
|
|
141
|
+
this.stderr = stderr;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const runCommand = (command, options = {})=>new Promise((resolve, reject)=>{
|
|
145
|
+
const stdoutChunks = [];
|
|
146
|
+
const stderrChunks = [];
|
|
147
|
+
const child = spawn(command, {
|
|
148
|
+
cwd: options.cwd,
|
|
149
|
+
env: {
|
|
150
|
+
...getRuntimeEnv(),
|
|
151
|
+
...options.env
|
|
152
|
+
},
|
|
153
|
+
shell: true,
|
|
154
|
+
stdio: [
|
|
155
|
+
'ignore',
|
|
156
|
+
'pipe',
|
|
157
|
+
'pipe'
|
|
158
|
+
]
|
|
159
|
+
});
|
|
160
|
+
child.stdout.on('data', (chunk)=>{
|
|
161
|
+
stdoutChunks.push(chunk.toString());
|
|
162
|
+
});
|
|
163
|
+
child.stderr.on('data', (chunk)=>{
|
|
164
|
+
stderrChunks.push(chunk.toString());
|
|
165
|
+
});
|
|
166
|
+
child.on('error', (error)=>{
|
|
167
|
+
reject(error);
|
|
168
|
+
});
|
|
169
|
+
child.on('close', (exitCode)=>{
|
|
170
|
+
const stdout = stdoutChunks.join('');
|
|
171
|
+
const stderr = stderrChunks.join('');
|
|
172
|
+
if (0 === exitCode) return void resolve({
|
|
173
|
+
stdout,
|
|
174
|
+
stderr,
|
|
175
|
+
exitCode
|
|
176
|
+
});
|
|
177
|
+
reject(new CommandExecutionError(command, exitCode ?? -1, stdout, stderr));
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
let installs = 0;
|
|
181
|
+
let pruneScheduled = false;
|
|
182
|
+
let pruning = false;
|
|
183
|
+
const DEFAULT_INTERVAL = 3600000;
|
|
184
|
+
const markInstallStart = ()=>{
|
|
185
|
+
installs++;
|
|
186
|
+
};
|
|
187
|
+
const markInstallEnd = ()=>{
|
|
188
|
+
installs = Math.max(0, installs - 1);
|
|
189
|
+
schedulePruneSoon();
|
|
190
|
+
};
|
|
191
|
+
const schedulePruneSoon = (delay = 30000)=>{
|
|
192
|
+
if (pruneScheduled) return;
|
|
193
|
+
pruneScheduled = true;
|
|
194
|
+
setTimeout(()=>{
|
|
195
|
+
pruneScheduled = false;
|
|
196
|
+
maybePrune();
|
|
197
|
+
}, delay);
|
|
198
|
+
};
|
|
199
|
+
const maybePrune = async ()=>{
|
|
200
|
+
if (pruning || installs > 0) return void schedulePruneSoon(60000);
|
|
201
|
+
pruning = true;
|
|
202
|
+
try {
|
|
203
|
+
await runCommand('pnpm store prune');
|
|
204
|
+
} catch {} finally{
|
|
205
|
+
pruning = false;
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
const startPeriodicPrune = (intervalMs = DEFAULT_INTERVAL)=>{
|
|
209
|
+
if (intervalMs <= 0) return;
|
|
210
|
+
setInterval(()=>{
|
|
211
|
+
maybePrune();
|
|
212
|
+
}, intervalMs);
|
|
213
|
+
};
|
|
214
|
+
const createUniqueTempDirByKey = (key)=>{
|
|
215
|
+
const base = node_path.join(node_os.tmpdir(), `re-shake-share-${key}`);
|
|
216
|
+
let candidate = base;
|
|
217
|
+
for(;;)try {
|
|
218
|
+
node_fs.mkdirSync(candidate, {
|
|
219
|
+
recursive: false
|
|
220
|
+
});
|
|
221
|
+
return candidate;
|
|
222
|
+
} catch {
|
|
223
|
+
const rand = Math.floor(1e9 * Math.random());
|
|
224
|
+
candidate = `${base}-${rand}`;
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
const prepareProject = (config, excludeShared)=>{
|
|
228
|
+
const key = createHash('sha256').update(JSON.stringify(config)).digest('hex');
|
|
229
|
+
const dir = createUniqueTempDirByKey(key);
|
|
230
|
+
const templateDir = node_path.join(__dirname, '..', 'template', 're-shake-share');
|
|
231
|
+
node_fs.cpSync(templateDir, dir, {
|
|
232
|
+
recursive: true
|
|
233
|
+
});
|
|
234
|
+
const pkgPath = node_path.join(dir, 'package.json');
|
|
235
|
+
const pkg = JSON.parse(node_fs.readFileSync(pkgPath, 'utf-8'));
|
|
236
|
+
const deps = {
|
|
237
|
+
...pkg.dependencies || {}
|
|
238
|
+
};
|
|
239
|
+
const shared = {};
|
|
240
|
+
const mfConfig = {
|
|
241
|
+
name: 're_shake',
|
|
242
|
+
library: {
|
|
243
|
+
type: 'global'
|
|
244
|
+
},
|
|
245
|
+
manifest: true,
|
|
246
|
+
shared
|
|
247
|
+
};
|
|
248
|
+
let pluginImportStr = '';
|
|
249
|
+
let pluginOptionStr = '[\n';
|
|
250
|
+
let sharedImport = '';
|
|
251
|
+
Object.entries(config).forEach(([key, { plugins = [], libraryType, usedExports, hostName }], index)=>{
|
|
252
|
+
const { name, version } = parseNormalizedKey(key);
|
|
253
|
+
deps[name] = version;
|
|
254
|
+
if (!index) {
|
|
255
|
+
plugins.forEach(([pluginName, pluginVersion], pIndex)=>{
|
|
256
|
+
deps[pluginName] = pluginVersion ?? 'latest';
|
|
257
|
+
const pluginImportName = `plugin_${pIndex}`;
|
|
258
|
+
pluginImportStr += `import ${pluginImportName} from '${pluginName}';\n`;
|
|
259
|
+
pluginOptionStr += `new ${pluginImportName}(),`;
|
|
260
|
+
});
|
|
261
|
+
mfConfig.library.type = libraryType;
|
|
262
|
+
mfConfig.name = hostName;
|
|
263
|
+
}
|
|
264
|
+
shared[name] = {
|
|
265
|
+
requiredVersion: version,
|
|
266
|
+
version,
|
|
267
|
+
treeShaking: excludeShared.some(([n, v])=>n === name && v === version) ? void 0 : {
|
|
268
|
+
usedExports,
|
|
269
|
+
mode: 'server-calc'
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
sharedImport += `import shared_${index} from '${name}';\n`;
|
|
273
|
+
});
|
|
274
|
+
pluginOptionStr += '\n]';
|
|
275
|
+
pkg.dependencies = deps;
|
|
276
|
+
node_fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
277
|
+
const sharedImportPlaceholder = "${SHARED_IMPORT}";
|
|
278
|
+
const pluginsPlaceholder = "${ PLUGINS }";
|
|
279
|
+
const mfConfigPlaceholder = "${ MF_CONFIG }";
|
|
280
|
+
const indexPath = node_path.join(dir, 'index.ts');
|
|
281
|
+
const indexContent = node_fs.readFileSync(indexPath, 'utf-8');
|
|
282
|
+
node_fs.writeFileSync(indexPath, indexContent.replace(sharedImportPlaceholder, sharedImport));
|
|
283
|
+
const rspackConfigPath = node_path.join(dir, 'rspack.config.ts');
|
|
284
|
+
let cfg = node_fs.readFileSync(rspackConfigPath, 'utf-8');
|
|
285
|
+
cfg += pluginImportStr;
|
|
286
|
+
cfg = cfg.replace(pluginsPlaceholder, pluginOptionStr);
|
|
287
|
+
cfg = cfg.replace(mfConfigPlaceholder, JSON.stringify(mfConfig, null, 2));
|
|
288
|
+
node_fs.writeFileSync(rspackConfigPath, cfg);
|
|
289
|
+
return dir;
|
|
290
|
+
};
|
|
291
|
+
const installDependencies = async (cwd)=>{
|
|
292
|
+
markInstallStart();
|
|
293
|
+
try {
|
|
294
|
+
await runCommand('pnpm i', {
|
|
295
|
+
cwd,
|
|
296
|
+
env: {
|
|
297
|
+
npm_config_registry: 'https://registry.npmjs.org/'
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
} finally{
|
|
301
|
+
markInstallEnd();
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
const buildProject = async (cwd, type)=>{
|
|
305
|
+
const scripts = [];
|
|
306
|
+
if ('re-shake' === type) scripts.push('pnpm build');
|
|
307
|
+
else scripts.push('pnpm build:full');
|
|
308
|
+
await Promise.all(scripts.map((script)=>runCommand(script, {
|
|
309
|
+
cwd
|
|
310
|
+
})));
|
|
311
|
+
};
|
|
312
|
+
const retrieveSharedFilepaths = (projectDir, type)=>{
|
|
313
|
+
const sharedFilepaths = [];
|
|
314
|
+
const collectSharedFilepaths = (t)=>{
|
|
315
|
+
const dir = 'full' === t ? 'full-shared' : 'dist';
|
|
316
|
+
const distDir = node_path.join(projectDir, dir);
|
|
317
|
+
const stats = JSON.parse(node_fs.readFileSync(node_path.join(distDir, STATS_NAME), 'utf-8'));
|
|
318
|
+
stats.shared.forEach((s)=>{
|
|
319
|
+
const { name, version, fallback, fallbackName } = s;
|
|
320
|
+
if (fallback && fallbackName) {
|
|
321
|
+
var _s_usedExports;
|
|
322
|
+
const filepath = node_path.join(distDir, fallback);
|
|
323
|
+
sharedFilepaths.push({
|
|
324
|
+
name,
|
|
325
|
+
version,
|
|
326
|
+
filepath,
|
|
327
|
+
globalName: fallbackName,
|
|
328
|
+
type: t,
|
|
329
|
+
modules: s.usedExports,
|
|
330
|
+
canTreeShaking: s.canTreeShaking ?? (null == (_s_usedExports = s.usedExports) ? void 0 : _s_usedExports.length) !== 0
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
};
|
|
335
|
+
collectSharedFilepaths(type);
|
|
336
|
+
return sharedFilepaths;
|
|
337
|
+
};
|
|
338
|
+
const runBuild = async (normalizedConfig, excludeShared, type)=>{
|
|
339
|
+
const tmpDir = prepareProject(normalizedConfig, excludeShared);
|
|
340
|
+
await installDependencies(tmpDir);
|
|
341
|
+
await buildProject(tmpDir, type);
|
|
342
|
+
const sharedFilePaths = retrieveSharedFilepaths(tmpDir, type);
|
|
343
|
+
return {
|
|
344
|
+
sharedFilePaths,
|
|
345
|
+
dir: tmpDir
|
|
346
|
+
};
|
|
347
|
+
};
|
|
348
|
+
function cleanUp(tmpDir) {
|
|
349
|
+
if (!tmpDir) return;
|
|
350
|
+
node_fs.rmSync(tmpDir, {
|
|
351
|
+
recursive: true,
|
|
352
|
+
force: true
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
const encodeName = function(name, prefix = '', withExt = false) {
|
|
356
|
+
const ext = withExt ? '.js' : '';
|
|
357
|
+
return `${prefix}${name.replace(/@/g, 'scope_').replace(/-/g, '_').replace(/\//g, '__').replace(/\./g, '')}${ext}`;
|
|
358
|
+
};
|
|
359
|
+
function retrieveGlobalName(mfName, sharedName, version) {
|
|
360
|
+
return encodeName(`${mfName}_${sharedName}_${version}`);
|
|
361
|
+
}
|
|
362
|
+
const SERVER_VERSION = 'v0-011501';
|
|
363
|
+
const UPLOADED_DIR = '_shared-tree-shaking';
|
|
364
|
+
function createCacheHash(config, type) {
|
|
365
|
+
const relevant = extractBuildConfig({
|
|
366
|
+
...config,
|
|
367
|
+
usedExports: 'full' === type ? [] : config.usedExports
|
|
368
|
+
}, type);
|
|
369
|
+
const json = json_stable_stringify(relevant);
|
|
370
|
+
if (!json) throw new Error('Can not stringify build config!');
|
|
371
|
+
return createHash('sha256').update(json).digest('hex');
|
|
372
|
+
}
|
|
373
|
+
function retrieveCDNPath({ config, sharedKey, type }) {
|
|
374
|
+
const configHash = createCacheHash(config, type);
|
|
375
|
+
return `tree-shaking-shared/${SERVER_VERSION}/${sharedKey}/${configHash}.js`;
|
|
376
|
+
}
|
|
377
|
+
async function cacheService_hitCache(sharedKey, config, type, store) {
|
|
378
|
+
const cdnPath = retrieveCDNPath({
|
|
379
|
+
config,
|
|
380
|
+
sharedKey,
|
|
381
|
+
type
|
|
382
|
+
});
|
|
383
|
+
const exists = await store.exists(cdnPath);
|
|
384
|
+
return exists ? store.publicUrl(cdnPath) : null;
|
|
385
|
+
}
|
|
386
|
+
const retrieveCacheItems = async (normalizedConfig, type, store)=>{
|
|
387
|
+
const cacheItems = [];
|
|
388
|
+
const restConfig = {};
|
|
389
|
+
const excludeShared = [];
|
|
390
|
+
for (const [sharedKey, config] of Object.entries(normalizedConfig)){
|
|
391
|
+
let cache = false;
|
|
392
|
+
const { name, version } = parseNormalizedKey(sharedKey);
|
|
393
|
+
const cdnUrl = await cacheService_hitCache(sharedKey, config, type, store);
|
|
394
|
+
if (cdnUrl) {
|
|
395
|
+
cache = true;
|
|
396
|
+
cacheItems.push({
|
|
397
|
+
type,
|
|
398
|
+
name,
|
|
399
|
+
version,
|
|
400
|
+
cdnUrl,
|
|
401
|
+
globalName: retrieveGlobalName(config.hostName, name, version)
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
if (cache) excludeShared.push([
|
|
405
|
+
name,
|
|
406
|
+
version
|
|
407
|
+
]);
|
|
408
|
+
else if (config.usedExports.length || 're-shake' !== type) restConfig[sharedKey] = config;
|
|
409
|
+
else excludeShared.push([
|
|
410
|
+
name,
|
|
411
|
+
version
|
|
412
|
+
]);
|
|
413
|
+
}
|
|
414
|
+
return {
|
|
415
|
+
cacheItems,
|
|
416
|
+
excludeShared,
|
|
417
|
+
restConfig
|
|
418
|
+
};
|
|
419
|
+
};
|
|
420
|
+
async function uploadToCacheStore(sharedFilePaths, normalizedConfig, store) {
|
|
421
|
+
const results = [];
|
|
422
|
+
for (const file of sharedFilePaths){
|
|
423
|
+
const { name, version, filepath, globalName, type, modules, canTreeShaking } = file;
|
|
424
|
+
try {
|
|
425
|
+
const sharedKey = normalizedKey(name, version);
|
|
426
|
+
logger_logger.info(`Uploading ${sharedKey} to CDN`);
|
|
427
|
+
const config = normalizedConfig[sharedKey];
|
|
428
|
+
const cdnPath = retrieveCDNPath({
|
|
429
|
+
config,
|
|
430
|
+
sharedKey,
|
|
431
|
+
type
|
|
432
|
+
});
|
|
433
|
+
const t0 = Date.now();
|
|
434
|
+
await store.uploadFile(filepath, cdnPath);
|
|
435
|
+
const tUpload = Date.now() - t0;
|
|
436
|
+
const cdnUrl = store.publicUrl(cdnPath);
|
|
437
|
+
const res = {
|
|
438
|
+
name: name,
|
|
439
|
+
version: version,
|
|
440
|
+
globalName: globalName,
|
|
441
|
+
cdnUrl,
|
|
442
|
+
type,
|
|
443
|
+
modules,
|
|
444
|
+
canTreeShaking
|
|
445
|
+
};
|
|
446
|
+
try {
|
|
447
|
+
const jsonFilePath = filepath.replace(/\.js$/, '.json');
|
|
448
|
+
const jsonFile = JSON.stringify(res);
|
|
449
|
+
const jsonCdnUrl = cdnPath.replace(/\.js$/, '.json');
|
|
450
|
+
node_fs.writeFileSync(jsonFilePath, jsonFile);
|
|
451
|
+
await store.uploadFile(jsonFilePath, jsonCdnUrl);
|
|
452
|
+
} catch (error) {
|
|
453
|
+
logger_logger.error(`Failed to upload ${name}@${version} json file: ${error}`);
|
|
454
|
+
}
|
|
455
|
+
results.push(res);
|
|
456
|
+
logger_logger.info(`Successfully uploaded ${name}@${version} to ${cdnUrl} in ${tUpload}ms`);
|
|
457
|
+
} catch (error) {
|
|
458
|
+
logger_logger.error(`Failed to upload ${name}@${version}: ${error}`);
|
|
459
|
+
throw error;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return results;
|
|
463
|
+
}
|
|
464
|
+
const downloadToFile = async (url, destPath)=>{
|
|
465
|
+
const res = await fetch(url);
|
|
466
|
+
if (!res.ok) throw new Error(`Download failed: ${res.status} ${res.statusText} - ${url}`);
|
|
467
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
468
|
+
await node_fs.promises.mkdir(node_path.dirname(destPath), {
|
|
469
|
+
recursive: true
|
|
470
|
+
});
|
|
471
|
+
await node_fs.promises.writeFile(destPath, buf);
|
|
472
|
+
return destPath;
|
|
473
|
+
};
|
|
474
|
+
async function uploadProject(uploadResults, sharedFilePaths, normalizedConfig, publisher) {
|
|
475
|
+
const tmpDir = createUniqueTempDirByKey(`upload-project${Date.now().toString()}`);
|
|
476
|
+
const uploaded = [];
|
|
477
|
+
try {
|
|
478
|
+
for (const item of uploadResults)try {
|
|
479
|
+
const config = normalizedConfig[normalizedKey(item.name, item.version)];
|
|
480
|
+
if (!config) {
|
|
481
|
+
logger_logger.error(`No config found for ${item.name}`);
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
const { uploadOptions } = config;
|
|
485
|
+
if (!uploadOptions) throw new Error(`No uploadOptions found for ${item.name}`);
|
|
486
|
+
const filename = node_path.basename(new URL(item.cdnUrl).pathname);
|
|
487
|
+
const localPath = node_path.join(tmpDir, filename);
|
|
488
|
+
const hash = createCacheHash({
|
|
489
|
+
...config,
|
|
490
|
+
plugins: config.plugins || []
|
|
491
|
+
}, item.type);
|
|
492
|
+
const cdnUrl = publisher.publicUrl({
|
|
493
|
+
sharedName: item.name,
|
|
494
|
+
hash,
|
|
495
|
+
fileName: filename,
|
|
496
|
+
options: uploadOptions
|
|
497
|
+
});
|
|
498
|
+
const hitCache = await publisher.exists(cdnUrl);
|
|
499
|
+
if (hitCache) {
|
|
500
|
+
logger_logger.info(`Hit cache for ${item.name}@${item.version} -> ${cdnUrl}`);
|
|
501
|
+
uploaded.push({
|
|
502
|
+
...item,
|
|
503
|
+
cdnUrl
|
|
504
|
+
});
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
logger_logger.info(`Downloading ${item.name}@${item.version} -> ${localPath}`);
|
|
508
|
+
const t0 = Date.now();
|
|
509
|
+
await downloadToFile(item.cdnUrl, localPath);
|
|
510
|
+
const tDownload = Date.now() - t0;
|
|
511
|
+
logger_logger.info(`Downloaded ${item.name}@${item.version} in ${tDownload}ms`);
|
|
512
|
+
const newCdnUrl = await publisher.publishFile({
|
|
513
|
+
localPath,
|
|
514
|
+
sharedName: item.name,
|
|
515
|
+
hash,
|
|
516
|
+
options: uploadOptions
|
|
517
|
+
});
|
|
518
|
+
uploaded.push({
|
|
519
|
+
...item,
|
|
520
|
+
cdnUrl: newCdnUrl
|
|
521
|
+
});
|
|
522
|
+
} catch (error) {
|
|
523
|
+
logger_logger.error(`Failed to upload ${item.name}@${item.version}: ${error}`);
|
|
524
|
+
}
|
|
525
|
+
for (const s of sharedFilePaths)try {
|
|
526
|
+
const config = normalizedConfig[normalizedKey(s.name, s.version)];
|
|
527
|
+
if (!config) {
|
|
528
|
+
logger_logger.error(`No config found for ${s.name}`);
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
const { uploadOptions } = config;
|
|
532
|
+
if (!uploadOptions) throw new Error(`No uploadOptions found for ${s.name}`);
|
|
533
|
+
const hash = createCacheHash({
|
|
534
|
+
...config,
|
|
535
|
+
plugins: config.plugins || []
|
|
536
|
+
}, s.type);
|
|
537
|
+
const cdnUrl = await publisher.publishFile({
|
|
538
|
+
localPath: s.filepath,
|
|
539
|
+
sharedName: s.name,
|
|
540
|
+
hash,
|
|
541
|
+
options: uploadOptions
|
|
542
|
+
});
|
|
543
|
+
uploaded.push({
|
|
544
|
+
name: s.name,
|
|
545
|
+
version: s.version,
|
|
546
|
+
globalName: s.globalName,
|
|
547
|
+
cdnUrl,
|
|
548
|
+
type: s.type
|
|
549
|
+
});
|
|
550
|
+
} catch (error) {
|
|
551
|
+
logger_logger.error(`Failed to upload ${s.name}@${s.version}: ${error}`);
|
|
552
|
+
}
|
|
553
|
+
return uploaded;
|
|
554
|
+
} finally{
|
|
555
|
+
node_fs.rmSync(tmpDir, {
|
|
556
|
+
recursive: true,
|
|
557
|
+
force: true
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
async function upload(sharedFilePaths, uploadResults, normalizedConfig, uploadOptions, store, publisher) {
|
|
562
|
+
const cacheUploaded = await uploadToCacheStore(sharedFilePaths, normalizedConfig, store);
|
|
563
|
+
if (!uploadOptions) {
|
|
564
|
+
const hydrated = await Promise.all(uploadResults.map(async (item)=>{
|
|
565
|
+
if ('full' !== item.type) return item;
|
|
566
|
+
const tmpDir = createUniqueTempDirByKey(`download-full-json${Date.now().toString()}`);
|
|
567
|
+
const jsonPath = node_path.join(tmpDir, `${item.name}-${item.version}.json`);
|
|
568
|
+
try {
|
|
569
|
+
const tJson0 = Date.now();
|
|
570
|
+
await downloadToFile(item.cdnUrl.replace('.js', '.json'), jsonPath);
|
|
571
|
+
const tJson = Date.now() - tJson0;
|
|
572
|
+
logger_logger.info(`Downloaded ${item.name}@${item.version} json in ${tJson}ms`);
|
|
573
|
+
const jsonContent = JSON.parse(node_fs.readFileSync(jsonPath, 'utf8'));
|
|
574
|
+
return {
|
|
575
|
+
...item,
|
|
576
|
+
canTreeShaking: jsonContent.canTreeShaking,
|
|
577
|
+
modules: jsonContent.modules
|
|
578
|
+
};
|
|
579
|
+
} catch (jsonError) {
|
|
580
|
+
logger_logger.error(`Failed to download ${item.name}@${item.version} json: ${jsonError}`);
|
|
581
|
+
return {
|
|
582
|
+
...item,
|
|
583
|
+
canTreeShaking: item.canTreeShaking ?? true
|
|
584
|
+
};
|
|
585
|
+
} finally{
|
|
586
|
+
node_fs.rmSync(tmpDir, {
|
|
587
|
+
recursive: true,
|
|
588
|
+
force: true
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
}));
|
|
592
|
+
return [
|
|
593
|
+
...cacheUploaded,
|
|
594
|
+
...hydrated
|
|
595
|
+
];
|
|
596
|
+
}
|
|
597
|
+
if (!publisher) throw new Error('uploadOptions provided but no projectPublisher configured (configure the selected adapter to enable it or omit uploadOptions)');
|
|
598
|
+
const projectUploadResults = await uploadProject(uploadResults, sharedFilePaths, normalizedConfig, publisher);
|
|
599
|
+
return projectUploadResults;
|
|
600
|
+
}
|
|
601
|
+
const buildRoute = new Hono();
|
|
602
|
+
buildRoute.post('/', zValidator('json', ConfigSchema), async (c)=>{
|
|
603
|
+
const logger = c.get('logger');
|
|
604
|
+
const startTime = Date.now();
|
|
605
|
+
const jobId = nanoid();
|
|
606
|
+
logger.info(jobId);
|
|
607
|
+
const body = c.req.valid('json');
|
|
608
|
+
logger.info(JSON.stringify(body));
|
|
609
|
+
const normalizedConfig = normalizeConfig(body);
|
|
610
|
+
const store = c.get('objectStore');
|
|
611
|
+
const publisher = c.get('projectPublisher');
|
|
612
|
+
try {
|
|
613
|
+
const { cacheItems, excludeShared, restConfig } = await retrieveCacheItems(normalizedConfig, 're-shake', store);
|
|
614
|
+
let sharedFilePaths = [];
|
|
615
|
+
let dir;
|
|
616
|
+
if (Object.keys(restConfig).length > 0) {
|
|
617
|
+
const buildResult = await runBuild(normalizedConfig, excludeShared, 're-shake');
|
|
618
|
+
sharedFilePaths = buildResult.sharedFilePaths;
|
|
619
|
+
dir = buildResult.dir;
|
|
620
|
+
}
|
|
621
|
+
const uploadResults = await upload(sharedFilePaths, cacheItems, normalizedConfig, body.uploadOptions, store, publisher);
|
|
622
|
+
cleanUp(dir);
|
|
623
|
+
return c.json({
|
|
624
|
+
jobId,
|
|
625
|
+
status: 'success',
|
|
626
|
+
data: uploadResults,
|
|
627
|
+
cached: cacheItems,
|
|
628
|
+
duration: Date.now() - startTime
|
|
629
|
+
});
|
|
630
|
+
} catch (error) {
|
|
631
|
+
if (error instanceof CommandExecutionError) {
|
|
632
|
+
if (137 === error.exitCode) maybePrune();
|
|
633
|
+
return c.json({
|
|
634
|
+
jobId,
|
|
635
|
+
status: 'failed',
|
|
636
|
+
error: error.message,
|
|
637
|
+
command: error.command,
|
|
638
|
+
exitCode: error.exitCode,
|
|
639
|
+
stdout: error.stdout,
|
|
640
|
+
stderr: error.stderr
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
return c.json({
|
|
644
|
+
jobId,
|
|
645
|
+
status: 'failed',
|
|
646
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
async function handleCheckTreeshake(c, body) {
|
|
651
|
+
const logger = c.get('logger');
|
|
652
|
+
const startTime = Date.now();
|
|
653
|
+
const jobId = nanoid();
|
|
654
|
+
logger.info(jobId);
|
|
655
|
+
logger.info(JSON.stringify(body));
|
|
656
|
+
const normalizedConfig = normalizeConfig(body);
|
|
657
|
+
const store = c.get('objectStore');
|
|
658
|
+
const publisher = c.get('projectPublisher');
|
|
659
|
+
try {
|
|
660
|
+
const { cacheItems, excludeShared, restConfig } = await retrieveCacheItems(normalizedConfig, 'full', store);
|
|
661
|
+
let sharedFilePaths = [];
|
|
662
|
+
let dir;
|
|
663
|
+
if (Object.keys(restConfig).length > 0) {
|
|
664
|
+
const buildResult = await runBuild(normalizedConfig, excludeShared, 'full');
|
|
665
|
+
sharedFilePaths = buildResult.sharedFilePaths;
|
|
666
|
+
dir = buildResult.dir;
|
|
667
|
+
}
|
|
668
|
+
const uploadResults = await upload(sharedFilePaths, cacheItems, normalizedConfig, body.uploadOptions, store, publisher);
|
|
669
|
+
cleanUp(dir);
|
|
670
|
+
return c.json({
|
|
671
|
+
jobId,
|
|
672
|
+
status: 'success',
|
|
673
|
+
data: uploadResults,
|
|
674
|
+
cached: cacheItems,
|
|
675
|
+
duration: Date.now() - startTime
|
|
676
|
+
});
|
|
677
|
+
} catch (error) {
|
|
678
|
+
if (error instanceof CommandExecutionError) {
|
|
679
|
+
if (137 === error.exitCode) maybePrune();
|
|
680
|
+
return c.json({
|
|
681
|
+
jobId,
|
|
682
|
+
status: 'failed',
|
|
683
|
+
error: error.message,
|
|
684
|
+
command: error.command,
|
|
685
|
+
exitCode: error.exitCode,
|
|
686
|
+
stdout: error.stdout,
|
|
687
|
+
stderr: error.stderr
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
return c.json({
|
|
691
|
+
jobId,
|
|
692
|
+
status: 'failed',
|
|
693
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
buildRoute.post('/check-tree-shaking', zValidator('json', CheckTreeShakingSchema), async (c)=>handleCheckTreeshake(c, c.req.valid('json')));
|
|
698
|
+
const maintenanceRoute = new Hono();
|
|
699
|
+
maintenanceRoute.post('/', async (c)=>{
|
|
700
|
+
const logger = c.get('logger');
|
|
701
|
+
const jobId = nanoid();
|
|
702
|
+
const startTime = Date.now();
|
|
703
|
+
logger.info(jobId);
|
|
704
|
+
await maybePrune();
|
|
705
|
+
return c.json({
|
|
706
|
+
jobId,
|
|
707
|
+
status: 'success',
|
|
708
|
+
duration: Date.now() - startTime
|
|
709
|
+
});
|
|
710
|
+
});
|
|
711
|
+
const routes = new Hono();
|
|
712
|
+
routes.route('/build', buildRoute);
|
|
713
|
+
routes.route('/clean-cache', maintenanceRoute);
|
|
714
|
+
function contentTypeByExt(filePath) {
|
|
715
|
+
if (filePath.endsWith('.js')) return "application/javascript";
|
|
716
|
+
if (filePath.endsWith('.json')) return 'application/json';
|
|
717
|
+
return 'application/octet-stream';
|
|
718
|
+
}
|
|
719
|
+
const DEFAULT_STATIC_ROOT = node_path.join(process.cwd(), 'log', 'static');
|
|
720
|
+
async function serveLocalFile(c, rootDir) {
|
|
721
|
+
let requestPath = c.req.path;
|
|
722
|
+
try {
|
|
723
|
+
requestPath = decodeURIComponent(requestPath);
|
|
724
|
+
} catch {
|
|
725
|
+
return c.text('Not Found', 404);
|
|
726
|
+
}
|
|
727
|
+
const relPath = requestPath.replace(/^\/+/, '');
|
|
728
|
+
const rootResolved = node_path.resolve(rootDir);
|
|
729
|
+
const filePath = node_path.resolve(rootResolved, relPath);
|
|
730
|
+
if (filePath !== rootResolved && !filePath.startsWith(`${rootResolved}${node_path.sep}`)) return c.text('Not Found', 404);
|
|
731
|
+
try {
|
|
732
|
+
const buf = await node_fs.promises.readFile(filePath);
|
|
733
|
+
return new Response(buf, {
|
|
734
|
+
status: 200,
|
|
735
|
+
headers: {
|
|
736
|
+
'Content-Type': contentTypeByExt(filePath)
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
} catch {
|
|
740
|
+
return c.text('Not Found', 404);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
function createStaticRoute(opts) {
|
|
744
|
+
const staticRoute = new Hono();
|
|
745
|
+
const rootDir = (null == opts ? void 0 : opts.rootDir) ?? DEFAULT_STATIC_ROOT;
|
|
746
|
+
staticRoute.get('/tree-shaking-shared/*', async (c)=>serveLocalFile(c, rootDir));
|
|
747
|
+
return staticRoute;
|
|
748
|
+
}
|
|
749
|
+
function createApp(deps, opts) {
|
|
750
|
+
var _opts_appExtensions, _opts_frontendAdapters;
|
|
751
|
+
if (null == opts ? void 0 : opts.logger) setLogger(opts.logger);
|
|
752
|
+
setRuntimeEnv((null == opts ? void 0 : opts.runtimeEnv) ?? process.env);
|
|
753
|
+
const app = new Hono();
|
|
754
|
+
const corsOrigin = (null == opts ? void 0 : opts.corsOrigin) ?? '*';
|
|
755
|
+
const staticRootDir = (null == opts ? void 0 : opts.staticRootDir) ?? DEFAULT_STATIC_ROOT;
|
|
756
|
+
app.use('*', cors({
|
|
757
|
+
origin: corsOrigin,
|
|
758
|
+
allowMethods: [
|
|
759
|
+
'GET',
|
|
760
|
+
'POST',
|
|
761
|
+
'OPTIONS'
|
|
762
|
+
],
|
|
763
|
+
allowHeaders: [
|
|
764
|
+
'Content-Type',
|
|
765
|
+
'Authorization'
|
|
766
|
+
]
|
|
767
|
+
}));
|
|
768
|
+
app.use('*', loggerMiddleware);
|
|
769
|
+
app.use('*', createDiMiddleware(deps));
|
|
770
|
+
if (null == opts ? void 0 : null == (_opts_appExtensions = opts.appExtensions) ? void 0 : _opts_appExtensions.length) for (const extend of opts.appExtensions)extend(app);
|
|
771
|
+
app.get('/tree-shaking-shared/healthz', (c)=>c.json({
|
|
772
|
+
status: 'ok',
|
|
773
|
+
timestamp: new Date().toISOString()
|
|
774
|
+
}));
|
|
775
|
+
app.route('/tree-shaking-shared', routes);
|
|
776
|
+
app.route('/', createStaticRoute({
|
|
777
|
+
rootDir: staticRootDir
|
|
778
|
+
}));
|
|
779
|
+
if (null == opts ? void 0 : null == (_opts_frontendAdapters = opts.frontendAdapters) ? void 0 : _opts_frontendAdapters.length) for (const adapter of opts.frontendAdapters)adapter.register(app);
|
|
780
|
+
startPeriodicPrune(null == opts ? void 0 : opts.pruneIntervalMs);
|
|
781
|
+
return app;
|
|
782
|
+
}
|
|
783
|
+
function createServer(opts) {
|
|
784
|
+
const port = opts.port ?? 3000;
|
|
785
|
+
const hostname = opts.hostname ?? '0.0.0.0';
|
|
786
|
+
return serve({
|
|
787
|
+
fetch: opts.app.fetch,
|
|
788
|
+
port,
|
|
789
|
+
hostname
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
async function createAdapterDeps(params) {
|
|
793
|
+
const adapterId = params.adapterId;
|
|
794
|
+
const adapter = params.registry.getAdapterById(adapterId);
|
|
795
|
+
return adapter.create(params.adapterConfig ?? {}, {
|
|
796
|
+
logger: params.logger ?? logger_logger,
|
|
797
|
+
uploadedDir: params.uploadedDir ?? UPLOADED_DIR
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
function localObjectStore_define_property(obj, key, value) {
|
|
801
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
802
|
+
value: value,
|
|
803
|
+
enumerable: true,
|
|
804
|
+
configurable: true,
|
|
805
|
+
writable: true
|
|
806
|
+
});
|
|
807
|
+
else obj[key] = value;
|
|
808
|
+
return obj;
|
|
809
|
+
}
|
|
810
|
+
class LocalObjectStore {
|
|
811
|
+
async exists(key) {
|
|
812
|
+
const filePath = node_path.join(this.rootDir, key.replace(/^\//, ''));
|
|
813
|
+
try {
|
|
814
|
+
const stat = await node_fs.promises.stat(filePath);
|
|
815
|
+
return stat.isFile();
|
|
816
|
+
} catch {
|
|
817
|
+
return false;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
async uploadFile(localPath, key) {
|
|
821
|
+
const rel = key.replace(/^\//, '');
|
|
822
|
+
const dest = node_path.join(this.rootDir, rel);
|
|
823
|
+
await node_fs.promises.mkdir(node_path.dirname(dest), {
|
|
824
|
+
recursive: true
|
|
825
|
+
});
|
|
826
|
+
await node_fs.promises.copyFile(localPath, dest);
|
|
827
|
+
}
|
|
828
|
+
publicUrl(key) {
|
|
829
|
+
return `${this.publicBaseUrl}${key.replace(/^\//, '')}`;
|
|
830
|
+
}
|
|
831
|
+
constructor(opts){
|
|
832
|
+
localObjectStore_define_property(this, "rootDir", void 0);
|
|
833
|
+
localObjectStore_define_property(this, "publicBaseUrl", void 0);
|
|
834
|
+
this.rootDir = (null == opts ? void 0 : opts.rootDir) ?? node_path.join(process.cwd(), 'log', 'static');
|
|
835
|
+
const base = (null == opts ? void 0 : opts.publicBaseUrl) ?? '/';
|
|
836
|
+
this.publicBaseUrl = base.endsWith('/') ? base : `${base}/`;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
function adapter_define_property(obj, key, value) {
|
|
840
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
841
|
+
value: value,
|
|
842
|
+
enumerable: true,
|
|
843
|
+
configurable: true,
|
|
844
|
+
writable: true
|
|
845
|
+
});
|
|
846
|
+
else obj[key] = value;
|
|
847
|
+
return obj;
|
|
848
|
+
}
|
|
849
|
+
function envStr(env, name, fallback) {
|
|
850
|
+
const v = env[name];
|
|
851
|
+
if (void 0 === v || '' === v) return fallback;
|
|
852
|
+
return v;
|
|
853
|
+
}
|
|
854
|
+
class LocalAdapter {
|
|
855
|
+
fromEnv(env) {
|
|
856
|
+
return {
|
|
857
|
+
rootDir: envStr(env, 'LOCAL_STORE_DIR', node_path.join(process.cwd(), 'log', 'static')),
|
|
858
|
+
publicBaseUrl: envStr(env, 'LOCAL_STORE_BASE_URL', '/')
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
async create(config, _context) {
|
|
862
|
+
const objectStore = new LocalObjectStore({
|
|
863
|
+
rootDir: config.rootDir,
|
|
864
|
+
publicBaseUrl: config.publicBaseUrl
|
|
865
|
+
});
|
|
866
|
+
return {
|
|
867
|
+
objectStore
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
constructor(){
|
|
871
|
+
adapter_define_property(this, "id", 'local');
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
function createAdapterRegistry(adapters) {
|
|
875
|
+
return {
|
|
876
|
+
adapters,
|
|
877
|
+
getAdapterById: (id)=>{
|
|
878
|
+
const found = adapters.find((a)=>a.id === id);
|
|
879
|
+
if (!found) throw new Error(`Unknown ADAPTER_ID: ${id}`);
|
|
880
|
+
return found;
|
|
881
|
+
}
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
function createOssAdapterRegistry() {
|
|
885
|
+
return createAdapterRegistry([
|
|
886
|
+
new LocalAdapter()
|
|
887
|
+
]);
|
|
888
|
+
}
|
|
889
|
+
export { LocalAdapter, LocalObjectStore, createAdapterDeps, createAdapterRegistry, createApp, createLogger, createOssAdapterRegistry, createServer };
|