@yunsoft/yuncms 0.1.9 → 0.1.10
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/package.json +3 -3
- package/src/project-backup.js +34 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yunsoft/yuncms",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Command-line setup and runtime launcher for YunCMS.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"test": "node --test"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@yunsoft/yuncms-api": "0.1.
|
|
34
|
-
"@yunsoft/yuncms-core": "0.1.
|
|
33
|
+
"@yunsoft/yuncms-api": "0.1.10",
|
|
34
|
+
"@yunsoft/yuncms-core": "0.1.10"
|
|
35
35
|
}
|
|
36
36
|
}
|
package/src/project-backup.js
CHANGED
|
@@ -32,6 +32,7 @@ const LEGACY_BACKUP_FORMAT_VERSION = 1;
|
|
|
32
32
|
export const BACKUP_FORMAT_VERSION = 2;
|
|
33
33
|
const SUPPORTED_BACKUP_FORMATS = new Set([LEGACY_BACKUP_FORMAT_VERSION, BACKUP_FORMAT_VERSION]);
|
|
34
34
|
const SHA256_PATTERN = /^[0-9a-f]{64}$/i;
|
|
35
|
+
const AI_SETTINGS_KEY_RELATIVE_PATH = join('.yuncms', 'ai-settings.key');
|
|
35
36
|
|
|
36
37
|
async function exists(path) {
|
|
37
38
|
try {
|
|
@@ -115,6 +116,9 @@ function validateBackupManifest(manifest, manifestPath) {
|
|
|
115
116
|
for (const key of ['env', 'packageJson', 'packageLock', 'extensions', 'localFiles']) {
|
|
116
117
|
assertManifestBoolean(manifest.project[key], `project.${key}`, manifestPath);
|
|
117
118
|
}
|
|
119
|
+
if (manifest.project.aiSettingsKey !== undefined) {
|
|
120
|
+
assertManifestBoolean(manifest.project.aiSettingsKey, 'project.aiSettingsKey', manifestPath);
|
|
121
|
+
}
|
|
118
122
|
assertManifestString(manifest.project.localFilesRoot, 'project.localFilesRoot', manifestPath);
|
|
119
123
|
|
|
120
124
|
if (!isObject(manifest.s3)) throw manifestError(manifestPath, 's3 metadata is required');
|
|
@@ -133,16 +137,21 @@ function validateBackupManifest(manifest, manifestPath) {
|
|
|
133
137
|
if (!isObject(manifest.integrity.project)) {
|
|
134
138
|
throw manifestError(manifestPath, 'integrity.project is required');
|
|
135
139
|
}
|
|
136
|
-
for (const [key, present] of [
|
|
137
|
-
['env', manifest.project.env],
|
|
138
|
-
['packageJson', manifest.project.packageJson],
|
|
139
|
-
['packageLock', manifest.project.packageLock],
|
|
140
|
-
['extensions', manifest.project.extensions],
|
|
141
|
-
['localFiles', manifest.project.localFiles],
|
|
140
|
+
for (const [key, present, optionalLegacy] of [
|
|
141
|
+
['env', manifest.project.env, false],
|
|
142
|
+
['packageJson', manifest.project.packageJson, false],
|
|
143
|
+
['packageLock', manifest.project.packageLock, false],
|
|
144
|
+
['extensions', manifest.project.extensions, false],
|
|
145
|
+
['localFiles', manifest.project.localFiles, false],
|
|
146
|
+
['aiSettingsKey', manifest.project.aiSettingsKey === true, manifest.project.aiSettingsKey === undefined],
|
|
142
147
|
]) {
|
|
143
148
|
const digest = manifest.integrity.project[key];
|
|
144
149
|
if (present) assertDigest(digest, `integrity.project.${key}`, manifestPath);
|
|
145
|
-
else if (digest !== null)
|
|
150
|
+
else if (!optionalLegacy && digest !== null) {
|
|
151
|
+
throw manifestError(manifestPath, `integrity.project.${key} must be null when the asset is absent`);
|
|
152
|
+
} else if (optionalLegacy && digest !== undefined && digest !== null) {
|
|
153
|
+
throw manifestError(manifestPath, `integrity.project.${key} must be null when the legacy manifest has no asset flag`);
|
|
154
|
+
}
|
|
146
155
|
}
|
|
147
156
|
}
|
|
148
157
|
|
|
@@ -208,6 +217,7 @@ async function assertExpectedBackupAssets(backupPath, manifest) {
|
|
|
208
217
|
[manifest.project.env, join(backupPath, 'project', '.env'), 'file'],
|
|
209
218
|
[manifest.project.packageJson, join(backupPath, 'project', 'package.json'), 'file'],
|
|
210
219
|
[manifest.project.packageLock, join(backupPath, 'project', 'package-lock.json'), 'file'],
|
|
220
|
+
[manifest.project.aiSettingsKey === true, join(backupPath, 'project', 'ai-settings.key'), 'file'],
|
|
211
221
|
[manifest.project.extensions, join(backupPath, 'extensions'), 'directory'],
|
|
212
222
|
[manifest.project.localFiles, join(backupPath, 'files'), 'directory'],
|
|
213
223
|
];
|
|
@@ -227,6 +237,7 @@ async function calculateBackupIntegrity(destination, presence) {
|
|
|
227
237
|
env: presence.env ? await hashFile(join(destination, 'project', '.env')) : null,
|
|
228
238
|
packageJson: presence.packageJson ? await hashFile(join(destination, 'project', 'package.json')) : null,
|
|
229
239
|
packageLock: presence.packageLock ? await hashFile(join(destination, 'project', 'package-lock.json')) : null,
|
|
240
|
+
aiSettingsKey: presence.aiSettingsKey ? await hashFile(join(destination, 'project', 'ai-settings.key')) : null,
|
|
230
241
|
extensions: presence.extensions ? await hashDirectory(join(destination, 'extensions')) : null,
|
|
231
242
|
localFiles: presence.localFiles ? await hashDirectory(join(destination, 'files')) : null,
|
|
232
243
|
},
|
|
@@ -240,6 +251,7 @@ async function verifyBackupIntegrity(backupPath, manifest) {
|
|
|
240
251
|
['env', ['project', '.env'], 'file'],
|
|
241
252
|
['packageJson', ['project', 'package.json'], 'file'],
|
|
242
253
|
['packageLock', ['project', 'package-lock.json'], 'file'],
|
|
254
|
+
['aiSettingsKey', ['project', 'ai-settings.key'], 'file'],
|
|
243
255
|
['extensions', ['extensions'], 'directory'],
|
|
244
256
|
['localFiles', ['files'], 'directory'],
|
|
245
257
|
]) {
|
|
@@ -308,6 +320,7 @@ async function assertRestoreTargets(cwd, localFilesPath, extensionsPath, allowDi
|
|
|
308
320
|
await assertRestoreTarget(extensionsPath, 'directory');
|
|
309
321
|
await assertRestoreTarget(resolve(cwd, 'package.json'), 'file');
|
|
310
322
|
await assertRestoreTarget(resolve(cwd, 'package-lock.json'), 'file');
|
|
323
|
+
await assertRestoreTarget(resolve(cwd, AI_SETTINGS_KEY_RELATIVE_PATH), 'file');
|
|
311
324
|
if (!allowDifferentDatabaseTarget) await assertRestoreTarget(resolve(cwd, '.env'), 'file');
|
|
312
325
|
}
|
|
313
326
|
|
|
@@ -393,6 +406,7 @@ export async function createProjectBackup({
|
|
|
393
406
|
env: await copyOptionalFile(resolve(cwd, '.env'), join(destination, 'project', '.env')),
|
|
394
407
|
packageJson: await copyOptionalFile(resolve(cwd, 'package.json'), join(destination, 'project', 'package.json')),
|
|
395
408
|
packageLock: await copyOptionalFile(resolve(cwd, 'package-lock.json'), join(destination, 'project', 'package-lock.json')),
|
|
409
|
+
aiSettingsKey: await copyOptionalFile(resolve(cwd, AI_SETTINGS_KEY_RELATIVE_PATH), join(destination, 'project', 'ai-settings.key')),
|
|
396
410
|
extensions: await copyOptionalDirectory(extensionsPath, join(destination, 'extensions')),
|
|
397
411
|
localFiles: await copyOptionalDirectory(localFilesPath, join(destination, 'files')),
|
|
398
412
|
};
|
|
@@ -462,7 +476,7 @@ async function restoreOptionalFile({ backupPath, existed, sourceName, target })
|
|
|
462
476
|
await rm(target, { force: true });
|
|
463
477
|
return;
|
|
464
478
|
}
|
|
465
|
-
await mkdir(dirname(target), { recursive: true });
|
|
479
|
+
await mkdir(dirname(target), { recursive: true, mode: 0o700 });
|
|
466
480
|
await copyFile(source, target);
|
|
467
481
|
}
|
|
468
482
|
|
|
@@ -548,6 +562,16 @@ export async function restoreProjectBackup({
|
|
|
548
562
|
sourceName: 'package-lock.json',
|
|
549
563
|
target: resolve(cwd, 'package-lock.json'),
|
|
550
564
|
});
|
|
565
|
+
if (manifest.project.aiSettingsKey !== undefined) {
|
|
566
|
+
await restoreOptionalFile({
|
|
567
|
+
backupPath: resolvedBackupPath,
|
|
568
|
+
existed: manifest.project.aiSettingsKey,
|
|
569
|
+
sourceName: 'ai-settings.key',
|
|
570
|
+
target: resolve(cwd, AI_SETTINGS_KEY_RELATIVE_PATH),
|
|
571
|
+
});
|
|
572
|
+
} else {
|
|
573
|
+
output.warn?.('Backup predates AI settings key snapshots; preserving the current .yuncms/ai-settings.key if present.');
|
|
574
|
+
}
|
|
551
575
|
if (allowDifferentDatabaseTarget) {
|
|
552
576
|
output.warn?.('Preserving the current .env because restore targets a different database; the backup .env remains available inside the backup directory.');
|
|
553
577
|
} else {
|
|
@@ -562,3 +586,5 @@ export async function restoreProjectBackup({
|
|
|
562
586
|
output.log?.(`Restore completed: ${resolvedBackupPath}`);
|
|
563
587
|
return { backupPath: resolvedBackupPath, manifest };
|
|
564
588
|
}
|
|
589
|
+
|
|
590
|
+
export { AI_SETTINGS_KEY_RELATIVE_PATH, validateBackupManifest };
|