@kne/fastify-app-manager 0.1.4 → 0.1.5

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 CHANGED
@@ -311,7 +311,7 @@ await fastify.listen({ port: 3000 });
311
311
  | version | string | 是 | 版本号;同应用内唯一 |
312
312
  | label | string | 否 | 版本说明 |
313
313
 
314
- 成功返回 `appVersion` 记录(含 `artifactPath`、`hasMigration` 等)。包非法或 npm 失败时回滚产物并返回 400。
314
+ 成功返回 `appVersion` 记录(含 `artifactPath`、`hasMigration` 等)。包在临时目录完成解压、校验与 `npm install` 后才写入版本表,上传中或失败的包不会出现在版本列表;包非法或 npm 失败时清理临时目录并返回 400。版本号已存在返回 409;同版本号仅残留软删除记录时会先彻底清除再写入。
315
315
 
316
316
  ##### GET `{prefix}/app/version/list`
317
317
 
@@ -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
- const dup = await models.appVersion.findOne({ where: { appName: name, version } });
188
- if (dup) {
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
- const versionRow = await models.appVersion.create({
193
- appName: name,
194
- version,
195
- label: label || null,
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
- const prepared = await prepareVersionArtifact({
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(artifactPath).catch(() => {});
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 }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kne/fastify-app-manager",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Fastify 单机应用市场插件:版本上传、PM2 托管、路径/域名反代与日志 SSE",
5
5
  "main": "index.js",
6
6
  "scripts": {