Skip to content

Commit

Permalink
feat: re-introducing update command (#5946)
Browse files Browse the repository at this point in the history
  • Loading branch information
benesjan authored Apr 23, 2024
1 parent 4465e3b commit 13153d0
Show file tree
Hide file tree
Showing 10 changed files with 408 additions and 29 deletions.
47 changes: 43 additions & 4 deletions yarn-project/noir-compiler/src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,55 @@
#!/usr/bin/env node
import { createConsoleLogger } from '@aztec/foundation/log';

import { Command } from 'commander';

import { addNoirCompilerCommanderActions } from './cli/add_noir_compiler_commander_actions.js';
import { Command, Option } from 'commander';
import { lookup } from 'dns/promises';
import { dirname } from 'path';

const program = new Command();
const log = createConsoleLogger('aztec:compiler-cli');

/**
* If we can successfully resolve 'host.docker.internal', then we are running in a container, and we should treat
* localhost as being host.docker.internal.
*/
const getLocalhost = () =>
lookup('host.docker.internal')
.then(() => 'host.docker.internal')
.catch(() => 'localhost');

const LOCALHOST = await getLocalhost();

const main = async () => {
const pxeOption = new Option('-u, --rpc-url <string>', 'URL of the PXE')
.env('PXE_URL')
.default(`http://${LOCALHOST}:8080`)
.makeOptionMandatory(true);

program.name('aztec-compile');
addNoirCompilerCommanderActions(program, log);
program
.command('codegen')
.argument('<noir-abi-path>', 'Path to the Noir ABI or project dir.')
.option('-o, --outdir <path>', 'Output folder for the generated code.')
.option('--force', 'Force code generation even when the contract has not changed.')
.description('Validates and generates an Aztec Contract ABI from Noir ABI.')
.action(async (noirAbiPath: string, { outdir, force }) => {
const { generateCode } = await import('./cli/codegen.js');
generateCode(outdir || dirname(noirAbiPath), noirAbiPath, { force });
});

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('--aztec-version <semver>', 'The version to update Aztec packages to. Defaults to latest', 'latest')
.addOption(pxeOption)
.action(async (projectPath: string, options) => {
const { update } = await import('./cli/update/update.js');
const { contract, aztecVersion, rpcUrl } = options;
await update(projectPath, contract, rpcUrl, aztecVersion, log);
});

await program.parseAsync(process.argv);
};

Expand Down

This file was deleted.

1 change: 0 additions & 1 deletion yarn-project/noir-compiler/src/cli/index.ts

This file was deleted.

16 changes: 16 additions & 0 deletions yarn-project/noir-compiler/src/cli/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;
}>;
};
3 changes: 3 additions & 0 deletions yarn-project/noir-compiler/src/cli/update/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';
57 changes: 57 additions & 0 deletions yarn-project/noir-compiler/src/cli/update/noir.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { type LogFn } from '@aztec/foundation/log';
import { parseNoirPackageConfig } from '@aztec/foundation/noir';

import TOML from '@iarna/toml';
import { readFile } from 'fs/promises';
import { join, relative, resolve } from 'path';

import { type DependencyChanges } from './common.js';
import { atomicUpdateFile, prettyPrintNargoToml } 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): 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;
dep.directory = dep.directory?.replace('yarn-project/', 'noir-projects/');
}
}

if (changes.dependencies.length > 0) {
const contents = prettyPrintNargoToml(packageConfig);
await atomicUpdateFile(configFilepath, contents);
}

return changes;
}
154 changes: 154 additions & 0 deletions yarn-project/noir-compiler/src/cli/update/npm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { type 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 { type SemVer, parse } from 'semver';

import { type DependencyChanges } from './common.js';
import { atomicUpdateFile } from './utils.js';

const deprecatedNpmPackages = new Set<string>(['@aztec/cli', '@aztec/aztec-sandbox']);
const npmDeprecationMessage = `
The following packages have been deprecated and will no longer be updated on the npm registry:
${Array.from(deprecatedNpmPackages)
.map(pkg => ` - ${pkg}`)
.join('\n')}
Remove them from package.json
`;

/**
* 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>;
/** devDependencies */
devDependencies?: 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: [],
};

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

let detectedDeprecatedPackages = false;

for (const depType of ['dependencies', 'devDependencies'] as const) {
const dependencies = pkg[depType];
if (!dependencies) {
continue;
}

for (const name of Object.keys(dependencies)) {
if (!name.startsWith('@aztec/')) {
continue;
}

// different release schedule
if (name === '@aztec/aztec-ui') {
continue;
}

if (deprecatedNpmPackages.has(name)) {
detectedDeprecatedPackages = true;
continue;
}

if (dependencies[name] !== version) {
changes.dependencies.push({
name,
from: dependencies[name],
to: version,
});

dependencies[name] = version;
}
}
}

if (detectedDeprecatedPackages) {
log(npmDeprecationMessage);
}

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

import { relative, resolve } from 'path';
import { parse } from 'semver';

import { type DependencyChanges } from './common.js';
import { GITHUB_TAG_PREFIX } from './github.js';
import { updateAztecNr } from './noir.js';
import { getNewestVersion, updateAztecDeps, updateLockfile } from './npm.js';

const AZTECJS_PACKAGE = '@aztec/aztec.js';
const UPDATE_DOCS_URL = 'https://docs.aztec.network/developers/updating';

export async function update(
projectPath: string,
contracts: string[],
pxeUrl: string,
aztecVersion: string,
log: LogFn,
): Promise<void> {
const targetAztecVersion =
aztecVersion === 'latest' ? await getNewestVersion(AZTECJS_PACKAGE, 'latest') : parse(aztecVersion);

if (!targetAztecVersion) {
throw new Error(`Invalid aztec version ${aztecVersion}`);
}

const projectDependencyChanges: DependencyChanges[] = [];
try {
const npmChanges = await updateAztecDeps(resolve(process.cwd(), projectPath), targetAztecVersion, log);
if (npmChanges.dependencies.length > 0) {
updateLockfile(projectPath, log);
}

projectDependencyChanges.push(npmChanges);
} catch (err) {
if (err instanceof Error && 'code' in err && err.code === 'ENOENT') {
log(`No package.json found in ${projectPath}. Skipping npm update...`);
} else {
throw err;
}
}

for (const contract of contracts) {
try {
projectDependencyChanges.push(
await updateAztecNr(
resolve(process.cwd(), projectPath, contract),
`${GITHUB_TAG_PREFIX}-v${targetAztecVersion.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...`);
} else {
throw err;
}
}
}

log(`To update Docker containers follow instructions at ${UPDATE_DOCS_URL}`);

projectDependencyChanges.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}`);
});
}
}
Loading

0 comments on commit 13153d0

Please sign in to comment.