Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(builder): Add preset reference in source/package name #415

Merged
merged 5 commits into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions packages/builder/src/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,75 @@ export type CopyPackageOpts = {
recursive?: boolean;
};

export interface CannonPackageReference {
name: string;
version: string;
preset: string;

includesPreset: boolean;
formatted(): string;
}

/**
* Used to format any reference to a cannon package and split it into it's core parts
*/
export class PackageReference implements CannonPackageReference {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dbeal-eth I added this package reference class and added it to provision and import steps for parsing out name, version and preset from package sources.

private ref: string;
includesPreset: boolean;

constructor(ref: string) {
this.ref = ref;
this.includesPreset = false;
}

/**
* Anything before the colon or an @ (if no version is present) is the package name.
*/
get name() {
if (this.ref.indexOf(':') !== -1) {
return this.ref.substring(0, this.ref.indexOf(':'));
} else if (this.ref.indexOf('@') !== -1) {
return this.ref.substring(0, this.ref.indexOf('@'));
} else {
return this.ref;
}
}

/**
* Anything between the colon and the @ is the package version
* Defaults to latest if not specified in reference
*/
get version() {
if (this.ref.indexOf('@') !== -1 && this.ref.indexOf(':') !== -1) {
return this.ref.substring(this.ref.indexOf(':') + 1, this.ref.indexOf('@'));
} else if (this.ref.indexOf(':') !== -1) {
return this.ref.substring(this.ref.indexOf(':') + 1);
} else {
return 'latest';
}
}

/**
* Anything after the @ is the package preset
* Defaults to main if not specified in reference
*/
get preset() {
if (this.ref.indexOf('@') !== -1) {
this.includesPreset = true;
return this.ref.substring(this.ref.indexOf('@') + 1);
} else {
return 'main';
}
}

/**
* Returns default format name:version
*/
formatted() {
return `${this.name}:${this.version}`;
}
}

/**
* Iterate Depth-First-Search over the given DeploymentInfo and its dependencies, and execute the given `action` function. Postfix execution (aka, `action` is only executed after dependants are completed).
* Each package executes one at a time. No paralellization.
Expand Down
6 changes: 3 additions & 3 deletions packages/builder/src/schemas.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const argsUnion = z.union([argtype, argtype2, argtype3, argtype4]);
// <%= string interpolation %>, step.names or property.names, packages:versions
const interpolatedRegex = RegExp(/^<%=\s\w+.+[\w()[\]-]+\s%>$/, 'i');
const stepRegex = RegExp(/^[\w-]+\.[.\w-]+$/, 'i');
const packageRegex = RegExp(/^[\w-]+\.*:*[\w.-]+$/, 'i');
const packageRegex = RegExp(/^[\w-]+\.*:*[\w.-@]+$/, 'i');
const jsonAbiPathRegex = RegExp(/^(?!.*\.d?$).*\.json?$/, 'i');

// This regex matches artifact names which are just capitalized words like solidity contract names
Expand Down Expand Up @@ -147,7 +147,7 @@ export const importSchema = z
source: z.string().refine(
(val) => !!val.match(packageRegex) || !!val.match(stepRegex) || !!val.match(interpolatedRegex),
(val) => ({
message: `Source value: ${val} must match package format "package:version" or step format "import.Contract" or be an interpolated value`,
message: `Source value: ${val} must match package formats: "package:version" or "package:version@preset" or step format "import.Contract" or be an interpolated value`,
})
),
})
Expand Down Expand Up @@ -343,7 +343,7 @@ export const provisionSchema = z
source: z.string().refine(
(val) => !!val.match(packageRegex) || !!val.match(interpolatedRegex),
(val) => ({
message: `Source value: ${val} must match package format "package:version" or be an interpolated value`,
message: `Source value: ${val} must match package formats: "package:version" or "package:version@preset" or be an interpolated value`,
})
),
})
Expand Down
78 changes: 77 additions & 1 deletion packages/builder/src/steps/import.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe('steps/import.ts', () => {
});

expect(result).toStrictEqual({
source: 'a',
source: 'a:latest',
chainId: 1234,
preset: 'c',
depends: [],
Expand Down Expand Up @@ -58,6 +58,7 @@ describe('steps/import.ts', () => {

it('works properly', async () => {
await registry.publish(['hello:1.0.0'], '1234-main', 'https://something.com', '');
await registry.publish(['hello:latest'], '1234-main', 'https://something.com', '');

jest.mocked(fakeRuntime.readDeploy).mockResolvedValue({
generator: 'cannon test',
Expand Down Expand Up @@ -116,6 +117,81 @@ describe('steps/import.ts', () => {
},
},
});

const withPreset = await action.exec(
fakeRuntime,
fakeCtx,
{ source: 'hello:1.0.0@main' },
{ name: 'package', version: '1.0.0', currentLabel: 'import.something' }
);

expect(withPreset).toStrictEqual({
imports: {
something: {
url: 'https://something.com',
contracts: {
Woot: {
address: '0xfoobar',
abi: [],
deployTxnHash: '0x',
contractName: 'Woot',
sourceName: 'Woot.sol',
deployedOn: 'contract.Woot',
},
},
},
},
});

const withoutVersion = await action.exec(
fakeRuntime,
fakeCtx,
{ source: 'hello@main' },
{ name: 'package', version: '1.0.0', currentLabel: 'import.something' }
);

expect(withoutVersion).toStrictEqual({
imports: {
something: {
url: 'https://something.com',
contracts: {
Woot: {
address: '0xfoobar',
abi: [],
deployTxnHash: '0x',
contractName: 'Woot',
sourceName: 'Woot.sol',
deployedOn: 'contract.Woot',
},
},
},
},
});

const onlyName = await action.exec(
fakeRuntime,
fakeCtx,
{ source: 'hello' },
{ name: 'package', version: '1.0.0', currentLabel: 'import.something' }
);

expect(onlyName).toStrictEqual({
imports: {
something: {
url: 'https://something.com',
contracts: {
Woot: {
address: '0xfoobar',
abi: [],
deployTxnHash: '0x',
contractName: 'Woot',
sourceName: 'Woot.sol',
deployedOn: 'contract.Woot',
},
},
},
},
});
});
});
});
41 changes: 29 additions & 12 deletions packages/builder/src/steps/import.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import _ from 'lodash';
import Debug from 'debug';
import { yellow, bold } from 'chalk';

import { z } from 'zod';
import { importSchema } from '../schemas.zod';
Expand All @@ -8,6 +9,7 @@ import { ChainBuilderContext, ChainArtifacts, ChainBuilderContextWithHelpers, Pa
import { getOutputs } from '../builder';
import { ChainDefinition } from '../definition';
import { ChainBuilderRuntime } from '../runtime';
import { PackageReference } from '../package';

const debug = Debug('cannon:builder:import');

Expand All @@ -33,11 +35,12 @@ export default {
async getState(runtime: ChainBuilderRuntime, ctx: ChainBuilderContextWithHelpers, config: Config) {
const cfg = this.configInject(ctx, config);

const preset = config.preset ?? 'main';
const chainId = config.chainId ?? runtime.chainId;
const source = cfg.source;
const preset = cfg.preset;
const chainId = cfg.chainId ?? runtime.chainId;

debug('resolved pkg', cfg.source, `${chainId}-${preset}`);
const url = await runtime.registry.getUrl(cfg.source, `${chainId}-${preset}`);
debug('resolved pkg', source, `${chainId}-${preset}`);
const url = await runtime.registry.getUrl(source, `${chainId}-${preset}`);

return {
url,
Expand All @@ -47,8 +50,20 @@ export default {
configInject(ctx: ChainBuilderContextWithHelpers, config: Config) {
config = _.cloneDeep(config);

config.source = _.template(config.source)(ctx);
config.preset = _.template(config.preset)(ctx) || 'main';
const packageRef = new PackageReference(_.template(config.source)(ctx));

// If both definitions of a preset exist, its a user error.
if (config.preset && packageRef.includesPreset) {
console.warn(
yellow(
bold(`Duplicate preset definitions in source name "${config.source}" and in preset definition: "${config.preset}"`)
)
);
console.warn(yellow(bold(`Defaulting to preset definition "${config.preset}"...`)));
}

config.source = packageRef.formatted();
config.preset = _.template(config.preset)(ctx) || packageRef.preset;

return config;
},
Expand All @@ -62,30 +77,32 @@ export default {
const importLabel = packageState.currentLabel?.split('.')[1] || '';
debug('exec', config);

const packageRef = config.source.includes(':') ? config.source : `${config.source}:latest`;
const preset = config.preset ?? 'main';
const packageRef = new PackageReference(config.source);
const source = packageRef.formatted();

const preset = config.preset || packageRef.preset;
const chainId = config.chainId ?? runtime.chainId;

// try to load the chain definition specific to this chain
// otherwise, load the top level definition
const deployInfo = await runtime.readDeploy(packageRef, preset, chainId);
const deployInfo = await runtime.readDeploy(source, preset, chainId);

if (!deployInfo) {
throw new Error(
`deployment not found: ${packageRef}. please make sure it exists for the cannon network and ${preset} preset.`
`deployment not found: ${source}. please make sure it exists for the cannon network and ${preset} preset.`
);
}

if (deployInfo.status === 'partial') {
throw new Error(
`deployment status is incomplete for ${packageRef}. cannot generate artifacts safely. please complete deployment to continue import.`
`deployment status is incomplete for ${source}. cannot generate artifacts safely. please complete deployment to continue import.`
);
}

return {
imports: {
[importLabel]: {
url: (await runtime.registry.getUrl(packageRef, `${chainId}-${preset}`))!, // todo: duplication
url: (await runtime.registry.getUrl(source, `${chainId}-${preset}`))!, // todo: duplication
...(await getOutputs(runtime, new ChainDefinition(deployInfo.def), deployInfo.state))!,
},
},
Expand Down
2 changes: 1 addition & 1 deletion packages/builder/src/steps/provision.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ describe('steps/provision.ts', () => {
);

expect(result).toStrictEqual({
source: 'a',
source: 'a:latest',
sourcePreset: 'main',
targetPreset: 'with-who',
});
Expand Down
Loading