Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

100 implement upload command #148

Merged
merged 6 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 });
});
});
Loading