-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: add `prismic-next` CLI to clear cached `/api/v2` requests * feat: make implementation testable
- Loading branch information
1 parent
2e01b67
commit e257ae1
Showing
10 changed files
with
492 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
#!/usr/bin/env node | ||
|
||
import("../dist/cli.cjs").then((mod) => mod.run(process.argv)); |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
import mri from "mri"; | ||
import * as path from "node:path"; | ||
import * as fs from "node:fs/promises"; | ||
import * as tty from "node:tty"; | ||
import { Buffer } from "node:buffer"; | ||
|
||
import * as pkg from "../../package.json"; | ||
|
||
async function pathExists(filePath: string) { | ||
try { | ||
await fs.access(filePath); | ||
|
||
return true; | ||
} catch { | ||
return false; | ||
} | ||
} | ||
|
||
function color(colorCode: number, string: string) { | ||
return tty.WriteStream.prototype.hasColors() | ||
? "\u001B[" + colorCode + "m" + string + "\u001B[39m" | ||
: string; | ||
} | ||
|
||
function warn(string: string) { | ||
// Yellow | ||
return console.warn(`${color(33, "warn")} - ${string}`); | ||
} | ||
|
||
function info(string: string) { | ||
// Magenta | ||
return console.info(`${color(35, "info")} - ${string}`); | ||
} | ||
|
||
type Args = { | ||
help: boolean; | ||
version: boolean; | ||
}; | ||
|
||
export async function run(argv: string[]) { | ||
const args = mri<Args>(argv.slice(2), { | ||
boolean: ["help", "version"], | ||
alias: { | ||
help: "h", | ||
version: "v", | ||
}, | ||
default: { | ||
help: false, | ||
version: false, | ||
}, | ||
}); | ||
|
||
const command = args._[0]; | ||
|
||
switch (command) { | ||
case "clear-cache": { | ||
warn( | ||
"`prismic-next clear-cache` is an experimental utility. It may be replaced with a different solution in the future.", | ||
); | ||
|
||
async function getAppRootDir() { | ||
let currentDir = process.cwd(); | ||
|
||
while ( | ||
!(await pathExists(path.join(currentDir, ".next"))) && | ||
!(await pathExists(path.join(currentDir, "package.json"))) | ||
) { | ||
if (currentDir === path.resolve("/")) { | ||
break; | ||
} | ||
|
||
currentDir = path.join(currentDir, ".."); | ||
} | ||
|
||
if ( | ||
(await pathExists(path.join(currentDir, ".next"))) || | ||
(await pathExists(path.join(currentDir, "package.json"))) | ||
) { | ||
return currentDir; | ||
} | ||
} | ||
|
||
const appRootDir = await getAppRootDir(); | ||
|
||
if (!appRootDir) { | ||
warn( | ||
"Could not find the Next.js app root. Run `prismic-next clear-cache` in a Next.js project with a `.next` directory or `package.json` file.", | ||
); | ||
|
||
return; | ||
} | ||
|
||
const fetchCacheDir = path.join( | ||
appRootDir, | ||
".next", | ||
"cache", | ||
"fetch-cache", | ||
); | ||
|
||
if (!(await pathExists(fetchCacheDir))) { | ||
info("No Next.js fetch cache directory found. You are good to go!"); | ||
|
||
return; | ||
} | ||
|
||
const cacheEntries = await fs.readdir(fetchCacheDir); | ||
|
||
await Promise.all( | ||
cacheEntries.map(async (entry) => { | ||
try { | ||
const contents = await fs.readFile( | ||
path.join(fetchCacheDir, entry), | ||
"utf8", | ||
); | ||
const payload = JSON.parse(contents); | ||
|
||
if (payload.kind !== "FETCH") { | ||
return; | ||
} | ||
|
||
const bodyPayload = JSON.parse( | ||
Buffer.from(payload.data.body, "base64").toString(), | ||
); | ||
|
||
// Delete `/api/v2` requests. | ||
if (/\.prismic\.io\/auth$/.test(bodyPayload.oauth_initiate)) { | ||
await fs.unlink(path.join(fetchCacheDir, entry)); | ||
|
||
info(`Prismic /api/v2 request cache cleared: ${entry}`); | ||
} | ||
} catch (e) { | ||
// noop | ||
} | ||
}), | ||
); | ||
|
||
info( | ||
"The Prismic request cache has been cleared. Uncached requests will begin on the next Next.js server start-up.", | ||
); | ||
|
||
return; | ||
} | ||
|
||
default: { | ||
if (command && (!args.version || !args.help)) { | ||
warn("Invalid command.\n"); | ||
} | ||
|
||
if (args.version) { | ||
console.info(pkg.version); | ||
|
||
return; | ||
} | ||
|
||
console.info( | ||
` | ||
Usage: | ||
prismic-next <command> [options...] | ||
Available commands: | ||
clear-cache | ||
Options: | ||
--help, -h Show help text | ||
--version, -v Show version | ||
`.trim(), | ||
); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,43 @@ | ||
import { beforeEach, vi } from "vitest"; | ||
import { beforeAll, beforeEach, vi } from "vitest"; | ||
import { createMockFactory, MockFactory } from "@prismicio/mock"; | ||
import { Headers } from "node-fetch"; | ||
import * as fs from "node:fs/promises"; | ||
import * as path from "node:path"; | ||
import * as os from "node:os"; | ||
|
||
declare module "vitest" { | ||
export interface TestContext { | ||
mock: MockFactory; | ||
appRoot: string; | ||
} | ||
} | ||
|
||
vi.stubGlobal("Headers", Headers); | ||
|
||
beforeEach((ctx) => { | ||
beforeAll(async () => { | ||
await fs.mkdir(os.tmpdir(), { recursive: true }); | ||
|
||
vi.spyOn(process, "cwd"); | ||
}); | ||
|
||
beforeEach(async (ctx) => { | ||
ctx.mock = createMockFactory({ seed: ctx.meta.name }); | ||
ctx.appRoot = await fs.mkdtemp( | ||
path.join(os.tmpdir(), "@prismicio__next___cli"), | ||
); | ||
|
||
vi.clearAllMocks(); | ||
|
||
await fs.mkdir(path.join(ctx.appRoot, "foo", "bar"), { recursive: true }); | ||
vi.mocked(process.cwd).mockReturnValue(path.join(ctx.appRoot, "foo", "bar")); | ||
|
||
return async () => { | ||
await fs.rm(ctx.appRoot, { recursive: true }); | ||
}; | ||
}); | ||
|
||
vi.mock("node:fs/promises", async () => { | ||
const memfs: typeof import("memfs") = await vi.importActual("memfs"); | ||
|
||
return memfs.fs.promises; | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export function argv(...args: string[]): string[] { | ||
return ["/node", "./prismic-next", ...args]; | ||
} |
Oops, something went wrong.