Skip to content
This repository was archived by the owner on May 10, 2021. It is now read-only.

track NoN files when configured dirs are used, clean before each new run #134

Merged
merged 2 commits into from
Jan 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@ const {
* publishDir: string to path
* }
*/

const nextOnNetlify = (options = {}) => {
const functionsPath = options.functionsDir || NETLIFY_FUNCTIONS_PATH;
const publishPath = options.publishDir || NETLIFY_PUBLISH_PATH;

prepareFolders({ functionsPath, publishPath });
const trackNextOnNetlifyFiles = prepareFolders({
functionsPath,
publishPath,
});

copyPublicFiles(publishPath);

Expand All @@ -33,6 +37,8 @@ const nextOnNetlify = (options = {}) => {
setupRedirects(publishPath);

setupHeaders(publishPath);

trackNextOnNetlifyFiles();
};

module.exports = nextOnNetlify;
80 changes: 80 additions & 0 deletions lib/helpers/handleFileTracking.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
const { join } = require("path");
const {
existsSync,
readdirSync,
readFileSync,
writeFileSync,
removeSync,
} = require("fs-extra");
const findCacheDir = require("find-cache-dir");
const { NETLIFY_PUBLISH_PATH, NETLIFY_FUNCTIONS_PATH } = require("../config");

const TRACKING_FILE_SEPARATOR = "---";

// Clean configured publish and functions folders and track next-on-netlify files
// for future cleans
const handleFileTracking = ({ functionsPath, publishPath }) => {
const isConfiguredFunctionsDir = functionsPath !== NETLIFY_FUNCTIONS_PATH;
const isConfiguredPublishDir = publishPath !== NETLIFY_PUBLISH_PATH;

const cacheDir = findCacheDir({ name: "next-on-netlify", create: true });
const trackingFilePath = join(cacheDir, ".nonfiletracking");

if (existsSync(trackingFilePath)) {
const trackingFile = readFileSync(trackingFilePath, "utf8");
const [trackedFunctions, trackedPublish] = trackingFile.split(
TRACKING_FILE_SEPARATOR
);

const cleanConfiguredFiles = (trackedFiles) => {
trackedFiles.forEach((file) => {
const filePath = join(publishPath, file);
if (file !== "" && existsSync(filePath)) {
removeSync(filePath);
}
});
};

if (isConfiguredPublishDir) {
cleanConfiguredFiles(trackedPublish.split("\n"));
}
if (isConfiguredFunctionsDir) {
cleanConfiguredFiles(trackedFunctions.split("\n"));
}
}

const functionsBeforeRun = existsSync(functionsPath)
? readdirSync(functionsPath)
: [];
const publishBeforeRun = existsSync(publishPath)
? readdirSync(publishPath)
: [];

// this callback will run at the end of nextOnNetlify()
const trackNewFiles = () => {
const functionsAfterRun = isConfiguredFunctionsDir
? readdirSync(functionsPath)
: functionsBeforeRun;
const publishAfterRun = isConfiguredPublishDir
? readdirSync(publishPath)
: publishBeforeRun;
const getDifference = (before, after) =>
after.filter((filePath) => !before.includes(filePath));
const newFunctionsFiles = getDifference(
functionsBeforeRun,
functionsAfterRun
);
const newPublishFiles = getDifference(publishBeforeRun, publishAfterRun);

const allTrackedFiles = [
...newFunctionsFiles,
TRACKING_FILE_SEPARATOR,
...newPublishFiles,
];
writeFileSync(trackingFilePath, allTrackedFiles.join("\n"));
};

return trackNewFiles;
};

module.exports = handleFileTracking;
29 changes: 20 additions & 9 deletions lib/steps/prepareFolders.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,37 @@
const { join } = require("path");
const { emptyDirSync } = require("fs-extra");
const findCacheDir = require("find-cache-dir");
const { logTitle, log } = require("../helpers/logger");
const { NETLIFY_PUBLISH_PATH, NETLIFY_FUNCTIONS_PATH } = require("../config");
const handleFileTracking = require("../helpers/handleFileTracking");

// Empty existing publish and functions folders
// Clean existing publish and functions folders
const prepareFolders = ({ functionsPath, publishPath }) => {
logTitle("🚀 Next on Netlify 🚀");

if (functionsPath === NETLIFY_FUNCTIONS_PATH) {
const isNotConfiguredFunctionsDir = functionsPath === NETLIFY_FUNCTIONS_PATH;
const isNotConfiguredPublishDir = publishPath === NETLIFY_PUBLISH_PATH;

if (isNotConfiguredFunctionsDir) {
log(" ", "Functions directory: ", functionsPath);
}
if (publishPath === NETLIFY_PUBLISH_PATH) {
if (isNotConfiguredPublishDir) {
log(" ", "Publish directory: ", publishPath);
}
if (
functionsPath === NETLIFY_FUNCTIONS_PATH ||
publishPath === NETLIFY_PUBLISH_PATH
) {
if (isNotConfiguredFunctionsDir || isNotConfiguredPublishDir) {
log(" ", "Make sure these are set in your netlify.toml file.");
}

if (publishPath === NETLIFY_PUBLISH_PATH) emptyDirSync(publishPath);
if (functionsPath === NETLIFY_FUNCTIONS_PATH) emptyDirSync(functionsPath);
// We can empty these dirs knowing there will only be stale NoN-generated files inside
if (isNotConfiguredPublishDir) {
emptyDirSync(publishPath);
}
if (isNotConfiguredFunctionsDir) {
emptyDirSync(functionsPath);
}

// This returns a function that runs as the last step of nextOnNetlify()
return handleFileTracking({ functionsPath, publishPath });
};

module.exports = prepareFolders;
147 changes: 147 additions & 0 deletions tests/configurableDirs.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Test next-on-netlify when config is set from a function in next.config.js
// See: https://github.com/netlify/next-on-netlify/issues/25

const { parse, join } = require("path");
const { existsSync, readdirSync, readFileSync } = require("fs-extra");
const buildNextApp = require("./helpers/buildNextApp");

// The name of this test file (without extension)
const FILENAME = parse(__filename).name;

// The directory which will be used for testing.
// We simulate a NextJS app within that directory, with pages, and a
// package.json file.
const PROJECT_PATH = join(__dirname, "builds", FILENAME);
const FUNCTIONS_DIR = "my-functions";
const PUBLISH_DIR = "my-publish";

// Capture the output to verify successful build
let buildOutput;

beforeAll(
async () => {
runOutput = await buildNextApp()
.forTest(__filename)
.withPages("pages")
.withNextConfig("next.config.js")
.withPackageJson("package.json")
.withCustomFunctions("my-functions")
.runWithRequire({ functionsDir: FUNCTIONS_DIR, publishDir: PUBLISH_DIR });
},
// time out after 180 seconds
180 * 1000
);

describe("next-on-netlify", () => {
const functionsDir = join(PROJECT_PATH, FUNCTIONS_DIR);

test("builds successfully", () => {
expect(runOutput).toMatch("Built successfully!");
});

test("copies custom Netlify Function to configured functions directory", () => {
expect(existsSync(join(functionsDir, "someTestFunction.js"))).toBe(true);
});

test("creates a Netlify Function for each SSR page", () => {
expect(existsSync(join(functionsDir, "next_index", "next_index.js"))).toBe(
true
);
expect(
existsSync(join(functionsDir, "next_shows_id", "next_shows_id.js"))
).toBe(true);
expect(
existsSync(
join(functionsDir, "next_shows_params", "next_shows_params.js")
)
).toBe(true);
expect(
existsSync(
join(
functionsDir,
"next_getServerSideProps_static",
"next_getServerSideProps_static.js"
)
)
).toBe(true);
expect(
existsSync(
join(
functionsDir,
"next_getServerSideProps_id",
"next_getServerSideProps_id.js"
)
)
).toBe(true);
});

test("copies static pages to output directory", () => {
const OUTPUT_PATH = join(PROJECT_PATH, PUBLISH_DIR);

expect(existsSync(join(OUTPUT_PATH, "static.html"))).toBe(true);
expect(existsSync(join(OUTPUT_PATH, "static/[id].html"))).toBe(true);
});

test("copies static assets to out_publish/_next/ directory", () => {
const dirs = readdirSync(
join(PROJECT_PATH, PUBLISH_DIR, "_next", "static")
);

expect(dirs.length).toBe(2);
expect(dirs).toContain("chunks");
});
});

describe("clean up of NoN files", () => {
test("creates a .nonfiletracking to audit NoN-specific files between builds", () => {
const cacheDir = join(PROJECT_PATH, "/node_modules/.cache/next-on-netlify");
const dirs = readdirSync(cacheDir);
expect(dirs[0]).toEqual(".nonfiletracking");
});

test(".nonfiletracking contains NoN-specific files", () => {
const cacheDir = join(PROJECT_PATH, "/node_modules/.cache/next-on-netlify");
const fileList = readFileSync(join(cacheDir, ".nonfiletracking"), "utf8");
// had to test equality this way because of windows :)
const isSameList = (arr1, arr2) =>
arr1.reduce((isSame, func) => {
if (arr2.includes(func)) {
isSame = true;
} else {
isSame = false;
}
return isSame;
}, true);
const nextFunctions = [
"next_api_shows_id",
"next_api_shows_params",
"next_api_static",
"next_getServerSideProps_all_slug",
"next_getServerSideProps_id",
"next_getServerSideProps_static",
"next_getStaticProps_id",
"next_getStaticProps_static",
"next_getStaticProps_withFallback_id",
"next_getStaticProps_withFallback_slug",
"next_getStaticProps_withRevalidate_id",
"next_getStaticProps_withRevalidate_withFallback_id",
"next_getStaticProps_withrevalidate",
"next_index",
"next_shows_id",
"next_shows_params",
];
const fileListFunctions = fileList.split("---")[0].split("\n");
expect(isSameList(nextFunctions, fileListFunctions)).toBe(true);
expect(fileListFunctions.includes("someTestFunction.js")).toBe(false);
const publishFiles = [
"404.html",
"_next",
"_redirects",
"getStaticProps",
"static",
"static.html",
];
const fileListPublish = fileList.split("---")[1].split("\n");
expect(isSameList(publishFiles, fileListPublish)).toBe(true);
});
});
Empty file.
29 changes: 25 additions & 4 deletions tests/helpers/buildNextApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ class NextAppBuilder {
return this.withFile(packageJsonFile, "package.json");
}

withCustomFunctions(functionsDir) {
return this.withFile(functionsDir);
}

// Copy a file from the fixtures folder to the app's staging folder
withFile(fixture, target = null) {
// If no target file name is given, use the same name as the fixture
Expand All @@ -61,9 +65,8 @@ class NextAppBuilder {
return this;
}

// Build the application with next build
async build() {
// Generate a cach hash ID from the current contents of the staging folder.
async buildNextApp() {
// Generate a cache hash ID from the current contents of the staging folder.
const { hash: cacheHash } = await hashElement(this.__stagingPath, {
encoding: "hex",
});
Expand All @@ -83,11 +86,29 @@ class NextAppBuilder {
// run next-on-netlify
copySync(this.__cachePath, this.__appPath);

// Run next-on-netlify
process.chdir(this.__appPath);
}

async build() {
await this.buildNextApp();

// Run next-on-netlify as postbuild script
const { stdout } = await npmRun("next-on-netlify", this.__appPath);
return stdout;
}

async runWithRequire(options) {
await this.buildNextApp();

// Run next-on-netlify as an imported module
const nextOnNetlify = require("../..");
nextOnNetlify({
functionsDir: join(this.__appPath, options.functionsDir),
publishDir: join(this.__appPath, options.publishDir),
});
return "Built successfully!";
}

/*****************************************************************************
* Private functions
****************************************************************************/
Expand Down