Repository: settlemint/sdk
Pull request #811: feat: add version option to create command
Author: login: janb87
Labels: name: feat
description: New feature
name: e2e
Created: 2025-03-07T16:44:36Z | Merged: 2025-03-07T17:09:34Z

## Summary by Sourcery

Adds the ability to specify a template version when creating a new project using the create command.

New Features:
- Adds a --version option to the create command to specify the template version to use.
- Displays the description and npm package name for each template in the platform config command output.

Tests:
- Adds a test case to verify the --version option in the create command.


Diff:
diff --git a/.gitignore b/.gitignore
index 8a4e7f8bc..fd9f8580b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,7 +42,7 @@ settlemint.config.mjs
 .idea
 
 # Testing
-test/starter-kit-demo
+test/kit-demo
 test/unknown
 test/contracts
 test/contracts-subgraphs
diff --git a/bun.lock b/bun.lock
old mode 100755
new mode 100644
index 5a9c6aa07..c8339bcaf
--- a/bun.lock
+++ b/bun.lock
@@ -1,5 +1,5 @@
 {
-  "lockfileVersion": 0,
+  "lockfileVersion": 1,
   "workspaces": {
     "": {
       "devDependencies": {
@@ -24,7 +24,7 @@
       "name": "@settlemint/sdk-blockscout",
       "version": "0.9.0",
       "dependencies": {
-        "@settlemint/sdk-utils": "sdk/utils",
+        "@settlemint/sdk-utils": "workspace:*",
         "gql.tada": "^1",
         "graphql-request": "^7",
         "zod": "^3",
@@ -33,6 +33,9 @@
     "sdk/cli": {
       "name": "@settlemint/sdk-cli",
       "version": "0.9.0",
+      "bin": {
+        "settlemint": "dist/cli.js",
+      },
       "dependencies": {
         "tinyexec": "0.3.2",
       },
@@ -42,8 +45,8 @@
         "@inquirer/input": "4.1.6",
         "@inquirer/password": "4.0.9",
         "@inquirer/select": "4.0.9",
-        "@settlemint/sdk-js": "sdk/js",
-        "@settlemint/sdk-utils": "sdk/utils",
+        "@settlemint/sdk-js": "workspace:*",
+        "@settlemint/sdk-utils": "workspace:*",
         "@types/node": "22.13.9",
         "@types/semver": "7.5.8",
         "@types/which": "3.0.4",
@@ -67,7 +70,7 @@
       "name": "@settlemint/sdk-hasura",
       "version": "0.9.0",
       "dependencies": {
-        "@settlemint/sdk-utils": "sdk/utils",
+        "@settlemint/sdk-utils": "workspace:*",
         "drizzle-kit": "^0.30",
         "drizzle-orm": "^0.40.0",
         "gql.tada": "^1",
@@ -83,7 +86,7 @@
       "name": "@settlemint/sdk-ipfs",
       "version": "0.9.0",
       "dependencies": {
-        "@settlemint/sdk-utils": "sdk/utils",
+        "@settlemint/sdk-utils": "workspace:*",
         "kubo-rpc-client": "^5",
         "zod": "^3",
       },
@@ -92,7 +95,7 @@
       "name": "@settlemint/sdk-js",
       "version": "0.9.0",
       "dependencies": {
-        "@settlemint/sdk-utils": "sdk/utils",
+        "@settlemint/sdk-utils": "workspace:*",
         "gql.tada": "^1",
         "graphql-request": "^7",
         "zod": "^3",
@@ -102,7 +105,7 @@
       "name": "@settlemint/sdk-minio",
       "version": "0.9.0",
       "dependencies": {
-        "@settlemint/sdk-utils": "sdk/utils",
+        "@settlemint/sdk-utils": "workspace:*",
         "minio": "^8",
         "zod": "^3",
       },
@@ -111,7 +114,7 @@
       "name": "@settlemint/sdk-next",
       "version": "0.9.0",
       "dependencies": {
-        "@settlemint/sdk-utils": "sdk/utils",
+        "@settlemint/sdk-utils": "workspace:*",
         "path-to-regexp": "^8",
       },
       "devDependencies": {
@@ -126,7 +129,7 @@
       "name": "@settlemint/sdk-portal",
       "version": "0.9.0",
       "dependencies": {
-        "@settlemint/sdk-utils": "sdk/utils",
+        "@settlemint/sdk-utils": "workspace:*",
         "gql.tada": "^1",
         "graphql-request": "^7",
         "zod": "^3",
@@ -136,7 +139,7 @@
       "name": "@settlemint/sdk-thegraph",
       "version": "0.9.0",
       "dependencies": {
-        "@settlemint/sdk-utils": "sdk/utils",
+        "@settlemint/sdk-utils": "workspace:*",
         "gql.tada": "^1",
         "graphql-request": "^7",
         "zod": "^3",
@@ -457,25 +460,25 @@
 
     "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.34.9", "", { "os": "win32", "cpu": "x64" }, "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw=="],
 
-    "@settlemint/sdk-blockscout": ["@settlemint/sdk-blockscout@workspace:sdk/blockscout", { "dependencies": { "@settlemint/sdk-utils": "sdk/utils", "gql.tada": "^1", "graphql-request": "^7", "zod": "^3" } }],
+    "@settlemint/sdk-blockscout": ["@settlemint/sdk-blockscout@workspace:sdk/blockscout"],
 
-    "@settlemint/sdk-cli": ["@settlemint/sdk-cli@workspace:sdk/cli", { "dependencies": { "tinyexec": "0.3.2" }, "devDependencies": { "@commander-js/extra-typings": "13.1.0", "@inquirer/confirm": "5.1.6", "@inquirer/input": "4.1.6", "@inquirer/password": "4.0.9", "@inquirer/select": "4.0.9", "@settlemint/sdk-js": "sdk/js", "@settlemint/sdk-utils": "sdk/utils", "@types/node": "22.13.9", "@types/semver": "7.5.8", "@types/which": "3.0.4", "get-tsconfig": "4.10.0", "giget": "2.0.0", "is-in-ci": "1.0.0", "semver": "7.7.1", "slugify": "1.6.6", "which": "5.0.0", "yaml": "2.7.0", "yoctocolors": "2.1.1" }, "peerDependencies": { "hardhat": "2.22.19" }, "optionalPeers": ["hardhat"], "bin": { "settlemint": "dist/cli.js" } }],
+    "@settlemint/sdk-cli": ["@settlemint/sdk-cli@workspace:sdk/cli"],
 
-    "@settlemint/sdk-hasura": ["@settlemint/sdk-hasura@workspace:sdk/hasura", { "dependencies": { "@settlemint/sdk-utils": "sdk/utils", "drizzle-kit": "^0.30", "drizzle-orm": "^0.40.0", "gql.tada": "^1", "graphql-request": "^7", "pg": "^8", "zod": "^3" }, "devDependencies": { "@types/pg": "^8" } }],
+    "@settlemint/sdk-hasura": ["@settlemint/sdk-hasura@workspace:sdk/hasura"],
 
-    "@settlemint/sdk-ipfs": ["@settlemint/sdk-ipfs@workspace:sdk/ipfs", { "dependencies": { "@settlemint/sdk-utils": "sdk/utils", "kubo-rpc-client": "^5", "zod": "^3" } }],
+    "@settlemint/sdk-ipfs": ["@settlemint/sdk-ipfs@workspace:sdk/ipfs"],
 
-    "@settlemint/sdk-js": ["@settlemint/sdk-js@workspace:sdk/js", { "dependencies": { "@settlemint/sdk-utils": "sdk/utils", "gql.tada": "^1", "graphql-request": "^7", "zod": "^3" } }],
+    "@settlemint/sdk-js": ["@settlemint/sdk-js@workspace:sdk/js"],
 
-    "@settlemint/sdk-minio": ["@settlemint/sdk-minio@workspace:sdk/minio", { "dependencies": { "@settlemint/sdk-utils": "sdk/utils", "minio": "^8", "zod": "^3" } }],
+    "@settlemint/sdk-minio": ["@settlemint/sdk-minio@workspace:sdk/minio"],
 
-    "@settlemint/sdk-next": ["@settlemint/sdk-next@workspace:sdk/next", { "dependencies": { "@settlemint/sdk-utils": "sdk/utils", "path-to-regexp": "^8" }, "devDependencies": { "@types/react": ">=18" }, "peerDependencies": { "next": ">=15", "react": ">=18" } }],
+    "@settlemint/sdk-next": ["@settlemint/sdk-next@workspace:sdk/next"],
 
-    "@settlemint/sdk-portal": ["@settlemint/sdk-portal@workspace:sdk/portal", { "dependencies": { "@settlemint/sdk-utils": "sdk/utils", "gql.tada": "^1", "graphql-request": "^7", "zod": "^3" } }],
+    "@settlemint/sdk-portal": ["@settlemint/sdk-portal@workspace:sdk/portal"],
 
-    "@settlemint/sdk-thegraph": ["@settlemint/sdk-thegraph@workspace:sdk/thegraph", { "dependencies": { "@settlemint/sdk-utils": "sdk/utils", "gql.tada": "^1", "graphql-request": "^7", "zod": "^3" } }],
+    "@settlemint/sdk-thegraph": ["@settlemint/sdk-thegraph@workspace:sdk/thegraph"],
 
-    "@settlemint/sdk-utils": ["@settlemint/sdk-utils@workspace:sdk/utils", { "dependencies": { "@antfu/install-pkg": "^1", "@dotenvx/dotenvx": "^1", "@manypkg/find-root": "^2", "@npmcli/package-json": "^6", "console-table-printer": "^2", "deepmerge-ts": "^7", "environment": "^1", "find-up": "^7", "is-in-ci": "^1", "nano-spawn": "^0.2", "package-manager-detector": "^0.2", "yocto-spinner": "^0.2.0", "yoctocolors": "^2" }, "devDependencies": { "@types/npmcli__package-json": "^4" } }],
+    "@settlemint/sdk-utils": ["@settlemint/sdk-utils@workspace:sdk/utils"],
 
     "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@1.29.2", "", { "dependencies": { "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1" } }, "sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA=="],
 
@@ -521,7 +524,7 @@
 
     "aggregate-error": ["aggregate-error@3.1.0", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="],
 
-    "ansi-escapes": ["ansi-escapes@7.0.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw=="],
+    "ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
 
     "ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
 
@@ -1309,12 +1312,6 @@
 
     "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
 
-    "@inquirer/core/ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
-
-    "@inquirer/password/ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
-
-    "@inquirer/select/ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
-
     "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
 
     "@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
@@ -1343,6 +1340,8 @@
 
     "it-pushable/p-defer": ["p-defer@4.0.1", "", {}, "sha512-Mr5KC5efvAK5VUptYEIopP1bakB85k2IWXaRC0rsh1uwn1L6M0LVml8OIQ4Gudg4oyZakf7FmeRLkMMtZW1i5A=="],
 
+    "marked-terminal/ansi-escapes": ["ansi-escapes@7.0.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw=="],
+
     "marked-terminal/chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
 
     "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
diff --git a/sdk/cli/README.md b/sdk/cli/README.md
index 85834b755..2b31add2b 100644
--- a/sdk/cli/README.md
+++ b/sdk/cli/README.md
@@ -35,7 +35,7 @@
   - [Get the version of the CLI](#get-the-version-of-the-cli)
   - [Get help for a command](#get-help-for-a-command)
   - [Login to the platform](#login-to-the-platform)
-  - [Creating a new project from a starter kit template](#creating-a-new-project-from-a-starter-kit-template)
+  - [Creating a new project from a template](#creating-a-new-project-from-a-template)
     - [Installing dependencies](#installing-dependencies)
     - [Connecting to your SettleMint infrastructure](#connecting-to-your-settlemint-infrastructure)
     - [Deploying your smart contracts and subgraphs](#deploying-your-smart-contracts-and-subgraphs)
@@ -139,9 +139,9 @@ Then run the login command and paste your token when prompted:
 settlemint login
 ```
 
-### Creating a new project from a starter kit template
+### Creating a new project from a template
 
-To create a new project from a starter kit template, use the `create` command with the `--template` flag:
+To create a new project from a template, use the `create` command with the `--template` flag:
 
 ```bash
 settlemint create --project-name <project-name> --template <template-name>
diff --git a/sdk/cli/docs/ABOUT.md b/sdk/cli/docs/ABOUT.md
index 11889edcd..90c670cbb 100644
--- a/sdk/cli/docs/ABOUT.md
+++ b/sdk/cli/docs/ABOUT.md
@@ -87,9 +87,9 @@ Then run the login command and paste your token when prompted:
 settlemint login
 ```
 
-### Creating a new project from a starter kit template
+### Creating a new project from a template
 
-To create a new project from a starter kit template, use the `create` command with the `--template` flag:
+To create a new project from a template, use the `create` command with the `--template` flag:
 
 ```bash
 settlemint create --project-name <project-name> --template <template-name>
diff --git a/sdk/cli/docs/settlemint/create.md b/sdk/cli/docs/settlemint/create.md
index 6c2a65497..89235202d 100644
--- a/sdk/cli/docs/settlemint/create.md
+++ b/sdk/cli/docs/settlemint/create.md
@@ -9,6 +9,8 @@ Options:
   -t, --template &lt;template&gt;  The template for your SettleMint project (run
                              `settlemint platform config` to see available
                              templates)
+  -v, --version &lt;version&gt;    Specify the template version to use (defaults to
+                             latest stable version)
   -i, --instance &lt;instance&gt;  The instance to connect to
   -h, --help                 display help for command
 </pre>
diff --git a/sdk/cli/src/commands/create.ts b/sdk/cli/src/commands/create.ts
index 47fabd8e2..1080e3d75 100644
--- a/sdk/cli/src/commands/create.ts
+++ b/sdk/cli/src/commands/create.ts
@@ -2,8 +2,8 @@ import { mkdir } from "node:fs/promises";
 import { join } from "node:path";
 import { nothingSelectedError } from "@/error/nothing-selected-error";
 import { instancePrompt } from "@/prompts/instance.prompt";
+import { templatePrompt } from "@/prompts/kit/template.prompt";
 import { projectNamePrompt } from "@/prompts/project-name.prompt";
-import { templatePrompt } from "@/prompts/starter-kit/template.prompt";
 import { downloadAndExtractNpmPackage } from "@/utils/download-extract";
 import { sanitizeAndValidateInstanceUrl } from "@/utils/instance-url-utils";
 import { Command } from "@commander-js/extra-typings";
@@ -31,11 +31,16 @@ export function createCommand(): Command {
         "-t, --template <template>",
         "The template for your SettleMint project (run `settlemint platform config` to see available templates)",
       )
+      .option("-v, --version <version>", "Specify the template version to use (defaults to latest stable version)")
       .option("-i, --instance <instance>", "The instance to connect to")
-      .action(async ({ projectName, template, instance }) => {
+      .action(async ({ projectName, template, version, instance }) => {
         intro("Creating a new SettleMint project");
         const env: Partial<DotEnv> = await loadEnv(false, false);
 
+        if (version && !template) {
+          cancel("The --version option requires the --template option to be set");
+        }
+
         const selectedInstance = instance ? sanitizeAndValidateInstanceUrl(instance) : await instancePrompt(env, true);
         const settlemint = createSettleMintClient({
           instance: selectedInstance,
@@ -71,7 +76,11 @@ export function createCommand(): Command {
         await spinner({
           startMessage: "Scaffolding the project",
           task: async () => {
-            await downloadAndExtractNpmPackage(selectedTemplate.npmPackageName, projectDir);
+            await downloadAndExtractNpmPackage({
+              template: selectedTemplate.npmPackageName,
+              version,
+              targetDir: projectDir,
+            });
             await setName(name, projectDir);
           },
           stopMessage: "Project fully scaffolded",
diff --git a/sdk/cli/src/commands/platform/config.ts b/sdk/cli/src/commands/platform/config.ts
index ed8125133..bd3395941 100644
--- a/sdk/cli/src/commands/platform/config.ts
+++ b/sdk/cli/src/commands/platform/config.ts
@@ -50,6 +50,8 @@ export function configCommand() {
           .map((kit) => ({
             id: kit.id,
             name: kit.name,
+            description: kit.description,
+            npmPackage: kit.npmPackageName,
           }))
           .sort((a, b) => a.name.localeCompare(b.name)),
         deploymentEngineTargets: platformConfig.deploymentEngineTargets
diff --git a/sdk/cli/src/prompts/starter-kit/template.prompt.ts b/sdk/cli/src/prompts/kit/template.prompt.ts
similarity index 100%
rename from sdk/cli/src/prompts/starter-kit/template.prompt.ts
rename to sdk/cli/src/prompts/kit/template.prompt.ts
diff --git a/sdk/cli/src/utils/download-extract.ts b/sdk/cli/src/utils/download-extract.ts
index 288e7df6f..27115a39d 100644
--- a/sdk/cli/src/utils/download-extract.ts
+++ b/sdk/cli/src/utils/download-extract.ts
@@ -4,12 +4,21 @@ import { downloadTemplate } from "giget";
 /**
  * Downloads and extracts an npm package template to the specified target directory.
  *
- * @param template - The template to download
+ * @param npmPackageName - The npm package name to download
+ * @param version - Optional specific version of the package to download (defaults to latest)
  * @param targetDir - The directory to extract the template to
  * @returns A Promise that resolves when the download and extraction are complete
  * @throws Will throw an error if the download or extraction fails
  */
-export async function downloadAndExtractNpmPackage(template: string, targetDir: string): Promise<void> {
+export async function downloadAndExtractNpmPackage({
+  template,
+  version,
+  targetDir,
+}: {
+  template: string;
+  version?: string;
+  targetDir: string;
+}): Promise<void> {
   // Create target directory if it doesn't exist
   await mkdir(targetDir, { recursive: true });
 
@@ -20,7 +29,7 @@ export async function downloadAndExtractNpmPackage(template: string, targetDir:
   }
   const data = await response.json();
   const latestVersion = data["dist-tags"].latest as string;
-  const tarball = data.versions[latestVersion].dist.tarball as string;
+  const tarball = data.versions[version ?? latestVersion].dist.tarball as string;
 
   // Download and extract the package using giget
   await downloadTemplate(tarball, {
diff --git a/sdk/utils/src/validation/unique-name.schema.test.ts b/sdk/utils/src/validation/unique-name.schema.test.ts
index 1a6fca42c..beccd82d0 100644
--- a/sdk/utils/src/validation/unique-name.schema.test.ts
+++ b/sdk/utils/src/validation/unique-name.schema.test.ts
@@ -4,7 +4,7 @@ import { UniqueNameSchema } from "./unique-name.schema.js";
 
 describe("UniqueNameSchema", () => {
   test("should validate valid unique names", () => {
-    const validNames = ["starter-kit-demo-workspace-dcfea", "starter-kit-dc58a"];
+    const validNames = ["kit-demo-workspace-dcfea", "kit-dc58a"];
 
     for (const validName of validNames) {
       expect(() => UniqueNameSchema.parse(validName)).not.toThrow();
diff --git a/test/constants/test-resources.ts b/test/constants/test-resources.ts
index 698c434ca..a98c2f69a 100644
--- a/test/constants/test-resources.ts
+++ b/test/constants/test-resources.ts
@@ -1,22 +1,22 @@
 import { isLocalEnv } from "../utils/is-local-env";
 
-export const WORKSPACE_NAME = "Starter Kit Demo Workspace";
-export const APPLICATION_NAME = "Starter Kit App";
-export const NETWORK_NAME = "Starter Kit Network";
-export const NODE_NAME = "Starter Kit Node";
-export const NODE_NAME_2_WITH_PK = "Starter Kit Node 2 (with activated PK)";
-export const NODE_NAME_3_WITHOUT_PK = "Starter Kit Node 3 (without activated PK)";
+export const WORKSPACE_NAME = "SDK Demo App Workspace";
+export const APPLICATION_NAME = "SDK Demo App";
+export const NETWORK_NAME = "SDK Demo Network";
+export const NODE_NAME = "SDK Demo Node";
+export const NODE_NAME_2_WITH_PK = "SDK Demo Node 2 (with activated PK)";
+export const NODE_NAME_3_WITHOUT_PK = "SDK Demo Node 3 (without activated PK)";
 
-export const PRIVATE_KEY_NAME = "Starter Kit Private Key";
-export const PRIVATE_KEY_2_NAME = "Starter Kit Private Key 2";
-export const PRIVATE_KEY_SMART_CONTRACTS_NAME = "Starter Kit Private Key Smart Contracts";
-export const MINIO_NAME = "Starter Kit MinIO";
-export const IPFS_NAME = "Starter Kit IPFS";
-export const GRAPH_NAME = "Starter Kit Graph";
-export const PORTAL_NAME = "Starter Kit Portal";
-export const HASURA_NAME = "Starter Kit Hasura";
-export const BLOCKSCOUT_NAME = "Starter Kit Blockscout";
-export const AAT_NAME = "Starter Kit Application Access Token";
+export const PRIVATE_KEY_NAME = "SDK Demo Private Key";
+export const PRIVATE_KEY_2_NAME = "SDK Demo Private Key 2";
+export const PRIVATE_KEY_SMART_CONTRACTS_NAME = "SDK Demo Private Key Smart Contracts";
+export const MINIO_NAME = "SDK Demo MinIO";
+export const IPFS_NAME = "SDK Demo IPFS";
+export const GRAPH_NAME = "SDK Demo Graph";
+export const PORTAL_NAME = "SDK Demo Portal";
+export const HASURA_NAME = "SDK Demo Hasura";
+export const BLOCKSCOUT_NAME = "SDK Demo Blockscout";
+export const AAT_NAME = "SDK Demo Application Access Token";
 
 export const CLUSTER_PROVIDER = isLocalEnv() ? "local" : "gke";
 export const CLUSTER_REGION = isLocalEnv() ? "orbstack" : "europe";
diff --git a/test/create-new-project.e2e.test.ts b/test/create-new-project.e2e.test.ts
index edbf6ea9d..81ea9ee58 100644
--- a/test/create-new-project.e2e.test.ts
+++ b/test/create-new-project.e2e.test.ts
@@ -16,6 +16,7 @@ import { forceExitAllCommands, runCommand } from "./utils/run-command";
 
 const PROJECT_NAME = "kit-demo";
 const TEMPLATE_NAME = "asset-tokenization";
+const TEMPLATE_VERSION = "0.1.15-main";
 const SUBGRAPH_NAMES = ["kit", "asset-tokenization"];
 
 const COMMAND_TEST_SCOPE = __filename;
@@ -50,10 +51,10 @@ afterEach(() => {
 describe("Setup a project using the SDK", () => {
   let contractsDeploymentInfo: Record<string, string>;
 
-  test("Create a starter kit project", async () => {
+  test(`Create a ${TEMPLATE_NAME} project`, async () => {
     const { output } = await runCommand(
       COMMAND_TEST_SCOPE,
-      ["create", "--project-name", PROJECT_NAME, "--template", TEMPLATE_NAME],
+      ["create", "--project-name", PROJECT_NAME, "--template", TEMPLATE_NAME, "--version", TEMPLATE_VERSION],
       {
         cwd: __dirname,
       },
@@ -116,7 +117,7 @@ describe("Setup a project using the SDK", () => {
     await $`rm -rf node_modules/next`.env(env);
   });
 
-  test("Connect starter kit", async () => {
+  test("Connect to platform", async () => {
     const { output } = await runCommand(COMMAND_TEST_SCOPE, ["connect", "--accept-defaults"], { cwd: projectDir })
       .result;
     expect(output).toInclude("Connected to SettleMint");
@@ -224,7 +225,7 @@ describe("Setup a project using the SDK", () => {
     }
   });
 
-  test("dApp - Codegen starter kit", async () => {
+  test("dApp - Codegen", async () => {
     const { output } = await runCommand(
       COMMAND_TEST_SCOPE,
       ["codegen", "--thegraph-subgraph-names", ...SUBGRAPH_NAMES],
@@ -242,7 +243,7 @@ describe("Setup a project using the SDK", () => {
     expect(output).toInclude("Codegen complete");
   });
 
-  test("Build starter kit", async () => {
+  test("Build app", async () => {
     const env = { ...process.env, NODE_ENV: "production" };
     try {
       await $`bun lint`.cwd(projectDir).env(env);
diff --git a/test/platform-list-services.e2e.test.ts b/test/platform-list-services.e2e.test.ts
index 9751f5f52..10bd7bf86 100644
--- a/test/platform-list-services.e2e.test.ts
+++ b/test/platform-list-services.e2e.test.ts
@@ -88,7 +88,7 @@ describe("Test platform list services command", () => {
     const blockchainNetwork = await settlemint.blockchainNetwork.read(env.SETTLEMINT_BLOCKCHAIN_NETWORK!);
     expect(json.services[0]).toMatchObject({
       group: "Blockchain networks",
-      name: "Starter Kit Network",
+      name: "SDK Demo Network",
       uniqueName: env.SETTLEMINT_BLOCKCHAIN_NETWORK,
       status: "Completed",
       healthStatus: "NOT BFT",
@@ -138,7 +138,7 @@ describe("Test platform list services command", () => {
     const blockchainNetwork = await settlemint.blockchainNetwork.read(env.SETTLEMINT_BLOCKCHAIN_NETWORK!);
     expect(parsedYaml.services[0]).toMatchObject({
       group: "Blockchain networks",
-      name: "Starter Kit Network",
+      name: "SDK Demo Network",
       uniqueName: env.SETTLEMINT_BLOCKCHAIN_NETWORK,
       status: "Completed",
       healthStatus: "NOT BFT",


Reviews:
user: login: sourcery-ai[bot]
body: Hey @janb87 - I've reviewed your changes - here's some feedback:

**Overall Comments**:
- Consider adding validation to ensure the specified version exists in the npm registry.
- The destructuring of arguments in `downloadAndExtractNpmPackage` is a good pattern for optional parameters.

<details>
<summary>Here's what I looked at during the review</summary>

- 🟡 **General issues**: 1 issue found
- 🟢 **Security**: all looks good
- 🟢 **Review instructions**: all looks good
- 🟡 **Testing**: 1 issue found
- 🟢 **Complexity**: all looks good
- 🟢 **Documentation**: all looks good
</details>

***

<details>
<summary>Sourcery is free for open source - if you like our reviews please consider sharing them ✨</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https://sourcery.ai)
- [Facebook](https://www.facebook.com/sharer/sharer.php?u=https://sourcery.ai)

</details>

<sub>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
</sub>
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T16:45:35Z
user: login: roderik
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-07T16:58:55Z

Review comments:
diff_hunk: @@ -31,11 +31,16 @@ export function createCommand(): Command {
         "-t, --template <template>",
         "The template for your SettleMint project (run `settlemint platform config` to see available templates)",
       )
+      .option("-v, --version <version>", "Specify the template version to use (defaults to latest stable version)")
       .option("-i, --instance <instance>", "The instance to connect to")
-      .action(async ({ projectName, template, instance }) => {
+      .action(async ({ projectName, template, version, instance }) => {
         intro("Creating a new SettleMint project");
         const env: Partial<DotEnv> = await loadEnv(false, false);
 
+        if (version && !template) {
+          cancel("The --version option requires the --template option to be set");
path: sdk/cli/src/commands/create.ts
user: login: sourcery-ai[bot]
body: **suggestion:** Validate the dependency between '--version' and '--template' early.

The new check ensures that '--version' isn't used without '--template'. This is clear and proactive. As a possible improvement, consider also validating that the provided version string conforms to expected version formats (if applicable) to help catch user input errors earlier.

Suggested implementation:

```typescript
import { loadEnv, instancePrompt, createSettleMintClient } from '../utils';
import semver from 'semver';

```

```typescript
        if (version && !template) {
          cancel("The --version option requires the --template option to be set");
        }
        if (version && template && !semver.valid(version)) {
          cancel(`Invalid version format: ${version}. Please use a valid semantic version (e.g., 1.0.0)`);
        }

```

Ensure that the semver package is installed in your project (e.g., run "npm install semver" or "yarn add semver") if it isn’t already.
created_at: 2025-03-07T16:45:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -53,7 +54,7 @@ describe("Setup a project using the SDK", () => {
   test("Create a starter kit project", async () => {
     const { output } = await runCommand(
       COMMAND_TEST_SCOPE,
-      ["create", "--project-name", PROJECT_NAME, "--template", TEMPLATE_NAME],
+      ["create", "--project-name", PROJECT_NAME, "--template", TEMPLATE_NAME, "--version", TEMPLATE_VERSION],
path: test/create-new-project.e2e.test.ts
user: login: sourcery-ai[bot]
body: **issue (testing):** Missing test for default version usage.

There's a new option to specify the template version. It's important to also test the default behavior (no version specified) to ensure it still works as expected and defaults to the latest version.
created_at: 2025-03-07T16:45:35Z
author_association: CONTRIBUTOR

PR comments:
user: login: sourcery-ai[bot]
created_at: 2025-03-07T16:44:41Z
body: <!-- Generated by sourcery-ai[bot]: start review_guide -->

## Reviewer's Guide by Sourcery

This pull request introduces the ability to specify a template version when creating a new project using the `create` command. It also adds npm package information to the platform config command.

_No diagrams generated as the changes look simple and do not need a visual representation._

### File-Level Changes

| Change | Details | Files |
| ------ | ------- | ----- |
| Added the ability to specify a template version when creating a new project. | <ul><li>Added a `--version` option to the `create` command.</li><li>Modified the `downloadAndExtractNpmPackage` function to accept a version parameter.</li><li>Updated the `create` command action to pass the version to the `downloadAndExtractNpmPackage` function.</li><li>Added validation to ensure that the `--version` option is only used with the `--template` option.</li></ul> | `sdk/cli/src/utils/download-extract.ts`<br/>`sdk/cli/src/commands/create.ts`<br/>`test/create-new-project.e2e.test.ts` |
| Added npm package information to the platform config command. | <ul><li>Added the `npmPackage` field to the output of the `platform config` command.</li></ul> | `sdk/cli/src/commands/platform/config.ts` |

---

<details>
<summary>Tips and commands</summary>

#### Interacting with Sourcery

- **Trigger a new review:** Comment `@sourcery-ai review` on the pull request.
- **Continue discussions:** Reply directly to Sourcery's review comments.
- **Generate a GitHub issue from a review comment:** Ask Sourcery to create an
  issue from a review comment by replying to it. You can also reply to a
  review comment with `@sourcery-ai issue` to create an issue from it.
- **Generate a pull request title:** Write `@sourcery-ai` anywhere in the pull
  request title to generate a title at any time. You can also comment
  `@sourcery-ai title` on the pull request to (re-)generate the title at any time.
- **Generate a pull request summary:** Write `@sourcery-ai summary` anywhere in
  the pull request body to generate a PR summary at any time exactly where you
  want it. You can also comment `@sourcery-ai summary` on the pull request to
  (re-)generate the summary at any time.
- **Generate reviewer's guide:** Comment `@sourcery-ai guide` on the pull
  request to (re-)generate the reviewer's guide at any time.
- **Resolve all Sourcery comments:** Comment `@sourcery-ai resolve` on the
  pull request to resolve all Sourcery comments. Useful if you've already
  addressed all the comments and don't want to see them anymore.
- **Dismiss all Sourcery reviews:** Comment `@sourcery-ai dismiss` on the pull
  request to dismiss all existing Sourcery reviews. Especially useful if you
  want to start fresh with a new review - don't forget to comment
  `@sourcery-ai review` to trigger a new review!
- **Generate a plan of action for an issue:** Comment `@sourcery-ai plan` on
  an issue to generate a plan of action for it.

#### Customizing Your Experience

Access your [dashboard](https://app.sourcery.ai) to:
- Enable or disable review features such as the Sourcery-generated pull request
  summary, the reviewer's guide, and others.
- Change the review language.
- Add, remove or edit custom review instructions.
- Adjust other review settings.

#### Getting Help

- [Contact our support team](mailto:support@sourcery.ai) for questions or feedback.
- Visit our [documentation](https://docs.sourcery.ai) for detailed guides and information.
- Keep in touch with the Sourcery team by following us on [X/Twitter](https://x.com/SourceryAI), [LinkedIn](https://www.linkedin.com/company/sourcery-ai/) or [GitHub](https://github.com/sourcery-ai).

</details>

<!-- Generated by sourcery-ai[bot]: end review_guide -->
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-07T16:46:23Z
body: # 📦 Packages
| Package | NPM | Docker |
| ------- | -------------------- | -------------------- |
| SDK Cli | `@settlemint/sdk-cli@1.1.15-prfbd7e3e2` |  |
| SDK The Graph | `@settlemint/sdk-thegraph@1.1.15-prfbd7e3e2` |  |
| SDK Portal | `@settlemint/sdk-portal@1.1.15-prfbd7e3e2` |  |
| SDK Hasura | `@settlemint/sdk-hasura@1.1.15-prfbd7e3e2` |  |
| SDK JS | `@settlemint/sdk-js@1.1.15-prfbd7e3e2` |  |
| SDK Utils | `@settlemint/sdk-utils@1.1.15-prfbd7e3e2` | |
| SDK Next | `@settlemint/sdk-next@1.1.15-prfbd7e3e2` | |
| SDK Minio | `@settlemint/sdk-minio@1.1.15-prfbd7e3e2` | |
| SDK IPFS | `@settlemint/sdk-ipfs@1.1.15-prfbd7e3e2` | |
| SDK Blockscout | `@settlemint/sdk-blockscout@1.1.15-prfbd7e3e2` | |

[^uniqueIdentifier]: Build, test and publish
author_association: NONE