@embeddable.com/sdk-core
Version:
Core Embeddable SDK module responsible for web-components bundling and publishing.
118 lines (102 loc) • 3.01 kB
text/typescript
import * as fs from "node:fs/promises";
import { ServerResponse } from "http";
import { createReadStream } from "node:fs";
/**
* Wraps a ServerResponse object to prevent setting the Content-Length header.
*/
export function preventContentLength(res: ServerResponse) {
const originalSetHeader = res.setHeader.bind(res);
res.setHeader = function (key: string, value: any) {
if (key.toLowerCase() === "content-length") {
return this;
}
return originalSetHeader(key, value);
};
}
export function createWatcherLock() {
let locked = false;
let waiters: (() => void)[] = [];
return {
lock() {
if (!locked) {
locked = true;
}
},
unlock() {
if (locked) {
locked = false;
waiters.forEach((fn) => fn());
waiters = [];
}
},
async waitUntilFree() {
if (!locked) return;
await new Promise<void>((resolve) => {
waiters.push(resolve);
});
},
};
}
export const delay = (ms: number) => new Promise((res) => setTimeout(res, ms));
/**
* This function waits until a file stabilizes, meaning its size does not change for a certain number of attempts
* and the content ends with a specific expected tail.
* It uses stream reading to check the file's content, the same wait serve-static uses.
* This will help to prevent when serve-static serves a file that is still being written to.
* One of the issues, related to this, is "Constructor for "embeddable-component#undefined" was not found", that we saw quite often in the past.
* @param filePath
* @param expectedTail
* @param maxAttempts
* @param stableCount
*/
export async function waitUntilFileStable(
filePath: string,
expectedTail: string,
{
maxAttempts = 100,
requiredStableCount = 2,
}: {
maxAttempts?: number;
requiredStableCount?: number;
} = {}
): Promise<void> {
let lastSize = -1;
let stableCounter = 0;
for (let i = 0; i < maxAttempts; i++) {
try {
const { size, tailMatches } = await checkFileTail(filePath, expectedTail);
if (size === lastSize && size > 0 && tailMatches) {
stableCounter++;
if (stableCounter >= requiredStableCount) {
return;
}
} else {
stableCounter = 0;
}
lastSize = size;
} catch {
}
await delay(50);
}
throw new Error("File did not stabilize");
}
async function checkFileTail(
filePath: string,
expectedTail: string,
tailLength = 500
): Promise<{ size: number; tailMatches: boolean }> {
const stats = await fs.stat(filePath);
const size = stats.size;
const start = Math.max(0, size - tailLength);
return new Promise((resolve, reject) => {
let tailBuffer = "";
createReadStream(filePath, { encoding: "utf-8", start })
.on("data", (chunk) => {
tailBuffer += chunk;
})
.on("end", () => {
resolve({ size, tailMatches: tailBuffer.includes(expectedTail) });
})
.on("error", reject);
});
}