Skip to content

Commit

Permalink
feat(cli): generate batch of artifacts (#113)
Browse files Browse the repository at this point in the history
* feat: override arguments of circuits in generate command

* refactor: do no export generate

* changeset

* feat: generate batch of artifacts

* feat: improve logging

* changeset

* chore: add lint override

* chore: update snapshot

* chore: decreases coverage thresholds

* chore: format

* chore: update cli rollup config
  • Loading branch information
sripwoud authored Jul 8, 2024
1 parent 466c53b commit 0be85ca
Show file tree
Hide file tree
Showing 11 changed files with 125 additions and 24 deletions.
4 changes: 2 additions & 2 deletions .biome.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@
},
"overrides": [
{
"include": ["packages/cli/src/spinner.ts"],
"include": ["packages/cli/src/spinner.ts", "packages/cli/src/commands/generate/action.ts"],
"linter": { "rules": { "suspicious": { "noExplicitAny": "off" } } },
},
{
"include": ["packages/cli/src/commands/download/index.ts", "packages/cli/src/commands/generate/action.ts"],
"linter": { "rules": { "style": { "noParameterAssign": "off" } } },
},
{
"include": ["apps/web/src/main.tsx"],
"include": ["apps/web/src/main.tsx", "packages/cli/src/commands/generate/action.ts"],
"linter": { "rules": { "style": { "noNonNullAssertion": "off" } } },
},
],
Expand Down
5 changes: 5 additions & 0 deletions .changeset/orange-camels-listen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@zk-kit/artifacts-cli": minor
---

Generate batch of artifacts
5 changes: 5 additions & 0 deletions .changeset/stupid-beers-beg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@zk-kit/artifacts-cli": minor
---

Can override circuits parameters in generate command
2 changes: 1 addition & 1 deletion jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const config: JestConfigWithTsJest = {
collectCoverageFrom: ['src/**/*.ts', '!src/index*.ts'],
coverageDirectory: join(__dirname, '..', 'coverage'),
coverageThreshold: {
global: { branches: 40, functions: 60, lines: 80, statements: 80 },
global: { branches: 40, functions: 55, lines: 75, statements: 75 },
},
moduleDirectories: ['node_modules', '<rootDir>/node_modules', '<rootDir>/src'],
moduleFileExtensions: ['js', 'ts'],
Expand Down
10 changes: 9 additions & 1 deletion packages/cli/rollup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,15 @@ const config: RollupOptions = {
plugins: [
typescript({ tsconfig: './tsconfig.build.json' }),
],
external: [...Object.keys(pkg.dependencies), 'node:console', 'node:fs', 'node:path', 'node:process'],
external: [
...Object.keys(pkg.dependencies),
'node:console',
'node:fs',
'node:os',
'node:path',
'node:process',
'node:stream',
],
}

export default config
20 changes: 20 additions & 0 deletions packages/cli/src/commands/generate-batch/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { readFileSync } from 'node:fs'
import { exit } from 'node:process'
import { spinner } from '../../spinner.ts'
import { generateActionNoExit } from '../generate/action.ts'

export default async function generateBatch(optionsPath: string, destination: string) {
const options = JSON.parse(readFileSync(optionsPath, 'utf8')) as Record<
string,
{ circuit: string; paramsList: string[][] }
>

spinner.start()
for (const [config, { circuit, paramsList }] of Object.entries(options)) {
for (const params of paramsList)
await generateActionNoExit(circuit, params, { config, destination })
}

spinner.succeed(`All snark artifacts generated successfully in ${destination}`)
exit(0)
}
12 changes: 12 additions & 0 deletions packages/cli/src/commands/generate-batch/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Command } from '@commander-js/extra-typings'
import generateBatchAction from './action.ts'

export const generateBatch = new Command('generate-batch').alias('gb').description(
'Generate snark artifacts for a list of circom circuits',
)
.argument(
'<optionsPath>',
'Path to the options definition json file: { [circomkitJsonPath]: { circuit:string, params: string[][] }}',
)
.argument('<destination>', 'Destination directory for the generated artifacts')
.action(generateBatchAction)
72 changes: 61 additions & 11 deletions packages/cli/src/commands/generate/action.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,81 @@
import { Circomkit, type CircomkitConfig } from 'circomkit'
import { existsSync, readFileSync } from 'node:fs'
import { Circomkit, type CircomkitConfig, type CircuitConfig } from 'circomkit'
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { dirname } from 'node:path'
import { chdir, cwd, exit } from 'node:process'
import { Writable } from 'node:stream'
import { spinner } from '../../spinner.ts'
import { validateJsonFileInput, validateOrThrow } from '../../validators.ts'
import { getCircomkitConfigInput, getDestinationInput, selectCircuit } from './prompts.ts'

export async function setup(config: string, dirBuild: string) {
const circomkitConfig = JSON.parse(readFileSync(config, 'utf8')) as CircomkitConfig
class SilentStream extends Writable {
_write(_chunk: any, _encoding: string, callback: () => void) {
// Discard chunk
callback()
}
}

async function setup(circuit: string | undefined, params: string[] | undefined, config: string, dirBuild: string) {
// parse circomkit.json
let circomkitConfig = JSON.parse(readFileSync(config, 'utf8')) as CircomkitConfig
chdir(dirname(config))
const circuits = Object.keys(JSON.parse(readFileSync(circomkitConfig.circuits, 'utf8')))
const circuit = await selectCircuit(circuits)
const circomkit = new Circomkit({ ...JSON.parse(readFileSync(config, 'utf8')), dirBuild })

return circomkit.setup(circuit)
// parse circuits.json
const circuitsConfig = JSON.parse(readFileSync(circomkitConfig.circuits, 'utf8')) as Record<string, CircuitConfig>
circuit ??= await selectCircuit(Object.keys(circuitsConfig))
const { params: defaultParams } = circuitsConfig[circuit]!
let { circuits } = circomkitConfig

if (params !== undefined && params.length > 0) {
circuits = `${tmpdir}/${[circuit, ...params, 'circuits'].join('-')}.json`
dirBuild += `/${[circuit, ...params].join('-')}`
writeFileSync(
circuits,
JSON.stringify({ ...circuitsConfig, [circuit]: { ...circuitsConfig[circuit], params } }),
'utf8',
)
}

// override circomkit config options
circomkitConfig = { ...circomkitConfig, circuits, dirBuild }
const circomkit = new Circomkit(circomkitConfig)

// temporarily redirect stdout to make all circomkit logs silent
const write = process.stdout.write
const silentStream = new SilentStream()
// @ts-ignore
process.stdout.write = silentStream.write.bind(silentStream)

await circomkit.setup(circuit).finally(() => {
process.stdout.write = write
})

return { circuit, params: params ?? defaultParams }
}

async function generateAction(
export async function generateActionNoExit(
circuit: string | undefined,
params: string[] | undefined,
{ config, destination }: { config?: string; destination?: string },
) {
validateOrThrow(config, validateJsonFileInput)
validateOrThrow(destination, existsSync)

config ??= await getCircomkitConfigInput()
const dirBuild = destination ?? await getDestinationInput(`${cwd()}/snark-artifacts`)
await setup(config, dirBuild)
spinner.succeed(`Snark artifacts generated successfully in ${dirBuild}`)
const result = await setup(circuit, params, config, dirBuild)

spinner.succeed(
`Snark artifacts for ${circuit ?? result.circuit} with parameters ${
params ?? result.params
} generated successfully in ${dirBuild}`,
)
}

async function generateAction(
...params: Parameters<typeof generateActionNoExit>
) {
await generateActionNoExit(...params)
exit(0)
}

Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/commands/generate/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ export const generate = new Command('generate').alias('g').description(
'Path to circomkit configuration file',
)
.option('-d, --destination <path>', 'Destination directory for the generated artifacts')
.argument('[circuit]', 'Circuit to generate snark artifacts for')
.argument('[params...]', 'Circuit parameters override')
.action(generateAction)
3 changes: 2 additions & 1 deletion packages/cli/src/commands/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { download } from './download/index.ts'
import { generateBatch } from './generate-batch/index.ts'
import { generate } from './generate/index.ts'
import { list } from './list.ts'

export default [download, generate, list]
export default [download, generate, generateBatch, list]
14 changes: 6 additions & 8 deletions packages/cli/test/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,14 @@ describe('CLI', () => {
"Usage: snarkli [options] [command]
Options:
-h, --help display help for command
-h, --help display help for command
Commands:
download|d [options] [project] Download all available artifacts for a given
project
generate|g [options] Generate snark artifacts for a given source
circom circuit
list|l List all projects and their available
packages versions
help [command] display help for command
download|d [options] [project] Download all available artifacts for a given project
generate|g [options] [circuit] [params...] Generate snark artifacts for a given source circom circuit
generate-batch|gb <optionsPath> <destination> Generate snark artifacts for a list of circom circuits
list|l List all projects and their available packages versions
help [command] display help for command
"
`)
},
Expand Down

0 comments on commit 0be85ca

Please sign in to comment.