From d12dd28549a4d9cb7f1f49e83b510c187b6d6b00 Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Mon, 30 Oct 2023 15:07:11 +0000 Subject: [PATCH 01/17] refactor: promote NoirPackageConfig to foundation --- yarn-project/foundation/package.json | 6 ++- yarn-project/foundation/src/noir/index.ts | 1 + .../src/noir/noir_package_config.ts | 54 +++++++++++++++++++ yarn-project/noir-compiler/package.json | 3 +- .../dependencies/dependency-manager.test.ts | 3 +- .../noir/dependencies/dependency-manager.ts | 2 +- .../noir/dependencies/dependency-resolver.ts | 3 +- .../github-dependency-resolver.test.ts | 17 +++--- .../github-dependency-resolver.ts | 2 +- .../dependencies/local-dependency-resolver.ts | 3 +- .../src/compile/noir/package-config.ts | 47 ---------------- .../noir-compiler/src/compile/noir/package.ts | 12 ++--- yarn-project/yarn.lock | 2 +- 13 files changed, 83 insertions(+), 72 deletions(-) create mode 100644 yarn-project/foundation/src/noir/index.ts create mode 100644 yarn-project/foundation/src/noir/noir_package_config.ts delete mode 100644 yarn-project/noir-compiler/src/compile/noir/package-config.ts diff --git a/yarn-project/foundation/package.json b/yarn-project/foundation/package.json index 9ac562c62f1..ef597d2b308 100644 --- a/yarn-project/foundation/package.json +++ b/yarn-project/foundation/package.json @@ -33,7 +33,8 @@ "./bigint-buffer": "./dest/bigint-buffer/index.js", "./types": "./dest/types/index.js", "./url": "./dest/url/index.js", - "./committable": "./dest/committable/index.js" + "./committable": "./dest/committable/index.js", + "./noir": "./dest/noir/index.js" }, "scripts": { "build": "yarn clean && tsc -b", @@ -69,7 +70,8 @@ "lodash.clonedeepwith": "^4.5.0", "memdown": "^6.1.1", "pako": "^2.1.0", - "sha3": "^2.1.4" + "sha3": "^2.1.4", + "zod": "^3.22.4" }, "devDependencies": { "@jest/globals": "^29.5.0", diff --git a/yarn-project/foundation/src/noir/index.ts b/yarn-project/foundation/src/noir/index.ts new file mode 100644 index 00000000000..e91eaa5cfec --- /dev/null +++ b/yarn-project/foundation/src/noir/index.ts @@ -0,0 +1 @@ +export * from './noir_package_config.js'; diff --git a/yarn-project/foundation/src/noir/noir_package_config.ts b/yarn-project/foundation/src/noir/noir_package_config.ts new file mode 100644 index 00000000000..a1cd84f37e1 --- /dev/null +++ b/yarn-project/foundation/src/noir/noir_package_config.ts @@ -0,0 +1,54 @@ +import { z } from 'zod'; + +const noirGitDependencySchema = z.object({ + git: z.string(), + tag: z.string(), + directory: z.string().optional(), +}); + +const noirLocalDependencySchema = z.object({ + path: z.string(), +}); + +const noirPackageConfigSchema = z.object({ + package: z.object({ + name: z.string().default(''), + type: z.enum(['lib', 'contract', 'binary']).default('binary'), + entry: z.string().optional(), + description: z.string().optional(), + authors: z.array(z.string()).optional(), + // eslint-disable-next-line camelcase + compiler_version: z.string().optional(), + backend: z.string().optional(), + license: z.string().optional(), + }), + dependencies: z.record(z.union([noirGitDependencySchema, noirLocalDependencySchema])).default({}), +}); + +/** + * Noir package configuration. + */ +export type NoirPackageConfig = z.infer; + +/** + * A remote package dependency. + */ +export type NoirGitDependencyConfig = z.infer; + +/** + * A local package dependency. + */ +export type NoirLocalDependencyConfig = z.infer; + +/** + * A package dependency. + */ +export type NoirDependencyConfig = NoirGitDependencyConfig | NoirLocalDependencyConfig; + +/** + * Checks that an object is a package configuration. + * @param config - Config to check + */ +export function parseNoirPackageConfig(config: any): NoirPackageConfig { + return noirPackageConfigSchema.parse(config); +} diff --git a/yarn-project/noir-compiler/package.json b/yarn-project/noir-compiler/package.json index 03a4837ebd3..0d4c1a8a5fe 100644 --- a/yarn-project/noir-compiler/package.json +++ b/yarn-project/noir-compiler/package.json @@ -60,8 +60,7 @@ "pako": "^2.1.0", "toml": "^3.0.0", "tslib": "^2.4.0", - "unzipit": "^1.4.3", - "zod": "^3.22.4" + "unzipit": "^1.4.3" }, "devDependencies": { "@jest/globals": "^29.5.0", diff --git a/yarn-project/noir-compiler/src/compile/noir/dependencies/dependency-manager.test.ts b/yarn-project/noir-compiler/src/compile/noir/dependencies/dependency-manager.test.ts index 42b53732733..1c32916c075 100644 --- a/yarn-project/noir-compiler/src/compile/noir/dependencies/dependency-manager.test.ts +++ b/yarn-project/noir-compiler/src/compile/noir/dependencies/dependency-manager.test.ts @@ -1,4 +1,5 @@ -import { NoirDependencyConfig } from '../package-config.js'; +import { NoirDependencyConfig } from '@aztec/foundation/noir'; + import { NoirPackage } from '../package.js'; import { NoirDependencyManager } from './dependency-manager.js'; import { DependencyResolver } from './dependency-resolver.js'; diff --git a/yarn-project/noir-compiler/src/compile/noir/dependencies/dependency-manager.ts b/yarn-project/noir-compiler/src/compile/noir/dependencies/dependency-manager.ts index 1be0002cb95..ac5220e907f 100644 --- a/yarn-project/noir-compiler/src/compile/noir/dependencies/dependency-manager.ts +++ b/yarn-project/noir-compiler/src/compile/noir/dependencies/dependency-manager.ts @@ -1,8 +1,8 @@ import { LogFn, createDebugOnlyLogger } from '@aztec/foundation/log'; +import { NoirDependencyConfig } from '@aztec/foundation/noir'; import { join } from 'node:path'; -import { NoirDependencyConfig } from '../package-config.js'; import { NoirPackage } from '../package.js'; import { DependencyResolver } from './dependency-resolver.js'; diff --git a/yarn-project/noir-compiler/src/compile/noir/dependencies/dependency-resolver.ts b/yarn-project/noir-compiler/src/compile/noir/dependencies/dependency-resolver.ts index af55dc21a2d..b4994190ed5 100644 --- a/yarn-project/noir-compiler/src/compile/noir/dependencies/dependency-resolver.ts +++ b/yarn-project/noir-compiler/src/compile/noir/dependencies/dependency-resolver.ts @@ -1,4 +1,5 @@ -import { NoirDependencyConfig } from '../package-config.js'; +import { NoirDependencyConfig } from '@aztec/foundation/noir'; + import { NoirPackage } from '../package.js'; /** diff --git a/yarn-project/noir-compiler/src/compile/noir/dependencies/github-dependency-resolver.test.ts b/yarn-project/noir-compiler/src/compile/noir/dependencies/github-dependency-resolver.test.ts index e369c63f5ed..4b6703484ab 100644 --- a/yarn-project/noir-compiler/src/compile/noir/dependencies/github-dependency-resolver.test.ts +++ b/yarn-project/noir-compiler/src/compile/noir/dependencies/github-dependency-resolver.test.ts @@ -1,3 +1,4 @@ +import { NoirGitDependencyConfig } from '@aztec/foundation/noir'; import { fileURLToPath } from '@aztec/foundation/url'; import { jest } from '@jest/globals'; @@ -7,7 +8,6 @@ import { dirname, join } from 'node:path'; import { FileManager } from '../file-manager/file-manager.js'; import { createMemFSFileManager } from '../file-manager/memfs-file-manager.js'; -import { NoirGitDependencyConfig } from '../package-config.js'; import { NoirPackage } from '../package.js'; import { DependencyResolver } from './dependency-resolver.js'; import { GithubDependencyResolver, resolveGithubCodeArchive, safeFilename } from './github-dependency-resolver.js'; @@ -87,6 +87,7 @@ describe('GithubDependencyResolver', () => { [ { git: 'https://github.com/example/lib.nr', + tag: 'HEAD', }, 'zip', 'https://github.com/example/lib.nr/archive/HEAD.zip', @@ -94,6 +95,7 @@ describe('GithubDependencyResolver', () => { [ { git: 'https://github.com/example/lib.nr', + tag: 'HEAD', }, 'tar', 'https://github.com/example/lib.nr/archive/HEAD.tar.gz', @@ -103,12 +105,13 @@ describe('GithubDependencyResolver', () => { expect(archiveUrl.href).toEqual(href); }); - it.each([{ git: 'https://github.com/' }, { git: 'https://github.com/foo' }, { git: 'https://example.com' }])( - 'throws if the Github URL is invalid', - dep => { - expect(() => resolveGithubCodeArchive(dep, 'zip')).toThrow(); - }, - ); + it.each([ + { git: 'https://github.com/', tag: 'v1' }, + { git: 'https://github.com/foo', tag: 'v1' }, + { git: 'https://example.com', tag: 'v1' }, + ])('throws if the Github URL is invalid', dep => { + expect(() => resolveGithubCodeArchive(dep, 'zip')).toThrow(); + }); it.each([ ['main', 'main'], diff --git a/yarn-project/noir-compiler/src/compile/noir/dependencies/github-dependency-resolver.ts b/yarn-project/noir-compiler/src/compile/noir/dependencies/github-dependency-resolver.ts index 5079fe4f766..36bbcbc3762 100644 --- a/yarn-project/noir-compiler/src/compile/noir/dependencies/github-dependency-resolver.ts +++ b/yarn-project/noir-compiler/src/compile/noir/dependencies/github-dependency-resolver.ts @@ -1,10 +1,10 @@ import { createDebugOnlyLogger } from '@aztec/foundation/log'; +import { NoirDependencyConfig, NoirGitDependencyConfig } from '@aztec/foundation/noir'; import { delimiter, join, sep } from 'node:path'; import { unzip } from 'unzipit'; import { FileManager } from '../file-manager/file-manager.js'; -import { NoirDependencyConfig, NoirGitDependencyConfig } from '../package-config.js'; import { NoirPackage } from '../package.js'; import { DependencyResolver } from './dependency-resolver.js'; diff --git a/yarn-project/noir-compiler/src/compile/noir/dependencies/local-dependency-resolver.ts b/yarn-project/noir-compiler/src/compile/noir/dependencies/local-dependency-resolver.ts index 481c338a416..29862ef2470 100644 --- a/yarn-project/noir-compiler/src/compile/noir/dependencies/local-dependency-resolver.ts +++ b/yarn-project/noir-compiler/src/compile/noir/dependencies/local-dependency-resolver.ts @@ -1,7 +1,8 @@ +import { NoirDependencyConfig } from '@aztec/foundation/noir'; + import { resolve } from 'path'; import { FileManager } from '../file-manager/file-manager.js'; -import { NoirDependencyConfig } from '../package-config.js'; import { NoirPackage } from '../package.js'; import { DependencyResolver } from './dependency-resolver.js'; diff --git a/yarn-project/noir-compiler/src/compile/noir/package-config.ts b/yarn-project/noir-compiler/src/compile/noir/package-config.ts deleted file mode 100644 index fa412b476ce..00000000000 --- a/yarn-project/noir-compiler/src/compile/noir/package-config.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { z } from 'zod'; - -const noirGitDependency = z.object({ - git: z.string(), - tag: z.string().optional(), - directory: z.string().optional(), -}); - -const noirLocalDependency = z.object({ - path: z.string(), -}); - -const noirPackageConfig = z.object({ - package: z.object({ - name: z.string(), - type: z.enum(['lib', 'contract', 'binary']), - }), - dependencies: z.record(z.union([noirGitDependency, noirLocalDependency])), -}); - -/** - * Noir package configuration. - */ -export type NoirPackageConfig = z.infer; - -/** - * A remote package dependency. - */ -export type NoirGitDependencyConfig = z.infer; - -/** - * A local package dependency. - */ -export type NoirLocalDependencyConfig = z.infer; - -/** - * A package dependency. - */ -export type NoirDependencyConfig = NoirGitDependencyConfig | NoirLocalDependencyConfig; - -/** - * Checks that an object is a package configuration. - * @param config - Config to check - */ -export function parsePackageConfig(config: any): NoirPackageConfig { - return noirPackageConfig.parse(config); -} diff --git a/yarn-project/noir-compiler/src/compile/noir/package.ts b/yarn-project/noir-compiler/src/compile/noir/package.ts index bdc00b2e34c..3d667df2b79 100644 --- a/yarn-project/noir-compiler/src/compile/noir/package.ts +++ b/yarn-project/noir-compiler/src/compile/noir/package.ts @@ -1,13 +1,9 @@ +import { NoirDependencyConfig, NoirPackageConfig, parseNoirPackageConfig } from '@aztec/foundation/noir'; + import { join } from 'node:path'; import { parse as parseToml } from 'toml'; import { FileManager } from './file-manager/file-manager.js'; -import { - NoirGitDependencyConfig, - NoirLocalDependencyConfig, - NoirPackageConfig, - parsePackageConfig, -} from './package-config.js'; const CONFIG_FILE_NAME = 'Nargo.toml'; @@ -71,7 +67,7 @@ export class NoirPackage { /** * Gets this package's dependencies. */ - public getDependencies(): Record { + public getDependencies(): Record { return this.#config.dependencies; } @@ -83,7 +79,7 @@ export class NoirPackage { */ public static open(path: string, fm: FileManager): NoirPackage { const fileContents = fm.readFileSync(join(path, CONFIG_FILE_NAME), 'utf-8'); - const config = parsePackageConfig(parseToml(fileContents)); + const config = parseNoirPackageConfig(parseToml(fileContents)); return new NoirPackage(path, join(path, 'src'), config); } diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index 7a328c4143d..45c3d020d0e 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -522,6 +522,7 @@ __metadata: ts-jest: ^29.1.0 ts-node: ^10.9.1 typescript: ^5.0.4 + zod: ^3.22.4 languageName: unknown linkType: soft @@ -615,7 +616,6 @@ __metadata: tslib: ^2.4.0 typescript: ^5.0.4 unzipit: ^1.4.3 - zod: ^3.22.4 bin: aztec-compile: dest/cli.js languageName: unknown From 9fe3007045db5374b9ebc9bf0371a9b73962b5c0 Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Mon, 30 Oct 2023 15:31:02 +0000 Subject: [PATCH 02/17] refactor: replace toml with j-toml --- yarn-project/acir-simulator/package.json | 1 - yarn-project/noir-compiler/package.json | 2 +- .../noir-compiler/src/compile/noir/package.ts | 4 ++-- yarn-project/yarn.lock | 17 ++++++++--------- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/yarn-project/acir-simulator/package.json b/yarn-project/acir-simulator/package.json index 74ff55000ce..c1a7dda3a84 100644 --- a/yarn-project/acir-simulator/package.json +++ b/yarn-project/acir-simulator/package.json @@ -49,7 +49,6 @@ "@types/node": "^18.7.23", "jest": "^29.5.0", "jest-mock-extended": "^3.0.4", - "toml": "^3.0.0", "ts-jest": "^29.1.0", "ts-node": "^10.9.1", "typescript": "^5.0.4", diff --git a/yarn-project/noir-compiler/package.json b/yarn-project/noir-compiler/package.json index 0d4c1a8a5fe..d0479220470 100644 --- a/yarn-project/noir-compiler/package.json +++ b/yarn-project/noir-compiler/package.json @@ -46,6 +46,7 @@ }, "dependencies": { "@aztec/foundation": "workspace:^", + "@ltd/j-toml": "^1.38.0", "@noir-lang/noir_wasm": "0.18.0-3919619.aztec", "@noir-lang/source-resolver": "0.18.0-3919619.aztec", "base64-js": "^1.5.1", @@ -58,7 +59,6 @@ "lodash.upperfirst": "^4.3.1", "memfs": "^4.6.0", "pako": "^2.1.0", - "toml": "^3.0.0", "tslib": "^2.4.0", "unzipit": "^1.4.3" }, diff --git a/yarn-project/noir-compiler/src/compile/noir/package.ts b/yarn-project/noir-compiler/src/compile/noir/package.ts index 3d667df2b79..9a295c11d8c 100644 --- a/yarn-project/noir-compiler/src/compile/noir/package.ts +++ b/yarn-project/noir-compiler/src/compile/noir/package.ts @@ -1,7 +1,7 @@ import { NoirDependencyConfig, NoirPackageConfig, parseNoirPackageConfig } from '@aztec/foundation/noir'; +import { parse } from '@ltd/j-toml'; import { join } from 'node:path'; -import { parse as parseToml } from 'toml'; import { FileManager } from './file-manager/file-manager.js'; @@ -79,7 +79,7 @@ export class NoirPackage { */ public static open(path: string, fm: FileManager): NoirPackage { const fileContents = fm.readFileSync(join(path, CONFIG_FILE_NAME), 'utf-8'); - const config = parseNoirPackageConfig(parseToml(fileContents)); + const config = parseNoirPackageConfig(parse(fileContents)); return new NoirPackage(path, join(path, 'src'), config); } diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index 45c3d020d0e..068de38710a 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -77,7 +77,6 @@ __metadata: jest-mock-extended: ^3.0.4 levelup: ^5.1.1 memdown: ^6.1.1 - toml: ^3.0.0 ts-jest: ^29.1.0 ts-node: ^10.9.1 tslib: ^2.4.0 @@ -587,6 +586,7 @@ __metadata: dependencies: "@aztec/foundation": "workspace:^" "@jest/globals": ^29.5.0 + "@ltd/j-toml": ^1.38.0 "@noir-lang/noir_wasm": 0.18.0-3919619.aztec "@noir-lang/source-resolver": 0.18.0-3919619.aztec "@rushstack/eslint-patch": ^1.1.4 @@ -610,7 +610,6 @@ __metadata: lodash.upperfirst: ^4.3.1 memfs: ^4.6.0 pako: ^2.1.0 - toml: ^3.0.0 ts-jest: ^29.1.0 ts-node: ^10.9.1 tslib: ^2.4.0 @@ -3237,6 +3236,13 @@ __metadata: languageName: node linkType: hard +"@ltd/j-toml@npm:^1.38.0": + version: 1.38.0 + resolution: "@ltd/j-toml@npm:1.38.0" + checksum: 34f5d0ec652e790a7a733f0d3a8d9957d63997bd0efc13a61beb9d772bae75519453884fbc3fd6a2d5fe15674834bdd57ca1824bb1de8f829e5ce195fc5fa3ea + languageName: node + linkType: hard + "@metamask/safe-event-emitter@npm:2.0.0, @metamask/safe-event-emitter@npm:^2.0.0": version: 2.0.0 resolution: "@metamask/safe-event-emitter@npm:2.0.0" @@ -18113,13 +18119,6 @@ __metadata: languageName: unknown linkType: soft -"toml@npm:^3.0.0": - version: 3.0.0 - resolution: "toml@npm:3.0.0" - checksum: 5d7f1d8413ad7780e9bdecce8ea4c3f5130dd53b0a4f2e90b93340979a137739879d7b9ce2ce05c938b8cc828897fe9e95085197342a1377dd8850bf5125f15f - languageName: node - linkType: hard - "toposort@npm:^2.0.2": version: 2.0.2 resolution: "toposort@npm:2.0.2" From 74869686c882ef4487cd581012edb69230b66390 Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Mon, 30 Oct 2023 16:24:44 +0000 Subject: [PATCH 03/17] feat: add cli command update-aztecnr --- yarn-project/cli/package.json | 1 + yarn-project/cli/src/index.ts | 9 +++ yarn-project/cli/src/update-aztecnr.ts | 84 ++++++++++++++++++++++++++ yarn-project/yarn.lock | 1 + 4 files changed, 95 insertions(+) create mode 100644 yarn-project/cli/src/update-aztecnr.ts diff --git a/yarn-project/cli/package.json b/yarn-project/cli/package.json index 21630e4a2c0..f1304f5fe44 100644 --- a/yarn-project/cli/package.json +++ b/yarn-project/cli/package.json @@ -42,6 +42,7 @@ "@aztec/noir-contracts": "workspace:^", "@aztec/types": "workspace:^", "@libp2p/peer-id-factory": "^3.0.4", + "@ltd/j-toml": "^1.38.0", "commander": "^9.0.0", "jszip": "^3.10.1", "lodash.startcase": "^4.4.0", diff --git a/yarn-project/cli/src/index.ts b/yarn-project/cli/src/index.ts index 2dd5ae41739..acc51419d69 100644 --- a/yarn-project/cli/src/index.ts +++ b/yarn-project/cli/src/index.ts @@ -33,6 +33,7 @@ import { mnemonicToAccount } from 'viem/accounts'; import { createCompatibleClient } from './client.js'; import { encodeArgs, parseStructString } from './encoding.js'; import { unboxContract } from './unbox.js'; +import { updateAztecNr } from './update-aztecnr.js'; import { deployAztecContracts, getContractArtifact, @@ -701,6 +702,14 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command { log(`${selector}`); }); + program + .command('update-aztecnr') + .description('Updates noir dependencies in Nargo.toml to their latest version') + .argument('', 'Path to the contract directory') + .action(async (contractPath: string) => { + await updateAztecNr(contractPath, `aztec-packages-${version}`, log); + }); + compileContract(program, 'compile', log); generateTypescriptInterface(program, 'generate-typescript', log); generateNoirInterface(program, 'generate-noir-interface', log); diff --git a/yarn-project/cli/src/update-aztecnr.ts b/yarn-project/cli/src/update-aztecnr.ts new file mode 100644 index 00000000000..13836650f05 --- /dev/null +++ b/yarn-project/cli/src/update-aztecnr.ts @@ -0,0 +1,84 @@ +import { LogFn } from '@aztec/foundation/log'; +import { NoirPackageConfig, parseNoirPackageConfig } from '@aztec/foundation/noir'; + +import TOML from '@ltd/j-toml'; +import { CommanderError } from 'commander'; +import { readFile, rename, writeFile } from 'fs/promises'; +import { EOL } from 'os'; +import { join, resolve } from 'path'; + +/** + * Opens an Aztec.nr contract and updates Aztec.nr to the requested version. + * @param contractPath - Path to an Aztec.nr contract + * @param tag - The version of Aztec.nr to update to + * @param log - Logging function + */ +export async function updateAztecNr(contractPath: string, tag: string, log: LogFn) { + const configFilepath = resolve(join(contractPath, 'Nargo.toml')); + const packageConfig = parseNoirPackageConfig(TOML.parse(await readFile(configFilepath, 'utf-8'))); + let dirty = false; + for (const [name, dep] of Object.entries(packageConfig.dependencies)) { + if (!('git' in dep)) { + continue; + } + + if (dep.git.toLowerCase() !== 'https://github.com/AztecProtocol/aztec-packages'.toLowerCase()) { + continue; + } + + if (dep.tag !== tag) { + log(`Updating ${name} to ${tag}`); + dep.tag = tag; + dirty = true; + } + } + + if (dirty) { + await writeNoirPackageConfigAsToml(configFilepath, packageConfig); + } else { + log('No updates required'); + } +} + +/** + * Writes a contract's configuration as TOML at the given path. + * @param filePath - Where to write the TOML + * @param packageConfig - The Noir configuration + */ +async function writeNoirPackageConfigAsToml(filePath: string, packageConfig: NoirPackageConfig): Promise { + // hint to TOML.stringify how we want the file to look like + const toml = TOML.stringify( + { + package: TOML.Section(packageConfig.package), + dependencies: TOML.Section( + Object.fromEntries(Object.entries(packageConfig.dependencies).map(([name, dep]) => [name, TOML.inline(dep)])), + ), + }, + { + indent: 2, + newline: EOL as any, + newlineAround: 'section', + }, + ); + + const tmpFilepath = filePath + '.tmp'; + try { + await writeFile(tmpFilepath, toml, { + // let's crash if the tmp file already exists + flag: 'wx', + }); + await rename(tmpFilepath, filePath); + } catch (e) { + if (e instanceof Error && 'code' in e && e.code === 'EEXIST') { + const commanderError = new CommanderError( + 1, + e.code, + `Temporary file already exists: ${tmpFilepath}. Delete this file and try again.`, + ); + commanderError.nestedError = e.message; + throw commanderError; + } else { + throw e; + } + } +} diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index 068de38710a..e46fc104a11 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -365,6 +365,7 @@ __metadata: "@aztec/types": "workspace:^" "@jest/globals": ^29.5.0 "@libp2p/peer-id-factory": ^3.0.4 + "@ltd/j-toml": ^1.38.0 "@rushstack/eslint-patch": ^1.1.4 "@types/jest": ^29.5.0 "@types/lodash.startcase": ^4.4.7 From b8a49008ff424b2a26a8c8218c2894db541acc58 Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Mon, 30 Oct 2023 17:02:57 +0000 Subject: [PATCH 04/17] feat: update to latest/specific version of Aztec.nr --- yarn-project/cli/src/github.ts | 34 ++++++++++++++++++++++++++ yarn-project/cli/src/index.ts | 27 +++++++++++++++----- yarn-project/cli/src/unbox.ts | 5 ++-- yarn-project/cli/src/update-aztecnr.ts | 27 ++++++++++++++++++++ 4 files changed, 84 insertions(+), 9 deletions(-) create mode 100644 yarn-project/cli/src/github.ts diff --git a/yarn-project/cli/src/github.ts b/yarn-project/cli/src/github.ts new file mode 100644 index 00000000000..a1dd078e355 --- /dev/null +++ b/yarn-project/cli/src/github.ts @@ -0,0 +1,34 @@ +export const GITHUB_OWNER = 'AztecProtocol'; +export const GITHUB_REPO = 'aztec-packages'; +export const GITHUB_TAG_PREFIX = 'aztec-packages'; + +/** + * A Github release + */ +export type GithubRelease = { + /** The Git tag of the release */ + tagName: string; + /** When it was created as an ISO 8601 string */ + createdAt: string; +}; + +/** + * Gets recent releases from the Aztec packages repository. + * @returns a sorted list of Aztec packages releases (newest first) + */ +export async function getRecentAztecReleases(): Promise> { + const response = await fetch(`https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases`); + if (!response.ok) { + return []; + } + + const allReleases: Array = await response.json(); + const aztecPackagesReleases = allReleases.filter((release: any) => release.tag_name.startsWith(GITHUB_TAG_PREFIX)); + const releases = aztecPackagesReleases.map((release: any) => ({ + tagName: release.tag_name, + createdAt: release.created_at, + })); + + releases.sort((a, b) => (a.createdAt < b.createdAt ? 1 : a.createdAt > b.createdAt ? -1 : 0)); + return releases; +} diff --git a/yarn-project/cli/src/index.ts b/yarn-project/cli/src/index.ts index acc51419d69..7b163ce57f4 100644 --- a/yarn-project/cli/src/index.ts +++ b/yarn-project/cli/src/index.ts @@ -32,8 +32,9 @@ import { mnemonicToAccount } from 'viem/accounts'; import { createCompatibleClient } from './client.js'; import { encodeArgs, parseStructString } from './encoding.js'; +import { GITHUB_TAG_PREFIX } from './github.js'; import { unboxContract } from './unbox.js'; -import { updateAztecNr } from './update-aztecnr.js'; +import { aztecNrVersionToTagName, getLatestAztecNrTag, updateAztecNr } from './update-aztecnr.js'; import { deployAztecContracts, getContractArtifact, @@ -71,9 +72,9 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command { const program = new Command(); const packageJsonPath = resolve(dirname(fileURLToPath(import.meta.url)), '../package.json'); - const version: string = JSON.parse(readFileSync(packageJsonPath).toString()).version; + const cliVersion: string = JSON.parse(readFileSync(packageJsonPath).toString()).version; - program.name('aztec-cli').description('CLI for interacting with Aztec.').version(version); + program.name('aztec-cli').description('CLI for interacting with Aztec.').version(cliVersion); const pxeOption = new Option('-u, --rpc-url ', 'URL of the PXE') .env('PXE_URL') @@ -650,7 +651,7 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command { ) .action(async (contractName, localDirectory) => { const unboxTo: string = localDirectory ? localDirectory : contractName; - await unboxContract(contractName, unboxTo, version, log); + await unboxContract(contractName, unboxTo, cliVersion, log); }); program @@ -706,8 +707,22 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command { .command('update-aztecnr') .description('Updates noir dependencies in Nargo.toml to their latest version') .argument('', 'Path to the contract directory') - .action(async (contractPath: string) => { - await updateAztecNr(contractPath, `aztec-packages-${version}`, log); + .option( + '--aztec-version ', + 'Semver version to update to or `current` for the current version compatible with the CLI or `latest` for the most recent stable version', + 'current', + ) + .action(async (contractPath: string, { aztecVersion }) => { + let tagName: string; + if (aztecVersion === 'current') { + tagName = `${GITHUB_TAG_PREFIX}-${cliVersion}`; + } else if (aztecVersion === 'latest') { + tagName = await getLatestAztecNrTag(); + } else { + tagName = await aztecNrVersionToTagName(aztecVersion); + } + + await updateAztecNr(contractPath, tagName, log); }); compileContract(program, 'compile', log); diff --git a/yarn-project/cli/src/unbox.ts b/yarn-project/cli/src/unbox.ts index 07fcdd21983..7cb010f3e3c 100644 --- a/yarn-project/cli/src/unbox.ts +++ b/yarn-project/cli/src/unbox.ts @@ -14,9 +14,8 @@ import JSZip from 'jszip'; import fetch from 'node-fetch'; import * as path from 'path'; -const GITHUB_OWNER = 'AztecProtocol'; -const GITHUB_REPO = 'aztec-packages'; -const GITHUB_TAG_PREFIX = 'aztec-packages'; +import { GITHUB_OWNER, GITHUB_REPO, GITHUB_TAG_PREFIX } from './github.js'; + const BOXES_PATH = 'yarn-project/boxes'; /** diff --git a/yarn-project/cli/src/update-aztecnr.ts b/yarn-project/cli/src/update-aztecnr.ts index 13836650f05..526019d364d 100644 --- a/yarn-project/cli/src/update-aztecnr.ts +++ b/yarn-project/cli/src/update-aztecnr.ts @@ -7,6 +7,32 @@ import { readFile, rename, writeFile } from 'fs/promises'; import { EOL } from 'os'; import { join, resolve } from 'path'; +import { GITHUB_TAG_PREFIX, getRecentAztecReleases } from './github.js'; + +/** + * Checks if a version exists on Github. + * @param version - A semver string + * @returns true if the version exists + */ +export async function aztecNrVersionToTagName(version: string): Promise { + const releases = await getRecentAztecReleases(); + const tagName = `${GITHUB_TAG_PREFIX}-v${version}`; + const exists = releases.find(release => release.tagName === tagName) !== undefined; + if (exists) { + return tagName; + } + + throw new CommanderError(1, '', `Unknown Aztec.nr version ${version}`); +} + +/** + * Gets the latest released version of Aztec.nr. + */ +export async function getLatestAztecNrTag(): Promise { + const releases = await getRecentAztecReleases(); + return releases[0].tagName; +} + /** * Opens an Aztec.nr contract and updates Aztec.nr to the requested version. * @param contractPath - Path to an Aztec.nr contract @@ -17,6 +43,7 @@ export async function updateAztecNr(contractPath: string, tag: string, log: LogF const configFilepath = resolve(join(contractPath, 'Nargo.toml')); const packageConfig = parseNoirPackageConfig(TOML.parse(await readFile(configFilepath, 'utf-8'))); let dirty = false; + log(`Updating ${packageConfig.package.name} to Aztec.nr@${tag}`); for (const [name, dep] of Object.entries(packageConfig.dependencies)) { if (!('git' in dep)) { continue; From 249e6a9c1d226529dc5a8e6effe7192edc695e11 Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Mon, 30 Oct 2023 17:05:47 +0000 Subject: [PATCH 05/17] fix: cli command description --- yarn-project/cli/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn-project/cli/src/index.ts b/yarn-project/cli/src/index.ts index 7b163ce57f4..7f38f137e0a 100644 --- a/yarn-project/cli/src/index.ts +++ b/yarn-project/cli/src/index.ts @@ -705,7 +705,7 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command { program .command('update-aztecnr') - .description('Updates noir dependencies in Nargo.toml to their latest version') + .description('Updates Aztec.nr dependencies in Nargo.toml to the given version') .argument('', 'Path to the contract directory') .option( '--aztec-version ', From 853e7f070a970a4fdb383e8e6a4c8b7f29a41e75 Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Mon, 30 Oct 2023 17:06:42 +0000 Subject: [PATCH 06/17] fix: add command to aztec-cli wrapper --- yarn-project/cli/aztec-cli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn-project/cli/aztec-cli b/yarn-project/cli/aztec-cli index 6fa3e72b27e..a3bce8d1acf 100755 --- a/yarn-project/cli/aztec-cli +++ b/yarn-project/cli/aztec-cli @@ -99,7 +99,7 @@ AZTEC_CLI_EXTRA_ARGS="" # first process commands with positional arguments # assumes positional arguments are the first thing -if [[ "$AZTEC_CLI_COMMAND" == "compile" || "$AZTEC_CLI_COMMAND" == "deploy" || "$AZTEC_CLI_COMMAND" == "inspect-contract" ]]; then +if [[ "$AZTEC_CLI_COMMAND" == "compile" || "$AZTEC_CLI_COMMAND" == "deploy" || "$AZTEC_CLI_COMMAND" == "inspect-contract" || "$AZTEC_CLI_COMMAND" == "update-aztecnr" ]]; then add_mount "$2" fi From 36b399d04c07302f14a9934364fd5c5eff51c740 Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Tue, 31 Oct 2023 17:09:44 +0000 Subject: [PATCH 07/17] feat: add update-nodejs --- yarn-project/cli/src/index.ts | 9 +++++ yarn-project/cli/src/update-aztec.ts | 54 ++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 yarn-project/cli/src/update-aztec.ts diff --git a/yarn-project/cli/src/index.ts b/yarn-project/cli/src/index.ts index 7f38f137e0a..5e1165f8648 100644 --- a/yarn-project/cli/src/index.ts +++ b/yarn-project/cli/src/index.ts @@ -34,6 +34,7 @@ import { createCompatibleClient } from './client.js'; import { encodeArgs, parseStructString } from './encoding.js'; import { GITHUB_TAG_PREFIX } from './github.js'; import { unboxContract } from './unbox.js'; +import { updateAztecDeps } from './update-aztec.js'; import { aztecNrVersionToTagName, getLatestAztecNrTag, updateAztecNr } from './update-aztecnr.js'; import { deployAztecContracts, @@ -703,6 +704,14 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command { log(`${selector}`); }); + program + .command('update-nodejs') + .description('Updates @aztec packages in package.json to their latest versions') + .argument('', 'Path to the project directory') + .action((projectPath: string) => { + updateAztecDeps(projectPath, cliVersion, log); + }); + program .command('update-aztecnr') .description('Updates Aztec.nr dependencies in Nargo.toml to the given version') diff --git a/yarn-project/cli/src/update-aztec.ts b/yarn-project/cli/src/update-aztec.ts new file mode 100644 index 00000000000..8b6b2887c27 --- /dev/null +++ b/yarn-project/cli/src/update-aztec.ts @@ -0,0 +1,54 @@ +import { LogFn } from '@aztec/foundation/log'; + +import { spawnSync } from 'child_process'; +import { existsSync, readFileSync } from 'fs'; +import { join, resolve } from 'path'; + +/** + * Updates \@aztec/* dependencies in a project to their latest version + * @param path - Path to Nodejs project + * @param log - Logging function + */ +export function updateAztecDeps(path: string, version: string, log: LogFn) { + const configFilepath = resolve(join(path, 'package.json')); + const pkg = JSON.parse(readFileSync(configFilepath, 'utf-8')); + const aztecDepsWithNewVersion = Object.keys(pkg.dependencies) + .filter( + dep => + dep.startsWith('@aztec/') && + // this is on a different release cycle + dep !== '@aztec/aztec-ui', + ) + .map(dep => `${dep}@${version}`); + + const isYarn = existsSync(resolve(join(path, 'yarn.lock'))); + + log(`Updating ${aztecDepsWithNewVersion.join(', ')}`); + + if (isYarn) { + const yarnVersion = spawnSync('yarn', ['--version'], { + cwd: path, + stdio: 'pipe', + }); + const yarnVersionStr = yarnVersion.stdout.toString().trim(); + const yarnVersionParts = yarnVersionStr.split('.'); + const yarnMajor = parseInt(yarnVersionParts[0]); + + if (yarnMajor === 1) { + spawnSync('yarn', ['add', ...aztecDepsWithNewVersion], { + cwd: path, + stdio: 'inherit', + }); + } else { + spawnSync('yarn', ['up', `@aztec/*@${version}`], { + cwd: path, + stdio: 'inherit', + }); + } + } else { + spawnSync('npm', ['install', ...aztecDepsWithNewVersion], { + cwd: path, + stdio: 'inherit', + }); + } +} From 7cf2486d5e3b6b54e313b2037bc9f048e572d229 Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Tue, 31 Oct 2023 22:32:33 +0000 Subject: [PATCH 08/17] refactor: merge update-nodejs and update-aztecnr --- yarn-project/cli/src/index.ts | 40 ++++++++------- yarn-project/cli/src/update-aztec.ts | 68 ++++++++++++++------------ yarn-project/cli/src/update-aztecnr.ts | 52 ++++++++++++-------- 3 files changed, 93 insertions(+), 67 deletions(-) diff --git a/yarn-project/cli/src/index.ts b/yarn-project/cli/src/index.ts index 5e1165f8648..22211d92313 100644 --- a/yarn-project/cli/src/index.ts +++ b/yarn-project/cli/src/index.ts @@ -705,33 +705,39 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command { }); program - .command('update-nodejs') - .description('Updates @aztec packages in package.json to their latest versions') + .command('update') + .description('Updates Nodejs and Noir dependencies') .argument('', 'Path to the project directory') - .action((projectPath: string) => { - updateAztecDeps(projectPath, cliVersion, log); - }); - - program - .command('update-aztecnr') - .description('Updates Aztec.nr dependencies in Nargo.toml to the given version') - .argument('', 'Path to the contract directory') .option( - '--aztec-version ', + '--aztec-version ', 'Semver version to update to or `current` for the current version compatible with the CLI or `latest` for the most recent stable version', 'current', ) - .action(async (contractPath: string, { aztecVersion }) => { - let tagName: string; + .option('--no-nodejs', 'Disable updating @aztec/* packages in package.json') + .option('--no-noir', 'Disable updating Aztec.nr libraries in Nargo.toml') + .action(async (projectPath: string, options) => { + const { aztecVersion, nodejs, noir } = options; + + let noirTagName = ''; + let npmVersion = ''; if (aztecVersion === 'current') { - tagName = `${GITHUB_TAG_PREFIX}-${cliVersion}`; + noirTagName = `${GITHUB_TAG_PREFIX}-${cliVersion}`; + npmVersion = cliVersion; } else if (aztecVersion === 'latest') { - tagName = await getLatestAztecNrTag(); + noirTagName = await getLatestAztecNrTag(); + npmVersion = 'latest'; } else { - tagName = await aztecNrVersionToTagName(aztecVersion); + noirTagName = await aztecNrVersionToTagName(aztecVersion); + npmVersion = aztecVersion; } - await updateAztecNr(contractPath, tagName, log); + if (nodejs) { + updateAztecDeps(projectPath, npmVersion, log); + } + + if (noir) { + await updateAztecNr(projectPath, noirTagName, log); + } }); compileContract(program, 'compile', log); diff --git a/yarn-project/cli/src/update-aztec.ts b/yarn-project/cli/src/update-aztec.ts index 8b6b2887c27..5bb7f2199ec 100644 --- a/yarn-project/cli/src/update-aztec.ts +++ b/yarn-project/cli/src/update-aztec.ts @@ -10,45 +10,53 @@ import { join, resolve } from 'path'; * @param log - Logging function */ export function updateAztecDeps(path: string, version: string, log: LogFn) { - const configFilepath = resolve(join(path, 'package.json')); - const pkg = JSON.parse(readFileSync(configFilepath, 'utf-8')); - const aztecDepsWithNewVersion = Object.keys(pkg.dependencies) - .filter( - dep => - dep.startsWith('@aztec/') && - // this is on a different release cycle - dep !== '@aztec/aztec-ui', - ) - .map(dep => `${dep}@${version}`); + try { + const configFilepath = resolve(join(path, 'package.json')); + const pkg = JSON.parse(readFileSync(configFilepath, 'utf-8')); + const aztecDepsWithNewVersion = Object.keys(pkg.dependencies) + .filter( + dep => + dep.startsWith('@aztec/') && + // this is on a different release cycle + dep !== '@aztec/aztec-ui', + ) + .map(dep => `${dep}@${version}`); - const isYarn = existsSync(resolve(join(path, 'yarn.lock'))); + const isYarn = existsSync(resolve(join(path, 'yarn.lock'))); - log(`Updating ${aztecDepsWithNewVersion.join(', ')}`); + log(`Updating ${aztecDepsWithNewVersion.join(', ')}`); - if (isYarn) { - const yarnVersion = spawnSync('yarn', ['--version'], { - cwd: path, - stdio: 'pipe', - }); - const yarnVersionStr = yarnVersion.stdout.toString().trim(); - const yarnVersionParts = yarnVersionStr.split('.'); - const yarnMajor = parseInt(yarnVersionParts[0]); - - if (yarnMajor === 1) { - spawnSync('yarn', ['add', ...aztecDepsWithNewVersion], { + if (isYarn) { + const yarnVersion = spawnSync('yarn', ['--version'], { cwd: path, - stdio: 'inherit', + stdio: 'pipe', }); + const yarnVersionStr = yarnVersion.stdout.toString().trim(); + const yarnVersionParts = yarnVersionStr.split('.'); + const yarnMajor = parseInt(yarnVersionParts[0]); + + if (yarnMajor === 1) { + spawnSync('yarn', ['add', ...aztecDepsWithNewVersion], { + cwd: path, + stdio: 'inherit', + }); + } else { + spawnSync('yarn', ['up', `@aztec/*@${version}`], { + cwd: path, + stdio: 'inherit', + }); + } } else { - spawnSync('yarn', ['up', `@aztec/*@${version}`], { + spawnSync('npm', ['install', ...aztecDepsWithNewVersion], { cwd: path, stdio: 'inherit', }); } - } else { - spawnSync('npm', ['install', ...aztecDepsWithNewVersion], { - cwd: path, - stdio: 'inherit', - }); + } catch (err) { + if (err instanceof Error && 'code' in err && err.code === 'ENOENT') { + log('No package.json found. Skipping update.'); + return; + } + throw err; } } diff --git a/yarn-project/cli/src/update-aztecnr.ts b/yarn-project/cli/src/update-aztecnr.ts index 526019d364d..f320698a551 100644 --- a/yarn-project/cli/src/update-aztecnr.ts +++ b/yarn-project/cli/src/update-aztecnr.ts @@ -40,30 +40,42 @@ export async function getLatestAztecNrTag(): Promise { * @param log - Logging function */ export async function updateAztecNr(contractPath: string, tag: string, log: LogFn) { - const configFilepath = resolve(join(contractPath, 'Nargo.toml')); - const packageConfig = parseNoirPackageConfig(TOML.parse(await readFile(configFilepath, 'utf-8'))); - let dirty = false; - log(`Updating ${packageConfig.package.name} to Aztec.nr@${tag}`); - for (const [name, dep] of Object.entries(packageConfig.dependencies)) { - if (!('git' in dep)) { - continue; - } + try { + const configFilepath = resolve(join(contractPath, 'Nargo.toml')); + const packageConfig = parseNoirPackageConfig(TOML.parse(await readFile(configFilepath, 'utf-8'))); + let dirty = false; + log(`Updating ${packageConfig.package.name} to Aztec.nr@${tag}`); + for (const [name, dep] of Object.entries(packageConfig.dependencies)) { + if (!('git' in dep)) { + continue; + } - if (dep.git.toLowerCase() !== 'https://github.com/AztecProtocol/aztec-packages'.toLowerCase()) { - continue; - } + // check both trailing an non-trailing slash as technically they are different URLs + if ( + dep.git.toLowerCase() !== 'https://github.com/AztecProtocol/aztec-packages'.toLowerCase() && + dep.git.toLowerCase() !== 'https://github.com/AztecProtocol/aztec-packages/'.toLowerCase() + ) { + continue; + } - if (dep.tag !== tag) { - log(`Updating ${name} to ${tag}`); - dep.tag = tag; - dirty = true; + if (dep.tag !== tag) { + log(`Updating ${name} to ${tag}`); + dep.tag = tag; + dirty = true; + } } - } - if (dirty) { - await writeNoirPackageConfigAsToml(configFilepath, packageConfig); - } else { - log('No updates required'); + if (dirty) { + await writeNoirPackageConfigAsToml(configFilepath, packageConfig); + } else { + log('No updates required'); + } + } catch (err) { + if (err instanceof Error && 'code' in err && err.code === 'ENOENT') { + log('No Nargo.toml found. Skipping update.'); + return; + } + throw err; } } From a291a707c7b3bfbee1e19f3b76173c52e00dcf2c Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Wed, 1 Nov 2023 13:42:36 +0000 Subject: [PATCH 09/17] refactor: update deps based on running sandbox --- yarn-project/cli/aztec-cli | 7 +- yarn-project/cli/src/index.ts | 39 ++------ yarn-project/cli/src/update-aztec.ts | 62 ------------- yarn-project/cli/src/update-aztecnr.ts | 123 ------------------------- yarn-project/cli/src/update/noir.ts | 69 ++++++++++++++ yarn-project/cli/src/update/npm.ts | 108 ++++++++++++++++++++++ yarn-project/cli/src/update/update.ts | 65 +++++++++++++ yarn-project/cli/src/utils.ts | 30 +++++- 8 files changed, 283 insertions(+), 220 deletions(-) delete mode 100644 yarn-project/cli/src/update-aztec.ts delete mode 100644 yarn-project/cli/src/update-aztecnr.ts create mode 100644 yarn-project/cli/src/update/noir.ts create mode 100644 yarn-project/cli/src/update/npm.ts create mode 100644 yarn-project/cli/src/update/update.ts diff --git a/yarn-project/cli/aztec-cli b/yarn-project/cli/aztec-cli index a3bce8d1acf..38132243049 100755 --- a/yarn-project/cli/aztec-cli +++ b/yarn-project/cli/aztec-cli @@ -99,7 +99,7 @@ AZTEC_CLI_EXTRA_ARGS="" # first process commands with positional arguments # assumes positional arguments are the first thing -if [[ "$AZTEC_CLI_COMMAND" == "compile" || "$AZTEC_CLI_COMMAND" == "deploy" || "$AZTEC_CLI_COMMAND" == "inspect-contract" || "$AZTEC_CLI_COMMAND" == "update-aztecnr" ]]; then +if [[ "$AZTEC_CLI_COMMAND" == "compile" || "$AZTEC_CLI_COMMAND" == "deploy" || "$AZTEC_CLI_COMMAND" == "inspect-contract" ]]; then add_mount "$2" fi @@ -115,6 +115,11 @@ if [[ "$AZTEC_CLI_COMMAND" == "unbox" ]]; then add_mount "$DIR" fi +if [[ "$AZTEC_CLI_COMMAND" == "update" ]]; then + # update command defaults to current directory + add_mount "$PWD" +fi + # process flags if [[ "$AZTEC_CLI_COMMAND" == "compile" || "$AZTEC_CLI_COMMAND" == "call" || "$AZTEC_CLI_COMMAND" == "send" ]]; then diff --git a/yarn-project/cli/src/index.ts b/yarn-project/cli/src/index.ts index 22211d92313..6edc3dcaec0 100644 --- a/yarn-project/cli/src/index.ts +++ b/yarn-project/cli/src/index.ts @@ -32,10 +32,8 @@ import { mnemonicToAccount } from 'viem/accounts'; import { createCompatibleClient } from './client.js'; import { encodeArgs, parseStructString } from './encoding.js'; -import { GITHUB_TAG_PREFIX } from './github.js'; import { unboxContract } from './unbox.js'; -import { updateAztecDeps } from './update-aztec.js'; -import { aztecNrVersionToTagName, getLatestAztecNrTag, updateAztecNr } from './update-aztecnr.js'; +import { update } from './update/update.js'; import { deployAztecContracts, getContractArtifact, @@ -707,37 +705,12 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command { program .command('update') .description('Updates Nodejs and Noir dependencies') - .argument('', 'Path to the project directory') - .option( - '--aztec-version ', - 'Semver version to update to or `current` for the current version compatible with the CLI or `latest` for the most recent stable version', - 'current', - ) - .option('--no-nodejs', 'Disable updating @aztec/* packages in package.json') - .option('--no-noir', 'Disable updating Aztec.nr libraries in Nargo.toml') + .argument('[projectPath]', 'Path to the project directory', process.cwd()) + .option('--contract [paths...]', 'Paths to contracts to update dependencies', []) + .addOption(pxeOption) .action(async (projectPath: string, options) => { - const { aztecVersion, nodejs, noir } = options; - - let noirTagName = ''; - let npmVersion = ''; - if (aztecVersion === 'current') { - noirTagName = `${GITHUB_TAG_PREFIX}-${cliVersion}`; - npmVersion = cliVersion; - } else if (aztecVersion === 'latest') { - noirTagName = await getLatestAztecNrTag(); - npmVersion = 'latest'; - } else { - noirTagName = await aztecNrVersionToTagName(aztecVersion); - npmVersion = aztecVersion; - } - - if (nodejs) { - updateAztecDeps(projectPath, npmVersion, log); - } - - if (noir) { - await updateAztecNr(projectPath, noirTagName, log); - } + const { contract } = options; + await update(projectPath, contract, options.rpcUrl, log, debugLogger); }); compileContract(program, 'compile', log); diff --git a/yarn-project/cli/src/update-aztec.ts b/yarn-project/cli/src/update-aztec.ts deleted file mode 100644 index 5bb7f2199ec..00000000000 --- a/yarn-project/cli/src/update-aztec.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { LogFn } from '@aztec/foundation/log'; - -import { spawnSync } from 'child_process'; -import { existsSync, readFileSync } from 'fs'; -import { join, resolve } from 'path'; - -/** - * Updates \@aztec/* dependencies in a project to their latest version - * @param path - Path to Nodejs project - * @param log - Logging function - */ -export function updateAztecDeps(path: string, version: string, log: LogFn) { - try { - const configFilepath = resolve(join(path, 'package.json')); - const pkg = JSON.parse(readFileSync(configFilepath, 'utf-8')); - const aztecDepsWithNewVersion = Object.keys(pkg.dependencies) - .filter( - dep => - dep.startsWith('@aztec/') && - // this is on a different release cycle - dep !== '@aztec/aztec-ui', - ) - .map(dep => `${dep}@${version}`); - - const isYarn = existsSync(resolve(join(path, 'yarn.lock'))); - - log(`Updating ${aztecDepsWithNewVersion.join(', ')}`); - - if (isYarn) { - const yarnVersion = spawnSync('yarn', ['--version'], { - cwd: path, - stdio: 'pipe', - }); - const yarnVersionStr = yarnVersion.stdout.toString().trim(); - const yarnVersionParts = yarnVersionStr.split('.'); - const yarnMajor = parseInt(yarnVersionParts[0]); - - if (yarnMajor === 1) { - spawnSync('yarn', ['add', ...aztecDepsWithNewVersion], { - cwd: path, - stdio: 'inherit', - }); - } else { - spawnSync('yarn', ['up', `@aztec/*@${version}`], { - cwd: path, - stdio: 'inherit', - }); - } - } else { - spawnSync('npm', ['install', ...aztecDepsWithNewVersion], { - cwd: path, - stdio: 'inherit', - }); - } - } catch (err) { - if (err instanceof Error && 'code' in err && err.code === 'ENOENT') { - log('No package.json found. Skipping update.'); - return; - } - throw err; - } -} diff --git a/yarn-project/cli/src/update-aztecnr.ts b/yarn-project/cli/src/update-aztecnr.ts deleted file mode 100644 index f320698a551..00000000000 --- a/yarn-project/cli/src/update-aztecnr.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { LogFn } from '@aztec/foundation/log'; -import { NoirPackageConfig, parseNoirPackageConfig } from '@aztec/foundation/noir'; - -import TOML from '@ltd/j-toml'; -import { CommanderError } from 'commander'; -import { readFile, rename, writeFile } from 'fs/promises'; -import { EOL } from 'os'; -import { join, resolve } from 'path'; - -import { GITHUB_TAG_PREFIX, getRecentAztecReleases } from './github.js'; - -/** - * Checks if a version exists on Github. - * @param version - A semver string - * @returns true if the version exists - */ -export async function aztecNrVersionToTagName(version: string): Promise { - const releases = await getRecentAztecReleases(); - const tagName = `${GITHUB_TAG_PREFIX}-v${version}`; - const exists = releases.find(release => release.tagName === tagName) !== undefined; - if (exists) { - return tagName; - } - - throw new CommanderError(1, '', `Unknown Aztec.nr version ${version}`); -} - -/** - * Gets the latest released version of Aztec.nr. - */ -export async function getLatestAztecNrTag(): Promise { - const releases = await getRecentAztecReleases(); - return releases[0].tagName; -} - -/** - * Opens an Aztec.nr contract and updates Aztec.nr to the requested version. - * @param contractPath - Path to an Aztec.nr contract - * @param tag - The version of Aztec.nr to update to - * @param log - Logging function - */ -export async function updateAztecNr(contractPath: string, tag: string, log: LogFn) { - try { - const configFilepath = resolve(join(contractPath, 'Nargo.toml')); - const packageConfig = parseNoirPackageConfig(TOML.parse(await readFile(configFilepath, 'utf-8'))); - let dirty = false; - log(`Updating ${packageConfig.package.name} to Aztec.nr@${tag}`); - for (const [name, dep] of Object.entries(packageConfig.dependencies)) { - if (!('git' in dep)) { - continue; - } - - // check both trailing an non-trailing slash as technically they are different URLs - if ( - dep.git.toLowerCase() !== 'https://github.com/AztecProtocol/aztec-packages'.toLowerCase() && - dep.git.toLowerCase() !== 'https://github.com/AztecProtocol/aztec-packages/'.toLowerCase() - ) { - continue; - } - - if (dep.tag !== tag) { - log(`Updating ${name} to ${tag}`); - dep.tag = tag; - dirty = true; - } - } - - if (dirty) { - await writeNoirPackageConfigAsToml(configFilepath, packageConfig); - } else { - log('No updates required'); - } - } catch (err) { - if (err instanceof Error && 'code' in err && err.code === 'ENOENT') { - log('No Nargo.toml found. Skipping update.'); - return; - } - throw err; - } -} - -/** - * Writes a contract's configuration as TOML at the given path. - * @param filePath - Where to write the TOML - * @param packageConfig - The Noir configuration - */ -async function writeNoirPackageConfigAsToml(filePath: string, packageConfig: NoirPackageConfig): Promise { - // hint to TOML.stringify how we want the file to look like - const toml = TOML.stringify( - { - package: TOML.Section(packageConfig.package), - dependencies: TOML.Section( - Object.fromEntries(Object.entries(packageConfig.dependencies).map(([name, dep]) => [name, TOML.inline(dep)])), - ), - }, - { - indent: 2, - newline: EOL as any, - newlineAround: 'section', - }, - ); - - const tmpFilepath = filePath + '.tmp'; - try { - await writeFile(tmpFilepath, toml, { - // let's crash if the tmp file already exists - flag: 'wx', - }); - await rename(tmpFilepath, filePath); - } catch (e) { - if (e instanceof Error && 'code' in e && e.code === 'EEXIST') { - const commanderError = new CommanderError( - 1, - e.code, - `Temporary file already exists: ${tmpFilepath}. Delete this file and try again.`, - ); - commanderError.nestedError = e.message; - throw commanderError; - } else { - throw e; - } - } -} diff --git a/yarn-project/cli/src/update/noir.ts b/yarn-project/cli/src/update/noir.ts new file mode 100644 index 00000000000..77e5b660d78 --- /dev/null +++ b/yarn-project/cli/src/update/noir.ts @@ -0,0 +1,69 @@ +import { LogFn } from '@aztec/foundation/log'; +import { NoirPackageConfig, parseNoirPackageConfig } from '@aztec/foundation/noir'; + +import TOML from '@ltd/j-toml'; +import { readFile } from 'fs/promises'; +import { EOL } from 'os'; +import { join, resolve } from 'path'; + +import { atomicUpdateFile } from '../utils.js'; + +/** + * Updates Aztec.nr dependencies + * @param contractPath - Path to the contract to be updated + * @param tag - The tag to update to + * @param log - Logging function + */ +export async function updateAztecNr(contractPath: string, tag: string, log: LogFn) { + const configFilepath = resolve(join(contractPath, 'Nargo.toml')); + const packageConfig = parseNoirPackageConfig(TOML.parse(await readFile(configFilepath, 'utf-8'))); + let dirty = false; + + log(`\nUpdating Aztec.nr libraries to ${tag} in ${contractPath}`); + for (const dep of Object.values(packageConfig.dependencies)) { + if (!('git' in dep)) { + continue; + } + + // remove trailing slash + const gitUrl = dep.git.toLowerCase().replace(/\/$/, ''); + if (gitUrl !== 'https://github.com/aztecprotocol/aztec-packages') { + continue; + } + + if (dep.tag !== tag) { + dirty = true; + dep.tag = tag; + } + } + + if (dirty) { + const contents = prettyPrintTOML(packageConfig); + await atomicUpdateFile(configFilepath, contents); + log(`${join(contractPath, 'Nargo.toml')} updated`); + } else { + log('No updates required'); + } +} + +/** + * Pretty prints a NoirPackageConfig to a string + * @param packageConfig - Nargo.toml contents + * @returns The Nargo.toml contents as a string + */ +function prettyPrintTOML(packageConfig: NoirPackageConfig): string { + // hint to TOML.stringify how we want the file to look like + return TOML.stringify( + { + package: TOML.Section(packageConfig.package), + dependencies: TOML.Section( + Object.fromEntries(Object.entries(packageConfig.dependencies).map(([name, dep]) => [name, TOML.inline(dep)])), + ), + }, + { + indent: 2, + newline: EOL as any, + newlineAround: 'section', + }, + ); +} diff --git a/yarn-project/cli/src/update/npm.ts b/yarn-project/cli/src/update/npm.ts new file mode 100644 index 00000000000..e97301cc122 --- /dev/null +++ b/yarn-project/cli/src/update/npm.ts @@ -0,0 +1,108 @@ +import { LogFn } from '@aztec/foundation/log'; + +import { spawnSync } from 'child_process'; +import { existsSync } from 'fs'; +import { readFile } from 'fs/promises'; +import { join, resolve } from 'path'; +import { SemVer, parse } from 'semver'; + +import { atomicUpdateFile } from '../utils.js'; + +/** + * Looks up a package.json file and returns its contents + * @param projectPath - Path to Nodejs project + * @returns The parsed package.json + */ +export async function readPackageJson(projectPath: string): Promise<{ + /** dependencies */ + dependencies?: Record; +}> { + const configFilepath = resolve(join(projectPath, 'package.json')); + const pkg = JSON.parse(await readFile(configFilepath, 'utf-8')); + + return pkg; +} + +/** + * Queries the npm registry for the latest version of a package + * @param packageName - The package to query + * @param distTag - The distribution tag + * @returns The latest version of the package on that distribution tag + */ +export async function getNewestVersion(packageName: string, distTag = 'latest'): Promise { + const url = new URL(packageName, 'https://registry.npmjs.org/'); + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to fetch ${url}`); + } + + const body = await response.json(); + const latestVersion = parse(body['dist-tags'][distTag]); + if (!latestVersion) { + throw new Error(`Failed to get latest version from registry`); + } + + return latestVersion; +} + +/** + * Updates a project's \@aztec/* dependencies to the specific version + * @param projectPath - Path to Nodejs project + * @param aztecVersion - The version to update to + * @returns True if the project was updated + */ +export async function updateAztecDeps(projectPath: string, aztecVersion: SemVer, log: LogFn): Promise { + const pkg = await readPackageJson(projectPath); + if (!pkg.dependencies) { + return false; + } + + log(`\nUpdating @aztec packages to ${aztecVersion} in ${projectPath}`); + const version = '^' + aztecVersion.version; + let dirty = false; + for (const name of Object.keys(pkg.dependencies)) { + if (!name.startsWith('@aztec/')) { + continue; + } + + // different release schedule + if (name === '@aztec/aztec-ui') { + continue; + } + + if (pkg.dependencies[name] !== version) { + dirty = true; + pkg.dependencies[name] = version; + } + } + + if (dirty) { + const contents = JSON.stringify(pkg, null, 2) + '\n'; + await atomicUpdateFile(resolve(join(projectPath, 'package.json')), contents); + log(`${join(projectPath, 'package.json')} updated`); + return true; + } else { + log('No updates required'); + return false; + } +} + +/** + * Updates a project's yarn.lock or package-lock.json + * @param projectPath - Path to Nodejs project + */ +export function updateLockfile(projectPath: string): void { + const isYarn = existsSync(resolve(join(projectPath, 'yarn.lock'))); + + if (isYarn) { + spawnSync('yarn', { + cwd: projectPath, + stdio: 'inherit', + }); + } else { + spawnSync('npm', ['install'], { + cwd: projectPath, + stdio: 'inherit', + }); + } +} diff --git a/yarn-project/cli/src/update/update.ts b/yarn-project/cli/src/update/update.ts new file mode 100644 index 00000000000..9428a22ac2c --- /dev/null +++ b/yarn-project/cli/src/update/update.ts @@ -0,0 +1,65 @@ +/* eslint-disable jsdoc/require-jsdoc */ +import { DebugLogger, LogFn } from '@aztec/foundation/log'; + +import { resolve } from 'path'; +import { SemVer, coerce, gt, lt, parse } from 'semver'; + +import { createCompatibleClient } from '../client.js'; +import { GITHUB_TAG_PREFIX } from '../github.js'; +import { updateAztecNr } from './noir.js'; +import { getNewestVersion, readPackageJson, updateAztecDeps, updateLockfile } from './npm.js'; + +const SANDBOX_PACKAGE = '@aztec/aztec-sandbox'; + +export async function update( + projectPath: string, + contracts: string[], + pxeUrl: string, + log: LogFn, + debugLog: DebugLogger, +): Promise { + const latestSandboxVersion = await getNewestVersion(SANDBOX_PACKAGE, 'latest'); + let currentSandboxVersion = await getNpmSandboxVersion(projectPath); + + if (!currentSandboxVersion) { + currentSandboxVersion = await getRemoteSandboxVersion(pxeUrl, debugLog); + + if (currentSandboxVersion && lt(currentSandboxVersion, latestSandboxVersion)) { + log(` +Sandbox is older than version ${latestSandboxVersion}. If running in docker update it with the following command then restart the container: +docker pull aztecprotocol/aztec-sandbox:latest`); + return; + } + } + + if (!currentSandboxVersion) { + throw new Error('Sandbox version could not be detected'); + } + + // sanity check + if (gt(currentSandboxVersion, latestSandboxVersion)) { + throw new Error('Local sandbox version is newer than latest version.'); + } + + const changed = await updateAztecDeps(projectPath, latestSandboxVersion, log); + if (changed) { + updateLockfile(projectPath); + } + + for (const contract of contracts) { + await updateAztecNr(resolve(projectPath, contract), `${GITHUB_TAG_PREFIX}-v${latestSandboxVersion}`, log); + } +} + +async function getNpmSandboxVersion(projectPath: string): Promise { + const pkg = await readPackageJson(projectPath); + // use coerce instead of parse because it eliminates semver operators like ~ and ^ + return coerce(pkg.dependencies?.[SANDBOX_PACKAGE]); +} + +async function getRemoteSandboxVersion(pxeUrl: string, debugLog: DebugLogger): Promise { + const client = await createCompatibleClient(pxeUrl, debugLog); + const nodeInfo = await client.getNodeInfo(); + + return parse(nodeInfo.sandboxVersion); +} diff --git a/yarn-project/cli/src/utils.ts b/yarn-project/cli/src/utils.ts index 6c9d81cdae5..9973d9fa2fc 100644 --- a/yarn-project/cli/src/utils.ts +++ b/yarn-project/cli/src/utils.ts @@ -16,7 +16,7 @@ import { } from '@aztec/l1-artifacts'; import { LogId } from '@aztec/types'; -import { InvalidArgumentError } from 'commander'; +import { CommanderError, InvalidArgumentError } from 'commander'; import fs from 'fs'; import { mnemonicToAccount, privateKeyToAccount } from 'viem/accounts'; @@ -403,3 +403,31 @@ export function parseField(field: string): Fr { export function parseFields(fields: string[]): Fr[] { return fields.map(parseField); } + +/** + * Updates a file in place atomically. + * @param filePath - Path to file + * @param contents - New contents to write + */ +export async function atomicUpdateFile(filePath: string, contents: string) { + const tmpFilepath = filePath + '.tmp'; + try { + await fs.promises.writeFile(tmpFilepath, contents, { + // let's crash if the tmp file already exists + flag: 'wx', + }); + await fs.promises.rename(tmpFilepath, filePath); + } catch (e) { + if (e instanceof Error && 'code' in e && e.code === 'EEXIST') { + const commanderError = new CommanderError( + 1, + e.code, + `Temporary file already exists: ${tmpFilepath}. Delete this file and try again.`, + ); + commanderError.nestedError = e.message; + throw commanderError; + } else { + throw e; + } + } +} From 26febe2592a47bcc3bec804a0554321d6a0de1e1 Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Thu, 2 Nov 2023 09:28:13 +0000 Subject: [PATCH 10/17] docs: update message --- yarn-project/cli/src/update/update.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/yarn-project/cli/src/update/update.ts b/yarn-project/cli/src/update/update.ts index 9428a22ac2c..329fb05d1d0 100644 --- a/yarn-project/cli/src/update/update.ts +++ b/yarn-project/cli/src/update/update.ts @@ -27,7 +27,8 @@ export async function update( if (currentSandboxVersion && lt(currentSandboxVersion, latestSandboxVersion)) { log(` Sandbox is older than version ${latestSandboxVersion}. If running in docker update it with the following command then restart the container: -docker pull aztecprotocol/aztec-sandbox:latest`); +docker pull aztecprotocol/aztec-sandbox:latest +Once the container is restarted, run the \`aztec-cli update\` command again`); return; } } From 308169b45504fb196e263ae2eaad422d627cf378 Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Thu, 2 Nov 2023 09:33:27 +0000 Subject: [PATCH 11/17] feat: support pnpm --- yarn-project/cli/src/update/npm.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/yarn-project/cli/src/update/npm.ts b/yarn-project/cli/src/update/npm.ts index e97301cc122..35217875047 100644 --- a/yarn-project/cli/src/update/npm.ts +++ b/yarn-project/cli/src/update/npm.ts @@ -93,9 +93,15 @@ export async function updateAztecDeps(projectPath: string, aztecVersion: SemVer, */ export function updateLockfile(projectPath: string): void { const isYarn = existsSync(resolve(join(projectPath, 'yarn.lock'))); + const isPnpm = existsSync(resolve(join(projectPath, 'pnpm-lock.yaml'))); - if (isYarn) { - spawnSync('yarn', { + if (isPnpm) { + spawnSync('pnpm', ['install'], { + cwd: projectPath, + stdio: 'inherit', + }); + } else if (isYarn) { + spawnSync('yarn', ['install'], { cwd: projectPath, stdio: 'inherit', }); From 95be091a1b6fefef193ca91a86f089b360fdb3eb Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Thu, 2 Nov 2023 10:10:10 +0000 Subject: [PATCH 12/17] feat: enable corepack for update cmd --- yarn-project/cli/Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/yarn-project/cli/Dockerfile b/yarn-project/cli/Dockerfile index 1d8608c7861..29ef3f559ea 100644 --- a/yarn-project/cli/Dockerfile +++ b/yarn-project/cli/Dockerfile @@ -23,6 +23,8 @@ ENV XDG_CACHE_HOME /cache RUN mkdir /cache && chmod 777 /cache VOLUME [ "/cache" ] +RUN corepack enable + # run as non-root user RUN addgroup -S aztec && adduser -S aztec -G aztec USER aztec From 31a48b63196ef857229ea99ed7aba54b48816bf7 Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Thu, 2 Nov 2023 10:21:51 +0000 Subject: [PATCH 13/17] feat: add target version to update command --- yarn-project/cli/src/index.ts | 3 ++- yarn-project/cli/src/update/npm.ts | 2 +- yarn-project/cli/src/update/update.ts | 21 ++++++++++++++------- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/yarn-project/cli/src/index.ts b/yarn-project/cli/src/index.ts index 6edc3dcaec0..2b2ec4834c2 100644 --- a/yarn-project/cli/src/index.ts +++ b/yarn-project/cli/src/index.ts @@ -707,10 +707,11 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command { .description('Updates Nodejs and Noir dependencies') .argument('[projectPath]', 'Path to the project directory', process.cwd()) .option('--contract [paths...]', 'Paths to contracts to update dependencies', []) + .option('--sandbox-version ', 'The sandbox version to update to. Defaults to latest', 'latest') .addOption(pxeOption) .action(async (projectPath: string, options) => { const { contract } = options; - await update(projectPath, contract, options.rpcUrl, log, debugLogger); + await update(projectPath, contract, options.rpcUrl, options.sandboxVersion, log, debugLogger); }); compileContract(program, 'compile', log); diff --git a/yarn-project/cli/src/update/npm.ts b/yarn-project/cli/src/update/npm.ts index 35217875047..1deb01495b5 100644 --- a/yarn-project/cli/src/update/npm.ts +++ b/yarn-project/cli/src/update/npm.ts @@ -58,7 +58,7 @@ export async function updateAztecDeps(projectPath: string, aztecVersion: SemVer, } log(`\nUpdating @aztec packages to ${aztecVersion} in ${projectPath}`); - const version = '^' + aztecVersion.version; + const version = aztecVersion.version; let dirty = false; for (const name of Object.keys(pkg.dependencies)) { if (!name.startsWith('@aztec/')) { diff --git a/yarn-project/cli/src/update/update.ts b/yarn-project/cli/src/update/update.ts index 329fb05d1d0..f2c3771056c 100644 --- a/yarn-project/cli/src/update/update.ts +++ b/yarn-project/cli/src/update/update.ts @@ -7,7 +7,7 @@ import { SemVer, coerce, gt, lt, parse } from 'semver'; import { createCompatibleClient } from '../client.js'; import { GITHUB_TAG_PREFIX } from '../github.js'; import { updateAztecNr } from './noir.js'; -import { getNewestVersion, readPackageJson, updateAztecDeps, updateLockfile } from './npm.js'; +import { getNewestVersion as getLatestVersion, readPackageJson, updateAztecDeps, updateLockfile } from './npm.js'; const SANDBOX_PACKAGE = '@aztec/aztec-sandbox'; @@ -15,18 +15,25 @@ export async function update( projectPath: string, contracts: string[], pxeUrl: string, + sandboxVersion: string, log: LogFn, debugLog: DebugLogger, ): Promise { - const latestSandboxVersion = await getNewestVersion(SANDBOX_PACKAGE, 'latest'); + const targetSandboxVersion = + sandboxVersion === 'latest' ? await getLatestVersion(SANDBOX_PACKAGE, 'latest') : parse(sandboxVersion); + + if (!targetSandboxVersion) { + throw new Error(`Invalid aztec version ${sandboxVersion}`); + } + let currentSandboxVersion = await getNpmSandboxVersion(projectPath); if (!currentSandboxVersion) { currentSandboxVersion = await getRemoteSandboxVersion(pxeUrl, debugLog); - if (currentSandboxVersion && lt(currentSandboxVersion, latestSandboxVersion)) { + if (currentSandboxVersion && lt(currentSandboxVersion, targetSandboxVersion)) { log(` -Sandbox is older than version ${latestSandboxVersion}. If running in docker update it with the following command then restart the container: +Sandbox is older than version ${targetSandboxVersion}. If running in docker update it with the following command then restart the container: docker pull aztecprotocol/aztec-sandbox:latest Once the container is restarted, run the \`aztec-cli update\` command again`); return; @@ -38,17 +45,17 @@ Once the container is restarted, run the \`aztec-cli update\` command again`); } // sanity check - if (gt(currentSandboxVersion, latestSandboxVersion)) { + if (gt(currentSandboxVersion, targetSandboxVersion)) { throw new Error('Local sandbox version is newer than latest version.'); } - const changed = await updateAztecDeps(projectPath, latestSandboxVersion, log); + const changed = await updateAztecDeps(projectPath, targetSandboxVersion, log); if (changed) { updateLockfile(projectPath); } for (const contract of contracts) { - await updateAztecNr(resolve(projectPath, contract), `${GITHUB_TAG_PREFIX}-v${latestSandboxVersion}`, log); + await updateAztecNr(resolve(projectPath, contract), `${GITHUB_TAG_PREFIX}-v${targetSandboxVersion.version}`, log); } } From 4b3efc4bd76e11d1bc919db15c8bede4dccb8cf5 Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Thu, 2 Nov 2023 11:28:07 +0000 Subject: [PATCH 14/17] feat: show list of changes --- yarn-project/cli/src/update/common.ts | 16 ++++++++++++ yarn-project/cli/src/update/noir.ts | 28 ++++++++++++++------- yarn-project/cli/src/update/npm.ts | 36 ++++++++++++++++++--------- yarn-project/cli/src/update/update.ts | 29 ++++++++++++++++++--- 4 files changed, 84 insertions(+), 25 deletions(-) create mode 100644 yarn-project/cli/src/update/common.ts diff --git a/yarn-project/cli/src/update/common.ts b/yarn-project/cli/src/update/common.ts new file mode 100644 index 00000000000..6041c1266e4 --- /dev/null +++ b/yarn-project/cli/src/update/common.ts @@ -0,0 +1,16 @@ +/** + * Tracks changes to dependencies + */ +export type DependencyChanges = { + /** Which file was changed */ + file: string; + /** changes done to the file */ + dependencies: Array<{ + /** Name of the dependency being changed */ + name: string; + /** Previous version of the dependency */ + from: string; + /** New version of the dependency (after the update) */ + to: string; + }>; +}; diff --git a/yarn-project/cli/src/update/noir.ts b/yarn-project/cli/src/update/noir.ts index 77e5b660d78..a86703e4e0d 100644 --- a/yarn-project/cli/src/update/noir.ts +++ b/yarn-project/cli/src/update/noir.ts @@ -4,9 +4,10 @@ import { NoirPackageConfig, parseNoirPackageConfig } from '@aztec/foundation/noi import TOML from '@ltd/j-toml'; import { readFile } from 'fs/promises'; import { EOL } from 'os'; -import { join, resolve } from 'path'; +import { join, relative, resolve } from 'path'; import { atomicUpdateFile } from '../utils.js'; +import { DependencyChanges } from './common.js'; /** * Updates Aztec.nr dependencies @@ -14,12 +15,15 @@ import { atomicUpdateFile } from '../utils.js'; * @param tag - The tag to update to * @param log - Logging function */ -export async function updateAztecNr(contractPath: string, tag: string, log: LogFn) { +export async function updateAztecNr(contractPath: string, tag: string, log: LogFn): Promise { const configFilepath = resolve(join(contractPath, 'Nargo.toml')); const packageConfig = parseNoirPackageConfig(TOML.parse(await readFile(configFilepath, 'utf-8'))); - let dirty = false; + const changes: DependencyChanges = { + dependencies: [], + file: configFilepath, + }; - log(`\nUpdating Aztec.nr libraries to ${tag} in ${contractPath}`); + log(`Updating Aztec.nr libraries to ${tag} in ${relative(process.cwd(), changes.file)}`); for (const dep of Object.values(packageConfig.dependencies)) { if (!('git' in dep)) { continue; @@ -32,18 +36,24 @@ export async function updateAztecNr(contractPath: string, tag: string, log: LogF } if (dep.tag !== tag) { - dirty = true; + // show the Aztec.nr package name rather than the lib name + const dirParts = dep.directory?.split('/') ?? []; + changes.dependencies.push({ + name: dirParts.slice(-2).join('/'), + from: dep.tag, + to: tag, + }); + dep.tag = tag; } } - if (dirty) { + if (changes.dependencies.length > 0) { const contents = prettyPrintTOML(packageConfig); await atomicUpdateFile(configFilepath, contents); - log(`${join(contractPath, 'Nargo.toml')} updated`); - } else { - log('No updates required'); } + + return changes; } /** diff --git a/yarn-project/cli/src/update/npm.ts b/yarn-project/cli/src/update/npm.ts index 1deb01495b5..92ec6acd7a9 100644 --- a/yarn-project/cli/src/update/npm.ts +++ b/yarn-project/cli/src/update/npm.ts @@ -3,10 +3,11 @@ import { LogFn } from '@aztec/foundation/log'; import { spawnSync } from 'child_process'; import { existsSync } from 'fs'; import { readFile } from 'fs/promises'; -import { join, resolve } from 'path'; +import { join, relative, resolve } from 'path'; import { SemVer, parse } from 'semver'; import { atomicUpdateFile } from '../utils.js'; +import { DependencyChanges } from './common.js'; /** * Looks up a package.json file and returns its contents @@ -51,15 +52,24 @@ export async function getNewestVersion(packageName: string, distTag = 'latest'): * @param aztecVersion - The version to update to * @returns True if the project was updated */ -export async function updateAztecDeps(projectPath: string, aztecVersion: SemVer, log: LogFn): Promise { +export async function updateAztecDeps( + projectPath: string, + aztecVersion: SemVer, + log: LogFn, +): Promise { const pkg = await readPackageJson(projectPath); + const changes: DependencyChanges = { + file: resolve(join(projectPath, 'package.json')), + dependencies: [], + }; + if (!pkg.dependencies) { - return false; + return changes; } - log(`\nUpdating @aztec packages to ${aztecVersion} in ${projectPath}`); + log(`Updating @aztec packages to ${aztecVersion} in ${relative(process.cwd(), changes.file)}`); const version = aztecVersion.version; - let dirty = false; + for (const name of Object.keys(pkg.dependencies)) { if (!name.startsWith('@aztec/')) { continue; @@ -71,20 +81,22 @@ export async function updateAztecDeps(projectPath: string, aztecVersion: SemVer, } if (pkg.dependencies[name] !== version) { - dirty = true; + changes.dependencies.push({ + name, + from: pkg.dependencies[name], + to: version, + }); + pkg.dependencies[name] = version; } } - if (dirty) { + if (changes.dependencies.length > 0) { const contents = JSON.stringify(pkg, null, 2) + '\n'; await atomicUpdateFile(resolve(join(projectPath, 'package.json')), contents); - log(`${join(projectPath, 'package.json')} updated`); - return true; - } else { - log('No updates required'); - return false; } + + return changes; } /** diff --git a/yarn-project/cli/src/update/update.ts b/yarn-project/cli/src/update/update.ts index f2c3771056c..65ce8b57781 100644 --- a/yarn-project/cli/src/update/update.ts +++ b/yarn-project/cli/src/update/update.ts @@ -1,11 +1,12 @@ /* eslint-disable jsdoc/require-jsdoc */ import { DebugLogger, LogFn } from '@aztec/foundation/log'; -import { resolve } from 'path'; +import { relative, resolve } from 'path'; import { SemVer, coerce, gt, lt, parse } from 'semver'; import { createCompatibleClient } from '../client.js'; import { GITHUB_TAG_PREFIX } from '../github.js'; +import { DependencyChanges } from './common.js'; import { updateAztecNr } from './noir.js'; import { getNewestVersion as getLatestVersion, readPackageJson, updateAztecDeps, updateLockfile } from './npm.js'; @@ -49,13 +50,33 @@ Once the container is restarted, run the \`aztec-cli update\` command again`); throw new Error('Local sandbox version is newer than latest version.'); } - const changed = await updateAztecDeps(projectPath, targetSandboxVersion, log); - if (changed) { + const npmChanges = await updateAztecDeps(projectPath, targetSandboxVersion, log); + if (npmChanges.dependencies.length > 0) { updateLockfile(projectPath); } + const contractChanges: DependencyChanges[] = []; for (const contract of contracts) { - await updateAztecNr(resolve(projectPath, contract), `${GITHUB_TAG_PREFIX}-v${targetSandboxVersion.version}`, log); + contractChanges.push( + await updateAztecNr(resolve(projectPath, contract), `${GITHUB_TAG_PREFIX}-v${targetSandboxVersion.version}`, log), + ); + } + + printChanges(npmChanges, log); + + contractChanges.forEach(changes => { + printChanges(changes, log); + }); +} + +function printChanges(changes: DependencyChanges, log: LogFn): void { + log(`\nIn ${relative(process.cwd(), changes.file)}:`); + if (changes.dependencies.length === 0) { + log(' No changes'); + } else { + changes.dependencies.forEach(({ name, from, to }) => { + log(` Updated ${name} from ${from} to ${to}`); + }); } } From 45ec96ee021df54ae15b7ae8dca711504785b86e Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Thu, 2 Nov 2023 11:41:24 +0000 Subject: [PATCH 15/17] fix: better error messages --- yarn-project/cli/src/update/update.ts | 57 +++++++++++++++++++++------ 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/yarn-project/cli/src/update/update.ts b/yarn-project/cli/src/update/update.ts index 65ce8b57781..3cc5d02bed3 100644 --- a/yarn-project/cli/src/update/update.ts +++ b/yarn-project/cli/src/update/update.ts @@ -27,10 +27,10 @@ export async function update( throw new Error(`Invalid aztec version ${sandboxVersion}`); } - let currentSandboxVersion = await getNpmSandboxVersion(projectPath); + let currentSandboxVersion = await getNpmSandboxVersion(projectPath, log); if (!currentSandboxVersion) { - currentSandboxVersion = await getRemoteSandboxVersion(pxeUrl, debugLog); + currentSandboxVersion = await getRemoteSandboxVersion(pxeUrl, log, debugLog); if (currentSandboxVersion && lt(currentSandboxVersion, targetSandboxVersion)) { log(` @@ -57,9 +57,22 @@ Once the container is restarted, run the \`aztec-cli update\` command again`); const contractChanges: DependencyChanges[] = []; for (const contract of contracts) { - contractChanges.push( - await updateAztecNr(resolve(projectPath, contract), `${GITHUB_TAG_PREFIX}-v${targetSandboxVersion.version}`, log), - ); + try { + contractChanges.push( + await updateAztecNr( + resolve(projectPath, contract), + `${GITHUB_TAG_PREFIX}-v${targetSandboxVersion.version}`, + log, + ), + ); + } catch (err) { + if (err instanceof Error && 'code' in err && err.code === 'ENOENT') { + log(`No Nargo.toml found in ${relative(process.cwd(), contract)}. Skipping...`); + process.exit(1); + } + + throw err; + } } printChanges(npmChanges, log); @@ -80,15 +93,33 @@ function printChanges(changes: DependencyChanges, log: LogFn): void { } } -async function getNpmSandboxVersion(projectPath: string): Promise { - const pkg = await readPackageJson(projectPath); - // use coerce instead of parse because it eliminates semver operators like ~ and ^ - return coerce(pkg.dependencies?.[SANDBOX_PACKAGE]); +async function getNpmSandboxVersion(projectPath: string, log: LogFn): Promise { + try { + const pkg = await readPackageJson(projectPath); + // use coerce instead of parse because it eliminates semver operators like ~ and ^ + return coerce(pkg.dependencies?.[SANDBOX_PACKAGE]); + } catch (err) { + if (err instanceof Error && 'code' in err && err.code === 'ENOENT') { + log(`No package.json found in ${projectPath}`); + process.exit(1); + } + + throw err; + } } -async function getRemoteSandboxVersion(pxeUrl: string, debugLog: DebugLogger): Promise { - const client = await createCompatibleClient(pxeUrl, debugLog); - const nodeInfo = await client.getNodeInfo(); +async function getRemoteSandboxVersion(pxeUrl: string, log: LogFn, debugLog: DebugLogger): Promise { + try { + const client = await createCompatibleClient(pxeUrl, debugLog); + const nodeInfo = await client.getNodeInfo(); - return parse(nodeInfo.sandboxVersion); + return parse(nodeInfo.sandboxVersion); + } catch (err) { + if (err instanceof Error && err.message === 'fetch failed') { + log(`Could not connect to Sandbox running on ${pxeUrl}`); + process.exit(1); + } + + throw err; + } } From 53c1e3c8f7b1217daf1f9f24ad749ba430b13365 Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Thu, 2 Nov 2023 12:43:11 +0000 Subject: [PATCH 16/17] fix: skip lockfile update if unknown package manager --- yarn-project/cli/src/update/npm.ts | 7 +++++-- yarn-project/cli/src/update/update.ts | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/yarn-project/cli/src/update/npm.ts b/yarn-project/cli/src/update/npm.ts index 92ec6acd7a9..41dadbf518e 100644 --- a/yarn-project/cli/src/update/npm.ts +++ b/yarn-project/cli/src/update/npm.ts @@ -103,7 +103,8 @@ export async function updateAztecDeps( * Updates a project's yarn.lock or package-lock.json * @param projectPath - Path to Nodejs project */ -export function updateLockfile(projectPath: string): void { +export function updateLockfile(projectPath: string, log: LogFn): void { + const isNpm = existsSync(resolve(join(projectPath, 'package-lock.json'))); const isYarn = existsSync(resolve(join(projectPath, 'yarn.lock'))); const isPnpm = existsSync(resolve(join(projectPath, 'pnpm-lock.yaml'))); @@ -117,10 +118,12 @@ export function updateLockfile(projectPath: string): void { cwd: projectPath, stdio: 'inherit', }); - } else { + } else if (isNpm) { spawnSync('npm', ['install'], { cwd: projectPath, stdio: 'inherit', }); + } else { + log(`No lockfile found in ${projectPath}. Skipping lockfile update...`); } } diff --git a/yarn-project/cli/src/update/update.ts b/yarn-project/cli/src/update/update.ts index 3cc5d02bed3..3a5940a44da 100644 --- a/yarn-project/cli/src/update/update.ts +++ b/yarn-project/cli/src/update/update.ts @@ -52,7 +52,7 @@ Once the container is restarted, run the \`aztec-cli update\` command again`); const npmChanges = await updateAztecDeps(projectPath, targetSandboxVersion, log); if (npmChanges.dependencies.length > 0) { - updateLockfile(projectPath); + updateLockfile(projectPath, log); } const contractChanges: DependencyChanges[] = []; From ce195476765bd0398149de721c2e18964d7a7c02 Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Thu, 2 Nov 2023 12:46:29 +0000 Subject: [PATCH 17/17] chore: delete unused code --- yarn-project/cli/src/github.ts | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/yarn-project/cli/src/github.ts b/yarn-project/cli/src/github.ts index a1dd078e355..0486c382369 100644 --- a/yarn-project/cli/src/github.ts +++ b/yarn-project/cli/src/github.ts @@ -1,34 +1,3 @@ export const GITHUB_OWNER = 'AztecProtocol'; export const GITHUB_REPO = 'aztec-packages'; export const GITHUB_TAG_PREFIX = 'aztec-packages'; - -/** - * A Github release - */ -export type GithubRelease = { - /** The Git tag of the release */ - tagName: string; - /** When it was created as an ISO 8601 string */ - createdAt: string; -}; - -/** - * Gets recent releases from the Aztec packages repository. - * @returns a sorted list of Aztec packages releases (newest first) - */ -export async function getRecentAztecReleases(): Promise> { - const response = await fetch(`https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases`); - if (!response.ok) { - return []; - } - - const allReleases: Array = await response.json(); - const aztecPackagesReleases = allReleases.filter((release: any) => release.tag_name.startsWith(GITHUB_TAG_PREFIX)); - const releases = aztecPackagesReleases.map((release: any) => ({ - tagName: release.tag_name, - createdAt: release.created_at, - })); - - releases.sort((a, b) => (a.createdAt < b.createdAt ? 1 : a.createdAt > b.createdAt ? -1 : 0)); - return releases; -}