mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 15:04:52 +09:00
Rename safeOutputFile to atomicOverwriteFile (#1167)
"safe" suggested the data being written survives a crash, it only guarantees the old file is either kept or fully replaced. The new name says what it does. No alias, plugin API breaks are allowed in minor versions and the 2.1 migration guide lists the rename. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com>
This commit is contained in:
19 files changed
+55
-49
No files matched your search
@@ -61,6 +61,12 @@ The `container` property of `WebPluginContext` is now the plugin's ES module nam
|
||||
`lib.PluginWebpackEnvInfo` is renamed to `lib.PluginWebEnvInfo`.
|
||||
|
||||
|
||||
## Renamed lib functions
|
||||
|
||||
`lib.safeOutputFile` is renamed to `lib.atomicOverwriteFile`.
|
||||
It takes the same arguments.
|
||||
|
||||
|
||||
## Host pairing
|
||||
|
||||
Host tokens are no longer generated by users.
|
||||
|
||||
@@ -209,7 +209,7 @@ async function handleBootstrapCommand(
|
||||
console.log(content);
|
||||
} else {
|
||||
logger.info(`Writing ${args.output}`);
|
||||
await lib.safeOutputFile(args.output, content, { mode: 0o600 });
|
||||
await lib.atomicOverwriteFile(args.output, content, { mode: 0o600 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ export default class Controller {
|
||||
const cachePath = path.join(this.config.get("controller.database_directory"), "latest-releases.json");
|
||||
try {
|
||||
const releases = await lib.fetchLatestReleases();
|
||||
await lib.safeOutputFile(cachePath, JSON.stringify(releases, null, "\t"));
|
||||
await lib.atomicOverwriteFile(cachePath, JSON.stringify(releases, null, "\t"));
|
||||
return releases;
|
||||
} catch (err: any) {
|
||||
logger.warn(`Failed to retrieve Factorio releases ${err.message}`);
|
||||
|
||||
@@ -40,8 +40,8 @@ export default class UserManager {
|
||||
}
|
||||
|
||||
// We intentionally write the roles first because if it fails then the old user file will still contain them
|
||||
await lib.safeOutputFile(rolesFilePath, JSON.stringify(combinedJson.roles));
|
||||
await lib.safeOutputFile(usersFilePath, JSON.stringify(combinedJson.users));
|
||||
await lib.atomicOverwriteFile(rolesFilePath, JSON.stringify(combinedJson.roles));
|
||||
await lib.atomicOverwriteFile(usersFilePath, JSON.stringify(combinedJson.users));
|
||||
} catch (err: any) {
|
||||
if (err.code !== "ENOENT") {
|
||||
throw err;
|
||||
|
||||
@@ -384,7 +384,7 @@ async function uploadExport(req: Request, res: Response) {
|
||||
let { name, ext } = path.posix.parse(filePath);
|
||||
let hash = await lib.hashStream(await zip.openStream(file));
|
||||
assets[name] = `${name}.${hash}${ext}`;
|
||||
await lib.safeOutputFile(path.join("static", `${name}.${hash}${ext}`), content);
|
||||
await lib.atomicOverwriteFile(path.join("static", `${name}.${hash}${ext}`), content);
|
||||
}
|
||||
|
||||
modPack.exportManifest = new lib.ExportManifest(assets, new Date().toISOString());
|
||||
|
||||
+11
-11
@@ -104,7 +104,7 @@ async function pathExists(filePath) {
|
||||
return true;
|
||||
}
|
||||
|
||||
async function safeOutputFile(file, data, options={}) {
|
||||
async function atomicOverwriteFile(file, data, options={}) {
|
||||
let { dir, name, ext } = path.parse(file);
|
||||
let temporary = path.join(dir, `${name}.tmp${ext}`);
|
||||
if (dir) {
|
||||
@@ -257,7 +257,7 @@ async function migrateRename(args) {
|
||||
|
||||
async function migrateConfig(source, destination) {
|
||||
if (await pathExists(source) && !await pathExists(destination)) {
|
||||
await safeOutputFile(destination, JSON.stringify(
|
||||
await atomicOverwriteFile(destination, JSON.stringify(
|
||||
renameConfig(JSON.parse(await fs.readFile(source))), null, "\t"
|
||||
), { mode: 0o600 });
|
||||
logger.info(`Migrated ${source} to ${destination}`);
|
||||
@@ -269,7 +269,7 @@ async function migrateRename(args) {
|
||||
|
||||
let instancesFile = path.join("database", "instances.json");
|
||||
if (await pathExists(instancesFile)) {
|
||||
await safeOutputFile(instancesFile, JSON.stringify(
|
||||
await atomicOverwriteFile(instancesFile, JSON.stringify(
|
||||
JSON.parse(await fs.readFile(instancesFile)).map(renameConfig), null, "\t"
|
||||
));
|
||||
logger.info(`Migrated ${instancesFile}`);
|
||||
@@ -281,7 +281,7 @@ async function migrateRename(args) {
|
||||
for (let role of users["roles"]) {
|
||||
role["permissions"] = role["permissions"].map(rename);
|
||||
}
|
||||
await safeOutputFile(usersFile, JSON.stringify(users, null, "\t"));
|
||||
await atomicOverwriteFile(usersFile, JSON.stringify(users, null, "\t"));
|
||||
logger.info(`Migrated ${usersFile}`);
|
||||
}
|
||||
|
||||
@@ -309,7 +309,7 @@ async function migrateRename(args) {
|
||||
lines.push(inputLine);
|
||||
}
|
||||
}
|
||||
safeOutputFile(outputFile, Buffer.concat(lines));
|
||||
atomicOverwriteFile(outputFile, Buffer.concat(lines));
|
||||
}
|
||||
|
||||
async function migrateLogsDir(inputLogs, outputLogs) {
|
||||
@@ -434,7 +434,7 @@ async function groupIdToName(gid) {
|
||||
async function writeScripts(mode, selfSignedCert) {
|
||||
if (["standalone", "controller"].includes(mode)) {
|
||||
if (process.platform === "win32") {
|
||||
await safeOutputFile(
|
||||
await atomicOverwriteFile(
|
||||
"run-controller.cmd",
|
||||
`\
|
||||
@echo off
|
||||
@@ -447,7 +447,7 @@ goto restart
|
||||
`
|
||||
);
|
||||
} else {
|
||||
await safeOutputFile(
|
||||
await atomicOverwriteFile(
|
||||
"run-controller.sh",
|
||||
`\
|
||||
#!/bin/bash
|
||||
@@ -459,7 +459,7 @@ done
|
||||
`,
|
||||
{ mode: 0o755 },
|
||||
);
|
||||
await safeOutputFile(
|
||||
await atomicOverwriteFile(
|
||||
"systemd/clusteriocontroller.service",
|
||||
`\
|
||||
[Unit]
|
||||
@@ -486,7 +486,7 @@ WantedBy=multi-user.target
|
||||
if (["standalone", "host"].includes(mode)) {
|
||||
if (process.platform === "win32") {
|
||||
let extraCaCertsLine = selfSignedCert ? `set "NODE_EXTRA_CA_CERTS=${selfSignedCert}"\n` : "";
|
||||
await safeOutputFile(
|
||||
await atomicOverwriteFile(
|
||||
"run-host.cmd",
|
||||
`\
|
||||
@echo off
|
||||
@@ -500,7 +500,7 @@ goto restart
|
||||
);
|
||||
} else {
|
||||
let extraCaCertsLine = selfSignedCert ? `export "NODE_EXTRA_CA_CERTS=${selfSignedCert}"\n` : "";
|
||||
await safeOutputFile(
|
||||
await atomicOverwriteFile(
|
||||
"run-host.sh",
|
||||
`\
|
||||
#!/bin/bash
|
||||
@@ -513,7 +513,7 @@ done
|
||||
{ mode: 0o755 },
|
||||
);
|
||||
extraCaCertsLine = selfSignedCert ? `Environment=NODE_EXTRA_CA_CERTS=${selfSignedCert}\n` : "";
|
||||
await safeOutputFile(
|
||||
await atomicOverwriteFile(
|
||||
"systemd/clusteriohost.service",
|
||||
`\
|
||||
[Unit]
|
||||
|
||||
@@ -693,7 +693,7 @@ end`.replace(/\r?\n/g, " ");
|
||||
players: [...this.playerStats],
|
||||
player_autosave_slot: this._playerAutosaveSlot,
|
||||
}, null, "\t");
|
||||
await lib.safeOutputFile(this.path("instance-stats.json"), content);
|
||||
await lib.atomicOverwriteFile(this.path("instance-stats.json"), content);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -870,7 +870,7 @@ end`.replace(/\r?\n/g, " ");
|
||||
"_comment_warning": warning,
|
||||
...await this.resolveServerSettings(this.config.get("factorio.settings"), includeCredentials),
|
||||
};
|
||||
await lib.safeOutputFile(
|
||||
await lib.atomicOverwriteFile(
|
||||
this.server.writePath("server-settings.json"),
|
||||
JSON.stringify(serverSettings, null, "\t")
|
||||
);
|
||||
@@ -996,7 +996,7 @@ end`.replace(/\r?\n/g, " ");
|
||||
|
||||
if (this.config.get("factorio.sync_adminlist") !== "disabled") {
|
||||
this.logger.verbose("Writing server-adminlist.json");
|
||||
await lib.safeOutputFile(
|
||||
await lib.atomicOverwriteFile(
|
||||
this.server.writePath("server-adminlist.json"),
|
||||
JSON.stringify([...this._host.adminlist], null, "\t")
|
||||
);
|
||||
@@ -1004,7 +1004,7 @@ end`.replace(/\r?\n/g, " ");
|
||||
|
||||
if (this.config.get("factorio.sync_banlist") !== "disabled") {
|
||||
this.logger.verbose("Writing server-banlist.json");
|
||||
await lib.safeOutputFile(
|
||||
await lib.atomicOverwriteFile(
|
||||
this.server.writePath("server-banlist.json"),
|
||||
JSON.stringify([...this._host.banlist].map(
|
||||
([username, reason]) => ({ username, reason })
|
||||
@@ -1014,7 +1014,7 @@ end`.replace(/\r?\n/g, " ");
|
||||
|
||||
if (this.config.get("factorio.sync_whitelist") !== "disabled") {
|
||||
this.logger.verbose("Writing server-whitelist.json");
|
||||
await lib.safeOutputFile(
|
||||
await lib.atomicOverwriteFile(
|
||||
this.server.writePath("server-whitelist.json"),
|
||||
JSON.stringify([...this._host.whitelist], null, "\t")
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ import path from "path";
|
||||
import events from "events";
|
||||
import { Static } from "@sinclair/typebox";
|
||||
import { logger } from "./logging.js";
|
||||
import { downloadFile, safeOutputFile } from "./file_ops.js";
|
||||
import { downloadFile, atomicOverwriteFile } from "./file_ops.js";
|
||||
import { ModPortalReleaseSchema, ModPortalDetailsSchema, ModNameVersionPair } from "./data/messages_mod.js";
|
||||
import { ModInfo, ModPack, MajorMinorVersion, ModVersionEquality, SourceVersion } from "./data/index.js";
|
||||
|
||||
@@ -148,7 +148,7 @@ export default class ModStore extends events.EventEmitter<ModStoreEvents> {
|
||||
if (files.size) {
|
||||
logger.info(`Loaded info for ${files.size} mods`);
|
||||
const content: ModInfoCache = [...files.values()].map(mod => mod.toJSON());
|
||||
await safeOutputFile(cacheFilename, JSON.stringify(content));
|
||||
await atomicOverwriteFile(cacheFilename, JSON.stringify(content));
|
||||
}
|
||||
|
||||
return new this(modsDirectory, files);
|
||||
|
||||
@@ -7,7 +7,7 @@ import isDeepStrictEqual from "#is_deep_strict_equal";
|
||||
import { basicType } from "../helpers.js";
|
||||
import * as libSchema from "../schema.js";
|
||||
import { StringEnum } from "../data/composites.js";
|
||||
import { safeOutputFile } from "../file_ops.js";
|
||||
import { atomicOverwriteFile } from "../file_ops.js";
|
||||
import * as validators from "./validators.js";
|
||||
import { InvalidAccessError, InvalidFieldError, InvalidValueError } from "../errors.js";
|
||||
import type { SyncHook } from "../SyncHook.js";
|
||||
@@ -475,7 +475,7 @@ export class Config<
|
||||
if (!this.filepath) {
|
||||
throw new Error("Cannot save config which has no filepath");
|
||||
}
|
||||
await safeOutputFile(this.filepath, JSON.stringify(this, null, "\t"), { mode: 0o600 });
|
||||
await atomicOverwriteFile(this.filepath, JSON.stringify(this, null, "\t"), { mode: 0o600 });
|
||||
this._dirty = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ export async function loadJsonAsMap(filePath: string): Promise<Map<string, unkno
|
||||
*/
|
||||
export async function saveMapAsJson(filePath: string, map: Map<string, unknown>) {
|
||||
let obj = mapToObject(map);
|
||||
await libFileOps.safeOutputFile(filePath, JSON.stringify(obj, null, "\t"));
|
||||
await libFileOps.atomicOverwriteFile(filePath, JSON.stringify(obj, null, "\t"));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,7 +139,7 @@ export async function loadJsonArrayAsMap(filePath: string): Promise<Map<unknown,
|
||||
* @throws {Error} if an error occured writing to the file.
|
||||
*/
|
||||
export async function saveMapAsJsonArray(filePath: string, map: Map<unknown, { id: unknown }>) {
|
||||
await libFileOps.safeOutputFile(filePath, JSON.stringify([...map.values()], null, "\t"));
|
||||
await libFileOps.atomicOverwriteFile(filePath, JSON.stringify([...map.values()], null, "\t"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import path from "path";
|
||||
import fs from "node:fs/promises";
|
||||
import { EventEmitter } from "events";
|
||||
|
||||
import { safeOutputFile } from "./file_ops.js";
|
||||
import { atomicOverwriteFile } from "./file_ops.js";
|
||||
import { SubscribableValue } from "./subscriptions.js";
|
||||
import { logger } from "./logging.js";
|
||||
import { ControllerConfig } from "./config/index.js";
|
||||
@@ -56,7 +56,7 @@ export class JsonDatastoreProvider<
|
||||
// Save the data to the json file
|
||||
async save(data: Map<string, V>) {
|
||||
logger.verbose(`Saving ${this.filePath}`);
|
||||
await safeOutputFile(this.filePath, JSON.stringify(Object.fromEntries(data), null, "\t"));
|
||||
await atomicOverwriteFile(this.filePath, JSON.stringify(Object.fromEntries(data), null, "\t"));
|
||||
}
|
||||
|
||||
// Load the data from the json file
|
||||
@@ -91,7 +91,7 @@ export class JsonDatastoreProvider<
|
||||
logger.warn(
|
||||
`Duplicates detected in ${this.filePath}, a backup was written to: ${backupPath}`
|
||||
);
|
||||
await safeOutputFile(backupPath, JSON.stringify(rawJson, null, "\t"));
|
||||
await atomicOverwriteFile(backupPath, JSON.stringify(rawJson, null, "\t"));
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -115,7 +115,7 @@ export class JsonIdDatastoreProvider<
|
||||
// Save the data to the json file
|
||||
async save(data: Map<V["id"], V>) {
|
||||
logger.verbose(`Saving ${this.filePath}`);
|
||||
await safeOutputFile(this.filePath, JSON.stringify([...data.values()], null, "\t"));
|
||||
await atomicOverwriteFile(this.filePath, JSON.stringify([...data.values()], null, "\t"));
|
||||
}
|
||||
|
||||
// Load the data from the json file
|
||||
@@ -150,7 +150,7 @@ export class JsonIdDatastoreProvider<
|
||||
logger.warn(
|
||||
`Duplicates detected in ${this.filePath}, a backup was written to: ${backupPath}`
|
||||
);
|
||||
await safeOutputFile(backupPath, JSON.stringify(rawJson, null, "\t"));
|
||||
await atomicOverwriteFile(backupPath, JSON.stringify(rawJson, null, "\t"));
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -89,7 +89,7 @@ export async function getTempFile(prefix = "tmp.", suffix = "", tmpdir = "./") {
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely overwrite file data
|
||||
* Atomically overwrite file data
|
||||
*
|
||||
* Writes the passed data to a temporary file and rename it over the target
|
||||
* file. If the operation fails because the parent directory does not exist, it
|
||||
@@ -105,7 +105,7 @@ export async function getTempFile(prefix = "tmp.", suffix = "", tmpdir = "./") {
|
||||
* @param data - Content to write.
|
||||
* @param options - see fs.writeFile, `flag` and `flush` may not be used.
|
||||
*/
|
||||
export async function safeOutputFile(
|
||||
export async function atomicOverwriteFile(
|
||||
file: string,
|
||||
data: string | Buffer,
|
||||
options?: Omit<
|
||||
|
||||
@@ -328,7 +328,7 @@ export async function loadPluginList(
|
||||
hasChanges = await findNpmPlugins(pluginList, skippedNpmPlugins) || hasChanges;
|
||||
|
||||
if (hasChanges) {
|
||||
await libFileOps.safeOutputFile(pluginListPath, JSON.stringify([...pluginList], null, "\t"));
|
||||
await libFileOps.atomicOverwriteFile(pluginListPath, JSON.stringify([...pluginList], null, "\t"));
|
||||
}
|
||||
|
||||
return pluginList;
|
||||
|
||||
@@ -312,7 +312,7 @@ export class LogIndex {
|
||||
* was loaded/created from.
|
||||
*/
|
||||
async save() {
|
||||
await libFileOps.safeOutputFile(
|
||||
await libFileOps.atomicOverwriteFile(
|
||||
path.join(this.logDirectory, "index.json"),
|
||||
JSON.stringify(this.serialize(), null, "\t")
|
||||
);
|
||||
|
||||
@@ -199,7 +199,7 @@ export async function handlePluginCommand(
|
||||
}
|
||||
|
||||
pluginList.set(pluginInfo.name, pluginPath);
|
||||
await libFileOps.safeOutputFile(pluginListPath, JSON.stringify([...pluginList], null, "\t"));
|
||||
await libFileOps.atomicOverwriteFile(pluginListPath, JSON.stringify([...pluginList], null, "\t"));
|
||||
print(`Added ${pluginInfo.name}`);
|
||||
|
||||
} else if (command === "remove") {
|
||||
@@ -209,7 +209,7 @@ export async function handlePluginCommand(
|
||||
return;
|
||||
}
|
||||
|
||||
await libFileOps.safeOutputFile(pluginListPath, JSON.stringify([...pluginList], null, "\t"));
|
||||
await libFileOps.atomicOverwriteFile(pluginListPath, JSON.stringify([...pluginList], null, "\t"));
|
||||
print(`Removed ${args.name}`);
|
||||
|
||||
} else if (command === "list") {
|
||||
|
||||
@@ -63,7 +63,7 @@ async function saveDatabase(
|
||||
let file = path.resolve(controllerConfig.get("controller.database_directory"), "player_sync.json");
|
||||
logger.verbose(`writing ${file}`);
|
||||
let content = JSON.stringify([...playerDatastore, ...tombstones]);
|
||||
await lib.safeOutputFile(file, content);
|
||||
await lib.atomicOverwriteFile(file, content);
|
||||
}
|
||||
|
||||
export default async function(context: ControllerPluginContext) {
|
||||
|
||||
@@ -49,7 +49,7 @@ async function saveTechnologies(
|
||||
) {
|
||||
let filePath = path.join(controllerConfig.get("controller.database_directory"), "technologies.json");
|
||||
logger.verbose(`writing ${filePath}`);
|
||||
await lib.safeOutputFile(filePath, JSON.stringify([...technologies.entries()], null, "\t"));
|
||||
await lib.atomicOverwriteFile(filePath, JSON.stringify([...technologies.entries()], null, "\t"));
|
||||
}
|
||||
|
||||
const MAX_QUEUE = 7;
|
||||
@@ -71,7 +71,7 @@ async function loadQueue(controllerConfig: lib.ControllerConfig, logger: lib.Log
|
||||
async function saveQueue(controllerConfig: lib.ControllerConfig, queue: string[], logger: lib.Logger) {
|
||||
let filePath = path.join(controllerConfig.get("controller.database_directory"), "research_queue.json");
|
||||
logger.verbose(`writing ${filePath}`);
|
||||
await lib.safeOutputFile(filePath, JSON.stringify(queue, null, "\t"));
|
||||
await lib.atomicOverwriteFile(filePath, JSON.stringify(queue, null, "\t"));
|
||||
}
|
||||
|
||||
export default async function(context: ControllerPluginContext) {
|
||||
|
||||
@@ -64,7 +64,7 @@ async function saveDatabase(
|
||||
let file = path.resolve(controllerConfig.get("controller.database_directory"), "items.json");
|
||||
logger.verbose(`writing ${file}`);
|
||||
let content = JSON.stringify(items.serialize());
|
||||
await lib.safeOutputFile(file, content);
|
||||
await lib.atomicOverwriteFile(file, content);
|
||||
} else if (items) {
|
||||
logger.error(`Item database too large, not saving (${items.size})`);
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ describe("lib/file_ops", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("safeOutputFile()", function() {
|
||||
describe("atomicOverwriteFile()", function() {
|
||||
async function assertNoTemporary(target) {
|
||||
let { dir, name } = path.parse(target);
|
||||
let entries = (await fs.readdir(dir)).filter(
|
||||
@@ -61,14 +61,14 @@ describe("lib/file_ops", function() {
|
||||
}
|
||||
it("should write new target file", async function() {
|
||||
let target = path.join(baseDir, "safe", "simple.txt");
|
||||
await lib.safeOutputFile(target, "a text file", "utf8");
|
||||
await lib.atomicOverwriteFile(target, "a text file", "utf8");
|
||||
await assertNoTemporary(target);
|
||||
assert.equal(await fs.readFile(target, "utf8"), "a text file");
|
||||
});
|
||||
it("should overwrite existing target file", async function() {
|
||||
let target = path.join(baseDir, "safe", "exists.txt");
|
||||
await fs.writeFile(target, "previous", "utf8");
|
||||
await lib.safeOutputFile(target, "current", "utf8");
|
||||
await lib.atomicOverwriteFile(target, "current", "utf8");
|
||||
await assertNoTemporary(target);
|
||||
assert.equal(await fs.readFile(target, "utf8"), "current");
|
||||
});
|
||||
@@ -77,7 +77,7 @@ describe("lib/file_ops", function() {
|
||||
this.skip();
|
||||
}
|
||||
let target = path.join(baseDir, "safe", "mode.txt");
|
||||
await lib.safeOutputFile(target, "private", { mode: 0o600 });
|
||||
await lib.atomicOverwriteFile(target, "private", { mode: 0o600 });
|
||||
assert.equal((await fs.stat(target)).mode & 0o777, 0o600);
|
||||
assert.equal(await fs.readFile(target, "utf8"), "private");
|
||||
});
|
||||
@@ -85,7 +85,7 @@ describe("lib/file_ops", function() {
|
||||
let target = path.join(baseDir, "safe", "concurrent.txt");
|
||||
let writes = [];
|
||||
for (let i = 0; i < 20; i++) {
|
||||
writes.push(lib.safeOutputFile(target, `write ${i}`, "utf8"));
|
||||
writes.push(lib.atomicOverwriteFile(target, `write ${i}`, "utf8"));
|
||||
}
|
||||
// Ignore errors from concurrently renaming onto the same file.
|
||||
await Promise.allSettled(writes);
|
||||
@@ -95,13 +95,13 @@ describe("lib/file_ops", function() {
|
||||
it("should remove the temporary file when the rename fails", async function() {
|
||||
let target = path.join(baseDir, "safe", "rename-fails");
|
||||
await fs.mkdir(target, { recursive: true });
|
||||
await assert.rejects(lib.safeOutputFile(target, "data", "utf8"));
|
||||
await assert.rejects(lib.atomicOverwriteFile(target, "data", "utf8"));
|
||||
await assertNoTemporary(target);
|
||||
});
|
||||
it("should handle creating file in current working directory", async function() {
|
||||
let target = "temporary-file-made-to-test-cwd.txt";
|
||||
try {
|
||||
await lib.safeOutputFile(target, "a text file", "utf8");
|
||||
await lib.atomicOverwriteFile(target, "a text file", "utf8");
|
||||
} finally {
|
||||
try {
|
||||
await fs.unlink(target);
|
||||
|
||||
Reference in new issue
Block a user