diff --git a/packages/cli/src/__tests__/e2e/deploy.spec.ts b/packages/cli/src/__tests__/e2e/deploy.spec.ts index 9c561370df..1de6bf1fab 100644 --- a/packages/cli/src/__tests__/e2e/deploy.spec.ts +++ b/packages/cli/src/__tests__/e2e/deploy.spec.ts @@ -8,9 +8,6 @@ import { providers } from "@polywrap/test-env-js"; import { GetPathToCliTestFiles } from "@polywrap/test-cases"; -import { PolywrapClient } from "@polywrap/client-js"; -import { ethereumPlugin, Connections, Connection } from "@polywrap/ethereum-plugin-js"; -import { Wallet } from "@ethersproject/wallet"; import path from "path"; import fs from "fs"; import yaml from "js-yaml"; @@ -35,78 +32,27 @@ const testCaseRoot = path.join(GetPathToCliTestFiles(), "wasm/deploy"); const getTestCaseDir = (index: number) => path.join(testCaseRoot, testCases[index]); -const setup = async (domainNames: string[]) => { +const setup = async () => { await stopTestEnvironment(); await initTestEnvironment(); // Wait a little longer just in case await new Promise((resolve) => setTimeout(resolve, 3000)); - const ensAddress = ensAddresses.ensAddress - const resolverAddress = ensAddresses.resolverAddress - const registrarAddress = ensAddresses.registrarAddress - const signer = new Wallet("0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d"); - // Setup environment variables process.env = { ...process.env, + IPFS_GATEWAY_URI: providers.ipfs, DOMAIN_NAME: "test1.eth", - ENS_REG_ADDR: ensAddress + ENS_REG_ADDR: ensAddresses.ensAddress, + ENS_REGISTRAR_ADDR: ensAddresses.registrarAddress, + ENS_RESOLVER_ADDR: ensAddresses.resolverAddress, }; - - const ethereumPluginUri = "wrap://ens/ethereum.polywrap.eth" - const client = new PolywrapClient({ - plugins: [ - { - uri: ethereumPluginUri, - plugin: ethereumPlugin({ - connections: new Connections({ - networks: { - testnet: new Connection({ - provider: providers.ethereum, - signer, - }), - }, - defaultNetwork: "testnet", - }), - }), - } - ], - }); - - const ensWrapperUri = `fs/${path.join( - path.dirname(require.resolve("@polywrap/test-env-js")), - "wrappers", "ens" - )}`; - - for await (const domainName of domainNames) { - const result = await client.invoke({ - uri: ensWrapperUri, - method: "registerDomainAndSubdomainsRecursively", - args: { - domain: domainName, - owner: signer.address, - registrarAddress, - registryAddress: ensAddress, - resolverAddress, - ttl: "0", - connection: { - networkNameOrChainId: "testnet", - }, - }, - }); - - if (result.error) { - throw Error( - `Failed to register ${domainName}: ${result.error.message}` - ); - } - } } describe("e2e tests for deploy command", () => { beforeAll(async () => { - await setup(["test1.eth", "test2.eth", "test3.eth"]) + await setup() for (let i = 0; i < testCases.length; ++i) { await runCLI( @@ -154,10 +100,22 @@ describe("e2e tests for deploy command", () => { expect(error).toBeFalsy(); expect(code).toEqual(0); expect(sanitizedOutput).toContain( - "Successfully executed stage 'ipfs_deploy'" + "Successfully executed step 'ipfs_deploy'" + ); + expect(sanitizedOutput).toContain( + "Successfully executed step 'from_deploy'" + ); + expect(sanitizedOutput).toContain( + "Successfully executed step 'from_deploy2'" + ); + expect(sanitizedOutput).toContain( + "Successfully executed 'fs_to_ens' deployment sequence" + ); + expect(sanitizedOutput).toContain( + "Successfully executed step 'from_uri'" ); expect(sanitizedOutput).toContain( - "Successfully executed stage 'from_deploy'" + "Successfully executed 'ipfs_to_ens' deployment sequence" ); }); @@ -204,51 +162,140 @@ describe("e2e tests for deploy command", () => { expect(yamlOutputFileContents).toMatchObject(jsonOutputFileContents); expect(jsonOutputFileContents).toMatchObject([ { - id: "ipfs_deploy", - name: "ipfs_deploy", - input: { - uri: "wrap://fs/./build" - }, - result: "wrap://ipfs/QmT5nBb8xwrfZnmFNRZexmrebzaaxW7CPfh1ZznQ6zsVaG" - }, - { - id: "ipfs_deploy.from_deploy", - name: "from_deploy", - input: { - uri: "wrap://ipfs/QmT5nBb8xwrfZnmFNRZexmrebzaaxW7CPfh1ZznQ6zsVaG", - config: { - domainName: "test1.eth", - provider: "http://localhost:8545", - ensRegistryAddress: "0xe78A0F7E598Cc8b0Bb87894B0F60dD2a88d6a8Ab" - } - }, - result: "wrap://ens/test1.eth" - }, - { - id: "ipfs_deploy.from_deploy2", - name: "from_deploy2", - input: { - uri: "wrap://ipfs/QmT5nBb8xwrfZnmFNRZexmrebzaaxW7CPfh1ZznQ6zsVaG", - config: { - domainName: "test2.eth", - provider: "http://localhost:8545", - ensRegistryAddress: "0xe78A0F7E598Cc8b0Bb87894B0F60dD2a88d6a8Ab" + "name": "fs_to_ens", + "steps": [ + { + "name": "ens_register", + "id": "fs_to_ens.ens_register", + "input": { + "_config": { + "uri": "wrap://ens/test1.eth", + "authority": "ens", + "path": "test1.eth" + } + }, + "result": { + "_config": { + "uri": "wrap://ens/testnet/test1.eth", + "authority": "ens", + "path": "testnet/test1.eth" + } + } + }, + { + "name": "ens_register2", + "id": "fs_to_ens.ens_register2", + "input": { + "_config": { + "uri": "wrap://ens/test2.eth", + "authority": "ens", + "path": "test2.eth" + } + }, + "result": { + "_config": { + "uri": "wrap://ens/testnet/test2.eth", + "authority": "ens", + "path": "testnet/test2.eth" + } + } + }, + { + "name": "ipfs_deploy", + "id": "fs_to_ens.ipfs_deploy", + "input": { + "_config": { + "uri": "wrap://fs/./build", + "authority": "fs", + "path": "./build" + } + }, + "result": { + "_config": { + "uri": "wrap://ipfs/QmT5nBb8xwrfZnmFNRZexmrebzaaxW7CPfh1ZznQ6zsVaG", + "authority": "ipfs", + "path": "QmT5nBb8xwrfZnmFNRZexmrebzaaxW7CPfh1ZznQ6zsVaG" + } + } + }, + { + "name": "from_deploy", + "id": "fs_to_ens.from_deploy", + "input": { + "_config": { + "uri": "wrap://ipfs/QmT5nBb8xwrfZnmFNRZexmrebzaaxW7CPfh1ZznQ6zsVaG", + "authority": "ipfs", + "path": "QmT5nBb8xwrfZnmFNRZexmrebzaaxW7CPfh1ZznQ6zsVaG" + } + }, + "result": { + "_config": { + "uri": "wrap://ens/testnet/test1.eth", + "authority": "ens", + "path": "testnet/test1.eth" + } + } + }, + { + "name": "from_deploy2", + "id": "fs_to_ens.from_deploy2", + "input": { + "_config": { + "uri": "wrap://ipfs/QmT5nBb8xwrfZnmFNRZexmrebzaaxW7CPfh1ZznQ6zsVaG", + "authority": "ipfs", + "path": "QmT5nBb8xwrfZnmFNRZexmrebzaaxW7CPfh1ZznQ6zsVaG" + } + }, + "result": { + "_config": { + "uri": "wrap://ens/testnet/test2.eth", + "authority": "ens", + "path": "testnet/test2.eth" + } + } } - }, - result: "wrap://ens/test2.eth" + ] }, { - id: "from_uri", - name: "from_uri", - input: { - uri: "wrap://ipfs/QmVdDR6QtigTt38Xwpj2Ki73X1AyZn5WRCreBCJq1CEtpF", - config: { - domainName: "test3.eth", - provider: "http://localhost:8545", - ensRegistryAddress: "0xe78A0F7E598Cc8b0Bb87894B0F60dD2a88d6a8Ab" + "name": "ipfs_to_ens", + "steps": [ + { + "name": "ens_register", + "id": "ipfs_to_ens.ens_register", + "input": { + "_config": { + "uri": "wrap://ens/test3.eth", + "authority": "ens", + "path": "test3.eth" + } + }, + "result": { + "_config": { + "uri": "wrap://ens/testnet/test3.eth", + "authority": "ens", + "path": "testnet/test3.eth" + } + } + }, + { + "name": "from_uri", + "id": "ipfs_to_ens.from_uri", + "input": { + "_config": { + "uri": "wrap://ipfs/QmVdDR6QtigTt38Xwpj2Ki73X1AyZn5WRCreBCJq1CEtpF", + "authority": "ipfs", + "path": "QmVdDR6QtigTt38Xwpj2Ki73X1AyZn5WRCreBCJq1CEtpF" + } + }, + "result": { + "_config": { + "uri": "wrap://ens/testnet/test3.eth", + "authority": "ens", + "path": "testnet/test3.eth" + } + } } - }, - result: "wrap://ens/test3.eth" + ] } ]) }); @@ -259,6 +306,7 @@ describe("e2e tests for deploy command", () => { args: ["deploy"], cwd: getTestCaseDir(1), cli: polywrapCli, + env: process.env as Record }, ); @@ -269,7 +317,7 @@ describe("e2e tests for deploy command", () => { "No manifest extension found in" ); expect(sanitizedOutput).toContain( - "Successfully executed stage 'ipfs_test'" + "Successfully executed step 'ipfs_test'" ); }); @@ -279,6 +327,7 @@ describe("e2e tests for deploy command", () => { args: ["deploy"], cwd: getTestCaseDir(2), cli: polywrapCli, + env: process.env as Record }, ); @@ -294,6 +343,7 @@ describe("e2e tests for deploy command", () => { args: ["deploy"], cwd: getTestCaseDir(3), cli: polywrapCli, + env: process.env as Record }, ); @@ -302,14 +352,14 @@ describe("e2e tests for deploy command", () => { expect(code).toEqual(1); expect(sanitizedOutput).toContain( - "Successfully executed stage 'ipfs_deploy'" + "Successfully executed step 'ipfs_deploy'" ); expect(sanitizedOutput).not.toContain( - "Successfully executed stage 'from_deploy2'" + "Successfully executed step 'from_deploy2'" ); expect(sanitizedErr).toContain( - "Failed to execute stage 'from_deploy'" + "Failed to execute step 'from_deploy'" ); }); @@ -319,6 +369,7 @@ describe("e2e tests for deploy command", () => { args: ["deploy"], cwd: getTestCaseDir(4), cli: polywrapCli, + env: process.env as Record }, ); diff --git a/packages/cli/src/commands/deploy.ts b/packages/cli/src/commands/deploy.ts index a438bc7b35..41f83fab69 100644 --- a/packages/cli/src/commands/deploy.ts +++ b/packages/cli/src/commands/deploy.ts @@ -2,12 +2,12 @@ import { Command, Program } from "./types"; import { defaultPolywrapManifest, - DeployerHandler, DeployPackage, intlMsg, parseManifestFileOption, PolywrapProject, - ResultList, + Sequence, + Step, } from "../lib"; import { DeployManifest } from "@polywrap/polywrap-manifest-types-js"; @@ -15,7 +15,6 @@ import fs from "fs"; import nodePath from "path"; import { print } from "gluegun"; import yaml from "js-yaml"; -import { Uri } from "@polywrap/core-js"; import { validate } from "jsonschema"; const defaultManifestStr = defaultPolywrapManifest.join(" | "); @@ -27,6 +26,9 @@ type DeployCommandOptions = { verbose?: boolean; }; +type ManifestSequence = DeployManifest["sequences"][number]; +type ManifestStep = ManifestSequence["steps"][number]; + export const deploy: Command = { setup: (program: Program) => { program @@ -69,104 +71,78 @@ async function run(options: DeployCommandOptions): Promise { throw new Error("No deploy manifest found."); } + const allStepsFromAllSequences = deployManifest.sequences.flatMap( + (sequence) => { + return sequence.steps.map((step) => ({ + sequenceName: sequence.name, + ...step, + })); + } + ); + const packageNames = [ - ...new Set(Object.values(deployManifest.stages).map((d) => d.package)), + ...new Set(allStepsFromAllSequences.map((step) => step.package)), ]; sanitizePackages(packageNames); await project.cacheDeployModules(packageNames); - const packageMap: Record = {}; - const stageToPackageMap: Record = {}; - - for await (const packageName of packageNames) { - packageMap[packageName] = await project.getDeployModule(packageName); - } + const packageMapEntries = await Promise.all( + packageNames.map(async (packageName) => { + const deployerPackage = await project.getDeployModule(packageName); + return [packageName, deployerPackage]; + }) + ); - Object.entries(deployManifest.stages).forEach(([stageName, stageValue]) => { - stageToPackageMap[stageName] = packageMap[stageValue.package]; - }); + const packageMap = Object.fromEntries(packageMapEntries); - validateManifestWithExts(deployManifest, stageToPackageMap); + const stepToPackageMap: Record< + string, + DeployPackage & { sequenceName: string } + > = {}; - const handlers: Record = {}; - const roots: { handler: DeployerHandler; uri: Uri }[] = []; + for (const step of allStepsFromAllSequences) { + stepToPackageMap[step.name] = { + ...packageMap[step.package], + sequenceName: step.sequenceName, + }; + } - // Create all handlers - Object.entries(deployManifest.stages).forEach(([stageName, stageValue]) => { - const publisher = stageToPackageMap[stageName].deployer; - handlers[stageName] = new DeployerHandler( - stageName, - publisher, - stageValue.config, - print - ); - }); + validateManifestWithExts(deployManifest, stepToPackageMap); - // Establish dependency chains - Object.entries(deployManifest.stages).forEach(([key, value]) => { - const thisHandler = handlers[key]; - - if (value.depends_on) { - // Depends on another stage - handlers[value.depends_on].addNext(thisHandler); - } else if (value.uri) { - // It is a root node - roots.push({ uri: new Uri(value.uri), handler: thisHandler }); - } else { - throw new Error( - `Stage '${key}' needs either previous (depends_on) stage or URI` - ); - } + const sequences = deployManifest.sequences.map((sequence) => { + const steps = sequence.steps.map((step) => { + return new Step({ + name: step.name, + uriOrStepResult: step.uri, + deployer: stepToPackageMap[step.name].deployer, + config: step.config ?? {}, + }); + }); + + return new Sequence({ + name: sequence.name, + steps, + config: sequence.config ?? {}, + printer: print, + }); }); - // Execute roots - - const resultLists: ResultList[] = []; - - for await (const root of roots) { - print.info(`\nExecuting deployment chain: \n`); - root.handler.getDependencyTree().printTree(); - print.info(""); - await root.handler.handle(root.uri); - resultLists.push(root.handler.getResultsList()); - } - - const getResults = ( - resultList: ResultList, - prefix?: string - ): { - name: string; - input: unknown; - result: string; - id: string; - }[] => { - const id = prefix ? `${prefix}.${resultList.name}` : resultList.name; - - return [ - { - id, - name: resultList.name, - input: resultList.input, - result: resultList.result, - }, - ...resultList.children.flatMap((r) => getResults(r, id)), - ]; - }; + const sequenceResults = await Promise.all( + sequences.map((sequence) => sequence.run()) + ); if (outputFile) { - const resultOutput = resultLists.flatMap((r) => getResults(r)); - const outputFileExt = nodePath.extname(outputFile).substring(1); if (!outputFileExt) throw new Error("Require output file extension"); switch (outputFileExt) { case "yaml": case "yml": - fs.writeFileSync(outputFile, yaml.dump(resultOutput)); + fs.writeFileSync(outputFile, yaml.dump(sequenceResults)); break; case "json": - fs.writeFileSync(outputFile, JSON.stringify(resultOutput, null, 2)); + fs.writeFileSync(outputFile, JSON.stringify(sequenceResults, null, 2)); break; default: throw new Error( @@ -199,17 +175,28 @@ function sanitizePackages(packages: string[]) { function validateManifestWithExts( deployManifest: DeployManifest, - stageToPackageMap: Record + stepToPackageMap: Record ) { - const errors = Object.entries( - stageToPackageMap - ).flatMap(([stageName, deployPackage]) => - deployPackage.manifestExt - ? validate( - deployManifest.stages[stageName].config, - deployPackage.manifestExt - ).errors - : [] + const errors = Object.entries(stepToPackageMap).flatMap( + ([stepName, step]) => { + const sequence = deployManifest.sequences.find( + (seq) => seq.name === step.sequenceName + ) as ManifestSequence; + + const stepToValidate = sequence.steps.find( + (s) => s.name === stepName + ) as ManifestStep; + + return step.manifestExt + ? validate( + { + ...sequence.config, + ...stepToValidate.config, + }, + step.manifestExt + ).errors + : []; + } ); if (errors.length) { diff --git a/packages/cli/src/lib/defaults/deploy-modules/ens-recursive-name-register/index.ts b/packages/cli/src/lib/defaults/deploy-modules/ens-recursive-name-register/index.ts new file mode 100644 index 0000000000..83ae04f700 --- /dev/null +++ b/packages/cli/src/lib/defaults/deploy-modules/ens-recursive-name-register/index.ts @@ -0,0 +1,135 @@ +/* eslint-disable @typescript-eslint/no-require-imports */ +/* eslint-disable @typescript-eslint/no-var-requires */ +import { Deployer } from "../../../deploy/deployer"; + +import { Wallet } from "@ethersproject/wallet"; +import { JsonRpcProvider } from "@ethersproject/providers"; +import { Uri } from "@polywrap/core-js"; +import { PolywrapClient } from "@polywrap/client-js"; +import { + ethereumPlugin, + Connections, + Connection, +} from "@polywrap/ethereum-plugin-js"; +import { embeddedWrappers } from "@polywrap/test-env-js"; + +class ENSRecursiveNameRegisterPublisher implements Deployer { + async execute( + uri: Uri, + config: { + provider: string; + privateKey?: string; + ensRegistryAddress: string; + ensRegistrarAddress: string; + ensResolverAddress: string; + } + ): Promise { + if (uri.authority !== "ens") { + throw new Error( + `ENS Recursive Name Register Deployer: argument URI needs to be an ENS URI. Example: wrap://ens/foo.bar.eth` + ); + } + + const ensDomain = uri.path; + + const connectionProvider = new JsonRpcProvider(config.provider); + const { + chainId: chainIdNum, + name: networkName, + } = await connectionProvider.getNetwork(); + + const network = chainIdNum === 1337 ? "testnet" : networkName; + + const signer = config.privateKey + ? new Wallet(config.privateKey).connect(connectionProvider) + : undefined; + + const ethereumPluginUri = "wrap://ens/ethereum.polywrap.eth"; + const ensWrapperUri = embeddedWrappers.ens; + + const client = new PolywrapClient({ + redirects: [ + { + from: "wrap://ens/uts46.polywrap.eth", + to: embeddedWrappers.uts46, + }, + { + from: "wrap://ens/sha3.polywrap.eth", + to: embeddedWrappers.sha3, + }, + ], + plugins: [ + { + uri: ethereumPluginUri, + plugin: ethereumPlugin({ + connections: new Connections({ + networks: { + [network]: new Connection({ + provider: config.provider, + signer, + }), + }, + defaultNetwork: network, + }), + }), + }, + ], + }); + + const { data: signerAddress } = await client.invoke({ + method: "getSignerAddress", + uri: ethereumPluginUri, + args: { + connection: { + networkNameOrChainId: network, + }, + }, + }); + + if (!signerAddress) { + throw new Error("Could not get signer"); + } + + const { data: registerData, error } = await client.invoke<{ hash: string }>( + { + method: "registerDomainAndSubdomainsRecursively", + uri: ensWrapperUri, + args: { + domain: ensDomain, + owner: signerAddress, + resolverAddress: config.ensResolverAddress, + ttl: "0", + registrarAddress: config.ensRegistrarAddress, + registryAddress: config.ensRegistryAddress, + connection: { + networkNameOrChainId: network, + }, + }, + } + ); + + if (!registerData) { + throw new Error( + `Could not register domain '${ensDomain}'` + + (error ? `\nError: ${error.message}` : "") + ); + } + + await client.invoke({ + method: "awaitTransaction", + uri: ethereumPluginUri, + args: { + txHash: registerData.hash, + confirmations: 1, + timeout: 15000, + connection: { + networkNameOrChainId: network, + }, + }, + }); + + return new Uri(`ens/${network}/${ensDomain}`); + } +} + +export default new ENSRecursiveNameRegisterPublisher(); diff --git a/packages/cli/src/lib/defaults/deploy-modules/ens-recursive-name-register/polywrap.deploy.ext.json b/packages/cli/src/lib/defaults/deploy-modules/ens-recursive-name-register/polywrap.deploy.ext.json new file mode 100644 index 0000000000..31ef9b84aa --- /dev/null +++ b/packages/cli/src/lib/defaults/deploy-modules/ens-recursive-name-register/polywrap.deploy.ext.json @@ -0,0 +1,27 @@ +{ + "id": "DeployManifest_ENSRecursiveNameRegister_WasmAsExt", + "type": "object", + "required": [ + "provider", + "ensRegistryAddress", + "ensRegistrarAddress", + "ensResolverAddress" + ], + "properties": { + "provider": { + "type": "string" + }, + "privateKey": { + "type": "string" + }, + "ensRegistryAddress": { + "type": "string" + }, + "ensRegistrarAddress": { + "type": "string" + }, + "ensResolverAddress": { + "type": "string" + } + } +} \ No newline at end of file diff --git a/packages/cli/src/lib/defaults/deploy-modules/ens/index.ts b/packages/cli/src/lib/defaults/deploy-modules/ens/index.ts index 9bfdfa350b..6710597bd6 100644 --- a/packages/cli/src/lib/defaults/deploy-modules/ens/index.ts +++ b/packages/cli/src/lib/defaults/deploy-modules/ens/index.ts @@ -6,12 +6,12 @@ import { Wallet } from "@ethersproject/wallet"; import { JsonRpcProvider } from "@ethersproject/providers"; import { Uri } from "@polywrap/core-js"; import { PolywrapClient } from "@polywrap/client-js"; -import path from "path"; import { ethereumPlugin, Connections, Connection, } from "@polywrap/ethereum-plugin-js"; +import { embeddedWrappers } from "@polywrap/test-env-js"; const contentHash = require("content-hash"); @@ -46,27 +46,32 @@ class ENSPublisher implements Deployer { : undefined; const ethereumPluginUri = "wrap://ens/ethereum.polywrap.eth"; - const ensWrapperUri = `fs/${path.join( - path.dirname(require.resolve("@polywrap/test-env-js")), - "wrappers", - "ens" - )}`; - - const connections = new Connections({ - networks: { - [network]: new Connection({ - provider: config.provider, - signer, - }), - }, - defaultNetwork: network, - }); + const ensWrapperUri = embeddedWrappers.ens; + const client = new PolywrapClient({ + redirects: [ + { + from: "wrap://ens/uts46.polywrap.eth", + to: embeddedWrappers.uts46, + }, + { + from: "wrap://ens/sha3.polywrap.eth", + to: embeddedWrappers.sha3, + }, + ], plugins: [ { uri: ethereumPluginUri, plugin: ethereumPlugin({ - connections, + connections: new Connections({ + networks: { + [network]: new Connection({ + provider: config.provider, + signer, + }), + }, + defaultNetwork: network, + }), }), }, ], @@ -124,7 +129,7 @@ class ENSPublisher implements Deployer { }, }); - return new Uri(`ens/${config.domainName}`); + return new Uri(`ens/${network}/${config.domainName}`); } } diff --git a/packages/cli/src/lib/defaults/deploy-modules/local-dev-ens/index.ts b/packages/cli/src/lib/defaults/deploy-modules/local-dev-ens/index.ts deleted file mode 100644 index 6041b39b6f..0000000000 --- a/packages/cli/src/lib/defaults/deploy-modules/local-dev-ens/index.ts +++ /dev/null @@ -1,149 +0,0 @@ -/* eslint-disable @typescript-eslint/no-require-imports */ -/* eslint-disable @typescript-eslint/no-var-requires */ -import { Deployer } from "../../../deploy/deployer"; - -import { Uri } from "@polywrap/core-js"; -import { ensAddresses } from "@polywrap/test-env-js"; -import path from "path"; -import { PolywrapClient } from "@polywrap/client-js"; -import { - ethereumPlugin, - Connections, - Connection, -} from "@polywrap/ethereum-plugin-js"; - -const contentHash = require("content-hash"); - -class LocalDevENSPublisher implements Deployer { - async execute( - uri: Uri, - config: { - domainName: string; - domainOwnerAddressOrIndex?: string | number; - ports: { - ethereum: number; - }; - } - ): Promise { - if (uri.authority !== "ipfs") { - throw new Error( - `ENS Deployer: resolved URI from ${uri} does not represent an IPFS contentHash` - ); - } - - const cid = uri.path; - const ethereumPluginUri = "wrap://ens/ethereum.polywrap.eth"; - - const client = new PolywrapClient({ - plugins: [ - { - uri: ethereumPluginUri, - plugin: ethereumPlugin({ - connections: new Connections({ - networks: { - testnet: new Connection({ - provider: `http://localhost:${config.ports.ethereum}`, - }), - }, - defaultNetwork: "testnet", - }), - }), - }, - ], - }); - - const { data: signer } = await client.invoke({ - method: "getSignerAddress", - uri: ethereumPluginUri, - args: { - connection: { - networkNameOrChainId: "testnet", - }, - }, - }); - - if (!signer) { - throw new Error("Could not get signer"); - } - - const ensWrapperUri = `fs/${path.join( - path.dirname(require.resolve("@polywrap/test-env-js")), - "wrappers", - "ens" - )}`; - - const { data: registerData, error } = await client.invoke<{ hash: string }>( - { - method: "registerDomainAndSubdomainsRecursively", - uri: ensWrapperUri, - args: { - domain: config.domainName, - owner: signer, - resolverAddress: ensAddresses.resolverAddress, - ttl: "0", - registrarAddress: ensAddresses.registrarAddress, - registryAddress: ensAddresses.ensAddress, - connection: { - networkNameOrChainId: "testnet", - }, - }, - } - ); - - if (!registerData) { - throw new Error( - `Could not register domain '${config.domainName}'` + - (error ? `\nError: ${error.message}` : "") - ); - } - - await client.invoke({ - method: "awaitTransaction", - uri: ethereumPluginUri, - args: { - txHash: registerData.hash, - confirmations: 1, - timeout: 15000, - connection: { - networkNameOrChainId: "testnet", - }, - }, - }); - - const hash = "0x" + contentHash.fromIpfs(cid); - - const { data: setContenthashData } = await client.invoke<{ hash: string }>({ - method: "setContentHash", - uri: ensWrapperUri, - args: { - domain: config.domainName, - cid: hash, - resolverAddress: ensAddresses.resolverAddress, - connection: { - networkNameOrChainId: "testnet", - }, - }, - }); - - if (!setContenthashData) { - throw new Error(`Could not set contentHash for '${config.domainName}'`); - } - - await client.invoke({ - method: "awaitTransaction", - uri: ethereumPluginUri, - args: { - txHash: setContenthashData.hash, - confirmations: 1, - timeout: 15000, - connection: { - networkNameOrChainId: "testnet", - }, - }, - }); - - return new Uri(`ens/${config.domainName}`); - } -} - -export default new LocalDevENSPublisher(); diff --git a/packages/cli/src/lib/defaults/deploy-modules/local-dev-ens/polywrap.deploy.ext.json b/packages/cli/src/lib/defaults/deploy-modules/local-dev-ens/polywrap.deploy.ext.json deleted file mode 100644 index 9a75c66c2e..0000000000 --- a/packages/cli/src/lib/defaults/deploy-modules/local-dev-ens/polywrap.deploy.ext.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "id": "DeployManifest_LocalDevENS_WasmAsExt", - "type": "object", - "required": [ - "domainName", - "ports" - ], - "properties": { - "domainName": { - "type": "string" - }, - "domainOwnerAddressOrIndex": { - "type": ["string", "number"] - }, - "ports": { - "type": "object", - "additionalProperties": false, - "required": ["ethereum"], - "properties": { - "ethereum": { - "type": "number" - } - } - } - } -} \ No newline at end of file diff --git a/packages/cli/src/lib/deploy/asciiTree.ts b/packages/cli/src/lib/deploy/asciiTree.ts deleted file mode 100644 index 81dd3bd3c3..0000000000 --- a/packages/cli/src/lib/deploy/asciiTree.ts +++ /dev/null @@ -1,128 +0,0 @@ -// https://github.com/aws/jsii/blob/main/packages/oo-ascii-tree/lib/ascii-tree.ts - -export class AsciiTree { - public parent?: AsciiTree; - - private readonly _children = new Array(); - - public constructor(public readonly text?: string, ...children: AsciiTree[]) { - for (const child of children) { - this.add(child); - } - } - - public printTree(output: Printer = process.stdout): void { - let ancestorsPrefix = ""; - - for (const parent of this.ancestors) { - if (parent.level <= 0) { - continue; - } - - if (parent.last) { - ancestorsPrefix += " "; - } else { - ancestorsPrefix += " │"; - } - } - - let myPrefix = ""; - let multilinePrefix = ""; - if (this.level > 0) { - if (this.last) { - if (!this.empty) { - myPrefix += " └─┬ "; - multilinePrefix += " └─┬ "; - } else { - myPrefix += " └── "; - multilinePrefix = " "; - } - } else { - if (!this.empty) { - myPrefix += " ├─┬ "; - multilinePrefix += " │ │ "; - } else { - myPrefix += " ├── "; - multilinePrefix += " │ "; - } - } - } - - if (this.text) { - output.write(ancestorsPrefix); - output.write(myPrefix); - const lines = this.text.split("\n"); - output.write(lines[0]); - output.write("\n"); - - for (const line of lines.splice(1)) { - output.write(ancestorsPrefix); - output.write(multilinePrefix); - output.write(line); - output.write("\n"); - } - } - - for (const child of this._children) { - child.printTree(output); - } - } - - public toString(): string { - let out = ""; - const printer: Printer = { - write: (data: Uint8Array | string) => { - out += data; - return true; - }, - }; - this.printTree(printer); - return out; - } - - public add(...children: AsciiTree[]): void { - for (const child of children) { - child.parent = this; - this._children.push(child); - } - } - - public get children(): AsciiTree[] { - return this._children.map((x) => x); - } - - public get root(): boolean { - return !this.parent; - } - - public get last(): boolean { - if (!this.parent) { - return true; - } - return ( - this.parent.children.indexOf(this) === this.parent.children.length - 1 - ); - } - - public get level(): number { - if (!this.parent) { - return this.text ? 0 : -1; - } - - return this.parent.level + 1; - } - - public get empty(): boolean { - return this.children.length === 0; - } - - public get ancestors(): AsciiTree[] { - if (!this.parent) { - return []; - } - - return [...this.parent.ancestors, this.parent]; - } -} - -export type Printer = Pick; diff --git a/packages/cli/src/lib/deploy/deployer.ts b/packages/cli/src/lib/deploy/deployer.ts index 1d5a6d9f36..42d19d3bd8 100644 --- a/packages/cli/src/lib/deploy/deployer.ts +++ b/packages/cli/src/lib/deploy/deployer.ts @@ -1,111 +1,5 @@ -/* eslint-disable @typescript-eslint/no-require-imports */ -import { AsciiTree } from "./asciiTree"; - import { Uri } from "@polywrap/core-js"; -import { GluegunPrint } from "gluegun"; export interface Deployer { execute(uri: Uri, config?: unknown): Promise; } - -interface List { - name: string; - children: List[]; -} - -export interface ResultList { - name: string; - input: { - uri: string; - config?: unknown; - }; - result: string; - children: ResultList[]; -} - -interface Handler { - addNext(handler: Handler): void; - handle(params: Uri): Promise; -} - -abstract class AbstractHandler implements Handler { - private dependencyTree: AsciiTree; - private nextHandlers: AbstractHandler[] = []; - protected input: { - uri: string; - config?: unknown; - }; - protected result: string; - - constructor(public name: string) { - this.dependencyTree = new AsciiTree(this.name); - } - - public addNext(handler: AbstractHandler): void { - this.nextHandlers.push(handler); - this.dependencyTree.add(handler.dependencyTree); - } - - public async handle(uri: Uri): Promise { - const uris: Uri[][] = []; - - for await (const handler of this.nextHandlers) { - uris.push(await handler.handle(uri)); - } - - return uris.flat(); - } - - public getList(): List { - return { - name: this.name, - children: this.nextHandlers.map((n) => n.getList()), - }; - } - - public getDependencyTree(): AsciiTree { - return this.dependencyTree; - } - - public getResultsList(): ResultList { - return { - name: this.name, - input: this.input, - result: this.result, - children: this.nextHandlers.map((n) => n.getResultsList()), - }; - } -} - -export class DeployerHandler extends AbstractHandler { - constructor( - name: string, - private deployer: Deployer, - private config: unknown, - private printer: GluegunPrint - ) { - super(name); - } - - public async handle(uri: Uri): Promise { - this.printer.info( - `Executing stage: '${this.name}', with URI: '${uri.toString()}'` - ); - - try { - this.input = { - uri: uri.toString(), - config: this.config, - }; - const nextUri = await this.deployer.execute(uri, this.config); - this.result = nextUri.toString(); - - this.printer.success( - `Successfully executed stage '${this.name}'. Result: '${this.result}'` - ); - return [nextUri, ...(await super.handle(nextUri))]; - } catch (e) { - throw new Error(`Failed to execute stage '${this.name}'. Error: ${e}`); - } - } -} diff --git a/packages/cli/src/lib/deploy/index.ts b/packages/cli/src/lib/deploy/index.ts index 0ef8bd95d5..d8fc260b9a 100644 --- a/packages/cli/src/lib/deploy/index.ts +++ b/packages/cli/src/lib/deploy/index.ts @@ -3,6 +3,8 @@ import { Deployer } from "./deployer"; import { Schema as JsonSchema } from "jsonschema"; export * from "./deployer"; +export * from "./step"; +export * from "./sequence"; export interface DeployPackage { deployer: Deployer; diff --git a/packages/cli/src/lib/deploy/sequence.ts b/packages/cli/src/lib/deploy/sequence.ts new file mode 100644 index 0000000000..0dcff7dfbb --- /dev/null +++ b/packages/cli/src/lib/deploy/sequence.ts @@ -0,0 +1,117 @@ +import { Step, StepName, StepResult, UriOrPrevStepResult } from "./step"; + +import { Uri } from "@polywrap/core-js"; +import { GluegunPrint } from "gluegun"; + +export interface SequenceResult { + name: string; + steps: { + id: string; + name: string; + input: Uri; + result: Uri; + }[]; +} + +interface SequenceArgs { + name: string; + steps: Step[]; + config: Record; + printer: GluegunPrint; +} + +export class Sequence { + public name: string; + public steps: Step[]; + public config: Record; + + private _printer: GluegunPrint; + private _resultMap: Map = new Map(); + + constructor(config: SequenceArgs) { + this.name = config.name; + this.steps = config.steps; + this.config = config.config; + + this._printer = config.printer; + + this.steps.forEach((step, index) => { + if (step.uriOrStepResult.startsWith("$")) { + const previousStepsNames = this.steps + .slice(0, index) + .map((s) => s.name); + + const dependencyStepName = step.uriOrStepResult.slice(1); + + if (!previousStepsNames.includes(dependencyStepName)) { + throw new Error( + `Step '${step.name}' depends on '${dependencyStepName}'s result, but '${dependencyStepName}' is not listed before '${step.name}' in sequence '${this.name}'` + ); + } + } + }); + } + + public async run(): Promise { + this._printer.info( + `\n\nExecuting '${this.name}' deployment sequence: \n${this.steps + .map((s) => `\n- ${s.name}`) + .join("")}\n\n` + ); + + for await (const step of this.steps) { + const uri = this._getUriArgument(step.uriOrStepResult); + + this._printer.info( + `Executing step: '${step.name}', with URI: '${uri.toString()}'` + ); + + try { + const result = await step.run(uri, { + // Step level config will override Sequence level config + ...this.config, + ...step.config, + }); + this._resultMap.set(step.name, { + id: `${this.name}.${step.name}`, + input: uri, + result, + }); + + this._printer.success( + `Successfully executed step '${ + step.name + }'. Result: '${result.toString()}'` + ); + } catch (e) { + throw new Error(`Failed to execute step '${step.name}'. Error: ${e}`); + } + } + + this._printer.info( + `\n\nSuccessfully executed '${this.name}' deployment sequence\n\n` + ); + + return { + name: this.name, + steps: this.steps.map((s) => ({ + name: s.name, + ...(this._resultMap.get(s.name) as StepResult), + })), + }; + } + + private _getUriArgument(uriOrStepResult: UriOrPrevStepResult): Uri { + if (uriOrStepResult.startsWith("$")) { + const previousStepResult = this._resultMap.get(uriOrStepResult.slice(1)); + + if (!previousStepResult) { + throw new Error(`Could not find ${uriOrStepResult.slice(1)}'s result`); + } + + return previousStepResult.result; + } + + return new Uri(uriOrStepResult); + } +} diff --git a/packages/cli/src/lib/deploy/step.ts b/packages/cli/src/lib/deploy/step.ts new file mode 100644 index 0000000000..233232ee3c --- /dev/null +++ b/packages/cli/src/lib/deploy/step.ts @@ -0,0 +1,37 @@ +import { Deployer } from "./deployer"; + +import { Uri } from "@polywrap/core-js"; + +export type StepName = string; +export type UriOrPrevStepResult = string; + +export interface StepResult { + id: string; + input: Uri; + result: Uri; +} + +interface StepArgs { + name: string; + uriOrStepResult: UriOrPrevStepResult; + deployer: Deployer; + config: Record; +} + +export class Step { + public name: string; + public deployer: Deployer; + public uriOrStepResult: string; + public config: Record; + + constructor(args: StepArgs) { + this.name = args.name; + this.deployer = args.deployer; + this.uriOrStepResult = args.uriOrStepResult; + this.config = args.config; + } + + public async run(uri: Uri, config: Record): Promise { + return await this.deployer.execute(uri, config); + } +} diff --git a/packages/interfaces/file-system/polywrap.deploy.yaml b/packages/interfaces/file-system/polywrap.deploy.yaml index 53f91cac12..194b222de9 100644 --- a/packages/interfaces/file-system/polywrap.deploy.yaml +++ b/packages/interfaces/file-system/polywrap.deploy.yaml @@ -1,4 +1,4 @@ -format: 0.1.0 +format: "0.1" stages: ipfs_deploy: package: ipfs diff --git a/packages/interfaces/ipfs/polywrap.deploy.yaml b/packages/interfaces/ipfs/polywrap.deploy.yaml index b941a2c4a2..a579d41fa4 100644 --- a/packages/interfaces/ipfs/polywrap.deploy.yaml +++ b/packages/interfaces/ipfs/polywrap.deploy.yaml @@ -1,4 +1,4 @@ -format: 0.1.0 +format: "0.1" stages: ipfs_deploy: package: ipfs diff --git a/packages/interfaces/uri-resolver/polywrap.deploy.yaml b/packages/interfaces/uri-resolver/polywrap.deploy.yaml index 53f91cac12..194b222de9 100644 --- a/packages/interfaces/uri-resolver/polywrap.deploy.yaml +++ b/packages/interfaces/uri-resolver/polywrap.deploy.yaml @@ -1,4 +1,4 @@ -format: 0.1.0 +format: "0.1" stages: ipfs_deploy: package: ipfs diff --git a/packages/js/manifests/polywrap/src/__tests__/manifest/deploy/migrations/polywrap-0.1.0.yaml b/packages/js/manifests/polywrap/src/__tests__/manifest/deploy/migrations/polywrap-0.1.0.yaml new file mode 100644 index 0000000000..87ce967088 --- /dev/null +++ b/packages/js/manifests/polywrap/src/__tests__/manifest/deploy/migrations/polywrap-0.1.0.yaml @@ -0,0 +1,26 @@ +format: 0.1.0 +stages: + ipfs_deploy: + package: ipfs + uri: fs/./build + from_deploy: + package: ens + depends_on: ipfs_deploy + config: + domainName: $DOMAIN_NAME + provider: 'http://localhost:8545' + ensRegistryAddress: $ENS_REG_ADDR + from_deploy2: + package: ens + depends_on: ipfs_deploy + config: + domainName: test2.eth + provider: 'http://localhost:8545' + ensRegistryAddress: $ENS_REG_ADDR + from_uri: + package: ens + uri: ipfs/QmVdDR6QtigTt38Xwpj2Ki73X1AyZn5WRCreBCJq1CEtpF + config: + domainName: test3.eth + provider: 'http://localhost:8545' + ensRegistryAddress: $ENS_REG_ADDR \ No newline at end of file diff --git a/packages/js/manifests/polywrap/src/__tests__/manifest/deploy/migrations/polywrap-0.2.0.yaml b/packages/js/manifests/polywrap/src/__tests__/manifest/deploy/migrations/polywrap-0.2.0.yaml new file mode 100644 index 0000000000..1327761856 --- /dev/null +++ b/packages/js/manifests/polywrap/src/__tests__/manifest/deploy/migrations/polywrap-0.2.0.yaml @@ -0,0 +1,30 @@ +format: "0.2.0" +sequences: + - name: ipfs_deploy + steps: + - name: ipfs_deploy + package: ipfs + uri: fs/./build + - name: from_deploy + package: ens + uri: $$ipfs_deploy + config: + domainName: $DOMAIN_NAME + provider: 'http://localhost:8545' + ensRegistryAddress: $ENS_REG_ADDR + - name: from_deploy2 + package: ens + uri: $$ipfs_deploy + config: + domainName: test2.eth + provider: 'http://localhost:8545' + ensRegistryAddress: $ENS_REG_ADDR + - name: from_uri + steps: + - name: from_uri + package: ens + uri: ipfs/QmVdDR6QtigTt38Xwpj2Ki73X1AyZn5WRCreBCJq1CEtpF + config: + domainName: test3.eth + provider: 'http://localhost:8545' + ensRegistryAddress: $ENS_REG_ADDR \ No newline at end of file diff --git a/packages/js/manifests/polywrap/src/__tests__/migrations.spec.ts b/packages/js/manifests/polywrap/src/__tests__/migrations.spec.ts index 28fc2c5d5c..49445fc31c 100644 --- a/packages/js/manifests/polywrap/src/__tests__/migrations.spec.ts +++ b/packages/js/manifests/polywrap/src/__tests__/migrations.spec.ts @@ -1,4 +1,4 @@ -import { deserializePolywrapManifest } from "../"; +import { deserializeDeployManifest, deserializePolywrapManifest } from "../"; import fs from "fs"; @@ -16,3 +16,18 @@ describe("Polywrap Manifest Migrations", () => { expect(manifest).toEqual(expectedManifest); }); }); + +describe("Polywrap Deploy Manifest Migrations", () => { + it("Should succesfully migrate from 0.1.0 to 0.2.0", async() => { + const manifestPath = __dirname + "/manifest/deploy/migrations/polywrap-0.1.0.yaml"; + const expectedManifestPath = __dirname + "/manifest/deploy/migrations/polywrap-0.2.0.yaml"; + + const manifestFile = fs.readFileSync(manifestPath, "utf-8"); + const expectedManifestFile = fs.readFileSync(expectedManifestPath, "utf-8"); + + const manifest = deserializeDeployManifest(manifestFile); + const expectedManifest = deserializeDeployManifest(expectedManifestFile); + + expect(manifest).toEqual(expectedManifest); + }); +}); \ No newline at end of file diff --git a/packages/js/manifests/polywrap/src/formats/polywrap.deploy/0.2.0.ts b/packages/js/manifests/polywrap/src/formats/polywrap.deploy/0.2.0.ts new file mode 100644 index 0000000000..19e80ffb09 --- /dev/null +++ b/packages/js/manifests/polywrap/src/formats/polywrap.deploy/0.2.0.ts @@ -0,0 +1,53 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +/* tslint:disable */ +/** + * This file was automatically generated by json-schema-to-typescript. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run json-schema-to-typescript to regenerate this file. + */ + +export interface DeployManifest { + /** + * Polywrap deployment manifest format version. + */ + format: "0.2.0"; + /** + * Sequences of deployment steps + */ + sequences: { + /** + * Name of the sequence + */ + name: string; + /** + * Deployment steps + */ + steps: { + /** + * Name of the step + */ + name: string; + /** + * Name of the deployer package. + */ + package: string; + /** + * Step-level custom configuration. + */ + config?: { + [k: string]: unknown; + }; + /** + * URI to pass into the deploy step. + */ + uri: string | string; + }[]; + /** + * Sequence-level custom configuration. + */ + config?: { + [k: string]: unknown; + }; + }[]; + __type: "DeployManifest"; +} diff --git a/packages/js/manifests/polywrap/src/formats/polywrap.deploy/index.ts b/packages/js/manifests/polywrap/src/formats/polywrap.deploy/index.ts index 4b4db6ddda..c16703a7f4 100644 --- a/packages/js/manifests/polywrap/src/formats/polywrap.deploy/index.ts +++ b/packages/js/manifests/polywrap/src/formats/polywrap.deploy/index.ts @@ -8,25 +8,31 @@ import { DeployManifest as DeployManifest_0_1_0, } from "./0.1.0"; +import { + DeployManifest as DeployManifest_0_2_0, +} from "./0.2.0"; export { DeployManifest_0_1_0, + DeployManifest_0_2_0, }; export enum DeployManifestFormats { // NOTE: Patch fix for backwards compatability "v0.1" = "0.1", "v0.1.0" = "0.1.0", + "v0.2.0" = "0.2.0", } export type AnyDeployManifest = | DeployManifest_0_1_0 + | DeployManifest_0_2_0 -export type DeployManifest = DeployManifest_0_1_0; +export type DeployManifest = DeployManifest_0_2_0; -export const latestDeployManifestFormat = DeployManifestFormats["v0.1.0"] +export const latestDeployManifestFormat = DeployManifestFormats["v0.2.0"] export { migrateDeployManifest } from "./migrate"; diff --git a/packages/js/manifests/polywrap/src/formats/polywrap.deploy/migrate.ts b/packages/js/manifests/polywrap/src/formats/polywrap.deploy/migrate.ts index 42f32509bb..8667f4ea10 100644 --- a/packages/js/manifests/polywrap/src/formats/polywrap.deploy/migrate.ts +++ b/packages/js/manifests/polywrap/src/formats/polywrap.deploy/migrate.ts @@ -11,12 +11,16 @@ import { latestDeployManifestFormat } from "."; +import { + migrate as migrate_0_1_0_to_0_2_0 +} from "./migrators/0.1.0_to_0.2.0"; type Migrator = { [key in DeployManifestFormats]?: (m: AnyDeployManifest) => DeployManifest; }; export const migrators: Migrator = { + "0.1.0": migrate_0_1_0_to_0_2_0, }; export function migrateDeployManifest( @@ -38,5 +42,12 @@ export function migrateDeployManifest( throw new Error(`Unrecognized DeployManifestFormat "${manifest.format}"`); } - throw new Error(`This should never happen, DeployManifest migrators is empty. from: ${from}, to: ${to}`); + const migrator = migrators[from]; + if (!migrator) { + throw new Error( + `Migrator from DeployManifestFormat "${from}" to "${to}" is not available` + ); + } + + return migrator(manifest); } diff --git a/packages/js/manifests/polywrap/src/formats/polywrap.deploy/migrators/0.1.0_to_0.2.0.ts b/packages/js/manifests/polywrap/src/formats/polywrap.deploy/migrators/0.1.0_to_0.2.0.ts new file mode 100644 index 0000000000..3f9a8c0026 --- /dev/null +++ b/packages/js/manifests/polywrap/src/formats/polywrap.deploy/migrators/0.1.0_to_0.2.0.ts @@ -0,0 +1,71 @@ +/* eslint-disable @typescript-eslint/naming-convention */ + +import { DeployManifest as OldManifest } from "../0.1.0"; +import { DeployManifest as NewManifest } from "../0.2.0"; + +type Step = NewManifest["sequences"][number]["steps"][number]; +type Sequence = NewManifest["sequences"][number]; + +export function migrate(old: OldManifest): NewManifest { + const steps: Record = {}; + const sequences: Record< + string, + Omit & { steps: Record } + > = {}; + + const stageEntries = Object.entries(old.stages); + + stageEntries.forEach(([stageName, stageValue]) => { + steps[stageName] = { + name: stageName, + package: stageValue.package, + uri: stageValue.uri ?? `$$${stageValue.depends_on}`, + }; + + if (stageValue.config) { + steps[stageName].config = stageValue.config; + } + }); + + stageEntries.forEach(([stageName, stageValue]) => { + if (!stageValue.depends_on) { + sequences[stageName] = { + name: stageName, + steps: { + [stageName]: steps[stageName], + }, + }; + + delete steps[stageName]; + } + }); + + while (Object.keys(steps).length > 0) { + const sequenceValues = Object.values(sequences); + stageEntries + .filter(([_, stageValue]) => !!stageValue.depends_on) + .forEach(([stageName, stageValue]) => { + if (sequences[stageValue.depends_on as string]) { + sequences[stageValue.depends_on as string].steps[stageName] = + steps[stageName]; + delete steps[stageName]; + } else { + sequenceValues.forEach((sequenceValue) => { + if (sequenceValue.steps[stageValue.depends_on as string]) { + sequenceValue.steps[stageName] = steps[stageName]; + delete steps[stageName]; + } + }); + } + }); + } + + return { + __type: "DeployManifest", + format: "0.2.0", + sequences: Object.values(sequences).map((sequence) => ({ + ...sequence, + steps: Object.values(sequence.steps), + })), + }; +} diff --git a/packages/js/manifests/polywrap/src/formats/polywrap.deploy/validate.ts b/packages/js/manifests/polywrap/src/formats/polywrap.deploy/validate.ts index 1f991ff5ea..af0395a0c7 100644 --- a/packages/js/manifests/polywrap/src/formats/polywrap.deploy/validate.ts +++ b/packages/js/manifests/polywrap/src/formats/polywrap.deploy/validate.ts @@ -10,6 +10,7 @@ import { } from "."; import DeployManifestSchema_0_1_0 from "@polywrap/polywrap-manifest-schemas/formats/polywrap.deploy/0.1.0.json"; +import DeployManifestSchema_0_2_0 from "@polywrap/polywrap-manifest-schemas/formats/polywrap.deploy/0.2.0.json"; import { Schema, @@ -26,6 +27,7 @@ const schemas: DeployManifestSchemas = { // NOTE: Patch fix for backwards compatability "0.1": DeployManifestSchema_0_1_0, "0.1.0": DeployManifestSchema_0_1_0, + "0.2.0": DeployManifestSchema_0_2_0, }; const validator = new Validator(); diff --git a/packages/js/test-env/package.json b/packages/js/test-env/package.json index 2bcfbd7bfe..ff60ce15fb 100644 --- a/packages/js/test-env/package.json +++ b/packages/js/test-env/package.json @@ -17,9 +17,7 @@ "copy:wrappers": "copyfiles ./src/wrappers/**/**/* ./build/wrappers/ -u 2" }, "dependencies": { - "@polywrap/client-js": "0.7.0", "@polywrap/core-js": "0.7.0", - "@polywrap/ethereum-plugin-js": "0.7.0", "@polywrap/polywrap-manifest-types-js": "0.7.0", "axios": "0.21.2", "js-yaml": "4.1.0", diff --git a/packages/js/test-env/src/index.ts b/packages/js/test-env/src/index.ts index b4ba6325ae..7e08700cc6 100644 --- a/packages/js/test-env/src/index.ts +++ b/packages/js/test-env/src/index.ts @@ -7,12 +7,6 @@ import axios from "axios"; import fs from "fs"; import yaml from "js-yaml"; import { Uri } from "@polywrap/core-js"; -import { PolywrapClient } from "@polywrap/client-js"; -import { - ethereumPlugin, - Connections, - Connection, -} from "@polywrap/ethereum-plugin-js"; import { deserializePolywrapManifest } from "@polywrap/polywrap-manifest-types-js"; export const ensAddresses = { @@ -27,6 +21,12 @@ export const providers = { ethereum: "http://localhost:8545", }; +export const embeddedWrappers = { + ens: `wrap://fs/${path.join(__dirname, "wrappers", "ens")}`, + uts46: `wrap://fs/${path.join(__dirname, "wrappers", "uts46")}`, + sha3: `wrap://fs/${path.join(__dirname, "wrappers", "sha3")}`, +}; + const monorepoCli = `${__dirname}/../../../cli/bin/polywrap`; const npmCli = `${__dirname}/../../../polywrap/bin/polywrap`; @@ -254,82 +254,6 @@ export async function buildAndDeployWrapper({ await buildWrapper(wrapperAbsPath); - // register ENS domain - const ensWrapperUri = `fs/${__dirname}/wrappers/ens`; - - const ethereumPluginUri = "wrap://ens/ethereum.polywrap.eth"; - - const testnetConnection = { - networks: { - testnet: new Connection({ - provider: ethereumProvider, - }), - }, - defaultNetwork: "testnet", - }; - - const connections = new Connections(testnetConnection); - const client = new PolywrapClient({ - plugins: [ - { - uri: ethereumPluginUri, - plugin: ethereumPlugin({ - connections, - }), - }, - ], - }); - - const { data: signerAddress } = await client.invoke({ - method: "getSignerAddress", - uri: ethereumPluginUri, - args: { - connection: { - networkNameOrChainId: "testnet", - }, - }, - }); - - if (!signerAddress) { - throw new Error("Could not get signer"); - } - - const { data: registerData, error } = await client.invoke<{ hash: string }>({ - method: "registerDomainAndSubdomainsRecursively", - uri: ensWrapperUri, - args: { - domain: wrapperEns, - owner: signerAddress, - resolverAddress: ensAddresses.resolverAddress, - ttl: "0", - registrarAddress: ensAddresses.registrarAddress, - registryAddress: ensAddresses.ensAddress, - connection: { - networkNameOrChainId: "testnet", - }, - }, - }); - - if (!registerData) { - throw new Error( - `Could not register domain '${wrapperEns}'` + - (error ? `\nError: ${error.message}` : "") - ); - } - - await client.invoke({ - method: "awaitTransaction", - uri: ethereumPluginUri, - args: { - txHash: registerData.hash, - confirmations: 1, - timeout: 15000, - connection: { - networkNameOrChainId: "testnet", - }, - }, - }); - // manually configure manifests const { __type, ...polywrapManifest } = deserializePolywrapManifest( fs.readFileSync(manifestPath, "utf-8") @@ -349,26 +273,41 @@ export async function buildAndDeployWrapper({ fs.writeFileSync( tempDeployManifestPath, yaml.dump({ - format: "0.1.0", - stages: { - ipfsDeploy: { - package: "ipfs", - uri: `fs/${wrapperAbsPath}/build`, + format: "0.2.0", + sequences: [ + { + name: "buildAndDeployWrapper", config: { - gatewayUri: ipfsProvider, - }, - }, - ensPublish: { - package: "ens", - // eslint-disable-next-line @typescript-eslint/naming-convention - depends_on: "ipfsDeploy", - config: { - domainName: wrapperEns, provider: ethereumProvider, ensRegistryAddress: ensAddresses.ensAddress, + ensRegistrarAddress: ensAddresses.registrarAddress, + ensResolverAddress: ensAddresses.resolverAddress, }, + steps: [ + { + name: "registerName", + package: "ens-recursive-name-register", + uri: `wrap://ens/${wrapperEns}`, + }, + { + name: "ipfsDeploy", + package: "ipfs", + uri: `fs/${wrapperAbsPath}/build`, + config: { + gatewayUri: ipfsProvider, + }, + }, + { + name: "ensPublish", + package: "ens", + uri: "$$ipfsDeploy", + config: { + domainName: wrapperEns, + }, + }, + ], }, - }, + ], }) ); diff --git a/packages/js/test-env/src/wrappers/ens/wrap.wasm b/packages/js/test-env/src/wrappers/ens/wrap.wasm index 0af8be234f..83bc300a12 100644 Binary files a/packages/js/test-env/src/wrappers/ens/wrap.wasm and b/packages/js/test-env/src/wrappers/ens/wrap.wasm differ diff --git a/packages/js/test-env/src/wrappers/sha3/wrap.info b/packages/js/test-env/src/wrappers/sha3/wrap.info new file mode 100644 index 0000000000..4c52e6c99d --- /dev/null +++ b/packages/js/test-env/src/wrappers/sha3/wrap.info @@ -0,0 +1 @@ +„§version£0.1¤name¬sha3-wasm-rs¤type¤wasm£abi‚§version£0.1ªmoduleTypeƒ¤type¦Module¤kindÌ€§methodsœ†¤name¨sha3_512¦return…¤type¦String¤name¨sha3_512¨requiredäkind"¦scalar„¤name¨sha3_512¤type¦String¨requiredäkind¤type¦Method¤kind@¨requiredéarguments‘…¤type¦String¤name§message¨requiredäkind"¦scalar„¤name§message¤type¦String¨requiredäkind†¤name¨sha3_384¦return…¤type¦String¤name¨sha3_384¨requiredäkind"¦scalar„¤name¨sha3_384¤type¦String¨requiredäkind¤type¦Method¤kind@¨requiredéarguments‘…¤type¦String¤name§message¨requiredäkind"¦scalar„¤name§message¤type¦String¨requiredäkind†¤name¨sha3_256¦return…¤type¦String¤name¨sha3_256¨requiredäkind"¦scalar„¤name¨sha3_256¤type¦String¨requiredäkind¤type¦Method¤kind@¨requiredéarguments‘…¤type¦String¤name§message¨requiredäkind"¦scalar„¤name§message¤type¦String¨requiredäkind†¤name¨sha3_224¦return…¤type¦String¤name¨sha3_224¨requiredäkind"¦scalar„¤name¨sha3_224¤type¦String¨requiredäkind¤type¦Method¤kind@¨requiredéarguments‘…¤type¦String¤name§message¨requiredäkind"¦scalar„¤name§message¤type¦String¨requiredäkind†¤nameªkeccak_512¦return…¤type¦String¤nameªkeccak_512¨requiredäkind"¦scalar„¤nameªkeccak_512¤type¦String¨requiredäkind¤type¦Method¤kind@¨requiredéarguments‘…¤type¦String¤name§message¨requiredäkind"¦scalar„¤name§message¤type¦String¨requiredäkind†¤nameªkeccak_384¦return…¤type¦String¤nameªkeccak_384¨requiredäkind"¦scalar„¤nameªkeccak_384¤type¦String¨requiredäkind¤type¦Method¤kind@¨requiredéarguments‘…¤type¦String¤name§message¨requiredäkind"¦scalar„¤name§message¤type¦String¨requiredäkind†¤nameªkeccak_256¦return…¤type¦String¤nameªkeccak_256¨requiredäkind"¦scalar„¤nameªkeccak_256¤type¦String¨requiredäkind¤type¦Method¤kind@¨requiredéarguments‘…¤type¦String¤name§message¨requiredäkind"¦scalar„¤name§message¤type¦String¨requiredäkind†¤nameªkeccak_224¦return…¤type¦String¤nameªkeccak_224¨requiredäkind"¦scalar„¤nameªkeccak_224¤type¦String¨requiredäkind¤type¦Method¤kind@¨requiredéarguments‘…¤type¦String¤name§message¨requiredäkind"¦scalar„¤name§message¤type¦String¨requiredäkind†¤name®hex_keccak_256¦return…¤type¦String¤name®hex_keccak_256¨requiredäkind"¦scalar„¤name®hex_keccak_256¤type¦String¨requiredäkind¤type¦Method¤kind@¨requiredéarguments‘…¤type¦String¤name§message¨requiredäkind"¦scalar„¤name§message¤type¦String¨requiredäkind†¤name±buffer_keccak_256¦return…¤type¦String¤name±buffer_keccak_256¨requiredäkind"¦scalar„¤name±buffer_keccak_256¤type¦String¨requiredäkind¤type¦Method¤kind@¨requiredéarguments‘…¤type¥Bytes¤name§message¨requiredäkind"¦scalar„¤name§message¤type¥Bytes¨requiredäkind†¤name©shake_256¦return…¤type¦String¤name©shake_256¨requiredäkind"¦scalar„¤name©shake_256¤type¦String¨requiredäkind¤type¦Method¤kind@¨requiredéarguments’…¤type¦String¤name§message¨requiredäkind"¦scalar„¤name§message¤type¦String¨requiredäkind…¤type£Int¤nameªoutputBits¨requiredäkind"¦scalar„¤nameªoutputBits¤type£Int¨requiredäkind†¤name©shake_128¦return…¤type¦String¤name©shake_128¨requiredäkind"¦scalar„¤name©shake_128¤type¦String¨requiredäkind¤type¦Method¤kind@¨requiredéarguments’…¤type¦String¤name§message¨requiredäkind"¦scalar„¤name§message¤type¦String¨requiredäkind…¤type£Int¤nameªoutputBits¨requiredäkind"¦scalar„¤nameªoutputBits¤type£Int¨requiredäkind \ No newline at end of file diff --git a/packages/js/test-env/src/wrappers/sha3/wrap.wasm b/packages/js/test-env/src/wrappers/sha3/wrap.wasm new file mode 100644 index 0000000000..a747138749 Binary files /dev/null and b/packages/js/test-env/src/wrappers/sha3/wrap.wasm differ diff --git a/packages/js/test-env/src/wrappers/uts46/wrap.info b/packages/js/test-env/src/wrappers/uts46/wrap.info new file mode 100644 index 0000000000..c63b448bb7 Binary files /dev/null and b/packages/js/test-env/src/wrappers/uts46/wrap.info differ diff --git a/packages/js/test-env/src/wrappers/uts46/wrap.wasm b/packages/js/test-env/src/wrappers/uts46/wrap.wasm new file mode 100644 index 0000000000..686b360813 Binary files /dev/null and b/packages/js/test-env/src/wrappers/uts46/wrap.wasm differ diff --git a/packages/manifests/polywrap/formats/polywrap.deploy/0.2.0.json b/packages/manifests/polywrap/formats/polywrap.deploy/0.2.0.json new file mode 100644 index 0000000000..565b9a3f21 --- /dev/null +++ b/packages/manifests/polywrap/formats/polywrap.deploy/0.2.0.json @@ -0,0 +1,70 @@ +{ + "id": "DeployManifest", + "type": "object", + "additionalProperties": false, + "required": ["format", "sequences"], + "properties": { + "format": { + "description": "Polywrap deployment manifest format version.", + "type": "string", + "const": "0.2.0" + }, + "sequences": { + "description": "Sequences of deployment steps", + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["steps", "name"], + "properties": { + "name": { + "description": "Name of the sequence", + "type": "string" + }, + "steps": { + "description": "Deployment steps", + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["name", "package", "uri"], + "properties": { + "name": { + "description": "Name of the step", + "type": "string" + }, + "package": { + "description": "Name of the deployer package.", + "type": "string" + }, + "config": { + "description": "Step-level custom configuration.", + "type": "object" + }, + "uri": { + "description": "URI to pass into the deploy step.", + "oneOf": [ + { + "type": "string", + "description": "Valid WRAP URI", + "pattern": "^[wrap://]*[a-z\\-\\_0-9]+\\/.+$" + }, + { + "type": "string", + "description": "Name of another step, prefixed with '$'", + "pattern": "^\\$.*" + } + ] + } + } + } + }, + "config": { + "description": "Sequence-level custom configuration.", + "type": "object" + } + } + } + } + } +} diff --git a/packages/templates/wasm/assemblyscript/polywrap.deploy.yaml b/packages/templates/wasm/assemblyscript/polywrap.deploy.yaml index 2b9ab565ce..f0444053fc 100644 --- a/packages/templates/wasm/assemblyscript/polywrap.deploy.yaml +++ b/packages/templates/wasm/assemblyscript/polywrap.deploy.yaml @@ -1,4 +1,4 @@ -format: 0.1.0 +format: "0.1" stages: ipfs_deploy: package: ipfs diff --git a/packages/templates/wasm/interface/polywrap.deploy.yaml b/packages/templates/wasm/interface/polywrap.deploy.yaml index 53f91cac12..194b222de9 100644 --- a/packages/templates/wasm/interface/polywrap.deploy.yaml +++ b/packages/templates/wasm/interface/polywrap.deploy.yaml @@ -1,4 +1,4 @@ -format: 0.1.0 +format: "0.1" stages: ipfs_deploy: package: ipfs diff --git a/packages/templates/wasm/rust/polywrap.deploy.yaml b/packages/templates/wasm/rust/polywrap.deploy.yaml index 2b9ab565ce..f0444053fc 100644 --- a/packages/templates/wasm/rust/polywrap.deploy.yaml +++ b/packages/templates/wasm/rust/polywrap.deploy.yaml @@ -1,4 +1,4 @@ -format: 0.1.0 +format: "0.1" stages: ipfs_deploy: package: ipfs diff --git a/packages/test-cases/cases/cli/wasm/deploy/001-sanity/polywrap.deploy.yaml b/packages/test-cases/cases/cli/wasm/deploy/001-sanity/polywrap.deploy.yaml index 87ce967088..756c7814f4 100644 --- a/packages/test-cases/cases/cli/wasm/deploy/001-sanity/polywrap.deploy.yaml +++ b/packages/test-cases/cases/cli/wasm/deploy/001-sanity/polywrap.deploy.yaml @@ -1,26 +1,44 @@ -format: 0.1.0 -stages: - ipfs_deploy: - package: ipfs - uri: fs/./build - from_deploy: - package: ens - depends_on: ipfs_deploy +format: "0.2.0" +sequences: + - name: fs_to_ens config: - domainName: $DOMAIN_NAME provider: 'http://localhost:8545' ensRegistryAddress: $ENS_REG_ADDR - from_deploy2: - package: ens - depends_on: ipfs_deploy + ensRegistrarAddress: $ENS_REGISTRAR_ADDR + ensResolverAddress: $ENS_RESOLVER_ADDR + gatewayUri: $IPFS_GATEWAY_URI + steps: + - name: ens_register + package: ens-recursive-name-register + uri: wrap://ens/test1.eth + - name: ens_register2 + package: ens-recursive-name-register + uri: wrap://ens/test2.eth + - name: ipfs_deploy + package: ipfs + uri: wrap://fs/./build + - name: from_deploy + package: ens + uri: $$ipfs_deploy + config: + domainName: test1.eth + - name: from_deploy2 + package: ens + uri: $$ipfs_deploy + config: + domainName: test2.eth + - name: ipfs_to_ens config: - domainName: test2.eth provider: 'http://localhost:8545' ensRegistryAddress: $ENS_REG_ADDR - from_uri: - package: ens - uri: ipfs/QmVdDR6QtigTt38Xwpj2Ki73X1AyZn5WRCreBCJq1CEtpF - config: - domainName: test3.eth - provider: 'http://localhost:8545' - ensRegistryAddress: $ENS_REG_ADDR \ No newline at end of file + ensRegistrarAddress: $ENS_REGISTRAR_ADDR + ensResolverAddress: $ENS_RESOLVER_ADDR + steps: + - name: ens_register + package: ens-recursive-name-register + uri: wrap://ens/test3.eth + - name: from_uri + package: ens + uri: wrap://ipfs/QmVdDR6QtigTt38Xwpj2Ki73X1AyZn5WRCreBCJq1CEtpF + config: + domainName: test3.eth \ No newline at end of file diff --git a/packages/test-cases/cases/cli/wasm/deploy/002-no-ext/polywrap.deploy.yaml b/packages/test-cases/cases/cli/wasm/deploy/002-no-ext/polywrap.deploy.yaml index 833f9998f0..9520faeff2 100644 --- a/packages/test-cases/cases/cli/wasm/deploy/002-no-ext/polywrap.deploy.yaml +++ b/packages/test-cases/cases/cli/wasm/deploy/002-no-ext/polywrap.deploy.yaml @@ -1,7 +1,9 @@ -format: 0.1.0 -stages: - ipfs_test: - package: ipfs-test - uri: fs/./build - config: - foo: bar \ No newline at end of file +format: "0.2.0" +sequences: + - name: test + steps: + - name: ipfs_test + package: ipfs-test + uri: wrap://fs/./build + config: + foo: bar \ No newline at end of file diff --git a/packages/test-cases/cases/cli/wasm/deploy/003-invalid-config/polywrap.deploy.yaml b/packages/test-cases/cases/cli/wasm/deploy/003-invalid-config/polywrap.deploy.yaml index af3fd6f637..0b3b4fe498 100644 --- a/packages/test-cases/cases/cli/wasm/deploy/003-invalid-config/polywrap.deploy.yaml +++ b/packages/test-cases/cases/cli/wasm/deploy/003-invalid-config/polywrap.deploy.yaml @@ -1,12 +1,16 @@ -format: 0.1.0 -stages: - ipfs_deploy: - package: ipfs - uri: fs/./build - from_deploy: - package: local-dev-ens - depends_on: ipfs_deploy +format: "0.2.0" +sequences: + - name: test config: - domainName: true - ports: - ethereum: 8545 \ No newline at end of file + gatewayUri: $IPFS_GATEWAY_URI + steps: + - name: ipfs_deploy + package: ipfs + uri: wrap://fs/./build + - name: from_deploy + package: ens + uri: $$ipfs_deploy + config: + domainName: true + ports: + ethereum: 8545 \ No newline at end of file diff --git a/packages/test-cases/cases/cli/wasm/deploy/004-fail-between/polywrap.deploy.yaml b/packages/test-cases/cases/cli/wasm/deploy/004-fail-between/polywrap.deploy.yaml index d2fc924e99..9f03a8b909 100644 --- a/packages/test-cases/cases/cli/wasm/deploy/004-fail-between/polywrap.deploy.yaml +++ b/packages/test-cases/cases/cli/wasm/deploy/004-fail-between/polywrap.deploy.yaml @@ -1,19 +1,30 @@ -format: 0.1.0 -stages: - ipfs_deploy: - package: ipfs - uri: fs/./build - from_deploy: - package: local-dev-ens - depends_on: ipfs_deploy +format: "0.2.0" +sequences: + - name: test config: - domainName: foo - ports: - ethereum: 8545 - from_deploy2: - package: local-dev-ens - depends_on: ipfs_deploy - config: - domainName: test2.eth - ports: - ethereum: 8545 \ No newline at end of file + provider: 'http://localhost:8545' + gatewayUri: $IPFS_GATEWAY_URI + ensRegistryAddress: $ENS_REG_ADDR + ensRegistrarAddress: $ENS_REGISTRAR_ADDR + ensResolverAddress: $ENS_RESOLVER_ADDR + steps: + - name: ipfs_deploy + package: ipfs + uri: fs/./build + - name: from_deploy + package: ens + uri: $$ipfs_deploy + config: + domainName: foo + ports: + ethereum: 8545 + - name: ens_register + package: ens-recursive-name-register + uri: wrap://ens/test2.eth + - name: from_deploy2 + package: ens + uri: $$ipfs_deploy + config: + domainName: test2.eth + ports: + ethereum: 8545 \ No newline at end of file diff --git a/packages/test-cases/cases/cli/wasm/deploy/005-non-loaded-env-var/polywrap.deploy.yaml b/packages/test-cases/cases/cli/wasm/deploy/005-non-loaded-env-var/polywrap.deploy.yaml index b6a8b74c4f..9d4da27288 100644 --- a/packages/test-cases/cases/cli/wasm/deploy/005-non-loaded-env-var/polywrap.deploy.yaml +++ b/packages/test-cases/cases/cli/wasm/deploy/005-non-loaded-env-var/polywrap.deploy.yaml @@ -1,12 +1,16 @@ -format: 0.1.0 -stages: - ipfs_deploy: - package: ipfs - uri: fs/./build - from_deploy: - package: ens - depends_on: ipfs_deploy +format: "0.2.0" +sequences: + - name: test config: - domainName: $NON_LOADED_VAR - provider: 'http://localhost:8545' - ensRegistryAddress: '0x9b1f7F645351AF3631a656421eD2e40f2802E6c0' + gatewayUri: $IPFS_GATEWAY_URI + steps: + - name: ipfs_deploy + package: ipfs + uri: fs/./build + - name: from_deploy + package: ens + uri: $$ipfs_deploy + config: + domainName: $NON_LOADED_VAR + provider: 'http://localhost:8545' + ensRegistryAddress: '0x9b1f7F645351AF3631a656421eD2e40f2802E6c0'