@openclaw/fs-safe 0.14.0 → 0.15.0
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/CHANGELOG.md +50 -0
- package/README.md +10 -4
- package/dist/advanced.d.ts +1 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +1 -1
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +113 -46
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +4 -4
- package/dist/archive-zip-directory.d.ts +4 -0
- package/dist/archive-zip-directory.d.ts.map +1 -1
- package/dist/archive-zip-directory.js +2 -0
- package/dist/archive-zip-entry.d.ts +6 -2
- package/dist/archive-zip-entry.d.ts.map +1 -1
- package/dist/archive-zip-entry.js +23 -8
- package/dist/archive-zip-integrity.d.ts.map +1 -1
- package/dist/archive-zip-integrity.js +3 -4
- package/dist/archive-zip-loader.d.ts.map +1 -1
- package/dist/archive-zip-loader.js +107 -31
- package/dist/archive-zip-names.d.ts +1 -0
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +6 -0
- package/dist/archive.js +6 -5
- package/dist/bounded-read-stream.d.ts +0 -1
- package/dist/bounded-read-stream.d.ts.map +1 -1
- package/dist/bounded-read-stream.js +0 -6
- package/dist/copy-publication.d.ts +6 -0
- package/dist/copy-publication.d.ts.map +1 -1
- package/dist/copy-publication.js +3 -0
- package/dist/copy-tree-portable.d.ts.map +1 -1
- package/dist/copy-tree-portable.js +44 -24
- package/dist/copy.d.ts.map +1 -1
- package/dist/copy.js +29 -11
- package/dist/directory-mode-owner.js +5 -5
- package/dist/file-handle-transfer.d.ts +2 -0
- package/dist/file-handle-transfer.d.ts.map +1 -1
- package/dist/file-handle-transfer.js +57 -2
- package/dist/file-identity.d.ts.map +1 -1
- package/dist/file-identity.js +18 -4
- package/dist/file-lock-sync-admission.d.ts +19 -0
- package/dist/file-lock-sync-admission.d.ts.map +1 -0
- package/dist/file-lock-sync-admission.js +93 -0
- package/dist/file-lock-sync-root-acquire.d.ts +4 -0
- package/dist/file-lock-sync-root-acquire.d.ts.map +1 -0
- package/dist/file-lock-sync-root-acquire.js +370 -0
- package/dist/file-lock-sync-root-arbitration.d.ts +18 -0
- package/dist/file-lock-sync-root-arbitration.d.ts.map +1 -0
- package/dist/file-lock-sync-root-arbitration.js +66 -0
- package/dist/file-lock-sync-root-held.d.ts +34 -0
- package/dist/file-lock-sync-root-held.d.ts.map +1 -0
- package/dist/file-lock-sync-root-held.js +393 -0
- package/dist/file-lock-sync-root-io.d.ts +44 -0
- package/dist/file-lock-sync-root-io.d.ts.map +1 -0
- package/dist/file-lock-sync-root-io.js +209 -0
- package/dist/file-lock-sync-root-mutation.d.ts +17 -0
- package/dist/file-lock-sync-root-mutation.d.ts.map +1 -0
- package/dist/file-lock-sync-root-mutation.js +277 -0
- package/dist/file-lock-sync-root-options.d.ts +20 -0
- package/dist/file-lock-sync-root-options.d.ts.map +1 -0
- package/dist/file-lock-sync-root-options.js +58 -0
- package/dist/file-lock-sync-root-registration.d.ts +2 -0
- package/dist/file-lock-sync-root-registration.d.ts.map +1 -0
- package/dist/file-lock-sync-root-registration.js +90 -0
- package/dist/file-lock-sync-root.d.ts +36 -0
- package/dist/file-lock-sync-root.d.ts.map +1 -0
- package/dist/file-lock-sync-root.js +361 -0
- package/dist/file-lock-sync-stale-admission.d.ts +24 -0
- package/dist/file-lock-sync-stale-admission.d.ts.map +1 -0
- package/dist/file-lock-sync-stale-admission.js +205 -0
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +245 -205
- package/dist/file-store-prune.d.ts.map +1 -1
- package/dist/file-store-prune.js +5 -1
- package/dist/file-store-sync-write.js +3 -3
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +47 -12
- package/dist/json-document-store.d.ts.map +1 -1
- package/dist/json-document-store.js +22 -15
- package/dist/json-durable-queue-ownership.d.ts +0 -1
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +0 -6
- package/dist/native-binding.d.ts +2 -0
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-parent-admission.d.ts +3 -2
- package/dist/native-parent-admission.d.ts.map +1 -1
- package/dist/native-parent-admission.js +24 -5
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +7 -7
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +7 -11
- package/dist/native-policy-parent-windows.d.ts +14 -0
- package/dist/native-policy-parent-windows.d.ts.map +1 -0
- package/dist/native-policy-parent-windows.js +200 -0
- package/dist/native-rename-outcome.d.ts +4 -0
- package/dist/native-rename-outcome.d.ts.map +1 -0
- package/dist/native-rename-outcome.js +8 -0
- package/dist/native-staged-file.d.ts +2 -2
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +42 -40
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +12 -8
- package/dist/path-prefix.d.ts.map +1 -1
- package/dist/path-prefix.js +30 -8
- package/dist/path-suffix-aliases.d.ts +2 -0
- package/dist/path-suffix-aliases.d.ts.map +1 -1
- package/dist/path-suffix-aliases.js +25 -17
- package/dist/permission-exec.d.ts +2 -0
- package/dist/permission-exec.d.ts.map +1 -1
- package/dist/permission-exec.js +150 -21
- package/dist/permissions-windows.js +1 -1
- package/dist/pinned-mutation-admission.d.ts.map +1 -1
- package/dist/pinned-mutation-admission.js +10 -5
- package/dist/pinned-mutation-observation.d.ts +0 -1
- package/dist/pinned-mutation-observation.d.ts.map +1 -1
- package/dist/pinned-mutation-observation.js +0 -19
- package/dist/pinned-mutation-shared-route.d.ts +1 -0
- package/dist/pinned-mutation-shared-route.d.ts.map +1 -1
- package/dist/pinned-mutation-shared-route.js +1 -1
- package/dist/pinned-write-types.d.ts +2 -0
- package/dist/pinned-write-types.d.ts.map +1 -1
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +75 -121
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +1 -15
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +256 -18
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +62 -70
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +10 -12
- package/dist/replace-file-temp-owner.d.ts +5 -2
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +65 -30
- package/dist/replace-file.js +6 -6
- package/dist/retained-directory-replacement.d.ts +26 -0
- package/dist/retained-directory-replacement.d.ts.map +1 -0
- package/dist/retained-directory-replacement.js +193 -0
- package/dist/root-boundary.d.ts +1 -0
- package/dist/root-boundary.d.ts.map +1 -1
- package/dist/root-boundary.js +4 -0
- package/dist/root-context.d.ts +0 -8
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +0 -3
- package/dist/root-create-input.d.ts +6 -0
- package/dist/root-create-input.d.ts.map +1 -1
- package/dist/root-create-input.js +5 -1
- package/dist/root-directory-list.d.ts +1 -0
- package/dist/root-directory-list.d.ts.map +1 -1
- package/dist/root-directory-list.js +1 -0
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +18 -10
- package/dist/root-move-noreplace.d.ts.map +1 -1
- package/dist/root-move-noreplace.js +2 -2
- package/dist/root-path-errors.d.ts +1 -0
- package/dist/root-path-errors.d.ts.map +1 -1
- package/dist/root-path-errors.js +11 -2
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +11 -35
- package/dist/root-path.js +1 -13
- package/dist/root-remove.d.ts +1 -0
- package/dist/root-remove.d.ts.map +1 -1
- package/dist/root-remove.js +4 -0
- package/dist/root-walk.d.ts +1 -1
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +17 -2
- package/dist/root-write-admission.d.ts +0 -2
- package/dist/root-write-admission.d.ts.map +1 -1
- package/dist/root-write-admission.js +1 -15
- package/dist/root-write-complete-parent.d.ts.map +1 -1
- package/dist/root-write-complete-parent.js +7 -23
- package/dist/root-write-verification.d.ts.map +1 -1
- package/dist/root-write-verification.js +29 -42
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +2 -24
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +3 -24
- package/dist/secret-read-policy.d.ts +6 -2
- package/dist/secret-read-policy.d.ts.map +1 -1
- package/dist/secret-read-policy.js +26 -2
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +23 -12
- package/dist/sidecar-lock-acquire.d.ts +2 -28
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +288 -199
- package/dist/sidecar-lock-admission-context.d.ts +19 -0
- package/dist/sidecar-lock-admission-context.d.ts.map +1 -0
- package/dist/sidecar-lock-admission-context.js +60 -0
- package/dist/sidecar-lock-admission-parser.d.ts +43 -0
- package/dist/sidecar-lock-admission-parser.d.ts.map +1 -0
- package/dist/sidecar-lock-admission-parser.js +113 -0
- package/dist/sidecar-lock-admission.d.ts +35 -0
- package/dist/sidecar-lock-admission.d.ts.map +1 -0
- package/dist/sidecar-lock-admission.js +7 -0
- package/dist/sidecar-lock-reclaim.d.ts +9 -4
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +80 -25
- package/dist/sidecar-lock-stale-admission.d.ts +39 -0
- package/dist/sidecar-lock-stale-admission.d.ts.map +1 -0
- package/dist/sidecar-lock-stale-admission.js +232 -0
- package/dist/sidecar-lock-target.d.ts +8 -0
- package/dist/sidecar-lock-target.d.ts.map +1 -0
- package/dist/sidecar-lock-target.js +55 -0
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +100 -16
- package/dist/temp-workspace-descriptor.d.ts.map +1 -1
- package/dist/temp-workspace-descriptor.js +9 -27
- package/dist/temp-workspace-owner.d.ts.map +1 -1
- package/dist/temp-workspace-owner.js +8 -8
- package/dist/walk.d.ts +5 -1
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +19 -6
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +2 -2
- package/docs/advanced.md +1 -1
- package/docs/archive.md +36 -9
- package/docs/atomic.md +85 -8
- package/docs/copy.md +35 -0
- package/docs/file-store.md +19 -0
- package/docs/json-store.md +5 -0
- package/docs/native-helper.md +10 -3
- package/docs/output.md +6 -0
- package/docs/path-prefix.md +10 -0
- package/docs/path-suffix-aliases.md +51 -6
- package/docs/permissions.md +13 -0
- package/docs/public-api.md +3 -2
- package/docs/root.md +22 -3
- package/docs/sidecar-lock.md +109 -4
- package/docs/staged-file.md +7 -3
- package/docs/temp.md +20 -3
- package/docs/walk.md +67 -1
- package/docs/writing.md +5 -1
- package/package.json +10 -10
- package/dist/darwin-acl.d.ts +0 -4
- package/dist/darwin-acl.d.ts.map +0 -1
- package/dist/darwin-acl.js +0 -24
|
@@ -107,30 +107,83 @@ function throwTempWorkspaceOpenFailure(failure) {
|
|
|
107
107
|
},
|
|
108
108
|
});
|
|
109
109
|
}
|
|
110
|
-
|
|
110
|
+
function tempWorkspaceSettings(options) {
|
|
111
111
|
const rootDir = options.rootDir;
|
|
112
112
|
assertNoWindowsPathAlias(rootDir, "filesystem", "temp workspace root uses a Windows filesystem namespace alias");
|
|
113
113
|
const dirMode = options.dirMode ?? 0o700;
|
|
114
114
|
validateTempWorkspaceDirMode(dirMode);
|
|
115
115
|
const mode = options.mode ?? 0o600;
|
|
116
116
|
const cleanupSafety = resolveTempWorkspaceCleanupSafety(options.cleanupSafety);
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
return { rootDir, dirMode, mode, cleanupSafety };
|
|
118
|
+
}
|
|
119
|
+
function tempWorkspaceChildPrefix(root, options, scopedPrefix) {
|
|
119
120
|
assertNoWindowsPathAlias(root, "filesystem", "temp workspace root uses a Windows filesystem namespace alias");
|
|
120
|
-
// Capture and sanitize caller-controlled
|
|
121
|
-
const
|
|
122
|
-
const workspacePrefix = scopedPrefix
|
|
123
|
-
? `${sanitizeTempPrefix(rawPrefix)}${randomUUID()}-`
|
|
124
|
-
: rawPrefix;
|
|
121
|
+
// Capture and sanitize caller-controlled data before retaining descriptors.
|
|
122
|
+
const prefix = options.prefix;
|
|
123
|
+
const workspacePrefix = scopedPrefix ? `${sanitizeTempPrefix(prefix)}${randomUUID()}-` : prefix;
|
|
125
124
|
const childPrefix = path.join(root, sanitizeTempPrefix(workspacePrefix));
|
|
126
125
|
assertNoWindowsPathAlias(childPrefix, "filesystem", "temp workspace path uses a Windows filesystem namespace alias");
|
|
126
|
+
return childPrefix;
|
|
127
|
+
}
|
|
128
|
+
function throwTempWorkspaceCreationFailure(error, retainedChild, capability, owner) {
|
|
129
|
+
try {
|
|
130
|
+
if (owner)
|
|
131
|
+
owner.cleanupSync();
|
|
132
|
+
else {
|
|
133
|
+
const closeErrors = [];
|
|
134
|
+
try {
|
|
135
|
+
retainedChild?.close();
|
|
136
|
+
}
|
|
137
|
+
catch (closeError) {
|
|
138
|
+
closeErrors.push(closeError);
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
capability.close();
|
|
142
|
+
}
|
|
143
|
+
catch (closeError) {
|
|
144
|
+
closeErrors.push(closeError);
|
|
145
|
+
}
|
|
146
|
+
if (closeErrors.length === 1)
|
|
147
|
+
throw closeErrors[0];
|
|
148
|
+
if (closeErrors.length > 1) {
|
|
149
|
+
throw new AggregateError(closeErrors, "temp workspace admission descriptor close failed");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (cleanupError) {
|
|
154
|
+
throw new AggregateError([error, cleanupError], "temp workspace creation and cleanup both failed");
|
|
155
|
+
}
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
function registerTempWorkspace(dir, retainedChild, capability, admission, dirMode) {
|
|
159
|
+
let owner;
|
|
160
|
+
let stat;
|
|
161
|
+
let unregisterTempDir;
|
|
162
|
+
try {
|
|
163
|
+
const retainChildDescriptor = capability.admitChildDescriptor(retainedChild.ensureReadable());
|
|
164
|
+
// Final adoption orders ancestry and cleanup-parent authority before the
|
|
165
|
+
// original child's descriptor and named security-state checks.
|
|
166
|
+
if (capability.parent)
|
|
167
|
+
capability.assertAncestryCurrent();
|
|
168
|
+
else
|
|
169
|
+
admission.assertAncestry();
|
|
170
|
+
stat = retainedChild.finalizeAdmission(admission.ownerUid, dirMode);
|
|
171
|
+
owner = new TempWorkspaceCleanupOwner(retainedChild, capability, retainChildDescriptor);
|
|
172
|
+
unregisterTempDir = registerTempPathForExit(dir, { cleanupSync: () => owner.cleanupSync() });
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
throwTempWorkspaceCreationFailure(error, retainedChild, capability, owner);
|
|
176
|
+
}
|
|
177
|
+
return { owner: owner, unregisterTempDir, identity: { dev: Number(stat.dev), ino: Number(stat.ino) } };
|
|
178
|
+
}
|
|
179
|
+
async function createTempWorkspace(options, scopedPrefix = false) {
|
|
180
|
+
const { rootDir, dirMode, mode, cleanupSafety } = tempWorkspaceSettings(options);
|
|
181
|
+
const admission = await admitTempWorkspaceRoot(rootDir);
|
|
182
|
+
const root = admission.dir;
|
|
183
|
+
const childPrefix = tempWorkspaceChildPrefix(root, options, scopedPrefix);
|
|
127
184
|
const capability = new TempWorkspaceCleanupCapability(root, cleanupSafety, admission, dirMode);
|
|
128
185
|
let dir;
|
|
129
|
-
let stat;
|
|
130
186
|
let retainedChild;
|
|
131
|
-
let retainChildDescriptor = false;
|
|
132
|
-
let cleanupOwner;
|
|
133
|
-
let unregisterTempDir;
|
|
134
187
|
try {
|
|
135
188
|
// Native capability discovery is complete before this synchronous
|
|
136
189
|
// boundary. The existing canonical-root route now captures the complete
|
|
@@ -143,7 +196,7 @@ async function createTempWorkspace(options, scopedPrefix = false) {
|
|
|
143
196
|
capability.assertCurrent();
|
|
144
197
|
else
|
|
145
198
|
admission.assertCurrent();
|
|
146
|
-
stat = inspectDirectoryIdentitySync(dir);
|
|
199
|
+
const stat = inspectDirectoryIdentitySync(dir);
|
|
147
200
|
const needsModeInitialization = validateInitialTempWorkspaceChild(stat, admission.ownerUid, dirMode);
|
|
148
201
|
// Retain while the child still has its private creation mode so an
|
|
149
202
|
// explicit dirMode such as 0 cannot make identity descriptor acquisition fail.
|
|
@@ -151,52 +204,11 @@ async function createTempWorkspace(options, scopedPrefix = false) {
|
|
|
151
204
|
const modeInitialization = admitRetainedTempWorkspaceChild(retainedChild, needsModeInitialization, admission, dirMode);
|
|
152
205
|
if (modeInitialization)
|
|
153
206
|
await modeInitialization;
|
|
154
|
-
retainChildDescriptor = capability.admitChildDescriptor(retainedChild.ensureReadable());
|
|
155
|
-
// Final adoption order is deliberate: complete ancestry, retained cleanup
|
|
156
|
-
// parent, then descriptor and named checks of the original child identity.
|
|
157
|
-
if (capability.parent)
|
|
158
|
-
capability.assertAncestryCurrent();
|
|
159
|
-
else
|
|
160
|
-
admission.assertAncestry();
|
|
161
|
-
stat = retainedChild.finalizeAdmission(admission.ownerUid, dirMode);
|
|
162
|
-
cleanupOwner = new TempWorkspaceCleanupOwner(retainedChild, capability, retainChildDescriptor);
|
|
163
|
-
retainedChild = undefined;
|
|
164
|
-
unregisterTempDir = registerTempPathForExit(dir, {
|
|
165
|
-
cleanupSync: () => cleanupOwner.cleanupSync(),
|
|
166
|
-
});
|
|
167
207
|
}
|
|
168
208
|
catch (error) {
|
|
169
|
-
|
|
170
|
-
if (cleanupOwner)
|
|
171
|
-
cleanupOwner.cleanupSync();
|
|
172
|
-
else {
|
|
173
|
-
const closeErrors = [];
|
|
174
|
-
try {
|
|
175
|
-
retainedChild?.close();
|
|
176
|
-
}
|
|
177
|
-
catch (closeError) {
|
|
178
|
-
closeErrors.push(closeError);
|
|
179
|
-
}
|
|
180
|
-
try {
|
|
181
|
-
capability.close();
|
|
182
|
-
}
|
|
183
|
-
catch (closeError) {
|
|
184
|
-
closeErrors.push(closeError);
|
|
185
|
-
}
|
|
186
|
-
if (closeErrors.length === 1)
|
|
187
|
-
throw closeErrors[0];
|
|
188
|
-
if (closeErrors.length > 1) {
|
|
189
|
-
throw new AggregateError(closeErrors, "temp workspace admission descriptor close failed");
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
catch (cleanupError) {
|
|
194
|
-
throw new AggregateError([error, cleanupError], "temp workspace creation and cleanup both failed");
|
|
195
|
-
}
|
|
196
|
-
throw error;
|
|
209
|
+
throwTempWorkspaceCreationFailure(error, retainedChild, capability);
|
|
197
210
|
}
|
|
198
|
-
const owner =
|
|
199
|
-
const identity = { dev: Number(stat.dev), ino: Number(stat.ino) };
|
|
211
|
+
const { owner, identity, unregisterTempDir } = registerTempWorkspace(dir, retainedChild, capability, admission, dirMode);
|
|
200
212
|
// Once registered, even a store-construction failure remains exit-cleanable.
|
|
201
213
|
const store = fileStore({ rootDir: dir, private: true, dirMode, mode });
|
|
202
214
|
return {
|
|
@@ -250,30 +262,13 @@ export async function withTempWorkspace(options, run) {
|
|
|
250
262
|
}
|
|
251
263
|
}
|
|
252
264
|
function createTempWorkspaceSync(options, scopedPrefix = false) {
|
|
253
|
-
const rootDir = options
|
|
254
|
-
assertNoWindowsPathAlias(rootDir, "filesystem", "temp workspace root uses a Windows filesystem namespace alias");
|
|
255
|
-
const dirMode = options.dirMode ?? 0o700;
|
|
256
|
-
validateTempWorkspaceDirMode(dirMode);
|
|
257
|
-
const mode = options.mode ?? 0o600;
|
|
258
|
-
const cleanupSafety = resolveTempWorkspaceCleanupSafety(options.cleanupSafety);
|
|
265
|
+
const { rootDir, dirMode, mode, cleanupSafety } = tempWorkspaceSettings(options);
|
|
259
266
|
const admission = admitTempWorkspaceRootSync(rootDir);
|
|
260
267
|
const root = admission.dir;
|
|
261
|
-
|
|
262
|
-
// Capture and sanitize caller-controlled prefix data before retaining descriptors.
|
|
263
|
-
const rawPrefix = options.prefix;
|
|
264
|
-
const workspacePrefix = scopedPrefix
|
|
265
|
-
? `${sanitizeTempPrefix(rawPrefix)}${randomUUID()}-`
|
|
266
|
-
: rawPrefix;
|
|
267
|
-
const childPrefix = path.join(root, sanitizeTempPrefix(workspacePrefix));
|
|
268
|
-
assertNoWindowsPathAlias(childPrefix, "filesystem", "temp workspace path uses a Windows filesystem namespace alias");
|
|
268
|
+
const childPrefix = tempWorkspaceChildPrefix(root, options, scopedPrefix);
|
|
269
269
|
const capability = new TempWorkspaceCleanupCapability(root, cleanupSafety, admission, dirMode);
|
|
270
270
|
let dir;
|
|
271
|
-
let stat;
|
|
272
271
|
let retainedChild;
|
|
273
|
-
let retainChildDescriptor = false;
|
|
274
|
-
let needsModeInitialization;
|
|
275
|
-
let cleanupOwner;
|
|
276
|
-
let unregisterTempDir;
|
|
277
272
|
try {
|
|
278
273
|
const directRequestedMode = canCreateTempWorkspaceWithRequestedMode(dirMode);
|
|
279
274
|
if (directRequestedMode) {
|
|
@@ -288,64 +283,23 @@ function createTempWorkspaceSync(options, scopedPrefix = false) {
|
|
|
288
283
|
capability.assertCurrent();
|
|
289
284
|
else
|
|
290
285
|
admission.assertCurrent();
|
|
286
|
+
let stat;
|
|
291
287
|
if (directRequestedMode) {
|
|
292
288
|
const created = TempWorkspaceRetainedChild.retainCreated(dir);
|
|
293
289
|
retainedChild = created.retained;
|
|
294
290
|
stat = created.stat;
|
|
295
|
-
needsModeInitialization = validateInitialTempWorkspaceChild(stat, admission.ownerUid, dirMode);
|
|
296
291
|
}
|
|
297
292
|
else {
|
|
298
293
|
stat = inspectDirectoryIdentitySync(dir);
|
|
299
|
-
needsModeInitialization = validateInitialTempWorkspaceChild(stat, admission.ownerUid, dirMode);
|
|
300
|
-
retainedChild = TempWorkspaceRetainedChild.retain(dir, stat);
|
|
301
294
|
}
|
|
295
|
+
const needsModeInitialization = validateInitialTempWorkspaceChild(stat, admission.ownerUid, dirMode);
|
|
296
|
+
retainedChild ??= TempWorkspaceRetainedChild.retain(dir, stat);
|
|
302
297
|
admitRetainedTempWorkspaceChildSync(retainedChild, needsModeInitialization, admission, dirMode);
|
|
303
|
-
retainChildDescriptor = capability.admitChildDescriptor(retainedChild.ensureReadable());
|
|
304
|
-
// Match async adoption: complete ancestry and retained cleanup authority
|
|
305
|
-
// precede descriptor and named child security-state checks.
|
|
306
|
-
if (capability.parent)
|
|
307
|
-
capability.assertAncestryCurrent();
|
|
308
|
-
else
|
|
309
|
-
admission.assertAncestry();
|
|
310
|
-
stat = retainedChild.finalizeAdmission(admission.ownerUid, dirMode);
|
|
311
|
-
cleanupOwner = new TempWorkspaceCleanupOwner(retainedChild, capability, retainChildDescriptor);
|
|
312
|
-
retainedChild = undefined;
|
|
313
|
-
unregisterTempDir = registerTempPathForExit(dir, {
|
|
314
|
-
cleanupSync: () => cleanupOwner.cleanupSync(),
|
|
315
|
-
});
|
|
316
298
|
}
|
|
317
299
|
catch (error) {
|
|
318
|
-
|
|
319
|
-
if (cleanupOwner)
|
|
320
|
-
cleanupOwner.cleanupSync();
|
|
321
|
-
else {
|
|
322
|
-
const closeErrors = [];
|
|
323
|
-
try {
|
|
324
|
-
retainedChild?.close();
|
|
325
|
-
}
|
|
326
|
-
catch (closeError) {
|
|
327
|
-
closeErrors.push(closeError);
|
|
328
|
-
}
|
|
329
|
-
try {
|
|
330
|
-
capability.close();
|
|
331
|
-
}
|
|
332
|
-
catch (closeError) {
|
|
333
|
-
closeErrors.push(closeError);
|
|
334
|
-
}
|
|
335
|
-
if (closeErrors.length === 1)
|
|
336
|
-
throw closeErrors[0];
|
|
337
|
-
if (closeErrors.length > 1) {
|
|
338
|
-
throw new AggregateError(closeErrors, "temp workspace admission descriptor close failed");
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
catch (cleanupError) {
|
|
343
|
-
throw new AggregateError([error, cleanupError], "temp workspace creation and cleanup both failed");
|
|
344
|
-
}
|
|
345
|
-
throw error;
|
|
300
|
+
throwTempWorkspaceCreationFailure(error, retainedChild, capability);
|
|
346
301
|
}
|
|
347
|
-
const owner =
|
|
348
|
-
const identity = { dev: Number(stat.dev), ino: Number(stat.ino) };
|
|
302
|
+
const { owner, identity, unregisterTempDir } = registerTempWorkspace(dir, retainedChild, capability, admission, dirMode);
|
|
349
303
|
// Once registered, even a store-construction failure remains exit-cleanable.
|
|
350
304
|
const store = fileStoreSync({ rootDir: dir, private: true, dirMode, mode });
|
|
351
305
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"regular-file.d.ts","sourceRoot":"","sources":["../src/regular-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,KAAK,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,MAAM,MAAM,SAAS,CAAC;AAuB7B,MAAM,MAAM,qBAAqB,GAAG;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC;AAExF,KAAK,8BAA8B,GAAG,IAAI,CACxC,OAAO,MAAM,CAAC,SAAS,EACvB,UAAU,GAAG,SAAS,GAAG,UAAU,CACpC,GACC,OAAO,CAAC,IAAI,CAAC,OAAO,MAAM,CAAC,SAAS,EAAE,YAAY,GAAG,YAAY,CAAC,CAAC,CAAC;AAEtE,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC,CAAC;AAEF,wBAAgB,6BAA6B,CAC3C,SAAS,GAAE,8BAAiD,GAC3D,MAAM,CASR;AAaD,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,
|
|
1
|
+
{"version":3,"file":"regular-file.d.ts","sourceRoot":"","sources":["../src/regular-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,KAAK,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,MAAM,MAAM,SAAS,CAAC;AAuB7B,MAAM,MAAM,qBAAqB,GAAG;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC;AAExF,KAAK,8BAA8B,GAAG,IAAI,CACxC,OAAO,MAAM,CAAC,SAAS,EACvB,UAAU,GAAG,SAAS,GAAG,UAAU,CACpC,GACC,OAAO,CAAC,IAAI,CAAC,OAAO,MAAM,CAAC,SAAS,EAAE,YAAY,GAAG,YAAY,CAAC,CAAC,CAAC;AAEtE,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC,CAAC;AAEF,wBAAgB,6BAA6B,CAC3C,SAAS,GAAE,8BAAiD,GAC3D,MAAM,CASR;AAaD,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAEtF;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,qBAAqB,CAe3E;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC,CA+D3C;AA2DD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IACpF,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,KAAK,CAAC;CACb,CAsCA;AAmBD,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkFxF;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,wBAAwB,GAAG,IAAI,CAkF7E"}
|
package/dist/regular-file.js
CHANGED
|
@@ -30,21 +30,7 @@ function translateBoundedReadOverflow(error, filePath, maxBytes) {
|
|
|
30
30
|
throw error;
|
|
31
31
|
}
|
|
32
32
|
export async function statRegularFile(filePath) {
|
|
33
|
-
|
|
34
|
-
let stat;
|
|
35
|
-
try {
|
|
36
|
-
stat = fsSync.lstatSync(filePath);
|
|
37
|
-
}
|
|
38
|
-
catch (err) {
|
|
39
|
-
if (isNotFoundPathError(err)) {
|
|
40
|
-
return { missing: true };
|
|
41
|
-
}
|
|
42
|
-
throw err;
|
|
43
|
-
}
|
|
44
|
-
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
45
|
-
throw new Error("path must be a regular file");
|
|
46
|
-
}
|
|
47
|
-
return { missing: false, stat };
|
|
33
|
+
return statRegularFileSync(filePath);
|
|
48
34
|
}
|
|
49
35
|
export function statRegularFileSync(filePath) {
|
|
50
36
|
assertNoWindowsPathAlias(filePath, "filesystem", "file path uses a Windows filesystem namespace alias");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replace-directory.d.ts","sourceRoot":"","sources":["../src/replace-directory.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"replace-directory.d.ts","sourceRoot":"","sources":["../src/replace-directory.ts"],"names":[],"mappings":"AAuBA,MAAM,MAAM,6BAA6B,GAAG;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAqIF,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,IAAI,CAAC,CAiBf"}
|
|
@@ -1,12 +1,94 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
+
import fsSync from "node:fs";
|
|
2
3
|
import fs from "node:fs/promises";
|
|
3
4
|
import path from "node:path";
|
|
4
|
-
import {
|
|
5
|
+
import { assertSyncDirectoryGuard, createAsyncDirectoryGuard, inspectDirectoryIdentitySync, } from "./directory-guard.js";
|
|
6
|
+
import { FsSafeError } from "./errors.js";
|
|
7
|
+
import { classifyNativeRenameFailure, NATIVE_RENAME_SOURCE_IDENTITY_MISMATCH, } from "./native-rename-outcome.js";
|
|
5
8
|
import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
9
|
+
import { RetainedDirectoryReplacement } from "./retained-directory-replacement.js";
|
|
6
10
|
import { assertSafePathPrefix } from "./safe-path-segment.js";
|
|
7
11
|
import { admitStandalonePublicationPath } from "./standalone-publication-path.js";
|
|
8
12
|
import { serializePathWrite } from "./write-queue.js";
|
|
9
13
|
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
14
|
+
function distinctGuards(source, target) {
|
|
15
|
+
return source === target ? [source] : [source, target];
|
|
16
|
+
}
|
|
17
|
+
function assertGuardsCurrent(guards) {
|
|
18
|
+
for (const guard of guards)
|
|
19
|
+
assertSyncDirectoryGuard(guard);
|
|
20
|
+
}
|
|
21
|
+
function copyOperationalCode(target, source) {
|
|
22
|
+
const code = source?.code;
|
|
23
|
+
if (typeof code === "string") {
|
|
24
|
+
Object.defineProperty(target, "code", { configurable: true, value: code });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function replacementFailure(error, message, details) {
|
|
28
|
+
if (error instanceof FsSafeError) {
|
|
29
|
+
return new FsSafeError(error.code, message, { cause: error, details });
|
|
30
|
+
}
|
|
31
|
+
const failure = new Error(message, { cause: error });
|
|
32
|
+
copyOperationalCode(failure, error);
|
|
33
|
+
Object.defineProperty(failure, "details", { configurable: true, value: details });
|
|
34
|
+
return failure;
|
|
35
|
+
}
|
|
36
|
+
function replacementAggregate(error, recoveryError, message, details) {
|
|
37
|
+
const combined = new AggregateError([error, recoveryError], "directory replacement operation and recovery both failed");
|
|
38
|
+
if (error instanceof FsSafeError) {
|
|
39
|
+
return new FsSafeError(error.code, message, { cause: combined, details });
|
|
40
|
+
}
|
|
41
|
+
const failure = new Error(message, { cause: combined });
|
|
42
|
+
copyOperationalCode(failure, error);
|
|
43
|
+
Object.defineProperty(failure, "details", { configurable: true, value: details });
|
|
44
|
+
return failure;
|
|
45
|
+
}
|
|
46
|
+
function isMissingDirectoryEntry(error) {
|
|
47
|
+
return error?.code === "ENOENT";
|
|
48
|
+
}
|
|
49
|
+
function observeOptionalDirectory(pathname) {
|
|
50
|
+
let initial;
|
|
51
|
+
try {
|
|
52
|
+
initial = fsSync.lstatSync(pathname, { bigint: true });
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
if (isMissingDirectoryEntry(error))
|
|
56
|
+
return undefined;
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
return inspectDirectoryIdentitySync(pathname, undefined, initial);
|
|
60
|
+
}
|
|
61
|
+
function normalizeNativeRenameFailure(error) {
|
|
62
|
+
const code = error?.code;
|
|
63
|
+
if (code === NATIVE_RENAME_SOURCE_IDENTITY_MISMATCH) {
|
|
64
|
+
return new FsSafeError("path-mismatch", "directory replacement source identity changed before native rename", { cause: error });
|
|
65
|
+
}
|
|
66
|
+
if (code === "EINVAL" || code === "ENOSYS" || code === "ENOTSUP" ||
|
|
67
|
+
code === "EOPNOTSUPP") {
|
|
68
|
+
return new FsSafeError("helper-unavailable", "native no-replace directory rename is unavailable on this filesystem", { cause: error });
|
|
69
|
+
}
|
|
70
|
+
return error;
|
|
71
|
+
}
|
|
72
|
+
function renameRetainedDirectory(params) {
|
|
73
|
+
const guards = distinctGuards(params.sourceParent, params.targetParent);
|
|
74
|
+
assertGuardsCurrent(guards);
|
|
75
|
+
const initial = fsSync.lstatSync(params.from, { bigint: true });
|
|
76
|
+
const identity = inspectDirectoryIdentitySync(params.from, params.expectedSource, initial);
|
|
77
|
+
try {
|
|
78
|
+
params.renameNoReplace(identity);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
if (classifyNativeRenameFailure(error) === "indeterminate") {
|
|
82
|
+
params.onIndeterminate();
|
|
83
|
+
}
|
|
84
|
+
throw normalizeNativeRenameFailure(error);
|
|
85
|
+
}
|
|
86
|
+
// Record the transition synchronously before any post-rename observation.
|
|
87
|
+
params.onRenamed?.(identity);
|
|
88
|
+
inspectDirectoryIdentitySync(params.to, identity);
|
|
89
|
+
assertGuardsCurrent(guards);
|
|
90
|
+
return identity;
|
|
91
|
+
}
|
|
10
92
|
export async function replaceDirectoryAtomic(options) {
|
|
11
93
|
const stagedDirInput = admitStandalonePublicationPath(options.stagedDir, "staged directory uses a Windows filesystem namespace alias");
|
|
12
94
|
const targetDirInput = admitStandalonePublicationPath(options.targetDir, "target directory uses a Windows filesystem namespace alias");
|
|
@@ -23,28 +105,184 @@ async function replaceDirectoryAtomicUnserialized(stagedDir, targetDir, backupPr
|
|
|
23
105
|
const parentDir = path.dirname(targetDir);
|
|
24
106
|
const backupPrefix = assertSafePathPrefix(backupPrefixInput ?? ".fs-safe-dir-backup-", { label: "atomic directory backup prefix" });
|
|
25
107
|
const backupDir = path.join(parentDir, `${backupPrefix}${process.pid}-${randomUUID()}`);
|
|
26
|
-
|
|
108
|
+
const backup = {
|
|
109
|
+
identity: undefined,
|
|
110
|
+
transition: "not-renamed",
|
|
111
|
+
};
|
|
112
|
+
const publication = {
|
|
113
|
+
status: "not-published",
|
|
114
|
+
};
|
|
115
|
+
let operationFailed = false;
|
|
116
|
+
let operationError;
|
|
117
|
+
let operationDetails = {
|
|
118
|
+
phase: "backup",
|
|
119
|
+
publication: publication.status,
|
|
120
|
+
recovery: "none",
|
|
121
|
+
};
|
|
122
|
+
const retainedBinding = RetainedDirectoryReplacement.requireBinding();
|
|
27
123
|
await fs.mkdir(recursiveMkdirPath(parentDir), { recursive: true });
|
|
124
|
+
const targetParent = await createAsyncDirectoryGuard(parentDir, { bigint: true });
|
|
125
|
+
const stagedParentDir = path.dirname(stagedDir);
|
|
126
|
+
const stagedParent = stagedParentDir === parentDir
|
|
127
|
+
? targetParent
|
|
128
|
+
: await createAsyncDirectoryGuard(stagedParentDir, { bigint: true });
|
|
129
|
+
const stagedIdentity = inspectDirectoryIdentitySync(stagedDir);
|
|
130
|
+
const targetIdentity = observeOptionalDirectory(targetDir);
|
|
131
|
+
const retained = RetainedDirectoryReplacement.retain({
|
|
132
|
+
targetParent: {
|
|
133
|
+
path: parentDir,
|
|
134
|
+
realPath: targetParent.realPath,
|
|
135
|
+
identity: targetParent.stat,
|
|
136
|
+
},
|
|
137
|
+
stagedParent: {
|
|
138
|
+
path: stagedParentDir,
|
|
139
|
+
realPath: stagedParent.realPath,
|
|
140
|
+
identity: stagedParent.stat,
|
|
141
|
+
},
|
|
142
|
+
...(targetIdentity ? {
|
|
143
|
+
originalTarget: { path: targetDir, identity: targetIdentity },
|
|
144
|
+
} : {}),
|
|
145
|
+
}, retainedBinding);
|
|
146
|
+
const targetBasename = path.basename(targetDir);
|
|
147
|
+
const stagedBasename = path.basename(stagedDir);
|
|
148
|
+
const backupBasename = path.basename(backupDir);
|
|
149
|
+
const details = (phase, recovery) => {
|
|
150
|
+
operationDetails = {
|
|
151
|
+
phase,
|
|
152
|
+
publication: publication.status,
|
|
153
|
+
recovery,
|
|
154
|
+
...(backup.transition === "not-renamed" ? {} : { backupPath: backupDir }),
|
|
155
|
+
};
|
|
156
|
+
return operationDetails;
|
|
157
|
+
};
|
|
158
|
+
const rollback = (error, failedPhase, identity) => {
|
|
159
|
+
const rollbackState = {
|
|
160
|
+
transition: "not-renamed",
|
|
161
|
+
};
|
|
162
|
+
try {
|
|
163
|
+
renameRetainedDirectory({
|
|
164
|
+
from: backupDir,
|
|
165
|
+
to: targetDir,
|
|
166
|
+
sourceParent: targetParent,
|
|
167
|
+
targetParent,
|
|
168
|
+
expectedSource: identity,
|
|
169
|
+
renameNoReplace: expectedSource => {
|
|
170
|
+
retained.renameNoReplace("target", backupBasename, targetBasename, expectedSource);
|
|
171
|
+
},
|
|
172
|
+
onIndeterminate() {
|
|
173
|
+
rollbackState.transition = "indeterminate";
|
|
174
|
+
backup.transition = "indeterminate";
|
|
175
|
+
},
|
|
176
|
+
onRenamed() {
|
|
177
|
+
rollbackState.transition = "renamed";
|
|
178
|
+
backup.transition = "not-renamed";
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
catch (rollbackError) {
|
|
183
|
+
const recovery = rollbackState.transition === "renamed"
|
|
184
|
+
? "restored-unverified"
|
|
185
|
+
: rollbackState.transition === "indeterminate"
|
|
186
|
+
? "indeterminate"
|
|
187
|
+
: "backup-path-preserved";
|
|
188
|
+
throw replacementAggregate(error, rollbackError, `directory ${failedPhase} and rollback failed`, details("rollback", recovery));
|
|
189
|
+
}
|
|
190
|
+
throw replacementFailure(error, `directory ${failedPhase} failed after restoring the original target`, details(failedPhase, "restored"));
|
|
191
|
+
};
|
|
28
192
|
try {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
193
|
+
if (targetIdentity) {
|
|
194
|
+
try {
|
|
195
|
+
backup.identity = renameRetainedDirectory({
|
|
196
|
+
from: targetDir,
|
|
197
|
+
to: backupDir,
|
|
198
|
+
sourceParent: targetParent,
|
|
199
|
+
targetParent,
|
|
200
|
+
expectedSource: targetIdentity,
|
|
201
|
+
renameNoReplace: expectedSource => {
|
|
202
|
+
retained.renameNoReplace("target", targetBasename, backupBasename, expectedSource);
|
|
203
|
+
},
|
|
204
|
+
onIndeterminate() {
|
|
205
|
+
backup.transition = "indeterminate";
|
|
206
|
+
},
|
|
207
|
+
onRenamed(identity) {
|
|
208
|
+
backup.identity = identity;
|
|
209
|
+
backup.transition = "renamed";
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
if (backup.transition === "renamed" && backup.identity) {
|
|
215
|
+
rollback(err, "backup", backup.identity);
|
|
216
|
+
}
|
|
217
|
+
const recovery = backup.transition === "indeterminate" ? "indeterminate" : "none";
|
|
218
|
+
throw replacementFailure(err, "directory backup failed before publication", details("backup", recovery));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
renameRetainedDirectory({
|
|
223
|
+
from: stagedDir,
|
|
224
|
+
to: targetDir,
|
|
225
|
+
sourceParent: stagedParent,
|
|
226
|
+
targetParent,
|
|
227
|
+
expectedSource: stagedIdentity,
|
|
228
|
+
renameNoReplace: expectedSource => {
|
|
229
|
+
retained.renameNoReplace("staged", stagedBasename, targetBasename, expectedSource);
|
|
230
|
+
},
|
|
231
|
+
onIndeterminate() {
|
|
232
|
+
publication.status = "indeterminate";
|
|
233
|
+
},
|
|
234
|
+
onRenamed() {
|
|
235
|
+
publication.status = "published";
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
if (publication.status === "published") {
|
|
241
|
+
throw replacementFailure(err, "published directory failed retained identity verification", details("publish", backup.transition === "renamed" ? "backup-path-preserved" : "none"));
|
|
242
|
+
}
|
|
243
|
+
if (publication.status === "indeterminate") {
|
|
244
|
+
throw replacementFailure(err, "directory publication outcome is indeterminate", details("publish", backup.transition === "renamed" ? "backup-path-preserved" : "none"));
|
|
245
|
+
}
|
|
246
|
+
if (backup.transition === "renamed" && backup.identity) {
|
|
247
|
+
rollback(err, "publish", backup.identity);
|
|
248
|
+
}
|
|
249
|
+
throw replacementFailure(err, "directory publication failed before commit", details("publish", "none"));
|
|
250
|
+
}
|
|
251
|
+
details("publish", backup.transition === "renamed" ? "backup-path-preserved" : "none");
|
|
252
|
+
if (backup.transition === "renamed" && backup.identity) {
|
|
253
|
+
let cleanupAttempted = false;
|
|
254
|
+
try {
|
|
255
|
+
assertSyncDirectoryGuard(targetParent);
|
|
256
|
+
inspectDirectoryIdentitySync(backupDir, backup.identity);
|
|
257
|
+
cleanupAttempted = true;
|
|
258
|
+
const outcome = await retained.removeOriginal(backupBasename);
|
|
259
|
+
if (outcome !== "removed") {
|
|
260
|
+
throw new FsSafeError("path-mismatch", "retained backup changed before bounded cleanup");
|
|
261
|
+
}
|
|
262
|
+
backup.transition = "not-renamed";
|
|
263
|
+
details("cleanup", "none");
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
throw replacementFailure(error, "published directory but retained backup cleanup failed", {
|
|
267
|
+
...details("cleanup", cleanupAttempted ? "cleanup-incomplete" : "backup-path-preserved"),
|
|
268
|
+
});
|
|
269
|
+
}
|
|
35
270
|
}
|
|
36
271
|
}
|
|
37
|
-
|
|
38
|
-
|
|
272
|
+
catch (error) {
|
|
273
|
+
operationFailed = true;
|
|
274
|
+
operationError = error;
|
|
275
|
+
throw error;
|
|
39
276
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
277
|
+
finally {
|
|
278
|
+
try {
|
|
279
|
+
retained.close();
|
|
280
|
+
}
|
|
281
|
+
catch (closeError) {
|
|
282
|
+
if (operationFailed) {
|
|
283
|
+
throw replacementAggregate(operationError, closeError, "directory replacement and retained descriptor close failed", operationDetails);
|
|
284
|
+
}
|
|
285
|
+
throw replacementFailure(closeError, "directory replacement retained descriptor close failed", operationDetails);
|
|
44
286
|
}
|
|
45
|
-
throw err;
|
|
46
|
-
}
|
|
47
|
-
if (backupCreated) {
|
|
48
|
-
await guardedRm({ target: backupDir, recursive: true, force: true, verifyAfter: false });
|
|
49
287
|
}
|
|
50
288
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replace-file-copy-fallback.d.ts","sourceRoot":"","sources":["../src/replace-file-copy-fallback.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,EAAE,EAAE,KAAK,WAAW,EAAc,MAAM,SAAS,CAAC;AAQ/D,MAAM,MAAM,oCAAoC,GAAG,QAAQ,CAAC;AAC5D,MAAM,MAAM,oCAAoC,GAAG,kBAAkB,GAAG,MAAM,CAAC;AAC/E,MAAM,MAAM,+BAA+B,GAAG,UAAU,GAAG,gBAAgB,CAAC;AAC5E,MAAM,MAAM,sCAAsC,GAAG;IACnD,OAAO,EAAE,+BAA+B,CAAC;CAC1C,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,KAAK,EAAE,cAAc,kBAAkB,EAAE,KAAK,CAAC;IAC/C,IAAI,EAAE,cAAc,kBAAkB,EAAE,IAAI,CAAC;IAC7C,EAAE,EAAE,cAAc,kBAAkB,EAAE,EAAE,CAAC;IACzC,MAAM,EAAE,cAAc,kBAAkB,EAAE,MAAM,CAAC;CAClD,CAAC;AAEF,KAAK,cAAc,GAAG,IAAI,CACxB,OAAO,MAAM,EACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,eAAe,GACf,WAAW,GACX,UAAU,GACV,UAAU,GACV,QAAQ,GACR,YAAY,GACZ,WAAW,CACd,CAAC;
|
|
1
|
+
{"version":3,"file":"replace-file-copy-fallback.d.ts","sourceRoot":"","sources":["../src/replace-file-copy-fallback.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,EAAE,EAAE,KAAK,WAAW,EAAc,MAAM,SAAS,CAAC;AAQ/D,MAAM,MAAM,oCAAoC,GAAG,QAAQ,CAAC;AAC5D,MAAM,MAAM,oCAAoC,GAAG,kBAAkB,GAAG,MAAM,CAAC;AAC/E,MAAM,MAAM,+BAA+B,GAAG,UAAU,GAAG,gBAAgB,CAAC;AAC5E,MAAM,MAAM,sCAAsC,GAAG;IACnD,OAAO,EAAE,+BAA+B,CAAC;CAC1C,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,KAAK,EAAE,cAAc,kBAAkB,EAAE,KAAK,CAAC;IAC/C,IAAI,EAAE,cAAc,kBAAkB,EAAE,IAAI,CAAC;IAC7C,EAAE,EAAE,cAAc,kBAAkB,EAAE,EAAE,CAAC;IACzC,MAAM,EAAE,cAAc,kBAAkB,EAAE,MAAM,CAAC;CAClD,CAAC;AAEF,KAAK,cAAc,GAAG,IAAI,CACxB,OAAO,MAAM,EACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,eAAe,GACf,WAAW,GACX,UAAU,GACV,UAAU,GACV,QAAQ,GACR,YAAY,GACZ,WAAW,CACd,CAAC;AAqHF,wBAAsB,+BAA+B,CACnD,QAAQ,EAAE,eAAe,EACzB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,oCAAoC,GAC5C,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,wBAAgB,mCAAmC,CACjD,QAAQ,EAAE,cAAc,EACxB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,oCAAoC,GAC5C,IAAI,CAIN;AA+HD,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,QAAQ,EAAE,eAAe,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,CAAC,EAAE,oCAAoC,CAAC;IAC5D,OAAO,EAAE,oCAAoC,CAAC;IAC9C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sBAAsB,CAAC,EAAE,WAAW,CAAC;IACrC,IAAI,EAAE,OAAO,CAAC;CACf,GAAG,OAAO,CAAC,IAAI,CAAC,CAuEhB;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,QAAQ,EAAE,cAAc,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,CAAC,EAAE,oCAAoC,CAAC;IAC5D,OAAO,EAAE,oCAAoC,CAAC;IAC9C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sBAAsB,CAAC,EAAE,WAAW,CAAC;IACrC,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,IAAI,EAAE,OAAO,CAAC;CACf,GAAG,IAAI,CAuEP"}
|