-
Notifications
You must be signed in to change notification settings - Fork 24
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
Feat/deploy #82
Feat/deploy #82
Changes from 9 commits
f76e124
3d1d660
7882619
3aa6edd
a57975d
8ff1cdf
8da4211
1cbb611
1f7febc
5b1b749
1705cfe
6f69cb7
9b99ff8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,105 @@ | ||
import { BaseConfig } from "./config"; | ||
import path from "path"; | ||
import { exec, ExecException } from "child_process"; | ||
|
||
import { BaseConfig, readConfig } from "@/lib/config"; | ||
import { buildApp } from "@/lib/build"; | ||
import { readWorkspace } from "@/lib/workspace"; | ||
import { Log, Network } from "@/lib/types"; | ||
import { readdir, remove, move } from "@/lib/utils/fs"; | ||
import { Logger } from "./logger"; | ||
|
||
const DEPLOY_DIST_FOLDER = "build"; | ||
|
||
export type DeployOptions = { | ||
deployAccountId?: string; | ||
signerAccountId?: string; | ||
signerPublicKey?: string; | ||
signerPrivateKey?: string; | ||
network?: Network; | ||
}; | ||
|
||
// translate files from src/widget to src | ||
export async function translateForBosCli(dist: string) { | ||
const translating = log.loading(`[${dist}] Translating files from src/widget to src`, LogLevels.BUILD); | ||
|
||
const srcDir = path.join(dist, "src", "widget"); | ||
const targetDir = path.join(dist, "src"); | ||
|
||
const new_files = await readdir(srcDir).catch(() => ([])); | ||
const original_files = await readdir(targetDir).catch(() => ([])); | ||
|
||
for (const file of new_files) { | ||
await move(path.join(srcDir, file), path.join(targetDir, file), { overwrite: true }).catch(() => { | ||
translating.error(`Failed to translate: ${path.join(srcDir, file)}`); | ||
}); | ||
} | ||
|
||
for (const file of original_files) { | ||
if (new_files.includes(file)) | ||
continue; | ||
|
||
await remove(path.join(targetDir, file)).catch(() => { | ||
translating.error(`Failed to remove: ${path.join(targetDir, file)}`); | ||
}); | ||
} | ||
|
||
translating.finish(`[${dist}] Translated successfully`); | ||
} | ||
|
||
// deploy the app widgets and modules | ||
export async function deployAppCode(src: string, config: BaseConfig) { | ||
export async function deployAppCode(src: string, dist: string, opts: DeployOptions) { | ||
const deploying = log.loading(`[${src}] Deploying app`, LogLevels.BUILD); | ||
|
||
await buildApp(src, dist, opts.network); | ||
|
||
await translateForBosCli(dist); | ||
|
||
// Deploy using bos-cli; | ||
const config = await readConfig(path.join(src, "bos.config.json"), opts.network); | ||
|
||
const BOS_DEPLOY_ACCOUNT_ID = config.accounts.deploy || opts.deployAccountId; | ||
const BOS_SIGNER_ACCOUNT_ID = config.accounts.signer || opts.signerAccountId; | ||
const BOS_SIGNER_PUBLIC_KEY = opts.signerPublicKey; | ||
const BOS_SIGNER_PRIVATE_KEY = opts.signerPrivateKey; | ||
|
||
exec( | ||
`cd ${dist} && npx bos components deploy "${BOS_DEPLOY_ACCOUNT_ID}" sign-as "${BOS_SIGNER_ACCOUNT_ID}" network-config "${opts.network}" sign-with-plaintext-private-key --signer-public-key "${BOS_SIGNER_PUBLIC_KEY}" --signer-private-key "${BOS_SIGNER_PRIVATE_KEY}" send`, | ||
(error: ExecException | null, stdout: string, stderr: string) => { | ||
if (!error) { | ||
deploying.finish(`[${src}] App deployed successfully`); | ||
return; | ||
} | ||
|
||
deploying.error(error.message); | ||
} | ||
); | ||
deploying.finish(`[${src}] App deployed successfully`); | ||
} | ||
|
||
// publish data.json to SocialDB | ||
export async function deployAppData(src: string, config: BaseConfig) { | ||
} | ||
|
||
export async function deploy(appName: string, opts: DeployOptions) { | ||
const src = '.'; | ||
if (!appName) { | ||
const dist = path.join(src, DEPLOY_DIST_FOLDER); | ||
|
||
await deployAppCode(src, dist, opts); | ||
|
||
} else { | ||
const { apps } = await readWorkspace(src); | ||
|
||
const findingApp = log.loading(`Finding ${appName} in the workspace`, LogLevels.BUILD); | ||
const appSrc = apps.find((app) => app.includes(appName)); | ||
if (!appSrc) { | ||
findingApp.error(`Not found ${appName} in the workspace`); | ||
return; | ||
} | ||
findingApp.finish(`Found ${appName} in the workspace`); | ||
|
||
const dist = path.join(DEPLOY_DIST_FOLDER, appSrc); | ||
|
||
await deployAppCode(appSrc, dist, opts); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,9 +47,9 @@ const app_example_1_output = { | |
"/build/ipfs.json": JSON.stringify({ | ||
"logo.svg": "QmHash", | ||
}, null, 2) + "\n", | ||
"/build/src/widget/hello.utils.module.js": "const hello = (name) => `Hello, ${name}!`;\nreturn { hello };\n", | ||
"/build/src/widget/hello/utils.module.js": "const hello = (name) => `Hello, ${name}!`;\nreturn { hello };\n", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should be a dot, not / |
||
"/build/src/widget/index.jsx": "const hello = \"hi\";\nreturn hello(props);\n", | ||
"/build/src/widget/nested.index.jsx": "const hello = \"hi\";\nreturn hello(props);\n", | ||
"/build/src/widget/nested/index.jsx": "const hello = \"hi\";\nreturn hello(props);\n", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here too |
||
"/build/src/widget/module.jsx": "VM.require(\"test.near/widget/hello.utils.module\");\nreturn hello(\"world\");\n", | ||
"/build/src/widget/config.jsx": "return <h1>test.neartest.near</h1>;\n", | ||
"/build/src/widget/alias.jsx": "return <h1>Hello world!</h1>;\n", | ||
|
@@ -118,7 +118,7 @@ const app_example_2_output = { | |
"/build/ipfs.json": JSON.stringify({ | ||
"logo.svg": "QmHash", | ||
}, null, 2) + "\n", | ||
"/build/src/widget/hello.utils.module.js": "const hello = (name) => `Hello, ${name}!`;\nreturn { hello };\n", | ||
"/build/src/widget/hello/utils.module.js": "const hello = (name) => `Hello, ${name}!`;\nreturn { hello };\n", | ||
"/build/src/widget/index.jsx": "const hello = \"hi\";\nreturn hello(props);\n", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here |
||
"/build/src/widget/module.jsx": "VM.require(\"test.near/widget/hello.utils.module\");\nreturn hello(\"world\");\n", | ||
"/build/src/widget/config.jsx": "return <h1>test.neartest.near</h1>;\n", | ||
|
@@ -168,9 +168,8 @@ describe('build', () => { | |
global.log = unmockedLog; | ||
}) | ||
|
||
it('should build correctly without logs', async () => { | ||
const { logs } = await buildApp('/app_example_1', '/build'); | ||
expect(logs).toEqual([]); | ||
it('should build correctly', async () => { | ||
await buildApp('/app_example_1', '/build'); | ||
expect(vol.toJSON('/build')).toEqual(app_example_1_output); | ||
}) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import * as process from "child_process"; | ||
import { deployAppCode } from '@/lib/deploy'; | ||
import { BaseConfig, DEFAULT_CONFIG } from '@/lib/config'; | ||
import * as fs from '@/lib/utils/fs'; | ||
import { LogLevel, Logger } from "@/lib/logger"; | ||
|
||
import { vol, } from 'memfs'; | ||
jest.mock('fs', () => require('memfs').fs); | ||
jest.mock('fs/promises', () => require('memfs').fs.promises); | ||
jest.mock('child_process', () => ({ | ||
exec: jest.fn((command: string) => { | ||
return command; | ||
}), | ||
})) | ||
|
||
const app_example = { | ||
"./bos.config.json": JSON.stringify({ | ||
...DEFAULT_CONFIG, | ||
account: "test.near", | ||
ipfs: { | ||
gateway: "https://testipfs/ipfs", | ||
}, | ||
format: true, | ||
}), | ||
"./aliases.json": JSON.stringify({ | ||
"name": "world", | ||
}), | ||
"./ipfs/logo.svg": "<svg viewBox='0 0 100 100'><circle cx='50' cy='50' r='50' fill='red' /></svg>", | ||
"./module/hello/utils.ts": "const hello = (name: string) => `Hello, ${name}!`; export default { hello };", | ||
"./widget/index.tsx": "type Hello = {}; const hello: Hello = 'hi'; export default hello;", | ||
"./widget/index.metadata.json": JSON.stringify({ | ||
name: "Hello", | ||
description: "Hello world widget", | ||
}), | ||
"./widget/nested/index.tsx": "type Hello = {}; const hello: Hello = 'hi'; export default hello;", | ||
"./widget/nested/index.metadata.json": JSON.stringify({ | ||
name: "Nested Hello", | ||
description: "Nested Hello world widget", | ||
}), | ||
"./widget/module.tsx": "VM.require('${module_hello_utils}'); export default hello('world');", | ||
"./widget/config.jsx": "return <h1>${config_account}${config_account_deploy}</h1>;", | ||
"./widget/alias.tsx": "export default <h1>Hello ${alias_name}!</h1>;", | ||
"./widget/ipfs.tsx": "export default <img height='100' src='${ipfs_logo.svg}' />;", | ||
"./data/thing/data.json": JSON.stringify({ | ||
"type": "efiz.near/type/thing", | ||
}), | ||
"./data/thing/datastring.jsonc": JSON.stringify({ | ||
name: "Thing", | ||
}), | ||
}; | ||
|
||
const app_example_output = { | ||
"/build/ipfs.json": JSON.stringify({ | ||
"logo.svg": "QmHash", | ||
}, null, 2) + "\n", | ||
"/build/src/hello/utils.module.js": "const hello = (name) => `Hello, ${name}!`;\nreturn { hello };\n", | ||
"/build/src/index.jsx": "const hello = \"hi\";\nreturn hello(props);\n", | ||
"/build/src/nested/index.jsx": "const hello = \"hi\";\nreturn hello(props);\n", | ||
"/build/src/module.jsx": "VM.require(\"test.near/widget/hello.utils.module\");\nreturn hello(\"world\");\n", | ||
"/build/src/config.jsx": "return <h1>test.neartest.near</h1>;\n", | ||
"/build/src/alias.jsx": "return <h1>Hello world!</h1>;\n", | ||
"/build/src/ipfs.jsx": "return <img height=\"100\" src=\"https://testipfs/ipfs/QmHash\" />;\n", | ||
"/build/data.json": JSON.stringify({ | ||
"test.near": { | ||
thing: { | ||
data: { | ||
"type": "efiz.near/type/thing", | ||
}, | ||
datastring: JSON.stringify({ | ||
name: "Thing", | ||
}) | ||
}, | ||
widget: { | ||
index: { | ||
metadata: { | ||
name: "Hello", | ||
description: "Hello world widget", | ||
} | ||
}, | ||
"nested.index": { | ||
metadata: { | ||
name: "Nested Hello", | ||
description: "Nested Hello world widget", | ||
} | ||
|
||
} | ||
} | ||
} | ||
}, null, 2) + "\n", | ||
}; | ||
|
||
const unmockedFetch = global.fetch; | ||
const unmockedLog = global.log; | ||
|
||
describe('deploy', () => { | ||
beforeEach(() => { | ||
vol.reset(); | ||
vol.fromJSON(app_example, '/app_example'); | ||
|
||
global.fetch = (() => { | ||
return Promise.resolve({ | ||
json: () => Promise.resolve({ | ||
cid: "QmHash", | ||
}) | ||
}) | ||
}) as any; | ||
global.log = new Logger(LogLevel.ERROR); | ||
}) | ||
afterAll(() => { | ||
global.fetch = unmockedFetch; | ||
global.log = unmockedLog; | ||
}) | ||
|
||
it('should build correctly', async () => { | ||
await deployAppCode('/app_example', '/build', {}); | ||
expect(vol.toJSON('/build')).toEqual(app_example_output); | ||
elliotBraem marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This all looks good, can you run
bw help
and update the README with this new command?