Skip to content

Commit

Permalink
feat: dump support
Browse files Browse the repository at this point in the history
  • Loading branch information
slon2015 committed Jul 31, 2024
1 parent ee0ba95 commit 9093f42
Show file tree
Hide file tree
Showing 11 changed files with 237 additions and 32 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ Used apis:
hardhat/v1/script
```

#### Dump in halfway and resume

```sh
# ton
drew deploy-ton DrewPackages/tonfaucet --params "{\"name\": \"Test Token\", \"symbol\": \"TT\", \"totalSupply\": 100000000000000 }" --dumpAfter 1
```

## Contributing

```sh
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"zod": "^3.23.8"
},
"dependencies": {
"@drewpackages/engine": "^0.2.0",
"@drewpackages/engine": "^0.3.0",
"commander": "^12.1.0",
"dockerode": "^4.0.2",
"fs-extra": "^11.2.0",
Expand Down
14 changes: 7 additions & 7 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

99 changes: 78 additions & 21 deletions src/commands/execute-ton/index.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,72 @@
import { parse, validate } from "@drewpackages/engine";
import { parse, StageInstruction, validate } from "@drewpackages/engine";
import { fetcher } from "../../fetcher";
import { CmdInfoSupplier } from "../types";
import { CombinedConfigResolver, DEFAULT_CONFIG_PATH } from "../../config";
import { TaskExecutor } from "../../executor/tasks";
import { OffchainExecutor } from "../../executor/offchain";
import { StateStorage } from "../../state";
import { ConfigStorage } from "../../config/storage";
import { FormulaExecutionDump } from "dump";
import { normalize, join } from "node:path";
import fs from "fs-extra";

export const ExecuteTonCommandInfo: CmdInfoSupplier = (program) =>
program
.command("deploy-ton")
.description("Deploy drew formula on Ton chain")
.argument("<string>", "Formula")
.argument("[string]", "Formula")
.option("-p --params <object>", "Formula parameters as json object")
.option("-c --config <path>", "Config file path")
.option("--dryRun", "Dry run formula deployment")
.option(
"--dumpAfter <number>",
"Number of executed steps before dump save",
"0"
)
.option("--dumpFileName <path>", "File name for dump save", "./dump.json")
.option("-f --fromDump", "Should read from dump", false)
.action(async (formula, opts) => {
const dumpPath = normalize(join(process.cwd(), opts.dumpFileName));
const dumpFileExists = fs.existsSync(dumpPath);

const dump: FormulaExecutionDump | undefined = Boolean(opts.fromDump)
? JSON.parse(fs.readFileSync(dumpPath).toString("utf-8"))
: undefined;

if (!formula && !dumpFileExists) {
console.log("Formula name or dump file should be provided");
}

let formulaName = formula || dump!.formulaName;

const state = new StateStorage();
const configResolver = new CombinedConfigResolver(
opts.config || DEFAULT_CONFIG_PATH
);

const steps = await validate(
{
formulaName: formula,
},
fetcher,
state,
opts.params !== "" && opts.params != null
? JSON.parse(opts.params)
: undefined
);

const instructions = await parse(steps, configResolver, state);
dump && state.fromDump(dump.state);
const config = new ConfigStorage();
dump && config.fromDump(dump.config);

let instructions: Array<StageInstruction>;

if (!dump) {
const configResolver = new CombinedConfigResolver(
opts.config || DEFAULT_CONFIG_PATH
);

const steps = await validate(
{
formulaName,
},
fetcher,
state,
opts.params !== "" && opts.params != null
? JSON.parse(opts.params)
: undefined
);

instructions = await parse(steps, configResolver, state, config);
} else {
await fetcher.fetchFormulaFileText(formulaName, "formula.js");
instructions = dump.instructions;
}

const tasks = new TaskExecutor(state);
const offchain = new OffchainExecutor(state);
Expand All @@ -40,15 +75,37 @@ export const ExecuteTonCommandInfo: CmdInfoSupplier = (program) =>
console.log("Instructions for execution");
console.log(JSON.stringify(instructions, null, 2));
} else {
for (let index = 0; index < instructions.length; index++) {
const dumpAfter = opts.dumpAfter ? Number.parseInt(opts.dumpAfter) : 0;
const isDumpRequired = dumpAfter > 0;
const formulaNameWithoutRev = formulaName.replace(/\@.+/, "");
for (
let index = dump?.executedSteps || 0;
index < (isDumpRequired ? dumpAfter : instructions.length);
index++
) {
const instruction = instructions[index];
if (instruction.type === "task") {
const outputs = await tasks.runStage(formula, instruction);
const outputs = await tasks.runStage(
formulaNameWithoutRev,
instruction
);
state.addResolvedValues(outputs);
}
if (instruction.type === "offchain") {
await offchain.runStage(formula, instruction);
await offchain.runStage(formulaNameWithoutRev, instruction);
}
}

if (isDumpRequired) {
const dump: FormulaExecutionDump = {
config: config.toDump(),
state: state.toDump(),
executedSteps: dumpAfter,
instructions,
formulaName: await fetcher.getUnambiguousFormulaName(formulaName),
};

fs.writeFileSync(dumpPath, JSON.stringify(dump, null, 2));
}
}
});
4 changes: 3 additions & 1 deletion src/commands/execute/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { OffchainExecutor } from "../../executor/offchain";
import { StateStorage } from "../../state";
import prompts from "prompts";
import z from "zod";
import { ConfigStorage } from "../../config/storage";

const AVAILABLE_NETWORK_RPCS: Array<{ title: string; value: string }> = [
{
Expand Down Expand Up @@ -88,6 +89,7 @@ export const ExecuteEVMCommandInfo: CmdInfoSupplier = (program) =>
.option("--dryRun", "Dry run formula deployment")
.action(async (formula, opts) => {
const state = new StateStorage();
const config = new ConfigStorage();
const configResolver = new CombinedConfigResolver(
opts.config || DEFAULT_CONFIG_PATH
);
Expand All @@ -104,7 +106,7 @@ export const ExecuteEVMCommandInfo: CmdInfoSupplier = (program) =>
opts.params !== "" ? JSON.parse(opts.params) : undefined
);

const instructions = await parse(steps, configResolver, state);
const instructions = await parse(steps, configResolver, state, config);

const tasks = new TaskExecutor(state);
const offchain = new OffchainExecutor(state);
Expand Down
53 changes: 53 additions & 0 deletions src/config/storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { errors, IConfigStorage, ConfigRef } from "@drewpackages/engine";
import { IDumpable } from "../dump";
import z from "zod";

const ConfigStorageDumpSchema = z
.object({
storage: z.record(z.string().min(1), z.any()),
})
.required({ storage: true });

export type IConfigStorageDump = z.infer<typeof ConfigStorageDumpSchema>;

export class ConfigStorage<T extends object>
implements IConfigStorage, IDumpable<IConfigStorageDump>
{
private readonly storage: Map<string, object> = new Map();

set(api: string, resolvedConfig: T) {
if (this.storage.has(api)) {
throw new errors.ApiConfigAlreadyResolvedError(api);
}
this.storage.set(api, resolvedConfig);
}

get(api: string): T {
if (!this.storage.has(api)) {
throw new errors.ApiConfigNotResolvedError(api);
}
return this.storage.get(api) as T;
}

resolve(ref: ConfigRef): string {
if (!this.storage.has(ref.group)) {
throw new errors.ApiConfigNotResolvedError(ref.group);
}

return (this.storage.get(ref.group) as any)[ref.key];
}

isCorrectDump(maybeDump: any): maybeDump is IConfigStorageDump {
return ConfigStorageDumpSchema.safeParse(maybeDump).success;
}

toDump(): IConfigStorageDump {
return { storage: Object.fromEntries(this.storage.entries()) };
}

fromDump(dump: IConfigStorageDump): undefined {
Object.getOwnPropertyNames(dump.storage!).forEach((key) =>
this.storage.set(key, dump.storage[key])
);
}
}
1 change: 1 addition & 0 deletions src/dump/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./types";
17 changes: 17 additions & 0 deletions src/dump/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { StageInstruction } from "@drewpackages/engine";
import { IConfigStorageDump } from "config/storage";
import { IStateStorageDump } from "state/dump";

export interface IDumpable<D extends object> {
isCorrectDump(maybeDump: any): maybeDump is D;
toDump(): D | Promise<D>;
fromDump(dump: D): void | Promise<void>;
}

export interface FormulaExecutionDump {
config: IConfigStorageDump;
state: IStateStorageDump;
instructions: Array<StageInstruction>;
executedSteps: number;
formulaName: string;
}
31 changes: 30 additions & 1 deletion src/fetcher/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ async function isEmptyDir(path: string) {
class GitHubFetcher implements IFormulaFetcher {
private parseRef(formulaRef: string): ParsedFormulaRef {
const rev = formulaRef.includes("@") ? formulaRef.split("@")[1] : undefined;
const segments = formulaRef.split("/").map((s) => s.replace(`@${rev}`, ""));
const segments = formulaRef.replace(/\@.*/, "").split("/");

return {
repo: `${segments[0]}/${segments[1]}`,
Expand All @@ -54,13 +54,29 @@ class GitHubFetcher implements IFormulaFetcher {
"--recurse-submodules",
]);
} else {
const branches = await git.branch();
if (branches.detached || !branches.current) {
await git.checkout(branches.all[0], ["--recurse-submodules"]);
}
await git.pull(undefined, undefined, ["--recurse-submodules"]);
}
if (formulaRef.rev) {
await git.checkout(formulaRef.rev, ["--recurse-submodules"]);
}
}

private async getFormulaCommitHash(
formulaRef: ParsedFormulaRef
): Promise<string | undefined> {
const repoPath = normalize(
join(process.env.HOME as string, `/.drew/formulas/${formulaRef.repo}`)
);
const git = simpleGit(repoPath);
const log = await git.log();

return log.latest?.hash;
}

private async readClonnedFile(
formulaRef: ParsedFormulaRef,
filePath: string
Expand All @@ -84,6 +100,19 @@ class GitHubFetcher implements IFormulaFetcher {
await this.clone(parsedFormulaRef);
return this.readClonnedFile(parsedFormulaRef, filePath);
}

async getUnambiguousFormulaName(formulaName: string): Promise<string> {
const parsedFormulaRef = this.parseRef(formulaName);
const commitHash = await this.getFormulaCommitHash(parsedFormulaRef);

return `${parsedFormulaRef.repo}${
parsedFormulaRef.subtree ? `/${parsedFormulaRef.subtree}` : ""
}${
commitHash || parsedFormulaRef.rev
? `@${commitHash || parsedFormulaRef.rev}`
: ""
}`;
}
}

export const fetcher = new GitHubFetcher();
10 changes: 10 additions & 0 deletions src/state/dump.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import z from "zod";

export const StateStorageDumpSchema = z.object({
outputIds: z.array(z.string().min(1)),
resolvedValues: z
.map(z.string().min(1), z.any())
.or(z.record(z.string(), z.any())),
});

export type IStateStorageDump = z.infer<typeof StateStorageDumpSchema>;
Loading

0 comments on commit 9093f42

Please sign in to comment.