Skip to content

Commit

Permalink
100 implement upload command (#148)
Browse files Browse the repository at this point in the history
* Add upload folders script

* Add support for nested folders

* Smart contract panic

* Fix data creation + add test

* Merge uplaod functions + README
  • Loading branch information
bb-face authored Aug 13, 2024
1 parent 0def6ee commit 9473101
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 23 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,15 @@ Commands:

**Command:** `deploy`

Deploys an app in the workspace via a convenient wrapper to [bos-cli-rs](https://github.com/bos-cli-rs/bos-cli-rs).
Deploys an app in the workspace via a convenient wrapper to [bos-cli-rs](https://github.com/bos-cli-rs/bos-cli-rs). It's also possible to add an optional string array in the `bos.config.json` to specify the data to upload:

```
"data": {
"include": ["folder"]
}
```

The upload script will bundle all the json files inside the specified folder and upload the data with the app.

```cmd
bw deploy [app name] --deploy-account-id [deployAccountId] --signer-account-id [signerAccountId] --signer-public-key [signerPublicKey] --signer-private-key [signerPrivateKey]
Expand Down
6 changes: 6 additions & 0 deletions lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ export interface BaseConfig {
index?: string; // widget to use as index
aliasPrefix?: string; // prefix to use for aliases, default is "alias"
aliasesContainsPrefix?: boolean; // aliases keys contains prefix (default is false)
data?: {
include: string[]; // specify folder's array to upload along with widget
}
gateway?: GatewayConfig // gateway config object
}

Expand Down Expand Up @@ -64,6 +67,9 @@ const baseConfigSchema = Joi.object({
aliasPrefix: Joi.string().allow(null),
aliasesContainsPrefix: Joi.boolean().allow(null),
index: Joi.string().allow(null),
data: Joi.object({
include: Joi.array().items(Joi.string()).min(1).required()
}).optional(),
gateway: Joi.object({
tagName: Joi.string(),
bundleUrl: Joi.string(),
Expand Down
71 changes: 51 additions & 20 deletions lib/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import fs from "fs";
import { buildApp } from "@/lib/build";
import { readConfig } from "@/lib/config";
import { Network } from "@/lib/types";
import { move, pathExists, readdir, remove } from "@/lib/utils/fs";
import { move, pathExists, processDirectory, readdir, remove } from "@/lib/utils/fs";
import { readWorkspace } from "@/lib/workspace";
import { SOCIAL_CONTRACT } from './server';

Expand Down Expand Up @@ -108,12 +108,22 @@ export async function deployAppCode(src: string, dist: string, opts: DeployOptio
});
}

export async function deployAppData(appName: string, opts: DeployOptions) {
const config = await readConfig(path.join(appName, "bos.config.json"), opts.network);
const BOS_SIGNER_ACCOUNT_ID = config.accounts.signer || opts.signerAccountId || config.account;
export async function deployAppData(
appName: string,
opts: DeployOptions
) {
const config = await readConfig(
path.join(appName, "bos.config.json"),
opts.network
);

const BOS_SIGNER_ACCOUNT_ID =
config.accounts.signer || opts.signerAccountId || config.account;

if (!BOS_SIGNER_ACCOUNT_ID) {
console.log(`App account is not defined for ${appName}. Skipping data upload`);
console.log(
`App account is not defined for ${appName}. Skipping data upload`
);
return;
}

Expand All @@ -123,26 +133,34 @@ export async function deployAppData(appName: string, opts: DeployOptions) {
);

const args = { data: JSON.parse(dataJSON) };
const argsBase64 = Buffer.from(JSON.stringify(args)).toString("base64");

const BOS_SIGNER_PUBLIC_KEY = opts?.signerPublicKey;
const BOS_SIGNER_PRIVATE_KEY = opts?.signerPrivateKey;

const automaticSignIn = [
"sign-with-plaintext-private-key",
"--signer-public-key",
BOS_SIGNER_PUBLIC_KEY,
"--signer-private-key",
BOS_SIGNER_PRIVATE_KEY,
"send"
]
if (config.data?.include) {
if (!Array.isArray(config.data.include) || config.data.include.length === 0)
throw new Error(
"Config must contain a data.include array with at least one folder"
);

const result = {};

for (const folder of config.data.include) {
const folderName = path.basename(folder);
result[folderName] = {};
await processDirectory(folder, "", result[folderName]);
}

Object.assign(args.data[config.account], result);
}

const argsBase64 = Buffer.from(JSON.stringify(args)).toString("base64");

let command = [
"near-cli-rs",
"contract",
"call-function",
"as-transaction",
opts.network === "mainnet" ? SOCIAL_CONTRACT.mainnet : SOCIAL_CONTRACT.testnet,
opts.network === "mainnet"
? SOCIAL_CONTRACT.mainnet
: SOCIAL_CONTRACT.testnet,
"set",
"base64-args",
`${argsBase64}`,
Expand All @@ -153,10 +171,23 @@ export async function deployAppData(appName: string, opts: DeployOptions) {
"sign-as",
BOS_SIGNER_ACCOUNT_ID,
"network-config",
opts.network,
opts.network,
];

const BOS_SIGNER_PUBLIC_KEY = opts?.signerPublicKey;
const BOS_SIGNER_PRIVATE_KEY = opts?.signerPrivateKey;

const automaticSignIn = [
"sign-with-plaintext-private-key",
"--signer-public-key",
BOS_SIGNER_PUBLIC_KEY,
"--signer-private-key",
BOS_SIGNER_PRIVATE_KEY,
"send",
];

if (BOS_SIGNER_PUBLIC_KEY && BOS_SIGNER_PRIVATE_KEY) command = command.concat(automaticSignIn)
if (BOS_SIGNER_PUBLIC_KEY && BOS_SIGNER_PRIVATE_KEY)
command = command.concat(automaticSignIn);

const deployProcess = spawn("npx", command, {
cwd: path.join(appName, DEPLOY_DIST_FOLDER),
Expand Down
29 changes: 28 additions & 1 deletion lib/utils/fs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { copy, ensureDir, move, outputFile, pathExists, readdir, readFile, readJson, remove, writeJson, existsSync, promises } from 'fs-extra';
import { readdirSync, readFileSync } from 'fs';
import path from 'path';

async function loopThroughFiles(pwd: string, callback: (file: string) => Promise<void>) {
Expand All @@ -16,4 +17,30 @@ async function loopThroughFiles(pwd: string, callback: (file: string) => Promise
}
}

export { copy, ensureDir, loopThroughFiles, move, outputFile, pathExists, readdir, readFile, readJson, remove, writeJson, existsSync, promises };
async function processDirectory(baseDir, currentDir, result) {
const files = await readdirSync(path.join(baseDir, currentDir), { withFileTypes: true });
for (const file of files) {
const relativePath = path.join(currentDir, file.name);
const fullPath = path.join(baseDir, relativePath);

if (file.isDirectory()) {
await processDirectory(baseDir, relativePath, result);
} else if (path.extname(file.name).toLowerCase() === '.json') {
try {
const fileContent = await readFileSync(fullPath, 'utf8');
const jsonContent = JSON.parse(fileContent);
let key;
if (currentDir === '') {
key = path.basename(file.name, '.json');
} else {
key = path.join(currentDir, path.basename(file.name, '.json')).replace(/[\\/]/g, '.');
}
result[key] = { "": JSON.stringify(jsonContent) };
} catch (error) {
console.error(`Error processing file ${fullPath}:`, error);
}
}
}
}

export { copy, ensureDir, loopThroughFiles, move, outputFile, pathExists, readdir, readFile, readJson, remove, writeJson, existsSync, promises, processDirectory };
65 changes: 64 additions & 1 deletion tests/unit/devNoMock.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { startFileWatcher } from "@/lib/watcher";
import path from "path";
import fs from "fs";
import { processDirectory } from "@/lib/utils/fs";

describe("File Watcher Tests", () => {
let watcher;
Expand Down Expand Up @@ -37,4 +38,66 @@ describe("File Watcher Tests", () => {
fs.rmSync(tempDirPath, { recursive: true, force: true });
}
});
});
});

describe('Folder structure processing', () => {
const tempDirPath = path.join(__dirname, 'temp_test_dir');
const testFolderPath = path.join(tempDirPath, 'test');
const nestedTestFolderPath = path.join(testFolderPath, 'nestedTest');
const test1FolderPath = path.join(tempDirPath, 'test1');

beforeAll(() => {
fs.mkdirSync(tempDirPath, { recursive: true });
fs.mkdirSync(testFolderPath, { recursive: true });
fs.mkdirSync(nestedTestFolderPath, { recursive: true });
fs.mkdirSync(test1FolderPath, { recursive: true });

fs.writeFileSync(
path.join(testFolderPath, 'file.json'),
JSON.stringify({ data: "this is test" })
);
fs.writeFileSync(
path.join(nestedTestFolderPath, 'nestedFile.json'),
JSON.stringify({ data: "this is a nested folder", data2: "other data" })
);
fs.writeFileSync(
path.join(test1FolderPath, 'file1.json'),
JSON.stringify({ data: "this is test1" })
);
});

it("should build the correct object based on folder structure", async () => {
const config = {
data: {
include: [tempDirPath]
},
account: 'myaccount'
};

const result = {};

for (const folder of config.data.include) {
const folderName = path.basename(folder);
result[folderName] = {};
await processDirectory(folder, '', result[folderName]);
}

expect(result).toEqual({
'temp_test_dir': {
'test.nestedTest.nestedFile': {
'': JSON.stringify({ data: "this is a nested folder", data2: "other data" })
},
'test.file': {
'': JSON.stringify({ data: "this is test" })
},
'test1.file1': {
'': JSON.stringify({ data: "this is test1" })
}
}
});
});

afterAll(() => {
fs.rmSync(tempDirPath, { recursive: true, force: true });
});
});

0 comments on commit 9473101

Please sign in to comment.