@yunsoft/yuncms 0.1.26 → 0.1.27

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.
Files changed (2) hide show
  1. package/package.json +3 -3
  2. package/src/update-lock.js +56 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yunsoft/yuncms",
3
- "version": "0.1.26",
3
+ "version": "0.1.27",
4
4
  "description": "Command-line setup and runtime launcher for YunCMS.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -30,8 +30,8 @@
30
30
  "test": "node --test"
31
31
  },
32
32
  "dependencies": {
33
- "@yunsoft/yuncms-api": "0.1.26",
34
- "@yunsoft/yuncms-core": "0.1.26"
33
+ "@yunsoft/yuncms-api": "0.1.27",
34
+ "@yunsoft/yuncms-core": "0.1.27"
35
35
  },
36
36
  "funding": {
37
37
  "type": "github",
@@ -1,14 +1,21 @@
1
1
  import { randomBytes } from 'node:crypto';
2
- import { mkdir, open, rm } from 'node:fs/promises';
2
+ import { lstat, mkdir, open, readFile, rm } from 'node:fs/promises';
3
3
  import { dirname, resolve } from 'node:path';
4
4
 
5
5
  import {
6
+ assertSecureUserParentDir,
6
7
  hashMaintenanceBypassToken,
8
+ legacyMaintenanceLockPath,
7
9
  maintenanceLockPath,
10
+ probeLegacyMaintenanceLock,
8
11
  } from '@yunsoft/yuncms-core';
9
12
 
10
- export function updateLockPath(cwd = process.cwd()) {
11
- return maintenanceLockPath(cwd);
13
+ export function updateLockPath(cwd = process.cwd(), options = {}) {
14
+ return maintenanceLockPath(cwd, options);
15
+ }
16
+
17
+ export function legacyUpdateLockPath(cwd = process.cwd(), options = {}) {
18
+ return legacyMaintenanceLockPath(cwd, options);
12
19
  }
13
20
 
14
21
  export async function acquireUpdateLock({
@@ -16,10 +23,54 @@ export async function acquireUpdateLock({
16
23
  now = new Date(),
17
24
  pid = process.pid,
18
25
  generateToken = () => randomBytes(32).toString('hex'),
26
+ tempRoot,
27
+ getuidFn,
28
+ userInfoFn,
29
+ readFileFn = readFile,
30
+ lstatFn = lstat,
19
31
  } = {}) {
20
- const path = updateLockPath(cwd);
21
- await mkdir(dirname(path), { recursive: true, mode: 0o700 });
32
+ const options = {
33
+ cwd,
34
+ tempRoot,
35
+ getuidFn,
36
+ userInfoFn,
37
+ readFileFn,
38
+ lstatFn,
39
+ };
40
+
41
+ // 1. An accessible active or corrupt legacy lock blocks new CLI acquisition
42
+ let legacy;
43
+ try {
44
+ legacy = await probeLegacyMaintenanceLock(options);
45
+ } catch (err) {
46
+ if (err?.code === 'YUNCMS_MAINTENANCE_ACTIVE') {
47
+ const locked = new Error(
48
+ `Another YunCMS backup/update/restore operation may already be running. Inspect and remove the stale lock only after verifying no operation is active: ${err.lockPath || legacyMaintenanceLockPath(cwd, options)}`,
49
+ );
50
+ locked.code = 'UPDATE_ALREADY_RUNNING';
51
+ locked.lockPath = err.lockPath || legacyMaintenanceLockPath(cwd, options);
52
+ locked.cause = err;
53
+ throw locked;
54
+ }
55
+ throw err;
56
+ }
57
+
58
+ if (legacy) {
59
+ const locked = new Error(
60
+ `Another YunCMS backup/update/restore operation may already be running. Inspect and remove the stale lock only after verifying no operation is active: ${legacy.path}`,
61
+ );
62
+ locked.code = 'UPDATE_ALREADY_RUNNING';
63
+ locked.lockPath = legacy.path;
64
+ throw locked;
65
+ }
66
+
67
+ // 2. Prepare per-user lock directory and validate security
68
+ const path = updateLockPath(cwd, options);
69
+ const parentDir = dirname(path);
70
+ await mkdir(parentDir, { recursive: true, mode: 0o700 });
71
+ await assertSecureUserParentDir(parentDir, { lstatFn, getuidFn });
22
72
 
73
+ // 3. Acquire exclusive lock file
23
74
  let handle;
24
75
  try {
25
76
  handle = await open(path, 'wx', 0o600);