This commit is contained in:
Benjamin Toby
2025-02-03 13:41:13 +01:00
parent cee4bc0478
commit 619afc8303
29 changed files with 722 additions and 169 deletions
+28
View File
@@ -0,0 +1,28 @@
# Rebuild your next project incrementally.
Create builds incrementally without stopping your server.
## Requirements
There are a few requirements to get this to work
### Update your `next-config.js` file
You need to update the distribution directory in your `next-config.js`. Like this:
```javascript
import type { NextConfig } from "next";
import grabDist from "@moduletrace/buncid/dist/rebuilds/next-js/grabDist";
const distDir = grabDist();
const nextConfig: NextConfig = {
/* config options here */
reactStrictMode: true,
distDir,
};
export default nextConfig;
```
That's it. This dynamically handles your distribution directory for both `dev` and `start` scripts. Your `development` environment uses the `.next` directory, while your `production` environment uses the `.dist` directory.
+50
View File
@@ -0,0 +1,50 @@
import fs from "fs";
import path from "path";
const production = process.env.NODE_ENV == "production";
const isBuilding = process.env.BUILDING_APP;
/**
* # Grab the current distribution directory
* @description This returns the relative path from the CWD. Eg `./.dist/build-1`
* @returns {string | undefined}
*/
export default function grabDist(): string | undefined {
const DIST_DIR = path.resolve(process.cwd(), "./.dist");
if (isBuilding) {
const distDir = (() => {
try {
const buildNumber = fs.readFileSync(
`${DIST_DIR}/BUILD`,
"utf-8"
);
return `.dist/build-${buildNumber}`;
} catch (/** @type {*} */ error: any) {
console.log("Build Number Generation Error =>", error.message);
process.exit();
}
})();
return distDir;
}
if (production) {
const distDir = (() => {
try {
const buildNumber = fs.readFileSync(
`${DIST_DIR}/BUILD`,
"utf-8"
);
return `.dist/build-${buildNumber}`;
} catch (/** @type {*} */ error: any) {
console.log("Build Number Parse Error =>", error.message);
process.exit();
}
})();
return distDir;
}
return undefined;
}
+139
View File
@@ -0,0 +1,139 @@
#!/usr/bin/env bun
import path from "path";
import fs from "fs";
import {
execSync,
spawnSync,
SpawnSyncOptionsWithStringEncoding,
} from "child_process";
const DIST_DIR = path.resolve(process.cwd(), "./.dist");
let PREV_BUILD_NO = "0";
const MAX_BUILDS = process.env.BUNCID_MAX_BUILDS
? Number(process.env.BUNCID_MAX_BUILDS)
: 10;
if (
MAX_BUILDS < 1 ||
Number.isNaN(MAX_BUILDS) ||
typeof MAX_BUILDS !== "number"
) {
throw new Error("Invalid MAX_BUILDS");
}
if (!fs.existsSync(DIST_DIR)) fs.mkdirSync(DIST_DIR);
if (fs.existsSync(`${DIST_DIR}/BUILD`)) {
PREV_BUILD_NO = fs.readFileSync(`${DIST_DIR}/BUILD`, "utf-8");
} else {
fs.writeFileSync(`${DIST_DIR}/BUILD`, "0", "utf-8");
}
try {
const buildNumber = fs.readFileSync(`${DIST_DIR}/BUILD`, "utf-8");
const newBuildNumber = Number(buildNumber) + 1;
if (newBuildNumber < 0) {
throw new Error("Invalid Build Number");
}
fs.writeFileSync(`${DIST_DIR}/BUILD`, String(newBuildNumber));
if (newBuildNumber > MAX_BUILDS) {
const builds = fs.readdirSync(DIST_DIR);
const buildDirs = builds.filter((build) => build.match(/build-\d+/));
for (const buildDir of buildDirs) {
const buildDirPath = path.join(DIST_DIR, buildDir);
const buildDirStat = fs.statSync(buildDirPath);
if (buildDirStat.isDirectory()) {
const buildDirName = buildDir.split("-")[1];
const buildDirNumber = Number(buildDirName);
if (buildDirNumber <= newBuildNumber - MAX_BUILDS) {
fs.rmdirSync(buildDirPath, { recursive: true });
console.log("Deleted Build Directory =>", buildDirPath);
}
}
}
}
} catch (error: any) {
console.log("Build Number Parse Error =>", error.message);
process.exit(1);
}
const spawnSyncOptions: SpawnSyncOptionsWithStringEncoding = {
stdio: "inherit",
encoding: "utf-8",
shell: process.platform?.match(/win32/i) ? "bash.exe" : undefined,
env: {
...process.env,
BUILDING_APP: "true",
},
};
const build = spawnSync("bunx", ["next", "build"], spawnSyncOptions);
/**
* @returns {string}
*/
function grabNewDistDir(): string {
try {
const buildNumber = fs.readFileSync(`${DIST_DIR}/BUILD`, "utf-8");
return `.dist/build-${buildNumber}`;
} catch (/** @type {*} */ error: any) {
console.log("Build Number Parse Error =>", error.message);
process.exit();
}
}
const newDistDir = grabNewDistDir();
// if (!fs.existsSync(newDistDir)) {
// fs.mkdirSync(newDistDir, { recursive: true });
// }
// if (!fs.existsSync(`${newDistDir}/BUILD_ID`)) {
// fs.writeFileSync(`${newDistDir}/BUILD_ID`, "4");
// }
/**
* # Revert Directories
* @param {string} dir - New Build Directory Path
*/
function revert(dir: string) {
console.log("Build Failed!", build?.error?.message || build.stderr);
fs.writeFileSync(`${DIST_DIR}/BUILD`, PREV_BUILD_NO, "utf-8");
execSync(`rm -Rf ${dir}`, { cwd: process.cwd() });
const writeErr = build.error
? build.error.message
: build.stderr
? build.stderr.toString()
: "NO BUILD_ID found in New Build Folder";
fs.writeFileSync(
`${DIST_DIR}/LAST_BUILD_FAIL`,
Date() + "\n\n" + writeErr,
"utf-8"
);
process.exit(1);
}
if (
build.error ||
build.stderr ||
build.status != 0 ||
!fs.existsSync(`${newDistDir}/BUILD_ID`)
) {
revert(newDistDir);
throw new Error("Build Failed!");
}
process.on("exit", () => {
const onExitDir = grabNewDistDir();
if (!fs.existsSync(`${onExitDir}/BUILD_ID`)) {
revert(onExitDir);
}
});