Skip to content

Commit

Permalink
Merge pull request polywrap#182 from Web3-API/prealpha-dev
Browse files Browse the repository at this point in the history
`w3 build --watch` support
  • Loading branch information
dOrgJelli authored Mar 3, 2021
2 parents f88b96f + 71e243b commit 8dddac6
Show file tree
Hide file tree
Showing 9 changed files with 221 additions and 46 deletions.
2 changes: 1 addition & 1 deletion demos/simple-storage/protocol/src/query/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export function getData(input: Input_getData): u32 {
const res = Ethereum_Query.callView({
address: input.address,
method: "function get() view returns (uint256)",
args: []
args: [],
});

return U32.parseInt(res);
Expand Down
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"assemblyscript": "0.17.14",
"axios": "0.19.2",
"chalk": "4.1.0",
"chokidar": "^3.5.1",
"fs-extra": "9.0.1",
"gluegun": "4.6.1",
"graphql-tag": "2.11.0",
Expand Down
151 changes: 109 additions & 42 deletions packages/cli/src/commands/build.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
/* eslint-disable prefer-const */
import { Compiler, Project, SchemaComposer } from "../lib";
import {
Compiler,
Project,
SchemaComposer,
Watcher,
WatchEvent,
watchEventName,
} from "../lib";
import { fixParameters } from "../lib/helpers/parameters";
import { publishToIPFS } from "../lib/publishers/ipfs-publisher";

import chalk from "chalk";
import axios from "axios";
import readline from "readline";
import { GluegunToolbox } from "gluegun";

const HELP = `
Expand All @@ -15,11 +23,9 @@ Options:
-i, --ipfs [<node>] Upload build results to an IPFS node (default: dev-server's node)
-o, --output-dir <path> Output directory for build results (default: build/)
-e, --test-ens <[address,]domain> Publish the package to a test ENS domain locally (requires --ipfs)
-w, --watch Automatically rebuild when changes are made (default: false)
`;

// TODO: add to the above options when implemented
// -w, --watch Regenerate types when web3api files change (default: false)

export default {
alias: ["b"],
description: "Builds a Web3API and (optionally) uploads it to IPFS",
Expand Down Expand Up @@ -145,56 +151,117 @@ export default {
schemaComposer,
});

let result = false;
const execute = async (): Promise<boolean> => {
compiler.clearCache();
const result = await compiler.compile();

/*if (watch) {
// TODO: https://github.com/Web3-API/prototype/issues/98
// compiler.watchAndCompile();
} else*/ {
result = await compiler.compile();
if (result === false) {
process.exitCode = 1;
return;
if (!result) {
return result;
}
}

const uris: string[][] = [];
const uris: string[][] = [];

// publish to IPFS
if (ipfs) {
const cid = await publishToIPFS(outputDir, ipfs);

print.success(`IPFS { ${cid} }`);
uris.push(["Web3API IPFS", `ipfs://${cid}`]);

if (testEns) {
if (!ensAddress) {
uris.push(["ENS Registry", `${ethProvider}/${ensAddress}`]);
}

// publish to IPFS
if (ipfs) {
const cid = await publishToIPFS(outputDir, ipfs);
// ask the dev server to publish the CID to ENS
const { data } = await axios.get(
"http://localhost:4040/register-ens",
{
params: {
domain: ensDomain,
cid,
},
}
);

print.success(`IPFS { ${cid} }`);
uris.push(["Web3API IPFS", `ipfs://${cid}`]);
if (data.success) {
uris.push(["Web3API ENS", `${testEns} => ${cid}`]);
} else {
print.error(
`ENS Resolution Failed { ${testEns} => ${cid} }\n` +
`Ethereum Provider: ${ethProvider}\n` +
`ENS Address: ${ensAddress}`
);
}

if (testEns) {
if (!ensAddress) {
uris.push(["ENS Registry", `${ethProvider}/${ensAddress}`]);
return data.success;
}
}

// ask the dev server to publish the CID to ENS
const { data } = await axios.get("http://localhost:4040/register-ens", {
params: {
domain: ensDomain,
cid,
},
if (uris.length) {
print.success("URI Viewers:");
print.table(uris);
return true;
} else {
return false;
}
};

if (!watch) {
const result = await execute();

if (!result) {
process.exitCode = 1;
return;
}
} else {
// Execute
await execute();

const keyPressListener = () => {
// Watch for escape key presses
print.info(`Watching: ${project.manifestDir}`);
print.info("Exit: [CTRL + C], [ESC], or [Q]");
readline.emitKeypressEvents(process.stdin);
process.stdin.on("keypress", async (str, key) => {
if (
key.name == "escape" ||
key.name == "q" ||
(key.name == "c" && key.ctrl)
) {
await watcher.stop();
process.kill(process.pid, "SIGINT");
}
});

if (data.success) {
uris.push(["Web3API ENS", `${testEns} => ${cid}`]);
} else {
print.error(
`ENS Resolution Failed { ${testEns} => ${cid} }\n` +
`Ethereum Provider: ${ethProvider}\n` +
`ENS Address: ${ensAddress}`
);
if (process.stdin.setRawMode) {
process.stdin.setRawMode(true);
}
}
}

if (uris.length) {
print.success("URI Viewers:");
print.table(uris);
process.stdin.resume();
};

keyPressListener();

// Watch the directory
const watcher = new Watcher();

watcher.start(project.manifestDir, {
ignored: [outputDir + "/**", project.manifestDir + "/**/w3/**"],
ignoreInitial: true,
execute: async (events: WatchEvent[]) => {
// Log all of the events encountered
for (const event of events) {
print.info(`${watchEventName(event.type)}: ${event.path}`);
}

// Execute the build
await execute();

// Process key presses
keyPressListener();
},
});
}

process.exitCode = 0;
Expand Down
6 changes: 5 additions & 1 deletion packages/cli/src/lib/Compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ export class Compiler {
}
}

public clearCache(): void {
this._config.project.clearCache();
this._config.schemaComposer.clearCache();
}

private async _compileWeb3Api(verbose?: boolean) {
const { outputDir, project, schemaComposer } = this._config;

Expand All @@ -44,7 +49,6 @@ export class Compiler {
this._cleanDir(this._config.outputDir);

const manifest = await project.getManifest();

// Get the fully composed schema
const composed = await schemaComposer.getComposedSchemas();

Expand Down
6 changes: 5 additions & 1 deletion packages/cli/src/lib/Project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,15 @@ export class Project {
return !!this._config.quiet;
}

async getManifest(): Promise<Manifest> {
public async getManifest(): Promise<Manifest> {
if (!this._manifest) {
this._manifest = await loadManifest(this.manifestPath, this.quiet);
}

return Promise.resolve(this._manifest);
}

public clearCache(): void {
this._manifest = undefined;
}
}
4 changes: 4 additions & 0 deletions packages/cli/src/lib/SchemaComposer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ export class SchemaComposer {
return this._composerOutput;
}

public clearCache(): void {
this._composerOutput = undefined;
}

private async _fetchExternalSchema(
uri: string,
manifest: Manifest
Expand Down
94 changes: 94 additions & 0 deletions packages/cli/src/lib/Watcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import chokidar from "chokidar";

export type WatchEventType =
| "add"
| "addDir"
| "change"
| "unlink"
| "unlinkDir";

export const watchEventNames: Record<WatchEventType, string> = {
add: "File Added",
addDir: "Folder Added",
change: "File Changed",
unlink: "File Removed",
unlinkDir: "Folder Removed",
};

export function watchEventName(type: WatchEventType): string {
return watchEventNames[type];
}

export interface WatchEvent {
type: WatchEventType;
path: string;
}

export interface WatchOptions extends chokidar.WatchOptions {
execute: (events: WatchEvent[]) => Promise<void>;
}

interface WatchSession {
stop: () => Promise<void>;
directory: string;
}

export class Watcher {
private _session: WatchSession | undefined;

public start(directory: string, options: WatchOptions): void {
if (this._session) {
throw Error(
`Watcher session is already in progress. Directory: ${this._session.directory}`
);
}

const watcher = chokidar.watch(directory, options);
let backlog: WatchEvent[] = [];

// Watch all file system events
watcher.on("all", (type: WatchEventType, path: string) => {
// Add the event to the backlog if it doesn't exist
if (!backlog.some((e) => e.path == path && e.type == type)) {
backlog.push({ type, path });
}
});

// Process the event backlog on a given interval
let instance: ReturnType<typeof setInterval>;
const interval = options.interval || 1000;

const updateLoop = async () => {
if (backlog.length > 0) {
// Reset the interval
clearInterval(instance);

// Execute
await options.execute(backlog);

// Reset the backlog
backlog = [];

// Start a new interval
instance = setInterval(updateLoop, interval);
}
};

instance = setInterval(updateLoop, interval);

this._session = {
stop: async () => {
await watcher.close();
clearInterval(instance);
},
directory,
};
}

public async stop(): Promise<void> {
if (this._session) {
await this._session.stop();
this._session = undefined;
}
}
}
1 change: 1 addition & 0 deletions packages/cli/src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from "./Project";
export * from "./Compiler";
export * from "./CodeGenerator";
export * from "./SchemaComposer";
export * from "./Watcher";
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5997,7 +5997,7 @@ chokidar@^2.0.4, chokidar@^2.1.8:
optionalDependencies:
fsevents "^1.2.7"

chokidar@^3.3.0, chokidar@^3.4.1:
chokidar@^3.3.0, chokidar@^3.4.1, chokidar@^3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a"
integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==
Expand Down

0 comments on commit 8dddac6

Please sign in to comment.