@semaphore-protocol/cli 4.0.0-beta.1 → 4.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -46,23 +46,25 @@
46
46
  </h4>
47
47
  </div>
48
48
 
49
- | Setting up a project, although not particularly complex, can be a lengthy process for some people. The Semaphore CLI reduces the set-up time from a few minutes to a few seconds. In addition, it can also be used to obtain on-chain group data. |
50
- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
49
+ | Semaphore CLI simplifies the process of setting up Semaphore projects and retrieving on-chain group data, reducing setup time from minutes to seconds. |
50
+ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
51
51
 
52
52
  ## 🛠 Install
53
53
 
54
- Install the `@semaphore-protocol/cli` package globally:
54
+ To install Semaphore CLI globally:
55
55
 
56
56
  ```bash
57
57
  npm i -g @semaphore-protocol/cli
58
58
  ```
59
59
 
60
- or run specific commands with `npx`:
60
+ Alternatively, you can use `npx` to run commands without installing the package globally:
61
61
 
62
62
  ```bash
63
63
  npx @semaphore-protocol/cli create my-app
64
64
  ```
65
65
 
66
+ This command sets up a new project in the `my-app` directory using the `monorepo-ethers` template.
67
+
66
68
  ## 📜 Usage
67
69
 
68
70
  ```
@@ -82,3 +84,15 @@ Commands:
82
84
  get-proofs [options] [group-id] Get the proofs of a group from a supported network (e.g. sepolia or arbitrum).
83
85
  help [command] Display help for a specific command.
84
86
  ```
87
+
88
+ ## 🌐 Supported Networks
89
+
90
+ Semaphore CLI supports multiple Ethereum networks. Use the `get-groups` command to interact with groups on networks like Sepolia or Arbitrum.
91
+
92
+ ## 📦 Supported Templates
93
+
94
+ When creating a new project, you can choose from several templates designed to integrate seamlessly with Semaphore's privacy protocols:
95
+
96
+ - **monorepo-ethers**: Hardhat + Next.js + SemaphoreEthers
97
+ - **monorepo-subgraph**: Hardhat + Next.js + SemaphoreSubgraph
98
+ - **contracts-hardhat**: Hardhat only, focused on smart contract development.
package/dist/index.js CHANGED
@@ -2,14 +2,15 @@
2
2
 
3
3
  /**
4
4
  * @module @semaphore-protocol/cli
5
- * @version 4.0.0-beta.1
5
+ * @version 4.0.0-beta.10
6
6
  * @file A command line tool to set up your Semaphore project and get group data.
7
7
  * @copyright Ethereum Foundation 2024
8
8
  * @license MIT
9
9
  * @see [Github]{@link https://github.com/semaphore-protocol/semaphore/tree/main/packages/cli}
10
10
  */
11
11
 
12
- import { SemaphoreSubgraph, SemaphoreEthers, getSupportedNetworks } from '@semaphore-protocol/data';
12
+ import { SemaphoreSubgraph, SemaphoreEthers } from '@semaphore-protocol/data';
13
+ import { isSupportedNetwork, supportedNetworks } from '@semaphore-protocol/utils/networks';
13
14
  import chalk from 'chalk';
14
15
  import { program } from 'commander';
15
16
  import decompress from 'decompress';
@@ -28,10 +29,10 @@ import inquirer from 'inquirer';
28
29
 
29
30
  const cliRegistryURL = "https://registry.npmjs.org/-/package/@semaphore-protocol/cli/dist-tags";
30
31
  /**
31
- * Checks the registry directly via the API, if that fails, tries the slower `npm view [package] version` command.
32
- * This is important for users in environments where direct access to npm is blocked by a firewall, and packages are
33
- * provided exclusively via a private registry.
34
- * @param currentVersion The current version of the CLI.
32
+ * Checks for the latest version of the CLI tool against the registry. It first attempts to fetch the version directly
33
+ * via an API call. If this fails, possibly due to network restrictions, it falls back to using the `npm view` command.
34
+ * This method ensures that users behind a firewall or using a private registry can still check for updates.
35
+ * @param currentVersion The current version of the CLI being used.
35
36
  */
36
37
  async function checkLatestVersion(currentVersion) {
37
38
  let latestVersion;
@@ -54,6 +55,11 @@ async function checkLatestVersion(currentVersion) {
54
55
  }
55
56
  }
56
57
 
58
+ /**
59
+ * A utility class for managing a CLI spinner. This class encapsulates the functionality of the `ora` spinner,
60
+ * providing methods to start and stop the spinner. It is used to give visual feedback to the user during operations
61
+ * that have a noticeable delay, such as network requests.
62
+ */
57
63
  class Spinner {
58
64
  constructor(text) {
59
65
  this.ora = ora({
@@ -71,8 +77,11 @@ class Spinner {
71
77
  }
72
78
 
73
79
  /**
74
- * Gets all group ids on the specified network
75
- * @param network The specified network
80
+ * Retrieves all group IDs from the Semaphore protocol on a specified network. This function first attempts to
81
+ * fetch the group IDs using the SemaphoreSubgraph interface. If that fails, it tries the SemaphoreEthers interface
82
+ * as a fallback. This dual-method approach ensures higher reliability in fetching data across different network conditions.
83
+ * @param network The blockchain network from which to fetch the group IDs.
84
+ * @returns A promise that resolves to an array of group IDs or null if an error occurs.
76
85
  */
77
86
  async function getGroupIds(network) {
78
87
  let groupIds;
@@ -98,6 +107,10 @@ async function getGroupIds(network) {
98
107
  return groupIds;
99
108
  }
100
109
 
110
+ /**
111
+ * Prompts the user to input the name of their project. Provides a default name of "my-app".
112
+ * @returns A promise that resolves to the user's input for the project name.
113
+ */
101
114
  async function getProjectName() {
102
115
  const { projectName } = await inquirer.prompt({
103
116
  name: "projectName",
@@ -107,6 +120,12 @@ async function getProjectName() {
107
120
  });
108
121
  return projectName;
109
122
  }
123
+ /**
124
+ * Prompts the user to select a template from a list of supported templates. Each template is presented
125
+ * with its value and name for better clarity.
126
+ * @param supportedTemplates An array of objects, each containing a 'value' and 'name' property for the template.
127
+ * @returns A promise that resolves to the selected template's value.
128
+ */
110
129
  async function getSupportedTemplate(supportedTemplates) {
111
130
  const { selectedTemplate } = await inquirer.prompt({
112
131
  name: "selectedTemplate",
@@ -120,6 +139,11 @@ async function getSupportedTemplate(supportedTemplates) {
120
139
  });
121
140
  return selectedTemplate;
122
141
  }
142
+ /**
143
+ * Prompts the user to select a network from a list of supported networks.
144
+ * @param supportedNetworks An array of strings representing the supported networks.
145
+ * @returns A promise that resolves to the selected network.
146
+ */
123
147
  async function getSupportedNetwork(supportedNetworks) {
124
148
  const { selectedNetwork } = await inquirer.prompt({
125
149
  name: "selectedNetwork",
@@ -130,6 +154,11 @@ async function getSupportedNetwork(supportedNetworks) {
130
154
  });
131
155
  return selectedNetwork;
132
156
  }
157
+ /**
158
+ * Prompts the user to select a group ID from a list of existing group IDs.
159
+ * @param groupIds An array of strings representing the group IDs.
160
+ * @returns A promise that resolves to the selected group ID.
161
+ */
133
162
  async function getGroupId(groupIds) {
134
163
  const { selectedGroupId } = await inquirer.prompt({
135
164
  name: "selectedGroupId",
@@ -141,9 +170,10 @@ async function getGroupId(groupIds) {
141
170
  return selectedGroupId;
142
171
  }
143
172
 
173
+ // Define the path to the package.json file to extract metadata for the CLI.
144
174
  const packagePath = `${dirname(fileURLToPath(import.meta.url))}/..`;
145
175
  const { description, version } = JSON.parse(readFileSync(`${packagePath}/package.json`, "utf8"));
146
- const supportedNetworks = getSupportedNetworks();
176
+ // List of supported templates for project creation.
147
177
  const supportedTemplates = [
148
178
  {
149
179
  value: "monorepo-ethers",
@@ -158,6 +188,7 @@ const supportedTemplates = [
158
188
  name: "Hardhat"
159
189
  }
160
190
  ];
191
+ // Setup the CLI program with basic information and help text.
161
192
  program
162
193
  .name("semaphore")
163
194
  .description(description)
@@ -171,6 +202,7 @@ program
171
202
  console.info(`\n ${logSymbols.error}`, message);
172
203
  }
173
204
  });
205
+ // Define the 'create' command to scaffold new Semaphore projects.
174
206
  program
175
207
  .command("create")
176
208
  .description("Create a Semaphore project with a supported template.")
@@ -196,15 +228,20 @@ program
196
228
  }
197
229
  spinner.start();
198
230
  await checkLatestVersion(version);
231
+ // Extract the template package into the project directory.
199
232
  await pacote.extract(`@semaphore-protocol/cli-template-${template}@${version}`, `${currentDirectory}/${projectDirectory}`);
233
+ // Decompress the template files after extraction.
200
234
  await decompress(`${currentDirectory}/${projectDirectory}/files.tgz`, `${currentDirectory}/${projectDirectory}`);
235
+ // Clean up the compressed file after extraction.
201
236
  unlinkSync(`${currentDirectory}/${projectDirectory}/files.tgz`);
237
+ // Copy the example environment file to the actual environment file.
202
238
  copyFileSync(`${currentDirectory}/${projectDirectory}/.env.example`, `${currentDirectory}/${projectDirectory}/.env`);
203
239
  spinner.stop();
204
240
  console.info(`\n ${logSymbols.success}`, `Your project is ready!\n`);
205
241
  console.info(` Please, install your dependencies by running:\n`);
206
242
  console.info(` ${chalk.cyan("cd")} ${projectDirectory}`);
207
243
  console.info(` ${chalk.cyan("yarn install")}\n`);
244
+ // Read the package.json to list available npm scripts.
208
245
  const { scripts } = JSON.parse(readFileSync(`${currentDirectory}/${projectDirectory}/package.json`, "utf8"));
209
246
  if (scripts) {
210
247
  console.info(` Available scripts:\n`);
@@ -221,9 +258,9 @@ program
221
258
  .allowExcessArguments(false)
222
259
  .action(async ({ network }) => {
223
260
  if (!network) {
224
- network = await getSupportedNetwork(supportedNetworks);
261
+ network = await getSupportedNetwork(Object.keys(supportedNetworks));
225
262
  }
226
- if (!supportedNetworks.includes(network)) {
263
+ if (!isSupportedNetwork(network)) {
227
264
  console.info(`\n ${logSymbols.error}`, `error: the network '${network}' is not supported\n`);
228
265
  return;
229
266
  }
@@ -245,9 +282,9 @@ program
245
282
  .allowExcessArguments(false)
246
283
  .action(async (groupId, { network }) => {
247
284
  if (!network) {
248
- network = await getSupportedNetwork(supportedNetworks);
285
+ network = await getSupportedNetwork(Object.keys(supportedNetworks));
249
286
  }
250
- if (!supportedNetworks.includes(network)) {
287
+ if (!isSupportedNetwork(network)) {
251
288
  console.info(`\n ${logSymbols.error}`, `error: the network '${network}' is not supported\n`);
252
289
  return;
253
290
  }
@@ -299,9 +336,9 @@ program
299
336
  .allowExcessArguments(false)
300
337
  .action(async (groupId, { network }) => {
301
338
  if (!network) {
302
- network = await getSupportedNetwork(supportedNetworks);
339
+ network = await getSupportedNetwork(Object.keys(supportedNetworks));
303
340
  }
304
- if (!supportedNetworks.includes(network)) {
341
+ if (!isSupportedNetwork(network)) {
305
342
  console.info(`\n ${logSymbols.error}`, `error: the network '${network}' is not supported\n`);
306
343
  return;
307
344
  }
@@ -351,9 +388,9 @@ program
351
388
  .allowExcessArguments(false)
352
389
  .action(async (groupId, { network }) => {
353
390
  if (!network) {
354
- network = await getSupportedNetwork(supportedNetworks);
391
+ network = await getSupportedNetwork(Object.keys(supportedNetworks));
355
392
  }
356
- if (!supportedNetworks.includes(network)) {
393
+ if (!isSupportedNetwork(network)) {
357
394
  console.info(`\n ${logSymbols.error}`, `error: the network '${network}' is not supported\n`);
358
395
  return;
359
396
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@semaphore-protocol/cli",
3
3
  "type": "module",
4
- "version": "4.0.0-beta.1",
4
+ "version": "4.0.0-beta.10",
5
5
  "description": "A command line tool to set up your Semaphore project and get group data.",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -31,15 +31,18 @@
31
31
  "access": "public"
32
32
  },
33
33
  "devDependencies": {
34
+ "@rollup/plugin-typescript": "^11.1.6",
34
35
  "@types/figlet": "^1.5.8",
35
36
  "@types/inquirer": "^9.0.7",
36
37
  "@types/pacote": "^11.1.8",
37
38
  "@types/semver": "^7.5.8",
38
- "rollup-plugin-cleanup": "^3.2.1",
39
- "rollup-plugin-typescript2": "^0.36.0"
39
+ "rimraf": "^5.0.5",
40
+ "rollup": "^4.12.0",
41
+ "rollup-plugin-cleanup": "^3.2.1"
40
42
  },
41
43
  "dependencies": {
42
- "@semaphore-protocol/data": "4.0.0-beta.1",
44
+ "@semaphore-protocol/data": "4.0.0-beta.10",
45
+ "@semaphore-protocol/utils": "4.0.0-beta.10",
43
46
  "axios": "^1.6.7",
44
47
  "boxen": "^7.1.1",
45
48
  "chalk": "^5.3.0",
@@ -7,10 +7,10 @@ import { lt as semverLt } from "semver"
7
7
  const cliRegistryURL = "https://registry.npmjs.org/-/package/@semaphore-protocol/cli/dist-tags"
8
8
 
9
9
  /**
10
- * Checks the registry directly via the API, if that fails, tries the slower `npm view [package] version` command.
11
- * This is important for users in environments where direct access to npm is blocked by a firewall, and packages are
12
- * provided exclusively via a private registry.
13
- * @param currentVersion The current version of the CLI.
10
+ * Checks for the latest version of the CLI tool against the registry. It first attempts to fetch the version directly
11
+ * via an API call. If this fails, possibly due to network restrictions, it falls back to using the `npm view` command.
12
+ * This method ensures that users behind a firewall or using a private registry can still check for updates.
13
+ * @param currentVersion The current version of the CLI being used.
14
14
  */
15
15
  export default async function checkLatestVersion(currentVersion: string) {
16
16
  let latestVersion: string
@@ -1,12 +1,16 @@
1
1
  import { SemaphoreSubgraph, SemaphoreEthers } from "@semaphore-protocol/data"
2
+ import { SupportedNetwork } from "@semaphore-protocol/utils"
2
3
  import logSymbols from "log-symbols"
3
4
  import Spinner from "./spinner.js"
4
5
 
5
6
  /**
6
- * Gets all group ids on the specified network
7
- * @param network The specified network
7
+ * Retrieves all group IDs from the Semaphore protocol on a specified network. This function first attempts to
8
+ * fetch the group IDs using the SemaphoreSubgraph interface. If that fails, it tries the SemaphoreEthers interface
9
+ * as a fallback. This dual-method approach ensures higher reliability in fetching data across different network conditions.
10
+ * @param network The blockchain network from which to fetch the group IDs.
11
+ * @returns A promise that resolves to an array of group IDs or null if an error occurs.
8
12
  */
9
- export default async function getGroupIds(network): Promise<string[]> {
13
+ export default async function getGroupIds(network: SupportedNetwork): Promise<string[]> {
10
14
  let groupIds: string[]
11
15
 
12
16
  const spinner = new Spinner("Fetching groups")
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { GroupResponse, SemaphoreEthers, SemaphoreSubgraph, getSupportedNetworks } from "@semaphore-protocol/data"
1
+ import { GroupResponse, SemaphoreEthers, SemaphoreSubgraph } from "@semaphore-protocol/data"
2
+ import { isSupportedNetwork, supportedNetworks } from "@semaphore-protocol/utils/networks"
2
3
  import chalk from "chalk"
3
4
  import { program } from "commander"
4
5
  import decompress from "decompress"
@@ -13,11 +14,11 @@ import getGroupIds from "./getGroupIds.js"
13
14
  import { getGroupId, getProjectName, getSupportedNetwork, getSupportedTemplate } from "./inquirerPrompts.js"
14
15
  import Spinner from "./spinner.js"
15
16
 
17
+ // Define the path to the package.json file to extract metadata for the CLI.
16
18
  const packagePath = `${dirname(fileURLToPath(import.meta.url))}/..`
17
19
  const { description, version } = JSON.parse(readFileSync(`${packagePath}/package.json`, "utf8"))
18
20
 
19
- const supportedNetworks = getSupportedNetworks()
20
-
21
+ // List of supported templates for project creation.
21
22
  const supportedTemplates = [
22
23
  {
23
24
  value: "monorepo-ethers",
@@ -33,6 +34,7 @@ const supportedTemplates = [
33
34
  }
34
35
  ]
35
36
 
37
+ // Setup the CLI program with basic information and help text.
36
38
  program
37
39
  .name("semaphore")
38
40
  .description(description)
@@ -47,6 +49,7 @@ program
47
49
  }
48
50
  })
49
51
 
52
+ // Define the 'create' command to scaffold new Semaphore projects.
50
53
  program
51
54
  .command("create")
52
55
  .description("Create a Semaphore project with a supported template.")
@@ -79,15 +82,19 @@ program
79
82
 
80
83
  await checkLatestVersion(version)
81
84
 
85
+ // Extract the template package into the project directory.
82
86
  await pacote.extract(
83
87
  `@semaphore-protocol/cli-template-${template}@${version}`,
84
88
  `${currentDirectory}/${projectDirectory}`
85
89
  )
86
90
 
91
+ // Decompress the template files after extraction.
87
92
  await decompress(`${currentDirectory}/${projectDirectory}/files.tgz`, `${currentDirectory}/${projectDirectory}`)
88
93
 
94
+ // Clean up the compressed file after extraction.
89
95
  unlinkSync(`${currentDirectory}/${projectDirectory}/files.tgz`)
90
96
 
97
+ // Copy the example environment file to the actual environment file.
91
98
  copyFileSync(
92
99
  `${currentDirectory}/${projectDirectory}/.env.example`,
93
100
  `${currentDirectory}/${projectDirectory}/.env`
@@ -100,6 +107,7 @@ program
100
107
  console.info(` ${chalk.cyan("cd")} ${projectDirectory}`)
101
108
  console.info(` ${chalk.cyan("yarn install")}\n`)
102
109
 
110
+ // Read the package.json to list available npm scripts.
103
111
  const { scripts } = JSON.parse(readFileSync(`${currentDirectory}/${projectDirectory}/package.json`, "utf8"))
104
112
 
105
113
  if (scripts) {
@@ -122,10 +130,10 @@ program
122
130
  .allowExcessArguments(false)
123
131
  .action(async ({ network }) => {
124
132
  if (!network) {
125
- network = await getSupportedNetwork(supportedNetworks)
133
+ network = await getSupportedNetwork(Object.keys(supportedNetworks))
126
134
  }
127
135
 
128
- if (!supportedNetworks.includes(network)) {
136
+ if (!isSupportedNetwork(network)) {
129
137
  console.info(`\n ${logSymbols.error}`, `error: the network '${network}' is not supported\n`)
130
138
  return
131
139
  }
@@ -152,10 +160,10 @@ program
152
160
  .allowExcessArguments(false)
153
161
  .action(async (groupId, { network }) => {
154
162
  if (!network) {
155
- network = await getSupportedNetwork(supportedNetworks)
163
+ network = await getSupportedNetwork(Object.keys(supportedNetworks))
156
164
  }
157
165
 
158
- if (!supportedNetworks.includes(network)) {
166
+ if (!isSupportedNetwork(network)) {
159
167
  console.info(`\n ${logSymbols.error}`, `error: the network '${network}' is not supported\n`)
160
168
  return
161
169
  }
@@ -220,10 +228,10 @@ program
220
228
  .allowExcessArguments(false)
221
229
  .action(async (groupId, { network }) => {
222
230
  if (!network) {
223
- network = await getSupportedNetwork(supportedNetworks)
231
+ network = await getSupportedNetwork(Object.keys(supportedNetworks))
224
232
  }
225
233
 
226
- if (!supportedNetworks.includes(network)) {
234
+ if (!isSupportedNetwork(network)) {
227
235
  console.info(`\n ${logSymbols.error}`, `error: the network '${network}' is not supported\n`)
228
236
  return
229
237
  }
@@ -286,10 +294,10 @@ program
286
294
  .allowExcessArguments(false)
287
295
  .action(async (groupId, { network }) => {
288
296
  if (!network) {
289
- network = await getSupportedNetwork(supportedNetworks)
297
+ network = await getSupportedNetwork(Object.keys(supportedNetworks))
290
298
  }
291
299
 
292
- if (!supportedNetworks.includes(network)) {
300
+ if (!isSupportedNetwork(network)) {
293
301
  console.info(`\n ${logSymbols.error}`, `error: the network '${network}' is not supported\n`)
294
302
  return
295
303
  }
@@ -1,5 +1,9 @@
1
1
  import inquirer from "inquirer"
2
2
 
3
+ /**
4
+ * Prompts the user to input the name of their project. Provides a default name of "my-app".
5
+ * @returns A promise that resolves to the user's input for the project name.
6
+ */
3
7
  export async function getProjectName() {
4
8
  const { projectName } = await inquirer.prompt({
5
9
  name: "projectName",
@@ -10,6 +14,12 @@ export async function getProjectName() {
10
14
  return projectName
11
15
  }
12
16
 
17
+ /**
18
+ * Prompts the user to select a template from a list of supported templates. Each template is presented
19
+ * with its value and name for better clarity.
20
+ * @param supportedTemplates An array of objects, each containing a 'value' and 'name' property for the template.
21
+ * @returns A promise that resolves to the selected template's value.
22
+ */
13
23
  export async function getSupportedTemplate(supportedTemplates: { value: string; name: string }[]) {
14
24
  const { selectedTemplate } = await inquirer.prompt({
15
25
  name: "selectedTemplate",
@@ -24,6 +34,11 @@ export async function getSupportedTemplate(supportedTemplates: { value: string;
24
34
  return selectedTemplate
25
35
  }
26
36
 
37
+ /**
38
+ * Prompts the user to select a network from a list of supported networks.
39
+ * @param supportedNetworks An array of strings representing the supported networks.
40
+ * @returns A promise that resolves to the selected network.
41
+ */
27
42
  export async function getSupportedNetwork(supportedNetworks: string[]) {
28
43
  const { selectedNetwork } = await inquirer.prompt({
29
44
  name: "selectedNetwork",
@@ -35,6 +50,11 @@ export async function getSupportedNetwork(supportedNetworks: string[]) {
35
50
  return selectedNetwork
36
51
  }
37
52
 
53
+ /**
54
+ * Prompts the user to select a group ID from a list of existing group IDs.
55
+ * @param groupIds An array of strings representing the group IDs.
56
+ * @returns A promise that resolves to the selected group ID.
57
+ */
38
58
  export async function getGroupId(groupIds: string[]) {
39
59
  const { selectedGroupId } = await inquirer.prompt({
40
60
  name: "selectedGroupId",
package/src/spinner.ts CHANGED
@@ -1,5 +1,10 @@
1
1
  import ora, { Ora } from "ora"
2
2
 
3
+ /**
4
+ * A utility class for managing a CLI spinner. This class encapsulates the functionality of the `ora` spinner,
5
+ * providing methods to start and stop the spinner. It is used to give visual feedback to the user during operations
6
+ * that have a noticeable delay, such as network requests.
7
+ */
3
8
  export default class Spinner {
4
9
  private ora: Ora
5
10