Skip to content
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
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -214,5 +214,5 @@ jobs:
with:
commit_message: "chore: update package versions [skip ci]"
branch: main
file_pattern: 'package.json **/package.json **/schema.graphql'
file_pattern: 'package.json **/schema.graphql'

Binary file modified bun.lockb
Binary file not shown.
3 changes: 2 additions & 1 deletion packages/cli/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.env*
.env*
*.schema.graphql
4 changes: 2 additions & 2 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@
"@inquirer/input": "^3",
"@inquirer/password": "^3",
"@inquirer/select": "^3",
"@settlemint/sdk-js": "0.5.0",
"@settlemint/sdk-utils": "0.5.0",
"@settlemint/sdk-js": "workspace:*",
"@settlemint/sdk-utils": "workspace:*",
"is-in-ci": "^1.0.0",
"yoctocolors": "^2"
},
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* ```
*/

import { codegenCommand } from "@/commands/codegen";
import { connectCommand } from "@/commands/connect";
import { Command } from "@commander-js/extra-typings";
import { ascii } from "@settlemint/sdk-utils/terminal";
Expand All @@ -37,6 +38,7 @@ sdkcli

// Add commands to the CLI
sdkcli.addCommand(connectCommand());
sdkcli.addCommand(codegenCommand());

/**
* Parses command line arguments and executes the appropriate command.
Expand Down
36 changes: 36 additions & 0 deletions packages/cli/src/commands/codegen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { gqltadaSpinner } from "@/commands/codegen/gqltada.spinner";
import { Command } from "@commander-js/extra-typings";
import { loadEnv } from "@settlemint/sdk-utils/environment";
import { intro, outro } from "@settlemint/sdk-utils/terminal";
import type { DotEnv } from "@settlemint/sdk-utils/validation";
import { bold, italic, underline } from "yoctocolors";

/**
* Creates and returns the 'connect' command for the SettleMint SDK.
* This command initializes the setup of the SettleMint SDK in the user's project.
* It guides the user through a series of prompts to configure their environment,
* select services, and set up necessary files.
*
* @returns {Command} The configured 'connect' command
*/
export function codegenCommand(): Command {
return (
new Command("codegen")
// Add options for various configuration parameters
.option("-e, --environment <environment>", "The name of your environment, defaults to development", "development")
// Set the command description
.description("Generate GraphQL and REST types and queries")
// Define the action to be executed when the command is run
.action(async ({ environment }) => {
intro(
`Generating GraphQL types and queries for your dApp's ${italic(underline(bold(environment)))} environment`,
);

const env: DotEnv = await loadEnv();

await gqltadaSpinner(env);

outro("Codegen complete");
})
);
}
117 changes: 117 additions & 0 deletions packages/cli/src/commands/codegen/gqltada.spinner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { generateSchema } from "@gql.tada/cli-utils";
import type { DotEnv } from "@settlemint/sdk-utils/validation";

/**
* Writes environment variables to .env files with a spinner for visual feedback.
*
* @param env - Partial environment variables to be written.
* @param environment - The name of the environment (e.g., "development", "production").
* @returns A promise that resolves when the environment variables are written.
* @throws If there's an error writing the environment files.
*
* @example
* await writeEnvSpinner(
* { SETTLEMINT_INSTANCE: "https://example.com", SETTLEMINT_ACCESS_TOKEN: "token123" },
* "development"
* );
*/
export async function gqltadaSpinner(env: DotEnv) {
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: Consider refactoring gqltadaCodegen to reduce repetition in the switch statement

You could create a configuration object that maps types to their respective settings, reducing the need for the switch statement and making it easier to add new types in the future.

export async function gqltadaSpinner(env: DotEnv) {
  const typeConfigs = {
    HASURA: { /* Hasura-specific config */ },
    APOLLO: { /* Apollo-specific config */ },
    // Add other types here
  };

  await gqltadaCodegen(typeConfigs[env.GQLTADA_TYPE] || {});
}

await gqltadaCodegen({
type: "HASURA",
env,
});
await gqltadaCodegen({
type: "PORTAL",
env,
});
await gqltadaCodegen({
type: "THEGRAPH",
env,
allowToFail: true,
});
await gqltadaCodegen({
type: "THEGRAPH_FALLBACK",
env,
});
}

async function gqltadaCodegen(options: {
type: "HASURA" | "PORTAL" | "THEGRAPH" | "THEGRAPH_FALLBACK";
env: DotEnv;
allowToFail?: boolean;
}) {
let gqlEndpoint: string | undefined = undefined;
let output: string;
let adminSecret: string | undefined = undefined;
const accessToken = options.env.SETTLEMINT_ACCESS_TOKEN;

switch (options.type) {
case "HASURA":
gqlEndpoint = options.env.SETTLEMINT_HASURA_ENDPOINT;
output = "hasura.schema.graphql";
adminSecret = options.env.SETTLEMINT_HASURA_ADMIN_SECRET;
break;
case "PORTAL":
gqlEndpoint = options.env.SETTLEMINT_PORTAL_GRAPHQL_ENDPOINT;
output = "portal.schema.graphql";
break;
case "THEGRAPH":
gqlEndpoint = options.env.SETTLEMINT_THEGRAPH_SUBGRAPH_ENDPOINT;
output = "thegraph.schema.graphql";
break;
case "THEGRAPH_FALLBACK":
gqlEndpoint = options.env.SETTLEMINT_THEGRAPH_SUBGRAPH_ENDPOINT_FALLBACK;
output = "thegraph-fallback.schema.graphql";
}

if (!gqlEndpoint) {
return;
}

const headers = {
...(adminSecret && { "x-hasura-admin-secret": adminSecret }),
"x-auth-token": accessToken,
"Content-Type": "application/json",
};

try {
// Test the endpoint with a simple introspection query
const response = await fetch(gqlEndpoint, {
method: "POST",
headers,
body: JSON.stringify({
query: `
query {
__schema {
queryType {
name
}
}
}
`,
}),
});

if (!response.ok) {
throw new Error(`Failed to fetch schema: ${response.statusText}`);
}

const data = await response.json();
if (data.errors) {
throw new Error(`GraphQL errors: ${JSON.stringify(data.errors)}`);
}

await generateSchema({
input: gqlEndpoint,
output,
tsconfig: undefined,
headers,
});
} catch (error) {
if (options.allowToFail) {
// ignore
} else {
throw error;
}
}
}
46 changes: 22 additions & 24 deletions packages/cli/src/commands/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,30 +68,28 @@ export function connectCommand(): Command {
const portal = await portalPrompt(env, middleware, autoAccept);
const hdPrivateKey = await hdPrivateKeyPrompt(env, privateKey, autoAccept);

await writeEnvSpinner(
{
SETTLEMINT_ACCESS_TOKEN: accessToken,
SETTLEMINT_INSTANCE: instance,
SETTLEMINT_WORKSPACE: workspace.id,
SETTLEMINT_APPLICATION: application.id,
SETTLEMINT_HASURA: hasura?.id,
SETTLEMINT_HASURA_ENDPOINT: hasura?.endpoints.find((endpoint) => endpoint.id === "graphql")?.displayValue,
SETTLEMINT_HASURA_ADMIN_SECRET: hasura?.credentials.find((credential) => credential.id === "admin-secret")
?.displayValue,
SETTLEMINT_THEGRAPH: thegraph?.id,
SETTLEMINT_THEGRAPH_SUBGRAPH_ENDPOINT: thegraph?.endpoints.find((endpoint) => endpoint.id === "graphql")
?.displayValue,
SETTLEMINT_THEGRAPH_SUBGRAPH_ENDPOINT_FALLBACK: thegraph?.endpoints.find(
(endpoint) => endpoint.id === "default-subgraph-graphql",
)?.displayValue,
SETTLEMINT_PORTAL: portal?.id,
SETTLEMINT_PORTAL_GRAPHQL_ENDPOINT: portal?.endpoints.find((endpoint) => endpoint.id === "graphql")
?.displayValue,
SETTLEMINT_PORTAL_REST_ENDPOINT: portal?.endpoints.find((endpoint) => endpoint.id === "rest")?.displayValue,
SETTLEMINT_HD_PRIVATE_KEY: hdPrivateKey?.uniqueName,
},
environment,
);
await writeEnvSpinner({
SETTLEMINT_ENVIRONMENT: environment,
SETTLEMINT_ACCESS_TOKEN: accessToken,
SETTLEMINT_INSTANCE: instance,
SETTLEMINT_WORKSPACE: workspace.id,
SETTLEMINT_APPLICATION: application.id,
SETTLEMINT_HASURA: hasura?.id,
SETTLEMINT_HASURA_ENDPOINT: hasura?.endpoints.find((endpoint) => endpoint.id === "graphql")?.displayValue,
SETTLEMINT_HASURA_ADMIN_SECRET: hasura?.credentials.find((credential) => credential.id === "admin-secret")
?.displayValue,
SETTLEMINT_THEGRAPH: thegraph?.id,
SETTLEMINT_THEGRAPH_SUBGRAPH_ENDPOINT: thegraph?.endpoints.find((endpoint) => endpoint.id === "graphql")
?.displayValue,
SETTLEMINT_THEGRAPH_SUBGRAPH_ENDPOINT_FALLBACK: thegraph?.endpoints.find(
(endpoint) => endpoint.id === "default-subgraph-graphql",
)?.displayValue,
SETTLEMINT_PORTAL: portal?.id,
SETTLEMINT_PORTAL_GRAPHQL_ENDPOINT: portal?.endpoints.find((endpoint) => endpoint.id === "graphql")
?.displayValue,
SETTLEMINT_PORTAL_REST_ENDPOINT: portal?.endpoints.find((endpoint) => endpoint.id === "rest")?.displayValue,
SETTLEMINT_HD_PRIVATE_KEY: hdPrivateKey?.uniqueName,
});

outro("Connected to SettleMint");
})
Expand Down
9 changes: 4 additions & 5 deletions packages/cli/src/commands/connect/write-env.spinner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ import type { DotEnv } from "@settlemint/sdk-utils/validation";
* "development"
* );
*/
export async function writeEnvSpinner(env: Partial<DotEnv>, environment: string) {
export async function writeEnvSpinner(env: Partial<DotEnv>) {
return spinner({
startMessage: `Saving .env.${environment} and .env.${environment}.local files`,
stopMessage: `Written .env.${environment} and .env.${environment}.local file`,
startMessage: `Saving .env.${env.SETTLEMINT_ENVIRONMENT} and .env.${env.SETTLEMINT_ENVIRONMENT}.local files`,
stopMessage: `Written .env.${env.SETTLEMINT_ENVIRONMENT} and .env.${env.SETTLEMINT_ENVIRONMENT}.local file`,
task: async () => {
await writeEnv(
{
SETTLEMINT_ENVIRONMENT: env.SETTLEMINT_ENVIRONMENT,
SETTLEMINT_INSTANCE: env.SETTLEMINT_INSTANCE,
SETTLEMINT_WORKSPACE: env.SETTLEMINT_WORKSPACE,
SETTLEMINT_APPLICATION: env.SETTLEMINT_APPLICATION,
Expand All @@ -36,7 +37,6 @@ export async function writeEnvSpinner(env: Partial<DotEnv>, environment: string)
SETTLEMINT_PORTAL_REST_ENDPOINT: env.SETTLEMINT_PORTAL_REST_ENDPOINT,
SETTLEMINT_HD_PRIVATE_KEY: env.SETTLEMINT_HD_PRIVATE_KEY,
},
environment,
false,
);

Expand All @@ -45,7 +45,6 @@ export async function writeEnvSpinner(env: Partial<DotEnv>, environment: string)
SETTLEMINT_ACCESS_TOKEN: env.SETTLEMINT_ACCESS_TOKEN,
SETTLEMINT_HASURA_ADMIN_SECRET: env.SETTLEMINT_HASURA_ADMIN_SECRET,
},
environment,
true,
);
},
Expand Down
2 changes: 1 addition & 1 deletion packages/hasura/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
},
"devDependencies": {},
"dependencies": {
"@settlemint/sdk-utils": "0.5.0",
"@settlemint/sdk-utils": "workspace:*",
"graphql-request": "^7",
"zod": "^3"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"gql.tada": "^1",
"graphql-request": "^7",
"zod": "^3",
"@settlemint/sdk-utils": "0.5.0"
"@settlemint/sdk-utils": "workspace:*"
},
"peerDependencies": {},
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/portal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
},
"devDependencies": {},
"dependencies": {
"@settlemint/sdk-utils": "0.5.0",
"@settlemint/sdk-utils": "workspace:*",
"graphql-request": "^7",
"zod": "^3"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/thegraph/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
},
"devDependencies": {},
"dependencies": {
"@settlemint/sdk-utils": "0.5.0",
"@settlemint/sdk-utils": "workspace:*",
"graphql-request": "^7",
"zod": "^3"
},
Expand Down
1 change: 1 addition & 0 deletions packages/utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"deepmerge-ts": "^7",
"environment": "^1",
"find-up": "^7",
"nano-spawn": "^0.1.0",
"yocto-spinner": "^0.1",
"yoctocolors": "^2"
},
Expand Down
14 changes: 10 additions & 4 deletions packages/utils/src/environment/load-env.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { join } from "node:path";
import { projectRoot } from "@/filesystem.js";
import { cancel } from "@/terminal.js";
import { type DotEnv, DotEnvSchema, validate } from "@/validation.js";
import { config } from "@dotenvx/dotenvx";
import type { DotenvParseOutput } from "dotenv";
Expand Down Expand Up @@ -32,6 +33,7 @@ export async function loadEnv<T extends boolean = true>(
export async function loadEnvironmentEnv<T extends boolean = true>(
validateEnv: T,
environment?: string,
override?: boolean,
): Promise<T extends true ? DotEnv : DotenvParseOutput> {
const projectDir = await projectRoot();

Expand All @@ -43,20 +45,24 @@ export async function loadEnvironmentEnv<T extends boolean = true>(
".env",
].map((file) => join(projectDir, file));

let { parsed } = config({ path: paths, logLevel: "error" });
let { parsed } = config({ path: paths, logLevel: "error", override: !!override });

if (!parsed) {
parsed = {};
}

const envToUse = environment || parsed.SETTLEMINT_ENVIRONMENT;
const envToUse = environment || parsed.SETTLEMINT_ENVIRONMENT || "development";

if (envToUse && envToUse !== environment) {
return loadEnvironmentEnv(validateEnv, envToUse);
return loadEnvironmentEnv(validateEnv, envToUse, true);
}

if (validateEnv) {
return validate(DotEnvSchema, parsed);
try {
return validate(DotEnvSchema, parsed);
} catch (error) {
cancel((error as Error).message);
}
}

return parsed as T extends true ? DotEnv : DotenvParseOutput;
Expand Down
7 changes: 5 additions & 2 deletions packages/utils/src/environment/write-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import type { DotEnv } from "@/validation.js";
import { config } from "@dotenvx/dotenvx";
import { deepmerge } from "deepmerge-ts";

export async function writeEnv(env: Partial<DotEnv>, environment: string, secrets: boolean) {
export async function writeEnv(env: Partial<DotEnv>, secrets: boolean) {
const projectDir = await projectRoot();
const envFile = join(projectDir, secrets ? `.env.${environment}.local` : `.env.${environment}`);
const envFile = join(
projectDir,
secrets ? `.env.${env.SETTLEMINT_ENVIRONMENT}.local` : `.env.${env.SETTLEMINT_ENVIRONMENT}`,
);

let { parsed: currentEnv } = config({
path: envFile,
Expand Down
1 change: 1 addition & 0 deletions packages/utils/src/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export { cancel } from "./terminal/cancel.js";
export { intro } from "./terminal/intro.js";
export { note } from "./terminal/note.js";
export { outro } from "./terminal/outro.js";
export { run } from "./terminal/run.js";
export { spinner } from "./terminal/spinner.js";
Loading