Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Commit

Permalink
Merge in develop branch and regenerate yarn.lock
Browse files Browse the repository at this point in the history
  • Loading branch information
eggplantzzz committed Mar 18, 2021
2 parents b209881 + 0699e26 commit 556c51f
Show file tree
Hide file tree
Showing 16 changed files with 4,300 additions and 52 deletions.
4 changes: 4 additions & 0 deletions packages/preserve-to-ipfs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/*
yarn.lock
package-lock.json
dist
20 changes: 20 additions & 0 deletions packages/preserve-to-ipfs/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module.exports = {
moduleFileExtensions: ["ts", "js", "json", "node"],
testEnvironment: "node",
transform: {
"^.+\\.ts$": "ts-jest"
},
globals: {
"ts-jest": {
tsConfig: "<rootDir>/tsconfig.json",
diagnostics: true
}
},
testMatch: [
// match files in test/ directories (exclude root `./test/`)
"<rootDir>/@(lib|test)/**/test/*.@(ts|js)",

// or files that use a test extension prefix
"<rootDir>/@(lib|test)/**/*.@(test|spec).@(ts|js)"
]
};
35 changes: 35 additions & 0 deletions packages/preserve-to-ipfs/lib/connect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import createIpfsClient from "ipfs-http-client";
import * as Preserve from "@truffle/preserve";
import { IpfsClient } from "./ipfs-adapter";

export interface ConnectOptions {
controls: Preserve.Controls;
address: string;
}

export async function* connect(
options: ConnectOptions
): Preserve.Process<IpfsClient> {
const { address: url, controls } = options;
const { step } = controls;

const task = yield* step({
message: `Connecting to IPFS node at ${url}...`
});

const ipfs = createIpfsClient({ url });

try {
const version = await ipfs.version();

yield* task.succeed({
result: version,
message: `Connected to IPFS node at ${url}`
});
} catch (error) {
yield* task.fail({ error });
return;
}

return ipfs;
}
52 changes: 52 additions & 0 deletions packages/preserve-to-ipfs/lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* @module @truffle/preserve-to-ipfs
*/ /** */

import CID from "cids";
import * as Preserve from "@truffle/preserve";

import { connect } from "./connect";
import { upload } from "./upload";

export interface Result {
cid: CID;
}

export interface ConstructorOptions
extends Preserve.Recipes.ConstructorOptions {
address: string;
}

export const defaultAddress = "http://localhost:5001";

export class Recipe implements Preserve.Recipe {
name = "@truffle/preserve-to-ipfs";

static help = "Preserve to IPFS";

dependencies: string[] = [];

private address: string;

constructor(options: ConstructorOptions) {
this.address = options.address || defaultAddress;
}

async *preserve(
options: Preserve.Recipes.PreserveOptions
): Preserve.Process<Result> {
const { address } = this;
const { target: rawTarget, controls } = options;
const { log } = controls;

yield* log({ message: "Preserving to IPFS..." });

const ipfs = yield* connect({ address, controls });

const { source } = Preserve.Targets.normalize(rawTarget);

const { cid } = yield* upload({ source, ipfs, controls });

return { cid };
}
}
23 changes: 23 additions & 0 deletions packages/preserve-to-ipfs/lib/ipfs-adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as Preserve from "@truffle/preserve";
import createIpfsHttpClient from "ipfs-http-client";

// extract the typing for an IIPFS client from the default export of "ipfs-http-client"
// since the actual type is not exported
export type IpfsClient = ReturnType<typeof createIpfsHttpClient>;

// Define an interface for a "FileObject", which gets uploaded to IPFS
export interface FileObject {
path: string;
content: Preserve.Targets.Normalized.Sources.Content;
}

// Extract the result interface of the IpfsClient::addAll method
export type IpfsAddAllResults = ReturnType<IpfsClient["addAll"]>;

// Define an interface for the result of the IpfsClient::get method
export interface IpfsGetResult {
path: string;
content?: AsyncIterable<Uint8Array>;
}

export type IpfsGetResults = AsyncIterable<IpfsGetResult>;
34 changes: 34 additions & 0 deletions packages/preserve-to-ipfs/lib/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as Preserve from "@truffle/preserve";
import { FileObject } from "./ipfs-adapter";

export type SearchOptions = {
source: Preserve.Targets.Normalized.Source;
path?: string;
};

export const search = ({
source,
path = "."
}: SearchOptions): AsyncIterable<FileObject> => {
if (Preserve.Targets.Sources.isContent(source)) {
return (async function* () {
yield {
path,
content: source
};
})();
}

return (async function* () {
for await (const entry of source.entries) {
const results = search({
source: entry.source,
path: `${path}/${entry.path}`
});

for await (const result of results) {
yield result;
}
}
})();
};
75 changes: 75 additions & 0 deletions packages/preserve-to-ipfs/lib/upload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import chalk from "chalk";
import CID from "cids";

import * as Preserve from "@truffle/preserve";
import { IpfsClient } from "./ipfs-adapter";
import { search } from "./search";
import { asyncToArray } from "iter-tools";

export interface UploadOptions {
controls: Preserve.Controls;
source: Preserve.Targets.Normalized.Source;
ipfs: IpfsClient;
}

export interface UploadResult {
cid: CID;
}

export async function* upload(
options: UploadOptions
): Preserve.Process<UploadResult> {
const { source, ipfs, controls } = options;
const { step } = controls;

const task = yield* step({
message: "Uploading..."
});

// depth-first search to add files to IPFS before parent directories
const data = await asyncToArray(search({ source }));

// define a dictionary of values for CIDs that are resolved asynchronously
const values: {
[name: string]: Preserve.Control.ValueResolutionController;
} = {};

values.root = yield* task.declare({ identifier: "Root CID" });

for await (const { path } of data) {
if (path === ".") continue;
values[path] = yield* values.root.extend({ identifier: path });
}

const results = ipfs.addAll(data, {
wrapWithDirectory: Preserve.Targets.Sources.isContainer(source)
});

let result;
try {
for await (result of results) {
const { path, cid } = result;

// path is prefixed with ./ to match the result format to the source format
const value = values[`./${path}`];

if (value) {
yield* value.resolve({
resolution: { cid },
payload: cid.toString()
});
}
}
} catch (error) {
yield* task.fail({ error });
}

yield* values.root.resolve({
resolution: result,
payload: chalk.bold(result.cid.toString())
});

yield* task.succeed();

return result;
}
38 changes: 38 additions & 0 deletions packages/preserve-to-ipfs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "@truffle/preserve-to-ipfs",
"version": "0.1.0",
"description": "Truffle `preserve` command support for IPFS",
"main": "dist/lib/index.js",
"types": "dist/lib/index.d.ts",
"files": [
"dist",
"truffle-plugin.json"
],
"scripts": {
"build": "tsc",
"prepare": "yarn build",
"test": "./scripts/test.sh"
},
"repository": "https://github.com/trufflesuite/truffle/tree/master/packages/preserve-to-ipfs",
"author": "g. nicholas d'andrea <gnidan@trufflesuite.com>",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@ganache/filecoin": "^0.1.2",
"@types/jest": "^26.0.20",
"@types/node": "^14.14.22",
"ganache": "^3.0.0-filecoin.3",
"jest": "^26.6.3",
"semver": "^7.3.4",
"ts-jest": "^26.5.0",
"typescript": "^4.1.3"
},
"dependencies": {
"@truffle/preserve": "^0.1.0",
"cids": "^1.1.5",
"ipfs-http-client": "^48.2.2",
"iter-tools": "^7.0.2"
}
}
11 changes: 11 additions & 0 deletions packages/preserve-to-ipfs/scripts/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#! /usr/bin/env sh

NODE_VERSION="$(node --version)"

# If Node version >=12 we run tests
if yarn semver -r ">=12" $NODE_VERSION; then
yarn jest --verbose --detectOpenHandles $@
# Otherwise we skip the tests since IPFS requires at least Node 12
else
true
fi
Loading

0 comments on commit 556c51f

Please sign in to comment.