ソースを参照

Generate program keypair using solana-keygen

Loris Leiva 1 年間 前
コミット
b758fafbf3
7 ファイル変更89 行追加2 行削除
  1. 2 0
      index.ts
  2. 2 1
      locales/en-US.json
  3. 2 1
      locales/fr-FR.json
  4. 35 0
      utils/generateKeypair.ts
  5. 1 0
      utils/getLanguage.ts
  6. 6 0
      utils/getRenderContext.ts
  7. 41 0
      utils/runCommands.ts

+ 2 - 0
index.ts

@@ -3,6 +3,7 @@
 import * as fs from "node:fs";
 import * as path from "node:path";
 
+import { generateKeypair } from "./utils/generateKeypair";
 import { logBanner, logEnd, logErrorAndExit, logStart } from "./utils/getLogs";
 import { RenderContext, getRenderContext } from "./utils/getRenderContext";
 import { renderTemplate } from "./utils/renderTemplates";
@@ -13,6 +14,7 @@ import { renderTemplate } from "./utils/renderTemplates";
   createOrEmptyTargetDirectory(ctx);
   logStart(ctx);
   renderTemplates(ctx);
+  await generateKeypair(ctx);
   logEnd(ctx);
 })().catch((e) => console.error(e));
 

+ 2 - 1
locales/en-US.json

@@ -51,7 +51,8 @@
   },
   "errors": {
     "operationCancelled": "Operation cancelled",
-    "cannotOverrideDirectory": "Cannot override target directory \"$targetDirectory\". Run with option --force to override."
+    "cannotOverrideDirectory": "Cannot override target directory \"$targetDirectory\". Run with option --force to override.",
+    "solanaKeygenNotFound": "Command `solana-keygen` unavailable. Please install the Solana CLI."
   },
   "defaultToggleOptions": {
     "active": "Yes",

+ 2 - 1
locales/fr-FR.json

@@ -54,7 +54,8 @@
   },
   "errors": {
     "operationCancelled": "Operation annulée",
-    "cannotOverrideDirectory": "Impossible de remplacer le répertoire cible \"$targetDirectory\". Exécutez avec l'option --force pour remplacer."
+    "cannotOverrideDirectory": "Impossible de remplacer le répertoire cible \"$targetDirectory\". Exécutez avec l'option --force pour remplacer.",
+    "solanaKeygenNotFound": "Commande `solana-keygen` indisponible. Veuillez installer Solana dans votre terminal."
   },
   "defaultToggleOptions": {
     "active": "Oui",

+ 35 - 0
utils/generateKeypair.ts

@@ -0,0 +1,35 @@
+import { logErrorAndExit } from "./getLogs";
+import { RenderContext } from "./getRenderContext";
+import {
+  spawnCommand,
+  hasCommand,
+  readStdout,
+  waitForCommand,
+} from "./runCommands";
+
+export async function generateKeypair(ctx: RenderContext): Promise<string> {
+  const hasSolanaKeygen = await hasCommand("solana-keygen");
+  if (!hasSolanaKeygen) {
+    logErrorAndExit(ctx.language.errors.solanaKeygenNotFound);
+  }
+
+  // Run the solana-keygen command to generate a new keypair.
+  const child = spawnCommand("solana-keygen", [
+    "new",
+    "--no-bip39-passphrase",
+    "--outfile",
+    `${ctx.programDirectory}/keygen.json`,
+  ]);
+
+  // Wait for the command to finish and read the stdout.
+  const [stdout] = await Promise.all([
+    readStdout(child),
+    waitForCommand(child),
+  ]);
+
+  // Update the render context with the generated address.
+  const address = stdout.join("").match(/pubkey: (\w+)/)?.[1];
+  ctx.programAddress = address;
+
+  return address;
+}

+ 1 - 0
utils/getLanguage.ts

@@ -31,6 +31,7 @@ export interface Language {
   errors: {
     operationCancelled: string;
     cannotOverrideDirectory: string;
+    solanaKeygenNotFound: string;
   };
   defaultToggleOptions: {
     active: string;

+ 6 - 0
utils/getRenderContext.ts

@@ -9,10 +9,12 @@ import {
 } from "./getPackageManager";
 
 export type RenderContext = Inputs & {
+  clientDirectory: string;
   clients: Client[];
   currentDirectory: string;
   getNpmCommand: (scriptName: string, args?: string) => string;
   language: Language;
+  programDirectory: string;
   packageManager: PackageManager;
   targetDirectory: string;
   templateDirectory: string;
@@ -35,14 +37,18 @@ export async function getRenderContext(): Promise<RenderContext> {
     currentDirectory,
     inputs.targetDirectoryName
   );
+  const programDirectory = path.join(targetDirectory, "program");
+  const clientDirectory = path.join(targetDirectory, "client");
 
   return {
     ...inputs,
+    clientDirectory,
     clients,
     currentDirectory,
     getNpmCommand,
     language,
     packageManager,
+    programDirectory,
     targetDirectory,
     templateDirectory,
   };

+ 41 - 0
utils/runCommands.ts

@@ -0,0 +1,41 @@
+import { spawn, ChildProcess, SpawnOptions } from "node:child_process";
+
+export function spawnCommand(
+  command: string,
+  args: string[] = [],
+  options?: SpawnOptions
+): ChildProcess {
+  return spawn(command, args, options);
+}
+
+export async function hasCommand(command: string): Promise<boolean> {
+  try {
+    await waitForCommand(spawnCommand("which", [command], { stdio: "ignore" }));
+    return true;
+  } catch {
+    return false;
+  }
+}
+
+export async function waitForCommand(child: ChildProcess): Promise<number> {
+  return new Promise((resolve, reject) => {
+    child.on("close", (code) => {
+      if (code !== 0) {
+        const message = `$(${child}) exited with code ${code}`;
+        reject(new Error(message));
+      } else {
+        resolve(code);
+      }
+    });
+  });
+}
+
+export async function readStdout(child: ChildProcess): Promise<string[]> {
+  const stdout: string[] = [];
+  return new Promise((resolve) => {
+    child.stdout?.on("data", (data) => {
+      stdout.push(data.toString());
+    });
+    child.on("close", () => resolve(stdout));
+  });
+}