@kne/fastify-app-manager 0.1.4 → 0.1.6
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 +1 -1
- package/libs/controllers/gateway.js +9 -1
- package/libs/services/app.js +44 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -311,7 +311,7 @@ await fastify.listen({ port: 3000 });
|
|
|
311
311
|
| version | string | 是 | 版本号;同应用内唯一 |
|
|
312
312
|
| label | string | 否 | 版本说明 |
|
|
313
313
|
|
|
314
|
-
成功返回 `appVersion` 记录(含 `artifactPath`、`hasMigration`
|
|
314
|
+
成功返回 `appVersion` 记录(含 `artifactPath`、`hasMigration` 等)。包在临时目录完成解压、校验与 `npm install` 后才写入版本表,上传中或失败的包不会出现在版本列表;包非法或 npm 失败时清理临时目录并返回 400。版本号已存在返回 409;同版本号仅残留软删除记录时会先彻底清除再写入。
|
|
315
315
|
|
|
316
316
|
##### GET `{prefix}/app/version/list`
|
|
317
317
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const fp = require('fastify-plugin');
|
|
2
|
+
const { Readable } = require('node:stream');
|
|
2
3
|
const { rewriteLocation, rewriteSetCookiePath, shouldRewriteBody, rewriteBody } = require('../utils/pathRewrite');
|
|
3
4
|
|
|
4
5
|
module.exports = fp(async (fastify, options) => {
|
|
@@ -20,6 +21,12 @@ module.exports = fp(async (fastify, options) => {
|
|
|
20
21
|
const dest = `http://127.0.0.1:${app.port}${targetPath}`;
|
|
21
22
|
const mountPrefix = `${pathPrefix}/${app.name}`;
|
|
22
23
|
|
|
24
|
+
// The gateway runs in onRequest, before body parsing; reply-from only forwards request.body,
|
|
25
|
+
// so hand it the raw stream or POST/PUT bodies reach the app empty.
|
|
26
|
+
if (request.method !== 'GET' && request.method !== 'HEAD') {
|
|
27
|
+
request.body = request.raw;
|
|
28
|
+
}
|
|
29
|
+
|
|
23
30
|
return reply.from(dest, {
|
|
24
31
|
rewriteRequestHeaders: (req, headers) => {
|
|
25
32
|
const next = { ...headers };
|
|
@@ -51,7 +58,8 @@ module.exports = fp(async (fastify, options) => {
|
|
|
51
58
|
}
|
|
52
59
|
const buf = Buffer.concat(chunks.map(c => (Buffer.isBuffer(c) ? c : Buffer.from(c))));
|
|
53
60
|
const text = rewriteBody(buf.toString('utf8'), mountPrefix);
|
|
54
|
-
|
|
61
|
+
// A stream payload keeps host onSend hooks that wrap string bodies (e.g. response envelopes) from altering app responses.
|
|
62
|
+
return reply.send(Readable.from([Buffer.from(text)], { objectMode: false }));
|
|
55
63
|
}
|
|
56
64
|
return reply.send(res.stream);
|
|
57
65
|
}
|
package/libs/services/app.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const fp = require('fastify-plugin');
|
|
2
2
|
const path = require('node:path');
|
|
3
|
+
const crypto = require('node:crypto');
|
|
3
4
|
const fs = require('fs-extra');
|
|
4
5
|
const portPool = require('../utils/portPool');
|
|
5
6
|
const { mergeEnv, maskEnvForResponse, applyEnvPatch, mergePm2Config, normalizeSecretKeys, collectSecretKeys } = require('../utils/env');
|
|
@@ -184,41 +185,62 @@ module.exports = fp(async (fastify, options) => {
|
|
|
184
185
|
if (!app) {
|
|
185
186
|
httpError(404, `app ${name} not found; create it first`);
|
|
186
187
|
}
|
|
187
|
-
|
|
188
|
-
|
|
188
|
+
// The (app_name, version) unique index also covers soft-deleted rows, so they must be purged before reuse.
|
|
189
|
+
const dup = await models.appVersion.findOne({ where: { appName: name, version }, paranoid: false });
|
|
190
|
+
if (dup && !dup.isSoftDeleted()) {
|
|
189
191
|
httpError(409, `version ${version} already exists`);
|
|
190
192
|
}
|
|
193
|
+
if (dup) {
|
|
194
|
+
await dup.destroy({ force: true });
|
|
195
|
+
}
|
|
191
196
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
artifactPath: path.join(app.rootPath, 'versions', 'pending'),
|
|
197
|
-
hasMigration: false,
|
|
198
|
-
migrationPath: options.sqlPath
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
const artifactPath = path.join(app.rootPath, 'versions', String(versionRow.id));
|
|
197
|
+
// Prepare outside the version table so failed or in-progress uploads never appear in the version list.
|
|
198
|
+
const versionsDir = path.join(app.rootPath, 'versions');
|
|
199
|
+
const stagingPath = path.join(versionsDir, `.staging-${crypto.randomUUID()}`);
|
|
200
|
+
let prepared;
|
|
202
201
|
try {
|
|
203
|
-
|
|
202
|
+
prepared = await prepareVersionArtifact({
|
|
204
203
|
zipBuffer,
|
|
205
|
-
artifactPath,
|
|
204
|
+
artifactPath: stagingPath,
|
|
206
205
|
maxZipSize: options.maxZipSize,
|
|
207
206
|
maxZipEntries: options.maxZipEntries,
|
|
208
207
|
npmInstallTimeoutMs: options.npmInstallTimeoutMs,
|
|
209
208
|
sqlPath: options.sqlPath
|
|
210
209
|
});
|
|
211
|
-
await versionRow.update({
|
|
212
|
-
artifactPath,
|
|
213
|
-
hasMigration: prepared.hasMigration,
|
|
214
|
-
migrationPath: prepared.migrationPath
|
|
215
|
-
});
|
|
216
|
-
return versionRow.toJSON();
|
|
217
210
|
} catch (e) {
|
|
218
|
-
await fs.remove(
|
|
219
|
-
await versionRow.destroy().catch(() => {});
|
|
211
|
+
await fs.remove(stagingPath).catch(() => {});
|
|
220
212
|
httpError(400, e.message);
|
|
221
213
|
}
|
|
214
|
+
|
|
215
|
+
let artifactPath = null;
|
|
216
|
+
try {
|
|
217
|
+
return await models.appVersion.sequelize.transaction(async transaction => {
|
|
218
|
+
const versionRow = await models.appVersion.create(
|
|
219
|
+
{
|
|
220
|
+
appName: name,
|
|
221
|
+
version,
|
|
222
|
+
label: label || null,
|
|
223
|
+
artifactPath: stagingPath,
|
|
224
|
+
hasMigration: prepared.hasMigration,
|
|
225
|
+
migrationPath: prepared.migrationPath
|
|
226
|
+
},
|
|
227
|
+
{ transaction }
|
|
228
|
+
);
|
|
229
|
+
artifactPath = path.join(versionsDir, String(versionRow.id));
|
|
230
|
+
await fs.move(stagingPath, artifactPath);
|
|
231
|
+
await versionRow.update({ artifactPath }, { transaction });
|
|
232
|
+
return versionRow.toJSON();
|
|
233
|
+
});
|
|
234
|
+
} catch (e) {
|
|
235
|
+
await fs.remove(stagingPath).catch(() => {});
|
|
236
|
+
if (artifactPath) {
|
|
237
|
+
await fs.remove(artifactPath).catch(() => {});
|
|
238
|
+
}
|
|
239
|
+
if (e.name === 'SequelizeUniqueConstraintError') {
|
|
240
|
+
httpError(409, `version ${version} already exists`);
|
|
241
|
+
}
|
|
242
|
+
throw e;
|
|
243
|
+
}
|
|
222
244
|
};
|
|
223
245
|
|
|
224
246
|
const listVersions = async ({ name, perPage = 20, currentPage = 1 }) => {
|