Skip to content

Commit

Permalink
Add create-ponder --from-subgraph-id (#110)
Browse files Browse the repository at this point in the history
* wip: subgraph conversion tool

* feat: add from-subgraph-id template and prompts

* chore: improve log style and simplify bin script

* chore: rename option

* chore: changeset

* fix: change directory to root before git init

* fix: use template passed as option

* fix: allow Bytes and String as id field type

* chore: changeset

* test: add create-ponder subgraph id test
  • Loading branch information
0xOlias authored Feb 28, 2023
1 parent 2e85241 commit 754f8dd
Show file tree
Hide file tree
Showing 16 changed files with 527 additions and 144 deletions.
5 changes: 5 additions & 0 deletions .changeset/curvy-otters-begin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"create-ponder": patch
---

Added `--from-subgraph-id` option, renamed `--from-subgraph` to `--from-subgraph-repo`, and added `prompts`
5 changes: 5 additions & 0 deletions .changeset/stupid-baboons-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ponder/core": patch
---

Updated schema definition to allow `Bytes` and `String` types for entity `id` fields
10 changes: 8 additions & 2 deletions packages/core/src/schema/buildSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,14 @@ const getIdField = (
throw new Error(`${entityName}.id field must be non-null`);
}

if (baseType.name !== "ID") {
throw new Error(`${entityName}.id field must have type ID`);
if (
baseType.name !== "ID" &&
baseType.name !== "String" &&
baseType.name !== "Bytes"
) {
throw new Error(
`${entityName}.id field must have type ID, String, or Bytes`
);
}

return <IDField>{
Expand Down
7 changes: 5 additions & 2 deletions packages/create-ponder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
"dist/src"
],
"bin": {
"create-ponder": "dist/src/bin/create-ponder"
"create-ponder": "dist/src/bin/create-ponder.js"
},
"scripts": {
"build": "rm -rf dist && tsc && tsconfig-replace-paths --src . && mv dist/src/bin/create-ponder.js dist/src/bin/create-ponder",
"build": "rm -rf dist && tsc && tsconfig-replace-paths --src .",
"test": "$npm_execpath build && export $(grep -v '^#' .env.local | xargs) && jest",
"typecheck": "tsc --noEmit"
},
Expand All @@ -24,6 +24,8 @@
"node-fetch": "^2.6.7",
"picocolors": "^1.0.0",
"prettier": "^2.6.2",
"prompts": "^2.4.2",
"rimraf": "^4.1.2",
"yaml": "^2.1.1"
},
"devDependencies": {
Expand All @@ -32,6 +34,7 @@
"@types/node": "^18.7.8",
"@types/node-fetch": "2",
"@types/prettier": "^2.7.1",
"@types/prompts": "^2.4.2",
"jest": "^29.3.1",
"tsconfig-replace-paths": "^0.0.11",
"typescript": "^4.5.5"
Expand Down
161 changes: 115 additions & 46 deletions packages/create-ponder/src/bin/create-ponder.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,126 @@
#!/usr/bin/env node
/* eslint-disable @typescript-eslint/no-var-requires */

import { cac } from "cac";
import path from "node:path";
import prompts from "prompts";

import { CreatePonderOptions, Template, TemplateKind } from "@/common";

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import packageJson from "../../../package.json";
import { run } from "../index";

const cli = cac(packageJson.name)
.version(packageJson.version)
.usage("[options]")
.help()
.option("--dir [path]", "Path to directory for generated project")
.option("--from-subgraph [path]", "Path to subgraph directory")
.option("--from-etherscan [url]", "Link to etherscan contract page")
.option("--etherscan-api-key [key]", "Etherscan API key");

const parsed = cli.parse(process.argv);

const options = parsed.options as {
help?: boolean;
dir?: string;
fromSubgraph?: string;
fromEtherscan?: string;
etherscanApiKey?: string;
};
const createPonder = async () => {
const cli = cac(packageJson.name)
.version(packageJson.version)
.usage("[options]")
.help()
.option("--dir [path]", "Path to directory for generated project")
.option("--from-subgraph-id [id]", "Subgraph deployment ID")
.option("--from-subgraph-repo [path]", "Path to subgraph repository")
.option("--from-etherscan [url]", "Link to etherscan contract page")
.option("--etherscan-api-key [key]", "Etherscan API key");

const parsed = cli.parse(process.argv);

const options = parsed.options as {
help?: boolean;
dir?: string;
fromSubgraphId?: string;
fromSubgraphRepo?: string;
fromEtherscan?: string;
etherscanApiKey?: string;
};

if (options.help) {
process.exit(0);
}

const { fromEtherscan, fromSubgraphId, fromSubgraphRepo } = options;

// Validate CLI options.
if (
(fromSubgraphId && fromSubgraphRepo) ||
(fromSubgraphId && fromEtherscan) ||
(fromSubgraphRepo && fromEtherscan)
) {
throw new Error(
`Cannot specify more than one "--from" option:\n --from-subgraph\n --from-etherscan-id\n --from-etherscan-repo`
);
}

const { projectName } = await prompts({
type: "text",
name: "projectName",
message: "What is your project named?",
initial: "my-app",
});

// Get template from options if provided.
let template: Template | undefined = undefined;
if (fromEtherscan) {
template = { kind: TemplateKind.ETHERSCAN, link: fromEtherscan };
}
if (fromSubgraphId) {
template = { kind: TemplateKind.SUBGRAPH_ID, id: fromSubgraphId };
}
if (fromSubgraphRepo) {
template = { kind: TemplateKind.SUBGRAPH_REPO, path: fromSubgraphRepo };
}

// Get template from prompts if not provided.
if (!fromSubgraphId && !fromSubgraphRepo && !fromEtherscan) {
const { template: templateKind } = await prompts({
type: "select",
name: "template",
message: "Would you like to use a template for this project?",
choices: [
{ title: "None" },
{ title: "Etherscan contract link" },
{ title: "Subgraph ID" },
{ title: "Subgraph repository" },
],
});

if (templateKind === TemplateKind.ETHERSCAN) {
const { link } = await prompts({
type: "text",
name: "link",
message: "Enter an Etherscan contract link",
initial: "https://etherscan.io/address/0x97...",
});
template = { kind: TemplateKind.ETHERSCAN, link };
}

if (templateKind === TemplateKind.SUBGRAPH_ID) {
const { id } = await prompts({
type: "text",
name: "id",
message: "Enter a subgraph deployment ID",
initial: "QmNus...",
});
template = { kind: TemplateKind.SUBGRAPH_ID, id };
}

if (templateKind === TemplateKind.SUBGRAPH_REPO) {
const { path } = await prompts({
type: "text",
name: "path",
message: "Enter a path to a subgraph repository",
initial: "../subgraph",
});
template = { kind: TemplateKind.SUBGRAPH_REPO, path };
}
}

const validatedOptions: CreatePonderOptions = {
projectName,
rootDir: path.resolve(".", options.dir ? options.dir : projectName),
template,
etherscanApiKey: options.etherscanApiKey,
};

if (options.help) {
process.exit(0);
}

// Validate CLI options.
if (options.fromSubgraph && options.fromEtherscan) {
throw new Error(`Cannot specify more than one "--from" option:
--from-subgraph
--from-etherscan
`);
}

export interface CreatePonderOptions {
ponderRootDir: string;
fromSubgraph?: string;
fromEtherscan?: string;
etherscanApiKey?: string;
}

const validatedOptions: CreatePonderOptions = {
// Default `dir` to "ponder".
ponderRootDir: path.resolve(".", options.dir ? options.dir : "ponder"),
fromSubgraph: options.fromSubgraph,
fromEtherscan: options.fromEtherscan,
etherscanApiKey: options.etherscanApiKey,
await run(validatedOptions);
};

require("../index").run(validatedOptions);
createPonder();
27 changes: 27 additions & 0 deletions packages/create-ponder/src/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export enum TemplateKind {
NONE,
ETHERSCAN,
SUBGRAPH_ID,
SUBGRAPH_REPO,
}

export type Template =
| {
kind: TemplateKind.ETHERSCAN;
link: string;
}
| {
kind: TemplateKind.SUBGRAPH_ID;
id: string;
}
| {
kind: TemplateKind.SUBGRAPH_REPO;
path: string;
};

export interface CreatePonderOptions {
rootDir: string;
projectName: string;
template?: Template;
etherscanApiKey?: string;
}
51 changes: 51 additions & 0 deletions packages/create-ponder/src/helpers/git.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/* eslint-disable no-empty */
import { execSync } from "child_process";
import path from "path";
import rimraf from "rimraf";

// File adapted from next.js
// https://github.dev/vercel/next.js/blob/9ad1f321b7902542acd2be041fb2f15f023a0ed9/packages/create-next-app/helpers/git.ts

function isInGitRepository(): boolean {
try {
execSync("git rev-parse --is-inside-work-tree", { stdio: "ignore" });
return true;
} catch (_) {}
return false;
}

function isInMercurialRepository(): boolean {
try {
execSync("hg --cwd . root", { stdio: "ignore" });
return true;
} catch (_) {}
return false;
}

export function tryGitInit(root: string): boolean {
let didInit = false;
try {
execSync("git --version", { stdio: "ignore" });
if (isInGitRepository() || isInMercurialRepository()) {
return false;
}

execSync("git init", { stdio: "ignore" });
didInit = true;

execSync("git checkout -b main", { stdio: "ignore" });

execSync("git add -A", { stdio: "ignore" });
execSync('git commit -m "chore: initial commit from create-ponder"', {
stdio: "ignore",
});
return true;
} catch (e) {
if (didInit) {
try {
rimraf.sync(path.join(root, ".git"));
} catch (_) {}
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type GraphSource = {
entities: string[]; // Corresponds to entities by name defined in schema.graphql
abis: {
name: string;
file: string;
file: any;
}[];
eventHandlers?: {
event: string;
Expand Down
2 changes: 2 additions & 0 deletions packages/create-ponder/src/helpers/wait.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const wait = (ms: number) =>
new Promise((resolve) => setTimeout(resolve, ms));
Loading

1 comment on commit 754f8dd

@vercel
Copy link

@vercel vercel bot commented on 754f8dd Feb 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.