From ca7b6e4ce8f2fac95752dbadc5e7acb8c2d909df Mon Sep 17 00:00:00 2001 From: Elliot Braem <16282460+elliotBraem@users.noreply.github.com> Date: Mon, 15 Apr 2024 00:07:54 -0400 Subject: [PATCH] deploy docs --- README.md | 11 + md/getting_started/installation.md | 19 ++ md/usage/aliases.md | 46 ++++ md/usage/configure_testnet.md | 8 - md/usage/deploy.md | 79 ++++++ md/usage/index.md | 37 +++ widget/components/Header.jsx | 2 +- widget/edit.jsx | 377 +++++++++++++++++++++++++++++ widget/home.jsx | 1 - widget/index.jsx | 7 + widget/modal/create.jsx | 204 ++++++++++++++++ widget/utils/db.js | 8 +- 12 files changed, 783 insertions(+), 16 deletions(-) create mode 100644 md/getting_started/installation.md create mode 100644 md/usage/aliases.md delete mode 100644 md/usage/configure_testnet.md create mode 100644 md/usage/deploy.md create mode 100644 md/usage/index.md create mode 100644 widget/edit.jsx create mode 100644 widget/modal/create.jsx diff --git a/README.md b/README.md index 38e5037..99a0d0a 100644 --- a/README.md +++ b/README.md @@ -17,3 +17,14 @@ yarn run dev ``` This will start a gateway at [127.0.0.1:8080](http://127.0.0.1:8080) which will render your local widgets. The entry point for this app is [docs.bos-workspace.testnet/widget/index](http://127.0.0.1:8080/docs.bos-workspace.testnet/widget/index) + +## Adding documents + +Currently, the process is: + +1. Write your document in the [/md](./md/) directory +2. Add the reference in [utils/db](./widget/utils/db.js), this will get picked up by the sidebar +3. Confirm section/subsection matches path after `/md` +4. Push to branch (configure branch in [github adapter](./widget/PR/adapter/github.jsx)) to see updates. + +Let's build better! diff --git a/md/getting_started/installation.md b/md/getting_started/installation.md new file mode 100644 index 0000000..97af8b1 --- /dev/null +++ b/md/getting_started/installation.md @@ -0,0 +1,19 @@ +# Installation 🏗️ + +You can install `bos-workspace` globally on your machine or within your existing project workspace using npm (or other package manager): + +global install: + +```cmd +npm -g install bos-workspace +``` +or navigate to your project directory and install: + +```cmd +npm install bos-workspace +``` +To verify `bos-workspace` in installed, you can check for a current version: + +```cmd +bos-workspace -V +``` diff --git a/md/usage/aliases.md b/md/usage/aliases.md new file mode 100644 index 0000000..6a65c06 --- /dev/null +++ b/md/usage/aliases.md @@ -0,0 +1,46 @@ +# aliases + +## Account + +The main acount in `bos.config.json`, can be replaced via `overrides`. + +pattern: `{CONFIG_ACCOUNT}` + +## Overrides + +Aliases from bos.config.json are used to replace comments with correct values, useful for widget sources. + +Pattern: `${ALIAS_KEY}` + +```json +{ + "account": "[MAINNET_ACCOUNT_ID]", + "aliases": ["./aliases.mainnet.json"], + "overrides": { + "testnet": { + "account": "[TESTNET_ACCOUNT_ID]", + "aliases": ["./aliases.testnet.json"] + } + } +} +``` + +with the jsons: + +`aliases.mainnet.json` + +```json +{ + "devs": "devs.near" +} +``` + +`aliases.testnet.json` + +```json +{ + "devs": "neardevs.testnet" +} +``` + +Replace all account and contract references with their network alternatives. If alternatives do not exist, we can add to [mainnet-on-testnet](https://github.com/NEARBuilders/mainnet-on-testnet) \ No newline at end of file diff --git a/md/usage/configure_testnet.md b/md/usage/configure_testnet.md deleted file mode 100644 index 3ae327f..0000000 --- a/md/usage/configure_testnet.md +++ /dev/null @@ -1,8 +0,0 @@ -# Configure Testnet - -1. upgrade to bos-workspace v1, [MIGRATION_GUIDE](?page=migration_guide.md) -2. create testnet override + aliases in bos.config.json -3. replace sdks.near with ${config_account} and any other alias with ${alias_devs} -4. configure testnet release workflow in github - -Reference: [quickstart](https://github.com/nearbuilders/quickstart) diff --git a/md/usage/deploy.md b/md/usage/deploy.md new file mode 100644 index 0000000..ca70c95 --- /dev/null +++ b/md/usage/deploy.md @@ -0,0 +1,79 @@ +# Deploy + +command: `deploy` + +Deploy an app in the workspace + +## Usage (CLI) + +Deploy the project with the option to specify an app name (must be name of the folder in /apps directory): + +```cmd +bos-workspace deploy [app name] +``` + +[lib/deploy.ts](https://github.com/NEARBuilders/bos-workspace/blob/main/lib/deploy.ts) +// TODO: embed github code block @hyperfiles.near + +[UNDER CONSTRUCTION] +[TASK: Implement deploy command](https://github.com/NEARBuilders/bos-workspace/issues/77) +@ittechhunter.near +@bos-workspace.near + +## Configuring environments + +### Prerequisites + +1. Must be upgraded to bos-workspace v1, see the [migration guide](?page=getting_started/migration_guide) +2. Specify testnet [overrides + aliases](?page=usage/aliases) in bos.config.json. + + +### Mainnet + +1. Create `.github/workflow/release-mainnet.yml` + +```yml +name: Deploy Components to Mainnet +on: + push: + branches: [main] +jobs: + deploy-mainnet: + uses: NEARBuilders/bos-workspace/.github/workflows/deploy.yml@main + with: + bw-legacy: false + deploy-env: "mainnet" + app-name: "[APP_NAME]" + deploy-account-address: "[DEPLOY_ACCOUNT]" + signer-account-address: "[DEPLOY_ACCOUNT]" + signer-public-key: [PUBLIC_KEY] + secrets: + SIGNER_PRIVATE_KEY: ${{ secrets.SIGNER_PRIVATE_KEY }} // then configure this in your Github/Settings/Actions +``` + +### Testnet + +1. Create `.github/workflow/release-testnet.yml` + +```yml +name: Deploy Components to Testnet +on: + push: + branches: [develop] +jobs: + deploy-mainnet: + uses: NEARBuilders/bos-workspace/.github/workflows/deploy.yml@main + with: + bw-legacy: false + build-env: "testnet" + deploy-env: "testnet" + app-name: "[APP_NAME]" + deploy-account-address: "[DEPLOY_ACCOUNT]" // testnet account + signer-account-address: "[DEPLOY_ACCOUNT]" + signer-public-key: [PUBLIC_KEY] + secrets: + SIGNER_PRIVATE_KEY: ${{ secrets.SIGNER_PRIVATE_KEY }} // then configure this in your Github/Settings/Actions +``` + + +Reference: [quickstart](https://github.com/nearbuilders/quickstart) diff --git a/md/usage/index.md b/md/usage/index.md new file mode 100644 index 0000000..1962b39 --- /dev/null +++ b/md/usage/index.md @@ -0,0 +1,37 @@ +# Usage 👷🏽‍♀️ + +You can use your `bos-workspace` for both single and multi app development by taking advantage of the relationship between `Apps` and `Workspaces` + +**App:** 🛠️ +- belongs to an Account +- described by a `bos.config.json` where the content is: +``` +{ + "account": "app.near" +} +``` + +- path to code: `{projectId}/widget/*` +- cloning: `bos-workspace clone {accountId}` to create an App with `bos.config.json` set up and pull in all of the widgets from that `accountId` +

+ +*Sample directory structure* + +sample app structure +

+ +**Workspace:** 🛠️ +- able to hold multiple Apps at the same time (similar to a monrepo) +- described by a `bos.workspace.json` where the content is: +``` +{ + "apps": ["/apps/*"] +} +``` +
+*Sample directory structure* + + +

+ +📝 **Note:** App names are not required to end in `.near` or be stored in a directory named `/apps`. Be sure your `bos.config.json` is located at the same level as directories like `/widget` and your `bos.workspace.json` reflects the name of the directory where your apps are located \ No newline at end of file diff --git a/widget/components/Header.jsx b/widget/components/Header.jsx index 3215c24..6ac493b 100644 --- a/widget/components/Header.jsx +++ b/widget/components/Header.jsx @@ -4,7 +4,7 @@ return (
-

bos-workspace

+

everything.dev

diff --git a/widget/edit.jsx b/widget/edit.jsx new file mode 100644 index 0000000..cf59452 --- /dev/null +++ b/widget/edit.jsx @@ -0,0 +1,377 @@ +const { MarkdownViewer } = VM.require("${alias_devs}/widget/markdown.view") || { + MarkdownViewer: () => null, +}; + +const PageContainer = styled.div` + display: flex; + flex-direction: column; + height: 100vh; +`; + +const Header = styled.div` + background-color: #333; + padding: 20px; + display: flex; + justify-content: space-between; +`; + +const EditorWrapper = styled.div` + flex: 1; + padding: 96px; + box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1); +`; + +const EditorTextarea = styled.textarea` + width: 100%; + height: 100%; + border: none; + font-size: 16px; + resize: none; + outline: none; +`; + +const PreviewContent = styled.div` + color: #333; + font-size: 16px; +`; + +const Select = styled.select``; + +const Option = styled.option``; + +const Label = styled.label` + margin-right: 10px; +`; + +const Button = styled.button` + // this could take in theme + padding: 10px 20px; +`; + +const ModalBox = styled.div` + background-color: white; + min-width: 400px; + max-width: 600px; + padding: 20px; + border-radius: 8px; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); + z-index: 1003; +`; + +const draftKey = "draft"; + +const set = (k, v) => { + Storage.privateSet(k, v); +}; + +const get = (k) => { + return Storage.privateGet(k); +}; + +const draft = get(draftKey); +const defaultViewMode = get("viewMode"); +const defaultPreview = get("preview"); +const defaultEditor = get("editor"); +const defaultLanguage = get("language"); +const defaultType = get("type"); +const defaultPath = get("path"); + +if ( + draft === null || + viewMode === null || + defaultPreview === null || + defaultEditor === null || + defaultLanguage === null || + defaultType === null || + defaultPath === null +) { + return ""; +} + +const [content, setContent] = useState(draft); +const [viewMode, setViewMode] = useState(defaultViewMode || "single"); // 'single' or 'split' +const [showPreview, setShowPreview] = useState(defaultPreview || false); +const [type, setType] = useState(defaultType || ""); +const [editor, setEditor] = useState(defaultEditor || ""); +const [language, setLanguage] = useState(defaultLanguage || "md"); +const [path, setPath] = useState(props.path || defaultPath || ""); + +const handleToggleViewMode = () => { + const newMode = viewMode === "single" ? "split" : "single"; + set("viewMode", newMode); + setViewMode(newMode); + set("preview", false); + setShowPreview(false); +}; + +const handleTogglePreview = () => { + set("preview", !showPreview); + setShowPreview(!showPreview); +}; + +const editors = [ + { + value: "", + label: "default textarea", + }, + { + value: "${alias_devs}/widget/markdown.SimpleMDE", + label: "SimpleMDE", + }, + { + value: "${alias_devs}/widget/markdown.MarkdownEditorIframe", + label: "MarkdownEditorIframe", + }, +]; + +const languages = [ + { + value: "md", + label: "Markdown", + }, + { + value: "json", + label: "JSON", + }, +]; + +const types = [ + { + value: "document", + label: "Document", + }, +]; + +const DefaultEditor = ({ value, onChange, onBlur }) => ( + +); + +return ( + +
+
+ {viewMode === "single" && ( + + )} + +
+
+ { + State.update({ + ...state, + saveModalOpen: open, + }); + }, + toggle: ( + + ), + content: ( +
+ + { + setPath(v); + set("path", v); + }, + data: JSON.stringify({ body: content }), + closeModal: () => { + State.update({ + ...state, + saveModalOpen: false, + }); + }, + }} + /> + +
+ ), + }} + /> + { + State.update({ + ...state, + postModalOpen: open, + }); + }, + toggle: ( + + ), + content: ( +
+ + { + State.update({ + ...state, + postModalOpen: false, + }); + }, + }} + /> + +
+ ), + }} + /> +
+
+
+ + + + + + +
+ {viewMode === "single" ? ( + + {showPreview ? ( + + ) : ( + <> + {editor ? ( + { + setContent(v); + set(draftKey, v); + }, + }} + /> + ) : ( + { + let v; + if (language === "json") { + v = JSON.stringify(JSON.parse(content), null, 2); + if (v !== "null") { + setContent(v); + set(draftKey, v); + } + } + }} + onChange={(e) => { + let v = e.target.value; + setContent(v); + Storage.privateSet(draftKey, v); + }} + /> + )} + + )} + + ) : ( +
+ + {editor ? ( + { + setContent(v); + set(draftKey, v); + }, + }} + /> + ) : ( + { + let v; + if (language === "json") { + v = JSON.stringify(JSON.parse(content), null, 2); + if (v !== "null") { + setContent(v); + set(draftKey, v); + } + } + }} + onChange={(e) => { + let v = e.target.value; + setContent(v); + Storage.privateSet(draftKey, v); + }} + /> + )} + + + + +
+ )} +
+); diff --git a/widget/home.jsx b/widget/home.jsx index 22344e0..9ef8a38 100644 --- a/widget/home.jsx +++ b/widget/home.jsx @@ -27,7 +27,6 @@ const Button = styled.button` `; const BosWorkspaceInfo = styled.p` - text-align: center; font-size: 1.1rem; color: #444; margin: 2rem; diff --git a/widget/index.jsx b/widget/index.jsx index 629b9f0..b763be0 100644 --- a/widget/index.jsx +++ b/widget/index.jsx @@ -62,6 +62,13 @@ const config = { initialProps: {}, }, }, + { + path: "/edit/:path*", + element: { + src: "${config_account}/widget/edit", + initialProps: {}, + }, + }, { path: "/settings", element: { diff --git a/widget/modal/create.jsx b/widget/modal/create.jsx new file mode 100644 index 0000000..4fdcbd7 --- /dev/null +++ b/widget/modal/create.jsx @@ -0,0 +1,204 @@ +const Wrapper = styled.div` + max-width: 400px; + margin: 0 auto; +`; + +const TabContent = styled.div` + margin-top: 1rem; +`; + +const Form = styled.div` + display: flex; + flex-direction: column; + gap: 4px; + width: 100%; +`; + +const Label = styled.label` + font-weight: bold; +`; + +const Input = styled.input` + padding: 5px; +`; + +const Select = styled.select` + padding: 8px; +`; + +const FormGroup = styled.div` + display: flex; + flex-direction: column; +`; + +const adapters = [ + { + title: "Social DB", + value: null, + }, + { + title: "IPFS", + value: "everycanvas.near/widget/adapter.ipfs", + }, + { + title: "Sputnik DAO", + value: "devs.near/widget/adapter.sputnik-dao", + }, +]; + +const defaultAdapter = adapters[0]; + +const { creatorId } = props; + +const [json, setJson] = useState(props.data ?? ""); +const [source, setSource] = useState(props.source ?? ""); +const [adapter, setAdapter] = useState(defaultAdapter.value ?? ""); +const [reference, setReference] = useState(undefined); +const [activeTab, setActiveTab] = useState("data"); +const [name, setName] = useState(props.name ?? ""); +const [description, setDescription] = useState(props.description ?? ""); +const [type, setType] = useState(props.type ?? "document"); +const [path, setPath] = useState(props.path ?? ""); + +const socialDbAdapter = { + get: (path, blockHeight) => { + if (!path) console.log("path not provided") && null; + if (!blockHeight) blockHeight = "final"; + return Social.get(path, blockHeight); + }, + create: (v, path, type) => { + const parts = path.split("/"); + let nestedObject = {}; + let currentLevel = nestedObject; + + for (let i = 1; i < parts.length - 1; i++) { + const part = parts[i]; + currentLevel[part] = {}; + currentLevel = currentLevel[part]; + } + + currentLevel[parts[parts.length - 1]] = { + "": v, + metadata: { + name: name, + description: description, + type: type, + }, + }; + + return Social.set(nestedObject, { + force: "true", + onCommit: (v) => { + if (props.closeModal) props.closeModal(); + }, + onCancel: (v) => { + if (props.closeModal) props.closeModal(); + }, + }); + }, +}; + +const handleCreate = () => { + // load in the state.adapter (modules for IPFS, Arweave, Ceramic, Verida, On Machina... ) + const { create } = adapter ? VM.require(adapter) : socialDbAdapter; + // const { create } = VM.require(adapter) || (() => {}); + if (create) { + // store the data somewhere, based on the adapter + create(json, path, type); + } + if (props.setPath) props.setPath(path); +}; + +return ( + +

create

+ + + + {activeTab === "data" && ( +
+ + + setPath(e.target.value)} + /> + + + + setType(e.target.value)} + /> + +