Pārlūkot izejas kodu

Detect Solana version

Loris Leiva 1 gadu atpakaļ
vecāks
revīzija
6be68cf913
6 mainītis faili ar 127 papildinājumiem un 63 dzēšanām
  1. 52 18
      index.ts
  2. 4 3
      locales/en-US.json
  3. 4 3
      locales/fr-FR.json
  4. 3 2
      utils/getLanguage.ts
  5. 23 16
      utils/getRenderContext.ts
  6. 41 21
      utils/solanaCli.ts

+ 52 - 18
index.ts

@@ -3,30 +3,59 @@
 import * as fs from "node:fs";
 import * as path from "node:path";
 
+import { getInputs } from "./utils/getInputs";
+import { Language, getLanguage } from "./utils/getLanguage";
 import { logBanner, logDone, logErrorAndExit, logStep } from "./utils/getLogs";
 import { RenderContext, getRenderContext } from "./utils/getRenderContext";
 import { renderTemplate } from "./utils/renderTemplates";
-import { generateKeypair } from "./utils/solanaCli";
+import { detectSolanaVersion, generateKeypair } from "./utils/solanaCli";
 
 (async function init() {
   logBanner();
 
-  // Get the args inputs, prompt inputs and computed values.
-  const ctx = await getRenderContext();
-  createOrEmptyTargetDirectory(ctx);
+  // Get arguments from CLI and prompt.
+  const language = getLanguage();
+  const inputs = await getInputs(language);
+
+  // Create or empty the target directory.
+  createOrEmptyTargetDirectory(
+    language,
+    inputs.targetDirectoryName,
+    inputs.shouldOverride
+  );
+
+  // Detect the solana version.
+  const solanaVersionDetected = await logStep(
+    language.infos.detectSolanaVersion,
+    () => detectSolanaVersion(language)
+  );
 
   // Generate a keypair if needed.
-  if (!ctx.hasCustomProgramAddress) {
-    await logStep(ctx.language.infos.generateKeypair, () =>
-      generateKeypair(ctx)
-    );
-  }
+  const programAddress =
+    inputs.programAddress ??
+    (await logStep(language.infos.generateKeypair, () => {
+      const outfile = path.join(
+        process.cwd(),
+        inputs.targetDirectoryName,
+        "program",
+        "keypair.json"
+      );
+      return generateKeypair(language, outfile);
+    }));
+
+  // Get the args inputs, prompt inputs and computed values.
+  const ctx = getRenderContext({
+    language,
+    inputs,
+    programAddress,
+    solanaVersionDetected,
+  });
 
   // Render the templates.
   await logStep(
-    ctx.language.infos.scaffold.replace(
+    language.infos.scaffold.replace(
       "$targetDirectory",
-      ctx.targetDirectoryName
+      inputs.targetDirectoryName
     ),
     () => renderTemplates(ctx)
   );
@@ -58,16 +87,21 @@ function renderTemplates(ctx: RenderContext) {
   });
 }
 
-function createOrEmptyTargetDirectory(ctx: RenderContext) {
-  if (!fs.existsSync(ctx.targetDirectory)) {
-    fs.mkdirSync(ctx.targetDirectory, { recursive: true });
-  } else if (ctx.shouldOverride) {
-    emptyDirectory(ctx.targetDirectory);
+function createOrEmptyTargetDirectory(
+  language: Language,
+  targetDirectoryName: string,
+  shouldOverride: boolean
+) {
+  const targetDirectory = path.join(process.cwd(), targetDirectoryName);
+  if (!fs.existsSync(targetDirectory)) {
+    fs.mkdirSync(targetDirectory, { recursive: true });
+  } else if (shouldOverride) {
+    emptyDirectory(targetDirectory);
   } else {
     logErrorAndExit(
-      ctx.language.errors.cannotOverrideDirectory.replace(
+      language.errors.cannotOverrideDirectory.replace(
         "$targetDirectory",
-        ctx.targetDirectoryName
+        targetDirectoryName
       )
     );
   }

+ 4 - 3
locales/en-US.json

@@ -50,8 +50,8 @@
     "cannotOverrideDirectory": "Cannot override target directory \"$targetDirectory\". Run with option --force to override.",
     "invalidSolanaVersion": "Invalid Solana version: $version.",
     "operationCancelled": "Operation cancelled",
-    "solanaKeygenFailed": "Failed to generate program keypair",
-    "solanaKeygenNotFound": "Command `solana-keygen` unavailable. Please install the Solana CLI."
+    "solanaCliNotFound": "Command `$command` unavailable. Please install the Solana CLI.",
+    "solanaKeygenFailed": "Failed to generate program keypair"
   },
   "defaultToggleOptions": {
     "active": "Yes",
@@ -62,8 +62,9 @@
     "multiselect": "[↑/↓]: Select / [space]: Toggle selection / [a]: Toggle all / [enter]: Submit answer"
   },
   "infos": {
-    "scaffold": "Scaffold project in $targetDirectory",
+    "detectSolanaVersion": "Detect Solana version",
     "generateKeypair": "Generate program keypair",
+    "scaffold": "Scaffold project in $targetDirectory",
     "done": "Done. Now run:"
   }
 }

+ 4 - 3
locales/fr-FR.json

@@ -53,8 +53,8 @@
     "cannotOverrideDirectory": "Impossible de remplacer le répertoire cible \"$targetDirectory\". Exécutez avec l'option --force pour remplacer.",
     "invalidSolanaVersion": "Version Solana invalide\u00a0: $version.",
     "operationCancelled": "Operation annulée",
-    "solanaKeygenFailed": "La génération de la paire de clés a échoué",
-    "solanaKeygenNotFound": "Commande `solana-keygen` indisponible. Veuillez installer Solana dans votre terminal."
+    "solanaCliNotFound": "Commande `$command` indisponible. Veuillez installer Solana dans votre terminal.",
+    "solanaKeygenFailed": "La génération de la paire de clés a échoué"
   },
   "defaultToggleOptions": {
     "active": "Oui",
@@ -65,8 +65,9 @@
     "multiselect": "[↑/↓]: Sélectionner / [espace]: Basculer la sélection / [a]: Basculer tout / [entrée]: Valider"
   },
   "infos": {
-    "scaffold": "Génére le projet dans $targetDirectory",
+    "detectSolanaVersion": "Détect la version de Solana",
     "generateKeypair": "Génére la paire de clés du program",
+    "scaffold": "Génére le projet dans $targetDirectory",
     "done": "Terminé. Exécutez maintenant\u00a0:"
   }
 }

+ 3 - 2
utils/getLanguage.ts

@@ -32,8 +32,8 @@ export interface Language {
     cannotOverrideDirectory: string;
     invalidSolanaVersion: string;
     operationCancelled: string;
+    solanaCliNotFound: string;
     solanaKeygenFailed: string;
-    solanaKeygenNotFound: string;
   };
   defaultToggleOptions: {
     active: string;
@@ -44,8 +44,9 @@ export interface Language {
     multiselect: string;
   };
   infos: {
-    scaffold: string;
+    detectSolanaVersion: string;
     generateKeypair: string;
+    scaffold: string;
     done: string;
   };
 }

+ 23 - 16
utils/getRenderContext.ts

@@ -1,41 +1,44 @@
 import * as path from "node:path";
 
-import {
-  Client,
-  Inputs,
-  allClients,
-  getDefaultInputs,
-  getInputs,
-} from "./getInputs";
-import { Language, getLanguage } from "./getLanguage";
+import { Client, Inputs, allClients } from "./getInputs";
+import { Language } from "./getLanguage";
 import {
   PackageManager,
   getPackageManager,
   getPackageManagerCommand,
 } from "./getPackageManager";
+import { toMinorSolanaVersion } from "./solanaCli";
 
-export type RenderContext = Inputs & {
+export type RenderContext = Omit<Inputs, "programAddress" | "solanaVersion"> & {
   clientDirectory: string;
   clients: Client[];
   currentDirectory: string;
   getNpmCommand: (scriptName: string, args?: string) => string;
-  hasCustomProgramAddress: boolean;
   language: Language;
+  programAddress: string;
   programDirectory: string;
   packageManager: PackageManager;
+  solanaVersion: string;
+  solanaVersionDetected: string;
   targetDirectory: string;
   templateDirectory: string;
 };
 
-export async function getRenderContext(): Promise<RenderContext> {
-  const language = getLanguage();
+export function getRenderContext({
+  inputs,
+  language,
+  programAddress,
+  solanaVersionDetected,
+}: {
+  inputs: Inputs;
+  language: Language;
+  programAddress: string;
+  solanaVersionDetected: string;
+}): RenderContext {
   const packageManager = getPackageManager();
-  const inputs = await getInputs(language);
   const clients = allClients.flatMap((client) =>
     inputs[`${client}Client`] ? [client] : []
   );
-  const hasCustomProgramAddress =
-    inputs.programAddress !== getDefaultInputs({}).programAddress;
   const getNpmCommand: RenderContext["getNpmCommand"] = (...args) =>
     getPackageManagerCommand(packageManager, ...args);
 
@@ -55,10 +58,14 @@ export async function getRenderContext(): Promise<RenderContext> {
     clients,
     currentDirectory,
     getNpmCommand,
-    hasCustomProgramAddress,
     language,
     packageManager,
+    programAddress,
     programDirectory,
+    solanaVersion:
+      inputs.solanaVersion ??
+      toMinorSolanaVersion(language, solanaVersionDetected),
+    solanaVersionDetected,
     targetDirectory,
     templateDirectory,
   };

+ 41 - 21
utils/solanaCli.ts

@@ -1,16 +1,51 @@
 import { Language } from "./getLanguage";
-import { RenderContext } from "./getRenderContext";
 import {
-  spawnCommand,
   hasCommand,
   readStdout,
+  spawnCommand,
   waitForCommand,
 } from "./runCommands";
 
-export async function generateKeypair(ctx: RenderContext): Promise<string> {
+export async function detectSolanaVersion(language: Language): Promise<string> {
+  const hasSolanaCli = await hasCommand("solana");
+  if (!hasSolanaCli) {
+    throw new Error(
+      language.errors.solanaCliNotFound.replace("$command", "solana")
+    );
+  }
+
+  const child = spawnCommand("solana", ["--version"]);
+  const [stdout] = await Promise.all([
+    readStdout(child),
+    waitForCommand(child),
+  ]);
+
+  const version = stdout.join("").match(/(\d+\.\d+\.\d+)/)?.[1];
+  return version!;
+}
+
+export function toMinorSolanaVersion(
+  language: Language,
+  version: string
+): string {
+  const validVersion = version.match(/^(\d+\.\d+)/);
+  if (!validVersion) {
+    throw new Error(
+      language.errors.invalidSolanaVersion.replace("$version", version)
+    );
+  }
+  return validVersion[0];
+}
+
+export async function generateKeypair(
+  language: Language,
+  outfile: string
+): Promise<string> {
   const hasSolanaKeygen = await hasCommand("solana-keygen");
   if (!hasSolanaKeygen) {
-    throw new Error(ctx.language.errors.solanaKeygenNotFound);
+    throw new Error(
+      language.errors.solanaCliNotFound.replace("$command", "solana-keygen")
+    );
   }
 
   // Run the solana-keygen command to generate a new keypair.
@@ -18,7 +53,7 @@ export async function generateKeypair(ctx: RenderContext): Promise<string> {
     "new",
     "--no-bip39-passphrase",
     "--outfile",
-    `${ctx.programDirectory}/keypair.json`,
+    outfile,
   ]);
 
   // Wait for the command to finish and read the stdout.
@@ -30,23 +65,8 @@ export async function generateKeypair(ctx: RenderContext): Promise<string> {
   // Update the render context with the generated address.
   const address = stdout.join("").match(/pubkey: (\w+)/)?.[1];
   if (!address) {
-    throw new Error(ctx.language.errors.solanaKeygenFailed);
+    throw new Error(language.errors.solanaKeygenFailed);
   }
 
-  ctx.programAddress = address;
-
   return address;
 }
-
-export function toMinorSolanaVersion(
-  language: Language,
-  version: string
-): string {
-  const validVersion = version.match(/^(\d+\.\d+)/);
-  if (!validVersion) {
-    throw new Error(
-      language.errors.invalidSolanaVersion.replace("$version", version)
-    );
-  }
-  return validVersion[0];
-}