Skip to content

Commit

Permalink
feat(cli): add interactive shell for automatic patch
Browse files Browse the repository at this point in the history
  • Loading branch information
vicary committed Mar 13, 2024
1 parent aea45f3 commit 692faa0
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 4 deletions.
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ A simple GraphQL server for Deno Fresh.
1. [Create a fresh project](https://fresh.deno.dev/docs/getting-started/create-a-project)
or checkout your existing Fresh project.

1. Add the following lines to your `dev.ts`:
1. Run `deno run jsr:@vicary/fresh-graphql` to patch your `dev.ts`, or do it
manually:

```diff
// dev.ts

import "https://deno.land/x/dotenv/load.ts";

import dev from "$fresh/dev.ts";
Expand All @@ -30,9 +33,9 @@ A simple GraphQL server for Deno Fresh.
await dev(import.meta.url, "./main.ts");
```

1. Include the `graphql/` directory in your deno.json.

```diff
// deno.json

"tasks": {
- "start": "deno run -A --watch=static/,routes/ dev.ts",
+ "start": "deno run -A --watch=static/,routes/,graphql/ dev.ts",
Expand Down
201 changes: 201 additions & 0 deletions cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import {
assert,
colors,
dirname,
fromFileUrl,
log,
resolvePath,
} from "./deps.ts";

log.setup({
handlers: {
console: new log.ConsoleHandler("DEBUG", {
formatter: ((): log.FormatterFunction => {
const levelFormatters: Record<
string,
(record: log.LogRecord) => string
> = {
CRITICAL: (record) => colors.red(colors.bold(`! ${record.msg}`)),
ERROR: (record) => `${colors.red(colors.bold("✖"))} ${record.msg}`,
WARN: (record) => `${colors.yellow(colors.bold("!"))} ${record.msg}`,
INFO: (record) => `${colors.blue(colors.bold("ℹ"))} ${record.msg}`,
DEBUG: (record) => `• ${record.msg}`,
};

const defaultFormatter = (record: log.LogRecord) =>
`${levelFormatters[record.levelName]} ${record.msg}`;

return (record) =>
(levelFormatters[record.levelName] ?? defaultFormatter)(record);
})(),
}),
},
loggers: {
default: {
level: "DEBUG",
handlers: ["console"],
},
},
});

async function main() {
if (!await ensurePatchExecutable()) {
log.warn("`patch` not found in your environment.");
return;
}

if (promptYesNo("Apply patch to `dev.ts`?", true)) {
if (!await patchDev()) {
log.error("Unable to patch dev.ts, please try to do it manually.");
}
}

if (promptYesNo("Apply patch to `deno.json`?", true)) {
if (!await patchDenoJson()) {
log.error("Unable to patch deno.json, please try to do it manually.");
}
}
}

await main();

async function ensurePatchExecutable() {
try {
const { success } = await new Deno.Command(
"patch",
{
args: ["--version"],
stdout: "null",
stderr: "null",
},
).spawn().status;

assert(success);

return true;
} catch (e) {
if (!(e instanceof Deno.errors.NotFound)) throw e;

return false;
}
}

function promptYesNo(question: string, defaultYes = false) {
const defaultAnswer = defaultYes ? "Y/n" : "y/N";
const answer = prompt(`${question} [${defaultAnswer}]`);

if (!answer) {
return defaultYes;
}

return answer.trim().toLowerCase() === "y";
}

async function patchDev() {
// Ensure file exists
await Deno.stat("dev.ts");

const diffFile = resolvePath(
dirname(fromFileUrl(import.meta.url)),
"dev.patch",
);

try {
await Deno.stat(diffFile);
} catch (e) {
if (!(e instanceof Deno.errors.NotFound)) throw e;

log.error("The patch file not found inside the package!");

return false;
}

log.info(
`Our patch works with unmodified dev.ts generated after Fresh 1.6.5.`,
);

return await attemptPatch();

async function attemptPatch(): Promise<boolean> {
const decodeOutput = (output: Uint8Array) =>
new TextDecoder().decode(output).trim();

// Dry-run
{
const out = await new Deno.Command(
"patch",
{ args: ["-NCs", "./dev.ts", diffFile] },
).output();

if (!out.success) {
const stdout = decodeOutput(out.stdout);

if (stdout.includes("previously applied")) {
log.info(`Patch already applied.`);

return true;
}

return false;
}
}

// Actual run
const out = await new Deno.Command(
"patch",
{ args: ["-Ns", "./dev.ts", diffFile] },
).output();

if (!out.success) {
return false;
}

log.info(`Patch applied successfully.`);

return true;
}
}

async function patchDenoJson(): Promise<boolean> {
// Ensure file exists
await Deno.stat("deno.json");

// Apply the patch
try {
// Read the JSON
const json = JSON.parse(
new TextDecoder().decode(await Deno.readFile("deno.json")),
);

const task = json.tasks.start;

if (!task.includes("--watch=static/,routes/ dev.ts")) {
if (
task.includes("--watch=static/,routes/,graphql/ dev.ts")
) {
log.info(`Patch already applied.`);

return true;
}

return false;
}

json.tasks.start = task.replace(
"--watch=static/,routes/ dev.ts",
"--watch=static/,routes/,graphql/ dev.ts",
);

// Write the JSON back
await Deno.writeFile(
"deno.json",
new TextEncoder().encode(JSON.stringify(json, null, 2) + "\n"),
);
} catch {
return false;
}

log.info(`Patch applied successfully.`);

return true;
}
2 changes: 1 addition & 1 deletion deno.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vicary/fresh-graphql",
"version": "0.1.5",
"version": "0.2.0",
"exports": "./mod.ts",
"lock": false,
"nodeModulesDir": false
Expand Down
3 changes: 3 additions & 0 deletions deps.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
export { assert } from "jsr:@std/assert@^0.219.1";
export { colors } from "jsr:@std/fmt@^0.219.1";
export { ensureDir, walk } from "jsr:@std/fs@^0.219.1";
export * as log from "jsr:@std/log@^0.219.1";
export {
dirname,
fromFileUrl,
join,
parse as parsePath,
resolve as resolvePath,
toFileUrl,
} from "jsr:@std/path@^0.219.1";
export {
Expand Down
10 changes: 10 additions & 0 deletions dev.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@@ -1,6 +1,8 @@
-#!/usr/bin/env -S deno run -A --watch=static/,routes/
+#!/usr/bin/env -S deno run -A --watch=static/,routes/,graphql/

import dev from "$fresh/dev.ts";
+import { dev as graphql } from "@vicary/fresh-graphql";
import config from "./fresh.config.ts";

+await graphql(import.meta.url);
await dev(import.meta.url, "./main.ts", config);
5 changes: 5 additions & 0 deletions mod.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
export { dev } from "./dev.ts";
export { fromManifest } from "./schema.ts";

// Start interactive shell that automatically patches the fresh project.
if (import.meta.main) {
await import("./cli.ts");
}

0 comments on commit 692faa0

Please sign in to comment.