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

Commit

Permalink
track NoN files when configured dirs are used and clean before runnin…
Browse files Browse the repository at this point in the history
…g NoN
  • Loading branch information
lindsaylevine committed Jan 8, 2021
1 parent ae79eb0 commit c468b53
Show file tree
Hide file tree
Showing 4 changed files with 249 additions and 6 deletions.
8 changes: 7 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,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 @@ -27,6 +31,8 @@ const nextOnNetlify = (options = {}) => {
setupPages({ functionsPath, publishPath });

setupRedirects(publishPath);

trackNextOnNetlifyFiles();
};

module.exports = nextOnNetlify;
71 changes: 67 additions & 4 deletions lib/steps/prepareFolders.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
const { emptyDirSync } = require("fs-extra");
const { join } = require("path");
const {
emptyDirSync,
existsSync,
readdirSync,
readFileSync,
writeFileSync,
removeSync,
} = require("fs-extra");
const findCacheDir = require("find-cache-dir");
const { logTitle, log } = require("../helpers/logger");
const { NETLIFY_PUBLISH_PATH, NETLIFY_FUNCTIONS_PATH } = require("../config");

// Empty existing publish and functions folders
const TRACKING_FILE_SEPARATOR = "---";

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

Expand All @@ -19,8 +30,60 @@ const prepareFolders = ({ functionsPath, publishPath }) => {
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);
const cacheDir = findCacheDir({ name: "next-on-netlify", create: true });
const trackingFilePath = join(cacheDir, ".nonfiletracking");
const trackingFile = existsSync(trackingFilePath)
? readFileSync(trackingFilePath, "utf8")
: "---";

const [trackedFunctions, trackedPublish] = trackingFile.split("---");
const functionsBeforeRun = existsSync(functionsPath)
? readdirSync(functionsPath)
: [];
const publishBeforeRun = existsSync(publishPath)
? readdirSync(publishPath)
: [];

const isConfiguredPublishDir = publishPath !== NETLIFY_PUBLISH_PATH;
const isConfiguredFunctionsDir = functionsPath !== NETLIFY_FUNCTIONS_PATH;

if (isConfiguredPublishDir) {
trackedPublish.split("\n").forEach((file) => {
const filePath = join(publishPath, file);
if (existsSync(filePath)) {
removeSync(filePath);
}
});
} else {
emptyDirSync(publishPath);
}
if (isConfiguredFunctionsDir) {
trackedFunctions.split("\n").forEach((file) => {
const filePath = join(functionsPath, file);
if (existsSync(filePath)) {
removeSync(filePath);
}
});
} else {
emptyDirSync(functionsPath);
}

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

const totalFilesDiff = [...functionsDiff, "---", ...publishDiff];
writeFileSync(trackingFilePath, totalFilesDiff.join("\n"));
};
};

module.exports = prepareFolders;
141 changes: 141 additions & 0 deletions tests/configurableDirs.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// 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")
.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("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].trim().split("\n");
expect(isSameList(nextFunctions, fileListFunctions)).toBe(true);
const publishFiles = [
"404.html",
"_next",
"_redirects",
"getStaticProps",
"static",
"static.html",
];
const fileListPublish = fileList.split("---")[1].trim().split("\n");
expect(isSameList(publishFiles, fileListPublish)).toBe(true);
});
});
35 changes: 34 additions & 1 deletion tests/helpers/buildNextApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class NextAppBuilder {

// Build the application with next build
async build() {
// Generate a cach hash ID from the current contents of the staging folder.
// Generate a cache hash ID from the current contents of the staging folder.
const { hash: cacheHash } = await hashElement(this.__stagingPath, {
encoding: "hex",
});
Expand All @@ -88,6 +88,39 @@ class NextAppBuilder {
return stdout;
}

async runWithRequire(options) {
// Generate a cach hash ID from the current contents of the staging folder.
const { hash: cacheHash } = await hashElement(this.__stagingPath, {
encoding: "hex",
});
this.__cacheHash = cacheHash;

// If we have no cached build for this NextJS app, let's run next build and
// cache the result
if (!existsSync(this.__cachePath)) {
// Build the nextJS app
await npmRun("next-build", this.__stagingPath);

// Cache the build
copySync(this.__stagingPath, this.__cachePath);
}

// Copy the built NextJS app from the cache to the app folder, where we will
// run next-on-netlify
copySync(this.__cachePath, this.__appPath);

process.chdir(this.__appPath);
const nextOnNetlify = require("../..");
nextOnNetlify({
functionsDir: join(this.__appPath, options.functionsDir),
publishDir: join(this.__appPath, options.publishDir),
});
return "Built successfully!";
}

// TO-DO: when I try to split out the shared logic between build & runWithRequire into its own
// function on NextBuilder, everything breaks; not sure why

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

0 comments on commit c468b53

Please sign in to comment.