Limits of Bower install --save: No automatic rollback on interruption
0 reputation · 22 Dec 2022, 20:21 UTC
0 reputation · 22 Dec 2022, 20:21 UTC
Make changes to bower.json by bower install --save atomic so that a partial write caused by an interruption is automatically reverted, keeping the manifest either fully updated or unchanged.
The current implementation updates bower.json in place without a backup or transaction log, as documented. If the process is killed or fails mid‑write, the file may contain invalid JSON, breaking later installs. It is unclear whether introducing a temporary‑file swap or a lightweight journal would preserve existing workflows, what performance impact it would have, and at which stable version it could be released while maintaining backward compatibility.
34775 reputation · 23 Dec 2022, 07:41 UTC
Bower’s install --save reads the current bower.json, merges the new dependency, and writes the result back with fs.writeFileSync. The write is performed directly on the original file; there is no backup, transaction log, or temporary staging area. If the process is killed while the write is in progress, the file may be left in a half‑written state containing truncated or malformed JSON. Subsequent Bower commands will then fail to parse the manifest.
On most POSIX filesystems (ext4, XFS, etc.) the rename system call is atomic: the target file is replaced only after the new file is completely written. The sequence is:
bower.json.tmp.fs.fsyncSync(fd)) to ensure the data is on disk.bower.json.tmp over bower.json with fs.renameSync.If the process dies between steps 1 and 3, bower.json remains unchanged. If it dies after step 3, the rename has already succeeded, so the file is fully updated. No half‑written state can survive.
Because Bower does not expose a flag for this behavior, you have two practical options:
Create a small Node script that invokes the real bower binary but intercepts the --save path. The script performs the temporary‑file trick before delegating to Bower’s core logic. A minimal example:
#!/usr/bin/env node
const {spawnSync} = require('child_process');
const fs = require('fs');
const path = require('path');
const args = process.argv.slice(2);
const isSave = args.includes('--save');
if (!isSave) {
// Delegate directly if no --save
spawnSync('bower', args, {stdio: 'inherit'});
process.exit();
}
// Run bower install to generate the new manifest in memory
const result = spawnSync('bower', [...args, '--json'], {encoding: 'utf8'});
if (result.status !== 0) {
console.error(result.stderr);
process.exit(result.status);
}
const manifest = JSON.parse(result.stdout);
const tmpPath = path.join(process.cwd(), 'bower.json.tmp');
fs.writeFileSync(tmpPath, JSON.stringify(manifest, null, 2));
fs.fsyncSync(fs.openSync(tmpPath, 'r'));
fs.renameSync(tmpPath, path.join(process.cwd(), 'bower.json'));
console.log('bower.json updated atomically');
Place the wrapper earlier in PATH or replace the original bower binary. The wrapper preserves exit codes for non‑--save commands and only changes the write path for --save.
If you maintain your own fork of Bower, locate the install command handler (typically commands/install.js). Replace the final writeFileSync with the temporary‑file logic shown above. Commit the change and bump the patch level (e.g., 1.10.0 → 1.10.1). Because the change is backward compatible – it only alters the internal file‑write strategy – existing scripts will continue to work. The only caveat is that users must upgrade to the patched version.
• Bower 1.10.x is the last actively maintained release; the community has largely migrated to npm or Yarn. If you are still on 1.10.x, a wrapper is the least invasive path.
• The atomic rename trick works on Unix‑like filesystems. On Windows, fs.renameSync is atomic only if the target file is not open elsewhere. Ensure the wrapper runs in the same user context and that no other process holds the file open.
• Because the wrapper writes the entire manifest to a temp file, the performance impact is negligible for typical project sizes (<10 KB). For very large manifests, the extra write may add a few milliseconds.
bower install --save jquery Verify bower.json contains the new dependency.Ctrl+C after the write has begun (you can add a setTimeout in the wrapper to pause before rename). Then inspect bower.json – it should either be unchanged or contain valid JSON.bower.json.tmp file remains.To fine‑tune the wrapper for your environment, it would help to know which filesystem you’re running on. Networked filesystems (e.g., NFS, SMB) may not guarantee that rename is strictly atomic. If you’re on such a system, consider adding a fallback: write to a temp file, then delete the original and hard‑link the temp file into place using fs.linkSync (supported on most Unix filesystems).
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.