Skip to content

Commit

Permalink
feat: add cli command update aztec dependencies (#3128)
Browse files Browse the repository at this point in the history
This PR adds a new command to aztec-cli to update a contract's Aztec.nr
and `@aztec` dependencies.

LE: This command was refactored to `update` and now updates both
Nargo.toml _and_ package.json.

Fix #2870 

# Checklist:
Remove the checklist to signal you've completed it. Enable auto-merge if
the PR is ready to merge.
- [ ] If the pull request requires a cryptography review (e.g.
cryptographic algorithm implementations) I have added the 'crypto' tag.
- [x] I have reviewed my diff in github, line by line and removed
unexpected formatting changes, testing logs, or commented-out code.
- [x] Every change is related to the PR description.
- [x] I have
[linked](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue)
this pull request to relevant issues (if any exist).
  • Loading branch information
alexghr authored Nov 2, 2023
1 parent 272eda1 commit 0c05d8b
Show file tree
Hide file tree
Showing 25 changed files with 501 additions and 91 deletions.
1 change: 0 additions & 1 deletion yarn-project/acir-simulator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/cli/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions yarn-project/cli/aztec-cli
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions yarn-project/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions yarn-project/cli/src/github.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const GITHUB_OWNER = 'AztecProtocol';
export const GITHUB_REPO = 'aztec-packages';
export const GITHUB_TAG_PREFIX = 'aztec-packages';
19 changes: 16 additions & 3 deletions yarn-project/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 { update } from './update/update.js';
import {
deployAztecContracts,
getContractArtifact,
Expand Down Expand Up @@ -70,9 +71,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 <string>', 'URL of the PXE')
.env('PXE_URL')
Expand Down Expand Up @@ -649,7 +650,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
Expand Down Expand Up @@ -701,6 +702,18 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
log(`${selector}`);
});

program
.command('update')
.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 <semver>', '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, options.sandboxVersion, log, debugLogger);
});

compileContract(program, 'compile', log);
generateTypescriptInterface(program, 'generate-typescript', log);
generateNoirInterface(program, 'generate-noir-interface', log);
Expand Down
5 changes: 2 additions & 3 deletions yarn-project/cli/src/unbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand Down
16 changes: 16 additions & 0 deletions yarn-project/cli/src/update/common.ts
Original file line number Diff line number Diff line change
@@ -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;
}>;
};
79 changes: 79 additions & 0 deletions yarn-project/cli/src/update/noir.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
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, relative, resolve } from 'path';

import { atomicUpdateFile } from '../utils.js';
import { DependencyChanges } from './common.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): Promise<DependencyChanges> {
const configFilepath = resolve(join(contractPath, 'Nargo.toml'));
const packageConfig = parseNoirPackageConfig(TOML.parse(await readFile(configFilepath, 'utf-8')));
const changes: DependencyChanges = {
dependencies: [],
file: configFilepath,
};

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;
}

// remove trailing slash
const gitUrl = dep.git.toLowerCase().replace(/\/$/, '');
if (gitUrl !== 'https://github.com/aztecprotocol/aztec-packages') {
continue;
}

if (dep.tag !== tag) {
// 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 (changes.dependencies.length > 0) {
const contents = prettyPrintTOML(packageConfig);
await atomicUpdateFile(configFilepath, contents);
}

return changes;
}

/**
* 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',
},
);
}
129 changes: 129 additions & 0 deletions yarn-project/cli/src/update/npm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { LogFn } from '@aztec/foundation/log';

import { spawnSync } from 'child_process';
import { existsSync } from 'fs';
import { readFile } from 'fs/promises';
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
* @param projectPath - Path to Nodejs project
* @returns The parsed package.json
*/
export async function readPackageJson(projectPath: string): Promise<{
/** dependencies */
dependencies?: Record<string, string>;
}> {
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<SemVer> {
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<DependencyChanges> {
const pkg = await readPackageJson(projectPath);
const changes: DependencyChanges = {
file: resolve(join(projectPath, 'package.json')),
dependencies: [],
};

if (!pkg.dependencies) {
return changes;
}

log(`Updating @aztec packages to ${aztecVersion} in ${relative(process.cwd(), changes.file)}`);
const version = aztecVersion.version;

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) {
changes.dependencies.push({
name,
from: pkg.dependencies[name],
to: version,
});

pkg.dependencies[name] = version;
}
}

if (changes.dependencies.length > 0) {
const contents = JSON.stringify(pkg, null, 2) + '\n';
await atomicUpdateFile(resolve(join(projectPath, 'package.json')), contents);
}

return changes;
}

/**
* Updates a project's yarn.lock or package-lock.json
* @param projectPath - Path to Nodejs project
*/
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')));

if (isPnpm) {
spawnSync('pnpm', ['install'], {
cwd: projectPath,
stdio: 'inherit',
});
} else if (isYarn) {
spawnSync('yarn', ['install'], {
cwd: projectPath,
stdio: 'inherit',
});
} else if (isNpm) {
spawnSync('npm', ['install'], {
cwd: projectPath,
stdio: 'inherit',
});
} else {
log(`No lockfile found in ${projectPath}. Skipping lockfile update...`);
}
}
Loading

0 comments on commit 0c05d8b

Please sign in to comment.