├── .github
└── workflows
│ └── release.yml
├── .gitignore
├── CHANGELOG.md
├── Cargo.toml
├── LICENSE
├── README.md
├── README.zh_CN.md
├── npm
├── .gitignore
├── README.md
├── binary.js
├── install.js
├── package.json
└── run.js
├── rsw.png
└── src
├── config.rs
├── core
├── build.rs
├── clean.rs
├── cli.rs
├── create.rs
├── error.rs
├── info.rs
├── init.rs
├── link.rs
├── mod.rs
└── watch.rs
├── lib.rs
├── main.rs
├── template
├── mod.rs
├── rsw.toml
├── rsw_cargo.toml
├── rsw_lib.rs
└── rsw_readme.md
└── utils.rs
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | tags:
4 | - 'v*' # Run when tag matches v*, i.e. v1.0, v20.15.10
5 |
6 | name: Release
7 |
8 | env:
9 | RELEASE_BIN: rsw
10 | RELEASE_DIR: artifacts
11 | GITHUB_REF: '${{ github.ref }}'
12 | WINDOWS_TARGET: x86_64-pc-windows-msvc
13 | MACOS_TARGET: x86_64-apple-darwin
14 | LINUX_AMD64_TARGET: x86_64-unknown-linux-musl
15 | LINUX_ARM64_TARGET: aarch64-unknown-linux-musl
16 |
17 | # Space separated paths to include in the archive.
18 | RELEASE_ADDS: README.md LICENSE
19 |
20 | jobs:
21 | build:
22 | name: Build artifacts
23 | runs-on: ${{ matrix.os }}
24 | strategy:
25 | matrix:
26 | include:
27 | - target: x86_64-unknown-linux-musl
28 | os: ubuntu-latest
29 | rust: stable
30 | - target: aarch64-unknown-linux-musl
31 | os: ubuntu-latest
32 | rust: stable
33 | - target: x86_64-apple-darwin
34 | os: macos-latest
35 | rust: stable
36 | - target: x86_64-pc-windows-msvc
37 | os: windows-latest
38 | rust: stable
39 |
40 | steps:
41 | - uses: actions/checkout@v2
42 | - uses: actions-rs/toolchain@v1
43 | with:
44 | toolchain: ${{ matrix.rust }}
45 | override: true
46 | target: wasm32-unknown-unknown
47 |
48 | - name: Query version number
49 | id: get_version
50 | shell: bash
51 | run: |
52 | echo "using version tag ${GITHUB_REF:10}"
53 | echo ::set-output name=version::"${GITHUB_REF:10}"
54 | - name: Install C compilation tooling (Linux)
55 | if: matrix.os == 'ubuntu-latest'
56 | run: |
57 | sudo apt-get update -y
58 | sudo apt-get install clang gcc-aarch64-linux-gnu -y
59 | echo "TARGET_CC=clang" >> $GITHUB_ENV
60 | echo "CFLAGS_aarch64_unknown_linux_musl=--sysroot=/usr/aarch64-linux-gnu" >> $GITHUB_ENV
61 | echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=/usr/aarch64-linux-gnu/bin/ld" >> $GITHUB_ENV
62 | - name: Install p7zip (MacOS)
63 | if: matrix.os == 'macos-latest'
64 | run: brew install p7zip
65 |
66 | - name: Add rustup target
67 | run: rustup target add ${{ matrix.target }}
68 |
69 | - name: Build
70 | run: cargo build --release --target ${{ matrix.target }}
71 |
72 | - name: Set RUSTFLAGS (Windows)
73 | if: matrix.os == 'windows-latest'
74 | run: echo "RUSTFLAGS=-Ctarget-feature=+crt-static" >> $GITHUB_ENV
75 |
76 | - name: Create artifact directory
77 | run: |
78 | mkdir ${{ env.RELEASE_DIR }}
79 | mkdir -p ${{ env.RELEASE_DIR }}/${{ env.RELEASE_BIN }}-${{ steps.get_version.outputs.VERSION }}-${{ matrix.target }}
80 | - name: Move binaries (Linux/MacOS)
81 | if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest'
82 | run: |
83 | mv ./target/${{ matrix.target }}/release/${{ env.RELEASE_BIN }} ${{ env.RELEASE_DIR }}/${{ env.RELEASE_BIN }}-${{ steps.get_version.outputs.VERSION }}-${{ matrix.target }}/${{ env.RELEASE_BIN }}
84 | mv ${{ env.RELEASE_ADDS }} ./${{ env.RELEASE_DIR }}/${{ env.RELEASE_BIN }}-${{ steps.get_version.outputs.VERSION }}-${{ matrix.target }}
85 | - name: Move binaries (Windows)
86 | if: matrix.os == 'windows-latest'
87 | shell: bash
88 | run: |
89 | cp ./target/${{ matrix.target }}/release/${{ env.RELEASE_BIN }}.exe ./${{ env.RELEASE_DIR }}/${{ env.RELEASE_BIN }}-${{ steps.get_version.outputs.VERSION }}-${{ matrix.target }}/${{ env.RELEASE_BIN }}.exe
90 | cp ./target/${{ matrix.target }}/release/${{ env.RELEASE_BIN }}.exe rsw.exe
91 | mv ${{ env.RELEASE_ADDS }} ./${{ env.RELEASE_DIR }}/${{ env.RELEASE_BIN }}-${{ steps.get_version.outputs.VERSION }}-${{ matrix.target }}
92 | mv rsw.exe ${{ env.RELEASE_DIR }}
93 | - name: Create tarball
94 | run: 7z a -ttar -so -an ./${{ env.RELEASE_DIR }}/${{ env.RELEASE_BIN }}-${{ steps.get_version.outputs.VERSION }}-${{ matrix.target }} | 7z a -si ./${{ env.RELEASE_DIR }}/${{ env.RELEASE_BIN }}-${{ steps.get_version.outputs.VERSION }}-${{ matrix.target }}.tar.gz
95 |
96 | - name: Upload Zip
97 | uses: actions/upload-artifact@v1
98 | with:
99 | name: ${{ matrix.target }}
100 | path: ./${{ env.RELEASE_DIR }}
101 |
102 | release:
103 | name: GitHub Release
104 | needs: build
105 | runs-on: ubuntu-latest
106 | steps:
107 | - name: Query version number
108 | id: get_version
109 | shell: bash
110 | run: |
111 | echo "using version tag ${GITHUB_REF:10}"
112 | echo ::set-output name=version::"${GITHUB_REF:10}"
113 | - name: Create Release
114 | id: create_release
115 | uses: actions/create-release@v1
116 | env:
117 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
118 | with:
119 | tag_name: ${{ steps.get_version.outputs.VERSION }}
120 | release_name: ${{ steps.get_version.outputs.VERSION }}
121 |
122 | - name: Download Linux amd64 tarball
123 | uses: actions/download-artifact@v2
124 | with:
125 | name: ${{ env.LINUX_AMD64_TARGET }}
126 |
127 | - name: Download Linux arm64 tarball
128 | uses: actions/download-artifact@v2
129 | with:
130 | name: ${{ env.LINUX_ARM64_TARGET }}
131 |
132 | - name: Download Windows tarball
133 | uses: actions/download-artifact@v2
134 | with:
135 | name: ${{ env.WINDOWS_TARGET }}
136 |
137 | - name: Download MacOS tarball
138 | uses: actions/download-artifact@v2
139 | with:
140 | name: ${{ env.MACOS_TARGET }}
141 |
142 | - name: Release Linux amd64 tarball
143 | uses: actions/upload-release-asset@v1
144 | env:
145 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
146 | with:
147 | upload_url: ${{ steps.create_release.outputs.upload_url }}
148 | asset_path: ./rsw-${{ steps.get_version.outputs.VERSION }}-${{ env.LINUX_AMD64_TARGET }}.tar.gz
149 | asset_content_type: application/gzip
150 | asset_name: rsw-${{ steps.get_version.outputs.VERSION }}-${{ env.LINUX_AMD64_TARGET }}.tar.gz
151 |
152 | - name: Release Linux arm64 tarball
153 | uses: actions/upload-release-asset@v1
154 | env:
155 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
156 | with:
157 | upload_url: ${{ steps.create_release.outputs.upload_url }}
158 | asset_path: ./rsw-${{ steps.get_version.outputs.VERSION }}-${{ env.LINUX_ARM64_TARGET }}.tar.gz
159 | asset_content_type: application/gzip
160 | asset_name: rsw-${{ steps.get_version.outputs.VERSION }}-${{ env.LINUX_ARM64_TARGET }}.tar.gz
161 |
162 | - name: Release Windows tarball
163 | uses: actions/upload-release-asset@v1
164 | env:
165 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
166 | with:
167 | upload_url: ${{ steps.create_release.outputs.upload_url }}
168 | asset_path: ./rsw-${{ steps.get_version.outputs.VERSION }}-${{ env.WINDOWS_TARGET }}.tar.gz
169 | asset_content_type: application/gzip
170 | asset_name: rsw-${{ steps.get_version.outputs.VERSION }}-${{ env.WINDOWS_TARGET }}.tar.gz
171 |
172 | - name: Release Windows init exe
173 | uses: actions/upload-release-asset@v1
174 | env:
175 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
176 | with:
177 | upload_url: ${{ steps.create_release.outputs.upload_url }}
178 | asset_path: ./rsw.exe
179 | asset_content_type: application/vnd.microsoft.portable-executable
180 | asset_name: rsw.exe
181 |
182 | - name: Release MacOS tarball
183 | uses: actions/upload-release-asset@v1
184 | env:
185 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
186 | with:
187 | upload_url: ${{ steps.create_release.outputs.upload_url }}
188 | asset_path: ./rsw-${{ steps.get_version.outputs.VERSION }}-${{ env.MACOS_TARGET }}.tar.gz
189 | asset_content_type: application/gzip
190 | asset_name: rsw-${{ steps.get_version.outputs.VERSION }}-${{ env.MACOS_TARGET }}.tar.gz
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | target/
4 | Cargo.lock
5 |
6 | node_modules/
7 | yarn.lock
8 | package-lock.json
9 |
10 | /rsw.toml
11 | /@rsw
12 | /rsw-*
13 | /.rsw
14 | /.watchignore
15 |
16 | # IDE
17 | .vscode/
18 | .idea/
19 | /npm/bin/
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 0.8.0
4 |
5 | - feat: `rsw.toml` adds `scope` field
6 |
7 | ## 0.7.12
8 |
9 | - fix: `config.cli` optional with default to `npm`
10 |
11 | ## 0.7.11
12 |
13 | - fix: watch mode in windows doesn't refresh
14 |
15 | ## 0.7.10
16 |
17 | - fix: build link
18 |
19 | ## 0.7.7
20 |
21 | - fix: npm binary install
22 |
23 | ## 0.7.6
24 |
25 | - feat: watch abort
26 |
27 | ## 0.7.5
28 |
29 | - feat: add .watchignore
30 |
31 | ## 0.7.4
32 |
33 | - fix: yarn link
34 |
35 | ## 0.7.3
36 |
37 | - fix: path clean
38 | - fix: `rsw.toml` - the `run` field is invalid
39 |
40 | ## 0.7.2
41 |
42 | - fix: check env
43 |
44 | ## 0.7.1
45 |
46 | - fix: crate root
47 |
48 | ## 0.7.0
49 |
50 | - add `.rsw/rsw.crates`
51 |
52 | ## 0.6.0
53 |
54 | - add `.rsw` dir
55 |
56 | ## 0.5.0
57 |
58 | - link - npm, yarn or pnpm
59 | - rsw clean - link & build
60 |
61 | ## 0.4.0
62 |
63 | - rsw.log - generate log files in watch mode
64 |
65 | ## 0.3.0
66 |
67 | - npm install
68 |
69 | ## 0.2.0
70 |
71 | - rsw new (rsw | user)
72 | - add logger
73 |
74 | ## 0.1.0
75 |
76 | - rsw new (wasm-pack new)
77 | - rsw init - generate `rsw.toml`
78 | - rsw watch
79 |
80 | ## 0.0.2
81 |
82 | - rsw build
83 | - rsw.toml
84 |
85 | ## 0.0.1
86 |
87 | Initial release.
88 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "rsw"
3 | version = "0.8.0"
4 | description = "wasm-pack based build tool"
5 | edition = "2021"
6 | authors = ["lencx "]
7 | homepage = "https://github.com/lencx/rsw-rs"
8 | repository = "https://github.com/lencx/rsw-rs"
9 | keywords = ["rsw", "wasm-pack", "webassembly", "wasm", "npm"]
10 | license = "MIT"
11 |
12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
13 |
14 | [dependencies]
15 | anyhow = "1.0.52"
16 | clap = { version = "3.0.5", features = ["derive"] }
17 | colored = "2.0.0"
18 | env_logger = "0.9.0"
19 | log = "0.4.14"
20 | notify = "4.0.17"
21 | path-clean = "0.1.0"
22 | regex = "1.5.4"
23 | serde = "1.0.133"
24 | serde_derive = "1.0.133"
25 | toml = "0.5.8"
26 | which = "4.2.5"
27 | ignore = "0.4.18"
28 | tokio = { version = "1.18.0", features = ["macros", "rt-multi-thread"] }
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 lencx
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
rsw-rs
4 |
5 |
6 | **`rsw = rs(rust) → w(wasm)`** - A command-line tool for automatically rebuilding local changes, based on the `wasm-pack` implementation.
7 |
8 | [](https://crates.io/crates/rsw)
9 | [](https://docs.rs/rsw)
10 |
11 | **Englist | [简体中文](./README.zh_CN.md)**
12 |
13 | ## Pre-installed
14 |
15 | - [rust](https://www.rust-lang.org/learn/get-started)
16 | - [nodejs](https://nodejs.org)
17 | - [wasm-pack](https://github.com/rustwasm/wasm-pack)
18 |
19 | ## Usage
20 |
21 | ```bash
22 | # Rust - install globally
23 | cargo install rsw
24 | ```
25 |
26 | ```bash
27 | # help
28 | rsw -h
29 |
30 | # rsw.toml - initial configuration
31 | rsw init
32 |
33 | # generate a wasm project
34 | rsw new
35 |
36 | # dev mode
37 | rsw watch
38 |
39 | # release mode
40 | rsw build
41 |
42 | # clean - link & build
43 | rsw clean
44 | ```
45 |
46 | ## Awesome rsw
47 |
48 | - [[rsw demo] learn-wasm](https://github.com/lencx/learn-wasm) - 🎲 Learning WebAssembly
49 | - [vite-plugin-rsw](https://github.com/lencx/vite-plugin-rsw) - 🦀 wasm-pack plugin for Vite
50 | - [create-mpl](https://github.com/lencx/create-mpl) - ⚡️ Create a project in seconds!
51 | - [Oh My Box](https://github.com/lencx/OhMyBox) - 🔮 Development toolbox, and more...
52 |
53 | ## Logger
54 |
55 | ```bash
56 | # @see: https://github.com/env-logger-rs/env_logger
57 | # RUST_LOG=rsw= rsw
58 | # 1. info
59 | RUST_LOG=rsw=info rsw
60 |
61 | # 2. all: info, trace, debug, error, warn
62 | RUST_LOG=rsw rsw
63 | ```
64 |
65 | ### .watchignore
66 |
67 | Defines files/paths to be ignored. Similar to `.gitignore`.
68 |
69 | Example:
70 |
71 | ```bash
72 | # .watchignore
73 | *.js
74 | a/b/**/*.txt
75 | !a/b/**/main.txt
76 | ```
77 |
78 | ## rsw.toml
79 |
80 | > configuration file
81 |
82 | - [TOML Doc](https://toml.io/en/)
83 | - [`wasm-pack build` Doc](https://rustwasm.github.io/docs/wasm-pack/commands/build.html)
84 |
85 | ### Options
86 |
87 | Create `rsw.toml` in the project root path, configure the `rust crate` parameter, and run the `rsw watch` or `rsw build` command.
88 |
89 | - **`name`** - Profile name (optional)
90 | - **`version`** - Profile version (optional)
91 | - **`interval`** - Development mode `rsw watch`, time interval for file changes to trigger `wasm-pack build`, default `50` milliseconds
92 | - **`cli`** - `npm` | `yarn` | `pnpm`, default is `npm`. Execute `link` using the specified `cli`, e.g. `npm link`
93 | - **`[new]`** - Quickly generate a crate with `wasm-pack new`, or set a custom template in `rsw.toml -> [new] -> using`
94 | - **`using`** - `wasm-pack` | `rsw` | `user`, default is `wasm-pack`
95 | - `wasm-pack` - `rsw new --template --mode ` [wasm-pack new doc](https://rustwasm.github.io/docs/wasm-pack/commands/new.html)
96 | - `rsw` - `rsw new `, built-in templates
97 | - `user` - `rsw new `, if `dir` is not configured, use `wasm-pack new ` to initialize the project.
98 | - **`dir`** - Copy all files in this directory. This field needs to be configured when `using = "user"`. `using = "wasm-pack"` or `using = "rsw"`, this field will be ignored
99 | - **`[[crates]]`** - Is an array that supports multiple `rust crate` configurations
100 | - **`name`** - npm package name, supporting organization, e.g. `@rsw/foo`
101 | - **`root`** - Relative to the project root path, default is `.`
102 | - **`link`** - `true` | `false`,default is `false`, Whether to execute the `link` command after this `rust crate` is built
103 | - **`target`** - `bundler` | `nodejs` | `web` | `no-modules`, default is `web`
104 | - **`scope`** - npm organization
105 | - **`out-dir`** - npm package output path, default `pkg`
106 | - **`[crates.watch]`** - Development mode
107 | - **`run`** - Whether this `crate` needs to be watching, default is `true`
108 | - **`profile`** - `dev` | `profiling`, default is `dev`
109 | - **`[crates.build]`** - Production mode
110 | - **`run`** - Whether this `crate` needs to be build, default is `true`
111 | - **`profile`** - `release` | `profiling`, default is `release`
112 |
113 | **Note: `name` in `[[crates]]` is required, other fields are optional.**
114 |
115 | ## .rsw
116 |
117 | > `rsw watch` - temp dir
118 |
119 | - rsw.info - information about the `watch` mode
120 | - `[RSW::OK]`
121 | - `[RSW::ERR]`
122 | - `[RSW::NAME]`
123 | - `[RSW::PATH]`
124 | - `[RSW::BUILD]`
125 | - rsw.err - `wasm-pack build` error
126 | - rsw.crates
127 |
128 | ### Example
129 |
130 | ```toml
131 | # rsw.toml
132 |
133 | name = "rsw"
134 | version = "0.1.0"
135 |
136 | #! time interval for file changes to trigger wasm-pack build, default `50` milliseconds
137 | interval = 50
138 |
139 | #! link
140 | #! npm link @see https://docs.npmjs.com/cli/v8/commands/npm-link
141 | #! yarn link @see https://classic.yarnpkg.com/en/docs/cli/link
142 | #! pnpm link @see https://pnpm.io/cli/link
143 | #! The link command will only be executed if `[[crates]] link = true`
144 | #! cli: `npm` | `yarn` | `pnpm`, default is `npm`
145 | cli = "npm"
146 |
147 | #! ---------------------------
148 |
149 | #! rsw new
150 | [new]
151 | #! @see https://rustwasm.github.io/docs/wasm-pack/commands/new.html
152 | #! using: `wasm-pack` | `rsw` | `user`, default is `wasm-pack`
153 | #! 1. wasm-pack: `rsw new --template --mode `
154 | #! 2. rsw: `rsw new `, built-in templates
155 | #! 3. user: `rsw new `, if `dir` is not configured, use `wasm-pack new ` to initialize the project
156 | using = "wasm-pack"
157 | #! this field needs to be configured when `using = "user"`
158 | #! `using = "wasm-pack"` or `using = "rsw"`, this field will be ignored
159 | #! copy all files in this directory
160 | dir = "my-template"
161 |
162 | #! ################# NPM Package #################
163 |
164 | #! When there is only `name`, other fields will use the default configuration
165 |
166 | #! 📦 -------- package: rsw-hello --------
167 | [[crates]]
168 | #! npm package name (path: $ROOT/rsw-hello)
169 | name = "rsw-hello"
170 | #! run `npm link`: `true` | `false`, default is `false`
171 | link = false
172 |
173 | #! 📦 -------- package: @rsw/utils --------
174 | [[crates]]
175 | #! npm package name (path: $ROOT/utils)
176 | name = "utils"
177 | # #! scope: npm org
178 | scope = "rsw"
179 | #! run `npm link`: `true` | `false`, default is `false`
180 | link = false
181 |
182 | #! 📦 -------- package: @rsw/hello --------
183 | [[crates]]
184 | #! npm package name (path: $ROOT/@rsw/hello)
185 | name = "@rsw/hello"
186 | #! default is `.`
187 | root = "."
188 | #! default is `pkg`
189 | out-dir = "pkg"
190 | #! target: bundler | nodejs | web | no-modules, default is `web`
191 | target = "web"
192 | #! run `npm link`: `true` | `false`, default is `false`
193 | link = false
194 | #! rsw watch
195 | [crates.watch]
196 | #! default is `true`
197 | run = true
198 | #! profile: `dev` | `profiling`, default is `dev`
199 | profile = "dev"
200 | #! rsw build
201 | [crates.build]
202 | #! default is `true`
203 | run = true
204 | #! profile: `release` | `profiling`, default is `release`
205 | profile = "release"
206 | ```
207 |
208 | ## License
209 |
210 | MIT License © 2022 lencx
211 |
--------------------------------------------------------------------------------
/README.zh_CN.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
rsw-rs
4 |
5 |
6 | **`rsw = rs(rust) → w(wasm)`** - 基于 `wasm-pack` 实现的一个命令行工具,当本地文件变更时自动构建。
7 |
8 | **[English](./README.md) | 简体中文**
9 |
10 | ## 预安装
11 |
12 | - [rust](https://www.rust-lang.org/learn/get-started)
13 | - [nodejs](https://nodejs.org)
14 | - [wasm-pack](https://github.com/rustwasm/wasm-pack)
15 |
16 | ## 用法
17 |
18 | ```bash
19 | # 在 Rust 环境下安装到全局
20 | cargo install rsw
21 | ```
22 |
23 | ```bash
24 | # 查看帮助
25 | rsw -h
26 |
27 | # rsw.toml - 初始化配置
28 | rsw init
29 |
30 | # 生成一个 wasm 项目
31 | rsw new
32 |
33 | # 开发模式
34 | rsw watch
35 |
36 | # 生产构建
37 | rsw build
38 |
39 | # 清除 link 及 build 产物
40 | rsw clean
41 | ```
42 |
43 | ## Awesome rsw
44 |
45 | - [[rsw demo] learn-wasm](https://github.com/lencx/learn-wasm) - 🎲 Learning WebAssembly
46 | - [vite-plugin-rsw](https://github.com/lencx/vite-plugin-rsw) - 🦀 wasm-pack plugin for Vite
47 | - [create-mpl](https://github.com/lencx/create-mpl) - ⚡️ Create a project in seconds!
48 |
49 | ## 日志
50 |
51 | ```bash
52 | # @see: https://github.com/env-logger-rs/env_logger
53 | # RUST_LOG=rsw= rsw
54 | # 1. info
55 | RUST_LOG=rsw=info rsw
56 |
57 | # 2. all: info, trace, debug, error, warn
58 | RUST_LOG=rsw rsw
59 | ```
60 |
61 | ### .watchignore
62 |
63 | 定义要忽略的文件/路径,类似于 `.gitignore`。
64 |
65 | 例如:
66 |
67 | ```bash
68 | # .watchignore
69 | *.js
70 | a/b/**/*.txt
71 | !a/b/**/main.txt
72 | ```
73 |
74 | ## rsw.toml
75 |
76 | > 配置文件
77 |
78 | - [TOML 文档](https://toml.io/cn/)
79 | - [`wasm-pack build` 文档](https://rustwasm.github.io/docs/wasm-pack/commands/build.html)
80 |
81 | ## 配置信息
82 |
83 | 在项目根路径下创建 `rsw.toml`,配置 `rust crate` 参数,然后执行 `rsw watch` 或者 `rsw build`。
84 |
85 | - **`name`** - 配置文件名称(无意义,可选)
86 | - **`version`** - 配置文件版本(无意义,可选)
87 | - **`interval`** - 开发模式 `rsw watch` 下,文件变更触发 `wasm-pack build` 的时间间隔,默认 `50` 毫秒
88 | - **`cli`** - `npm` | `yarn` | `pnpm`,默认是 `npm`。使用指定的 `cli` 执行 `link`,例如 `npm link`
89 | - **`[new]`** - 使用 `wasm-pack new` 快速生成一个 `rust crate`, 或者使用自定义模板 `rsw.toml -> [new] -> using`
90 | - **`using`** - `wasm-pack` | `rsw` | `user`, 默认是 `wasm-pack`
91 | - `wasm-pack` - `rsw new --template --mode `,了解更多 [wasm-pack new 文档](https://rustwasm.github.io/docs/wasm-pack/commands/new.html)
92 | - `rsw` - `rsw new `, 使用内置模板
93 | - `user` - `rsw new `, 如果未设置 `dir`,则使用 `wasm-pack new ` 初始化项目
94 | - **`dir`** - 如果 `using = "user"` 则复制此目录下的所有文件初始化项目,`using = "wasm-pack"` 或 `using = "rsw"` 时,则忽略这个字段
95 | - **`[[crates]]`** - 是一个数组,支持多个 `rust crate` 配置
96 | - **`name`** - npm 包名,支持组织,例如 `@rsw/foo`
97 | - **`root`** - 此 `rust crate` 在项目根路径下的相对路径,默认 `.`
98 | - **`link`** - `true` | `false`,默认为 `false`,此 `rust crate` 构建后是否执行 `link` 命令,与 `cli` 配合使用
99 | - **`target`** - `bundler` | `nodejs` | `web` | `no-modules`, 默认 `web`
100 | - **`scope`** - npm 组织
101 | - **`out-dir`** - npm 包输出路径,默认 `pkg`
102 | - **`[crates.watch]`** - 开发模式下的配置
103 | - **`run`** - 是否执行,默认为 `true`
104 | - **`profile`** - `dev` | `profiling`,默认 `dev`
105 | - **`[crates.build]`** - 生产构建下的配置
106 | - **`run`** - 是否执行,默认为 `true`
107 | - **`profile`** - `release` | `profiling`,默认 `release`
108 |
109 | **注意:`[[crates]]` 中 `name` 是必须的,其他字段均为可选。**
110 |
111 | ## .rsw
112 |
113 | > `rsw watch` - 临时目录
114 |
115 | - rsw.info - `watch` 模式下相关信息
116 | - `[RSW::OK]`
117 | - `[RSW::ERR]`
118 | - `[RSW::NAME]`
119 | - `[RSW::PATH]`
120 | - `[RSW::BUILD]`
121 | - rsw.err - `wasm-pack build` 失败信息
122 | - rsw.crates - `rsw.toml` 中的所有包信息
123 |
124 | ### 示例
125 |
126 | ```toml
127 | # rsw.toml
128 |
129 | name = "rsw"
130 | version = "0.1.0"
131 |
132 | #! time interval for file changes to trigger wasm-pack build, default `50` milliseconds
133 | interval = 50
134 |
135 | #! link
136 | #! npm link @see https://docs.npmjs.com/cli/v8/commands/npm-link
137 | #! yarn link @see https://classic.yarnpkg.com/en/docs/cli/link
138 | #! pnpm link @see https://pnpm.io/cli/link
139 | #! The link command will only be executed if `[[crates]] link = true`
140 | #! cli: `npm` | `yarn` | `pnpm`, default is `npm`
141 | cli = "npm"
142 |
143 | #! ---------------------------
144 |
145 | #! rsw new
146 | [new]
147 | #! @see https://rustwasm.github.io/docs/wasm-pack/commands/new.html
148 | #! using: `wasm-pack` | `rsw` | `user`, default is `wasm-pack`
149 | #! 1. wasm-pack: `rsw new --template --mode `
150 | #! 2. rsw: `rsw new `, built-in templates
151 | #! 3. user: `rsw new `, if `dir` is not configured, use `wasm-pack new ` to initialize the project
152 | using = "wasm-pack"
153 | #! this field needs to be configured when `using = "user"`
154 | #! `using = "wasm-pack"` or `using = "rsw"`, this field will be ignored
155 | #! copy all files in this directory
156 | dir = "my-template"
157 |
158 | #! ################# NPM Package #################
159 |
160 | #! When there is only `name`, other fields will use the default configuration
161 |
162 | #! 📦 -------- package: rsw-hello --------
163 | [[crates]]
164 | #! npm package name (path: $ROOT/rsw-hello)
165 | name = "rsw-hello"
166 | #! run `npm link`: `true` | `false`, default is `false`
167 | link = false
168 |
169 | #! 📦 -------- package: @rsw/utils --------
170 | [[crates]]
171 | #! npm package name (path: $ROOT/utils)
172 | name = "utils"
173 | # #! scope: npm org
174 | scope = "rsw"
175 | #! run `npm link`: `true` | `false`, default is `false`
176 | link = false
177 |
178 | #! 📦 -------- package: @rsw/hello --------
179 | [[crates]]
180 | #! npm package name (path: $ROOT/@rsw/hello)
181 | name = "@rsw/hello"
182 | #! default is `.`
183 | root = "."
184 | #! default is `pkg`
185 | out-dir = "pkg"
186 | #! target: bundler | nodejs | web | no-modules, default is `web`
187 | target = "web"
188 | #! run `npm link`: `true` | `false`, default is `false`
189 | link = false
190 | #! rsw watch
191 | [crates.watch]
192 | #! default is `true`
193 | run = true
194 | #! profile: `dev` | `profiling`, default is `dev`
195 | profile = "dev"
196 | #! rsw build
197 | [crates.build]
198 | #! default is `true`
199 | run = true
200 | #! profile: `release` | `profiling`, default is `release`
201 | profile = "release"
202 | ```
203 |
204 | ## License
205 |
206 | MIT License © 2022 lencx
207 |
--------------------------------------------------------------------------------
/npm/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/npm/README.md:
--------------------------------------------------------------------------------
1 | # @rsw/cli
2 |
3 | ## Pre-installed
4 |
5 | - [rust](https://www.rust-lang.org/learn/get-started)
6 | - [nodejs](https://nodejs.org)
7 | - [wasm-pack](https://github.com/rustwasm/wasm-pack)
8 |
9 | ## Quick start
10 |
11 | ```bash
12 | # https://github.com/lencx/create-mpl
13 | # npm 6.x
14 | npm init mpl@latest my-app --type wasm
15 |
16 | # npm 7+, extra double-dash is needed:
17 | npm init mpl@latest my-app -- --type wasm
18 | ```
19 |
20 | ---
21 |
22 | ## Usgae
23 |
24 | ```bash
25 | # install
26 | npm install -g @rsw/cli
27 | ```
28 |
29 | ```bash
30 | # help
31 | rsw -h
32 |
33 | # rsw.toml - initial configuration
34 | rsw init
35 |
36 | # generate a wasm project
37 | rsw new
38 |
39 | # dev mode
40 | rsw watch
41 |
42 | # release mode
43 | rsw build
44 |
45 | # clean - link & build
46 | rsw clean
47 | ```
48 |
49 | [rsw docs](https://github.com/lencx/rsw-rs/blob/main/README.md)
50 |
--------------------------------------------------------------------------------
/npm/binary.js:
--------------------------------------------------------------------------------
1 | const { Binary } = require('binary-install');
2 | const os = require('os');
3 |
4 | const name = 'rsw';
5 | const { rsw_version: version, repository } = require('./package.json');
6 |
7 | const supportedPlatforms = [
8 | {
9 | TYPE: 'Windows_NT',
10 | ARCHITECTURE: 'x64',
11 | RUST_TARGET: 'x86_64-pc-windows-msvc',
12 | BINARY_NAME: `${name}.exe`
13 | },
14 | {
15 | TYPE: 'Linux',
16 | ARCHITECTURE: 'x64',
17 | RUST_TARGET: 'x86_64-unknown-linux-musl',
18 | BINARY_NAME: name
19 | },
20 | {
21 | TYPE: 'Darwin',
22 | ARCHITECTURE: 'x64',
23 | RUST_TARGET: 'x86_64-apple-darwin',
24 | BINARY_NAME: name
25 | }
26 | ];
27 |
28 | const getPlatformMetadata = () => {
29 | const type = os.type();
30 | const architecture = os.arch();
31 |
32 | for (let supportedPlatform of supportedPlatforms) {
33 | if (
34 | type === supportedPlatform.TYPE &&
35 | architecture === supportedPlatform.ARCHITECTURE
36 | ) {
37 | return supportedPlatform;
38 | }
39 | }
40 |
41 | error(
42 | `Platform with type '${type}' and architecture '${architecture}' is not supported by ${name}.\nYour system must be one of the following:\n\n${cTable.getTable(
43 | supportedPlatforms
44 | )}`
45 | );
46 | };
47 |
48 | const getBinary = () => {
49 | const platformMetadata = getPlatformMetadata();
50 | // the url for this binary is constructed from values in `package.json`
51 | const url = `${repository.url}/releases/download/v${version}/rsw-v${version}-${platformMetadata.RUST_TARGET}.tar.gz`;
52 | return new Binary(platformMetadata.BINARY_NAME, url);
53 | };
54 |
55 | const run = () => {
56 | const binary = getBinary();
57 | binary.run();
58 | };
59 |
60 | const install = () => {
61 | const binary = getBinary();
62 | binary.install();
63 | };
64 |
65 | module.exports = {
66 | install,
67 | run
68 | };
69 |
--------------------------------------------------------------------------------
/npm/install.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const { install } = require("./binary");
4 | install();
--------------------------------------------------------------------------------
/npm/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@rsw/cli",
3 | "version": "0.7.24",
4 | "rsw_version": "0.7.12",
5 | "description": "🦞 wasm-pack based build tool",
6 | "author": "lencx ",
7 | "license": "MIT",
8 | "main": "binary.js",
9 | "scripts": {
10 | "postinstall": "node ./install.js"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/rwasm/rsw-rs"
15 | },
16 | "keywords": [
17 | "rsw",
18 | "wasm",
19 | "rust-wasm",
20 | "registry",
21 | "cli",
22 | "rust",
23 | "npm",
24 | "package"
25 | ],
26 | "bugs": {
27 | "url": "https://github.com/rwasm/rsw-rs/issues"
28 | },
29 | "homepage": "https://github.com/rwasm/rsw-rs#readme",
30 | "dependencies": {
31 | "axios": "^0.27.2",
32 | "binary-install": "^1.0.6",
33 | "rimraf": "^3.0.2",
34 | "tar": "^6.1.11"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/npm/run.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const { run } = require("./binary");
4 | run();
--------------------------------------------------------------------------------
/rsw.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwasm/rsw-rs/eccdc9c8f944deb08d6d435484cb0e05200861df/rsw.png
--------------------------------------------------------------------------------
/src/config.rs:
--------------------------------------------------------------------------------
1 | //! rsw.toml parse
2 |
3 | use anyhow::{Error, Result};
4 | use std::{env, fs, process};
5 |
6 | use crate::core::RswErr;
7 | use crate::utils::print;
8 |
9 | pub static RSW_FILE: &str = "rsw.toml";
10 | pub static RSW_DIR: &str = ".rsw";
11 | pub static RSW_CRATES: &str = "rsw.crates";
12 | pub static RSW_INFO: &str = "rsw.info";
13 | pub static RSW_ERR: &str = "rsw.err";
14 |
15 | /// rust crate config
16 | #[derive(Debug, Clone, Serialize, Deserialize)]
17 | // @see https://serde.rs/container-attrs.html#rename_all
18 | #[serde(rename_all = "kebab-case")]
19 | pub struct CrateConfig {
20 | ///
21 | ///
22 | /// Your package's name, and must be lowercase and one word,
23 | /// and may contain hyphens and underscores, support `scope`.
24 | /// For example: `rsw-foo`, `@rsw/foo`
25 | pub name: String,
26 | #[serde(default = "default_root")]
27 | /// crate root path, default is `.`
28 | pub root: Option,
29 | ///
30 | ///
31 | /// By default, wasm-pack will generate a directory for it's build output called `pkg`.
32 | /// You can use `out-dir` to customize the directory where files are generated.
33 | #[serde(default = "default_out_dir")]
34 | pub out_dir: Option,
35 | /// run npm link: `true` | `false`, default is `false`
36 | #[serde(default = "default_false")]
37 | pub link: Option,
38 | #[serde(default = "default_watch")]
39 | pub watch: Option,
40 | #[serde(default = "default_build")]
41 | pub build: Option,
42 | /// target: bundler | nodejs | web | no-modules
43 | ///
44 | ///
45 | #[serde(default = "default_target")]
46 | pub target: Option,
47 | /// scope: npm organization
48 | ///
49 | ///
50 | pub scope: Option,
51 | // TODO
52 | // pub mode: Option,
53 | }
54 |
55 | /// `rsw watch` - watch config
56 | #[derive(Debug, Clone, Serialize, Deserialize)]
57 | #[serde(rename_all = "kebab-case")]
58 | pub struct WatchOptions {
59 | /// When executing the command `rsw watch`, whether to include this `crate`.
60 | #[serde(default = "default_true")]
61 | pub run: Option,
62 | /// profile: dev | profiling
63 | ///
64 | ///
65 | ///
66 | /// When in `watch` mode, the value of `profile` is `dev`,
67 | /// building this way is faster but applies few optimizations to the output,
68 | /// and enables debug assertions and other runtime correctness checks.
69 | /// The `--profiling` and `--release` profiles use cargo's release profile,
70 | /// but the former enables debug info as well,
71 | /// which helps when investigating performance issues in a profiler.
72 | #[serde(default = "default_dev")]
73 | pub profile: Option,
74 | }
75 |
76 | /// `rsw build` - build config
77 | #[derive(Debug, Clone, Serialize, Deserialize)]
78 | #[serde(rename_all = "kebab-case")]
79 | pub struct BuildOptions {
80 | /// When executing the command `rsw build`, whether to include this `crate`.
81 | #[serde(default = "default_true")]
82 | pub run: Option,
83 | /// profile: release | profiling
84 | ///
85 | ///
86 | ///
87 | /// The `--profiling` and `--release` profiles use cargo's release profile,
88 | /// but the former enables debug info as well,
89 | /// which helps when investigating performance issues in a profiler.
90 | #[serde(default = "default_release")]
91 | pub profile: Option,
92 | }
93 |
94 | /// `rsw new` - new config
95 | #[derive(Debug, Clone, Serialize, Deserialize)]
96 | #[serde(rename_all = "kebab-case")]
97 | pub struct NewOptions {
98 | #[serde(default = "default_wasmpack")]
99 | /// using: `wasm-pack` | `rsw` | `user`, default is `wasm-pack`
100 | ///
101 | /// - `wasm-pack` - `rsw new --template --mode ` [wasm-pack new doc](https://rustwasm.github.io/docs/wasm-pack/commands/new.html)
102 | /// - `rsw` - `rsw new `, built-in templates
103 | /// - `user` - `rsw new `, if `dir` is not configured, use `wasm-pack new ` to initialize the project.
104 | pub using: Option,
105 | #[serde(default = "default_empty")]
106 | /// Copy all files in this directory.
107 | /// This field needs to be configured when `using = "user"`.
108 | /// `using = "wasm-pack"` or `using = "rsw"`, this field will be ignored.
109 | pub dir: Option,
110 | }
111 |
112 | /// rsw config
113 | #[derive(Debug, Clone, Serialize, Deserialize)]
114 | pub struct RswConfig {
115 | /// rsw name
116 | pub name: Option,
117 | /// rsw version
118 | pub version: Option,
119 | /// npm link - `npm` | `yarn` | `pnpm`, default is `npm`
120 | pub cli: Option,
121 | /// In `watch` mode, the time interval for `wasm-pack build`, in milliseconds.
122 | pub interval: Option,
123 | #[serde(default = "default_new")]
124 | pub new: Option,
125 | /// rust crates
126 | #[serde(default)]
127 | pub crates: Vec,
128 | }
129 |
130 | impl Default for RswConfig {
131 | fn default() -> Self {
132 | Self {
133 | name: Some("rsw".into()),
134 | version: Some("0.0.0".into()),
135 | interval: Some(50),
136 | cli: Some("npm".into()),
137 | new: default_new(),
138 | crates: vec![],
139 | }
140 | }
141 | }
142 |
143 | impl RswConfig {
144 | pub fn new() -> Result {
145 | let rsw_file = env::current_dir().unwrap().join(RSW_FILE);
146 | let rsw_content = fs::read_to_string(rsw_file).unwrap_or_else(|e| {
147 | print(RswErr::Config(e));
148 | process::exit(1);
149 | });
150 | let rsw_toml_parse = toml::from_str(&rsw_content).unwrap_or_else(|e| {
151 | print(RswErr::ParseToml(e));
152 | process::exit(1);
153 | });
154 |
155 | Ok(rsw_toml_parse)
156 | }
157 | }
158 |
159 | fn default_root() -> Option {
160 | Some(".".into())
161 | }
162 |
163 | fn default_out_dir() -> Option {
164 | Some("pkg".into())
165 | }
166 |
167 | fn default_release() -> Option {
168 | Some("release".into())
169 | }
170 |
171 | fn default_dev() -> Option {
172 | Some("dev".into())
173 | }
174 |
175 | fn default_target() -> Option {
176 | Some("web".into())
177 | }
178 |
179 | fn default_true() -> Option {
180 | Some(true)
181 | }
182 |
183 | fn default_false() -> Option {
184 | Some(false)
185 | }
186 |
187 | fn default_wasmpack() -> Option {
188 | Some("wasm-pack".into())
189 | }
190 |
191 | fn default_empty() -> Option {
192 | Some("".into())
193 | }
194 |
195 | fn default_new() -> Option {
196 | Some(NewOptions {
197 | using: default_wasmpack(),
198 | dir: default_empty(),
199 | })
200 | }
201 |
202 | fn default_watch() -> Option {
203 | Some(WatchOptions {
204 | run: default_true(),
205 | profile: default_dev(),
206 | })
207 | }
208 |
209 | fn default_build() -> Option {
210 | Some(BuildOptions {
211 | run: default_true(),
212 | profile: default_release(),
213 | })
214 | }
215 |
--------------------------------------------------------------------------------
/src/core/build.rs:
--------------------------------------------------------------------------------
1 | //! rsw build
2 |
3 | use std::path::PathBuf;
4 | use std::process::Command;
5 |
6 | use path_clean::PathClean;
7 |
8 | use crate::config::CrateConfig;
9 | use crate::core::Link;
10 | use crate::core::RswInfo;
11 | use crate::utils::{get_crate_metadata, get_pkg, print, rsw_watch_file};
12 |
13 | pub struct Build {
14 | config: CrateConfig,
15 | rsw_type: String,
16 | cli: String,
17 | is_link: bool,
18 | }
19 |
20 | impl Build {
21 | pub fn new(config: CrateConfig, rsw_type: &str, cli: String, is_link: bool) -> Build {
22 | Build {
23 | config,
24 | rsw_type: rsw_type.into(),
25 | cli,
26 | is_link,
27 | }
28 | }
29 |
30 | pub fn init(&self) -> bool {
31 | let config = &self.config;
32 | let rsw_type = &self.rsw_type;
33 | let name = &config.name;
34 | let root = config.root.as_ref().unwrap();
35 | let out_dir = config.out_dir.as_ref().unwrap();
36 | let crate_root = PathBuf::from(root).join(name).clean();
37 | let build_name = crate_root.to_string_lossy().to_string();
38 | let target = config.target.as_ref().unwrap();
39 | let scope = config.scope.as_ref();
40 | let mut args = vec![
41 | "build",
42 | &build_name,
43 | "--out-dir",
44 | out_dir,
45 | "--target",
46 | target,
47 | ];
48 |
49 | // profile
50 | let mut profile = config.build.as_ref().unwrap().profile.as_ref().unwrap();
51 | if rsw_type == "watch" {
52 | profile = config.watch.as_ref().unwrap().profile.as_ref().unwrap();
53 | }
54 | let arg_profile = format!("--{}", profile);
55 | args.push(&arg_profile);
56 |
57 | // scope
58 | let (_, scope2) = get_pkg(&self.config.name);
59 | if !scope2.is_empty() {
60 | args.push("--scope");
61 | args.push(scope2.as_str());
62 | } else if scope.is_some() && !scope.unwrap().is_empty() {
63 | args.push("--scope");
64 | args.push(scope.unwrap());
65 | }
66 |
67 | let metadata = get_crate_metadata(name, crate_root);
68 | info!("🚧 wasm-pack {}", args.join(" "));
69 |
70 | let status = Command::new("wasm-pack")
71 | .args(&args)
72 | .status()
73 | .expect("failed to execute process");
74 |
75 | println!(" ");
76 |
77 | let mut is_ok = true;
78 |
79 | if let Some(code) = status.code() {
80 | if code == 0 {
81 | print(RswInfo::CrateOk(
82 | name.into(),
83 | rsw_type.into(),
84 | metadata["package"]["version"].to_string(),
85 | ));
86 | } else {
87 | let output = Command::new("wasm-pack")
88 | .args(&args)
89 | .stderr(std::process::Stdio::piped())
90 | .output()
91 | .unwrap();
92 |
93 | let err = std::str::from_utf8(&output.stderr).unwrap();
94 | let info_content = format!(
95 | "[RSW::ERR]\n[RSW::NAME] :~> {}\n[RSW::BUILD] :~> wasm-pack {}",
96 | name,
97 | &args.join(" ")
98 | );
99 | rsw_watch_file(info_content.as_bytes(), err.as_bytes(), "err".into()).unwrap();
100 | print(RswInfo::CrateFail(name.into(), rsw_type.into()));
101 |
102 | is_ok = false;
103 | }
104 | }
105 |
106 | if config.link.unwrap() && self.is_link {
107 | let cli = &self.cli;
108 | Link::new(
109 | cli.into(),
110 | PathBuf::from(root).join(name).join(out_dir),
111 | name.to_string(),
112 | )
113 | .init();
114 | }
115 |
116 | print(RswInfo::SplitLine);
117 |
118 | is_ok
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/core/clean.rs:
--------------------------------------------------------------------------------
1 | //! rsw clean
2 |
3 | use std::collections::HashMap;
4 | use std::path::PathBuf;
5 |
6 | use crate::config::RswConfig;
7 | use crate::core::{Link, RswInfo};
8 | use crate::utils::{path_exists, print};
9 |
10 | pub struct Clean;
11 |
12 | // clean: link & build
13 | impl Clean {
14 | pub fn init(config: RswConfig) {
15 | let mut crates: Vec = Vec::new();
16 | let mut path_map: HashMap = HashMap::new();
17 |
18 | for i in &config.crates {
19 | let rsw_crate = i.clone();
20 | let crate_path = PathBuf::from(rsw_crate.root.as_ref().unwrap())
21 | .join(&i.name)
22 | .join(rsw_crate.out_dir.unwrap());
23 |
24 | let pkg_path = crate_path.to_string_lossy().to_string();
25 |
26 | if path_exists(&crate_path) {
27 | path_map.insert(pkg_path, crate_path);
28 | }
29 |
30 | crates.push(rsw_crate.name);
31 | }
32 |
33 | Link::unlink(&config.cli.unwrap(), crates);
34 |
35 | for (_crate, _path) in path_map {
36 | std::fs::remove_dir_all(_path).unwrap();
37 | print(RswInfo::Clean("package".into(), _crate));
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/core/cli.rs:
--------------------------------------------------------------------------------
1 | //! rsw command parse
2 |
3 | use clap::{AppSettings, Parser, Subcommand};
4 | use path_clean::PathClean;
5 | use std::cell::RefCell;
6 | use std::collections::HashMap;
7 | use std::path::PathBuf;
8 | use std::rc::Rc;
9 | use std::sync::Arc;
10 |
11 | use crate::config::{CrateConfig, RswConfig};
12 | use crate::core::{Build, Clean, Create, Init, Link, RswInfo, Watch};
13 | use crate::utils::{init_rsw_crates, print, rsw_watch_file};
14 |
15 | #[derive(Parser)]
16 | #[clap(version, about, long_about = None)]
17 | #[clap(global_setting(AppSettings::PropagateVersion))]
18 | #[clap(global_setting(AppSettings::UseLongFormatForHelpSubcommand))]
19 | pub struct Cli {
20 | #[clap(subcommand)]
21 | command: Commands,
22 | }
23 |
24 | #[derive(Subcommand)]
25 | pub enum Commands {
26 | /// generate `rsw.toml` configuration file
27 | Init,
28 | /// build rust crates, useful for shipping to production
29 | Build,
30 | /// automatically rebuilding local changes, useful for development and debugging
31 | Watch,
32 | /// clean - `npm link` and `wasm-pack build`
33 | Clean,
34 | /// quickly generate a crate with `wasm-pack new`, or set a custom template in `rsw.toml [new]`
35 | New {
36 | /// the name of the project
37 | name: String,
38 | /// `wasm-pack new`: The URL to the template
39 | #[clap(short = 't', long)]
40 | template: Option,
41 | /// `wasm-pack new`: Should we install or check the presence of binary tools. [possible values: no-install, normal, force] [default: normal]
42 | #[clap(short = 'm', long)]
43 | mode: Option,
44 | },
45 | }
46 |
47 | impl Cli {
48 | pub fn init() {
49 | match &Cli::parse().command {
50 | Commands::Init => Cli::rsw_init(),
51 | Commands::Clean => Cli::rsw_clean(),
52 | Commands::Build => {
53 | Cli::rsw_build();
54 | }
55 |
56 | Commands::Watch => {
57 | Cli::rsw_watch(Some(Arc::new(|a, b| {
58 | let name = &a.name;
59 | let path = &b.to_string_lossy().to_string();
60 | let info_content = format!(
61 | "[RSW::OK]\n[RSW::NAME] :~> {}\n[RSW::PATH] :~> {}",
62 | name, path
63 | );
64 | rsw_watch_file(info_content.as_bytes(), "".as_bytes(), "info".into()).unwrap();
65 | })));
66 | }
67 | Commands::New {
68 | name,
69 | template,
70 | mode,
71 | } => {
72 | Cli::rsw_new(name, template, mode);
73 | }
74 | }
75 | }
76 | pub fn rsw_build() {
77 | Cli::wp_build(Arc::new(Cli::parse_toml()), "build", true);
78 | }
79 | pub fn rsw_watch(
80 | callback: Option>,
81 | ) {
82 | // initial build
83 | let config = Arc::new(Cli::parse_toml());
84 | Cli::wp_build(config.clone(), "watch", true);
85 |
86 | Watch::new(config, callback.unwrap()).init();
87 | }
88 | pub fn rsw_init() {
89 | Init::init().unwrap();
90 | }
91 | pub fn rsw_clean() {
92 | Clean::init(Cli::parse_toml());
93 | }
94 | pub fn rsw_new(name: &String, template: &Option, mode: &Option) {
95 | Create::new(
96 | Cli::parse_toml().new.unwrap(),
97 | name.into(),
98 | template.to_owned(),
99 | mode.to_owned(),
100 | )
101 | .init();
102 | }
103 | pub fn parse_toml() -> RswConfig {
104 | let config = RswConfig::new().unwrap();
105 | trace!("{:#?}", config);
106 |
107 | let mut crates = Vec::new();
108 | for i in &config.crates {
109 | let name = &i.name;
110 | let root = i.root.as_ref().unwrap();
111 | let out = i.out_dir.as_ref().unwrap();
112 | let crate_out = PathBuf::from(root).join(name).join(out);
113 |
114 | crates.push(format!(
115 | "{} :~> {}",
116 | name,
117 | crate_out.clean().to_string_lossy()
118 | ));
119 | }
120 | init_rsw_crates(crates.join("\n").as_bytes()).unwrap();
121 |
122 | config
123 | }
124 | pub fn wp_build(config: Arc, rsw_type: &str, is_link: bool) {
125 | let crates_map = Rc::new(RefCell::new(HashMap::new()));
126 |
127 | let cli = &config.cli.to_owned().unwrap_or_else(|| "npm".to_string());
128 | let mut has_crates = false;
129 | let mut is_exit = true;
130 |
131 | for i in &config.crates {
132 | let run_build = rsw_type == "build" && i.build.as_ref().unwrap().run.unwrap();
133 | let run_watch = rsw_type == "watch" && i.watch.as_ref().unwrap().run.unwrap();
134 |
135 | if run_build || run_watch {
136 | is_exit = false;
137 | if cli == "npm" && i.link.unwrap() {
138 | has_crates = true;
139 | let rsw_crate = i.clone();
140 | let crate_path = PathBuf::from(rsw_crate.root.as_ref().unwrap())
141 | .join(&i.name)
142 | .join(rsw_crate.out_dir.unwrap());
143 | crates_map.borrow_mut().insert(
144 | rsw_crate.name.to_string(),
145 | crate_path.to_string_lossy().to_string(),
146 | );
147 | }
148 |
149 | Build::new(i.clone(), rsw_type, cli.into(), is_link).init();
150 | }
151 | }
152 |
153 | // exit: No crates found
154 | if is_exit {
155 | print(RswInfo::LoadCrate(rsw_type.into()));
156 | std::process::exit(1);
157 | }
158 |
159 | // npm link foo bar ...
160 | let crates = crates_map.borrow();
161 | if cli == "npm" && has_crates {
162 | Link::npm_link(
163 | cli.into(),
164 | Vec::from_iter(crates.values().map(|i| i.into())),
165 | );
166 | }
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/src/core/create.rs:
--------------------------------------------------------------------------------
1 | //! rsw new
2 |
3 | use anyhow::Result;
4 | use regex::Regex;
5 | use std::process::Command;
6 |
7 | use crate::config::NewOptions;
8 | use crate::core::RswInfo;
9 | use crate::template::Template;
10 | use crate::utils::{self, get_root, path_exists, print, write_file};
11 |
12 | pub struct Create {
13 | name: String,
14 | config: NewOptions,
15 | template: Option,
16 | mode: Option,
17 | }
18 |
19 | /// wasm-pack new
20 | ///
21 | ///
22 | impl Create {
23 | pub fn new(
24 | config: NewOptions,
25 | name: String,
26 | template: Option,
27 | mode: Option,
28 | ) -> Create {
29 | Create {
30 | name,
31 | config,
32 | template,
33 | mode,
34 | }
35 | }
36 | pub fn init(&self) {
37 | // println!("{:?}", self.config);
38 | let name = self.name.as_str();
39 | let (name2, scope) = utils::get_pkg(name);
40 |
41 | let scope2 = format!("@{}", scope);
42 |
43 | self.check_scope(&scope2);
44 |
45 | let mut args = vec!["new", &name2];
46 | let arg_tpl = self.template.as_deref();
47 | let arg_mode = self.mode.as_deref();
48 | let arg_use = self.config.using.as_ref().unwrap();
49 | let user_dirs = self.config.dir.as_ref().unwrap();
50 |
51 | self.check_crate();
52 |
53 | // --template: https://rustwasm.github.io/docs/wasm-pack/commands/new.html#template
54 | if arg_tpl.is_some() {
55 | args.push("--template");
56 | args.push(arg_tpl.unwrap_or("https://github.com/rustwasm/wasm-pack-template"));
57 | }
58 |
59 | // --mode: https://rustwasm.github.io/docs/wasm-pack/commands/new.html#mode
60 | if arg_mode.is_some() {
61 | args.push("--mode");
62 | args.push(arg_mode.unwrap_or("normal"));
63 | }
64 |
65 | // wasm-pack
66 | if arg_use == "wasm-pack" || arg_tpl.is_some() {
67 | self.wp_cmd(&args, &scope2);
68 | }
69 |
70 | // built-in template
71 | if arg_use == "rsw" {
72 | self.create_crate().unwrap();
73 | }
74 |
75 | // custom template
76 | if arg_use == "user" {
77 | if user_dirs.is_empty() {
78 | self.wp_cmd(&args, &scope2);
79 | } else {
80 | self.user_crate(user_dirs);
81 | }
82 | }
83 |
84 | print(RswInfo::CrateNewOk(name.into()));
85 | }
86 | fn check_scope(&self, scope: &String) {
87 | if scope != "@" {
88 | let scope_dir = get_root().join(scope);
89 | if !path_exists(scope_dir.as_path()) {
90 | std::fs::create_dir(&scope_dir).unwrap();
91 | }
92 | }
93 | }
94 | fn check_crate(&self) {
95 | let name = &self.name;
96 | let path = get_root().join(name);
97 | if utils::path_exists(path.as_path()) {
98 | print(RswInfo::CrateNewExist(name.into()));
99 | std::process::exit(1);
100 | }
101 | }
102 | fn wp_cmd(&self, args: &Vec<&str>, scope: &String) {
103 | let mut scope_dir = get_root();
104 | if scope != "@" {
105 | scope_dir = scope_dir.join(scope);
106 | }
107 | Command::new("wasm-pack")
108 | .current_dir(scope_dir)
109 | .args(args)
110 | .status()
111 | .expect("failed to execute process");
112 | }
113 | fn create_crate(&self) -> Result<()> {
114 | let target = get_root().join(&self.name);
115 | let root = std::path::Path::new(&target);
116 | let template = Template::new(root);
117 |
118 | let (name, _) = utils::get_pkg(&self.name);
119 | let re = Regex::new(r"(?Prsw-template)").unwrap();
120 | let cargo = re.replace(std::str::from_utf8(&template.cargo).unwrap(), name);
121 | let readme = re.replace(std::str::from_utf8(&template.readme).unwrap(), &self.name);
122 |
123 | write_file(root, "README.md", readme.as_bytes())?;
124 | write_file(root, "Cargo.toml", cargo.as_bytes())?;
125 | write_file(root, "src/lib.rs", &template.lib)?;
126 |
127 | Ok(())
128 | }
129 | fn user_crate(&self, dir: &str) {
130 | let root = get_root();
131 | let source = root.join(dir);
132 | if !path_exists(source.as_path()) {
133 | print(RswInfo::ConfigNewDir(dir.into(), source));
134 | std::process::exit(1);
135 | }
136 | utils::copy_dirs(root.join(dir), root.join(&self.name)).unwrap();
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/core/error.rs:
--------------------------------------------------------------------------------
1 | use colored::Colorize;
2 | use core::fmt::Display;
3 |
4 | pub enum RswErr {
5 | WasmPack,
6 | Config(std::io::Error),
7 | ParseToml(toml::de::Error),
8 | WatchFile(notify::Error),
9 | Crate(String, std::io::Error),
10 | }
11 |
12 | impl Display for RswErr {
13 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
14 | match self {
15 | RswErr::WasmPack => {
16 | write!(f,
17 | "{} wasm-pack {}\nCannot find wasm-pack in your PATH. Please make sure wasm-pack is installed.",
18 | "[⚙️ rsw::env]".red().on_black(),
19 | "https://github.com/rustwasm/wasm-pack".green(),
20 | )
21 | }
22 | RswErr::Config(_err) => {
23 | write!(
24 | f,
25 | "{} {} must exist in the project root path.",
26 | "[⚙️ rsw.toml]".red().on_black(),
27 | "rsw.toml".yellow(),
28 | // _err,
29 | )
30 | }
31 | RswErr::ParseToml(err) => {
32 | write!(f, "{} {}", "[⚙️ rsw.toml]".red().on_black(), err)
33 | }
34 | RswErr::WatchFile(e) => {
35 | write!(
36 | f,
37 | "{} Error while trying to watch the files:\n\t{}",
38 | "[🦀 rsw::crate]".red().on_black(),
39 | e
40 | )
41 | }
42 | RswErr::Crate(name, err) => {
43 | write!(
44 | f,
45 | "{} {} {}",
46 | "[🦀 rsw::crate]".red().on_black(),
47 | name.yellow(),
48 | err
49 | )
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/core/info.rs:
--------------------------------------------------------------------------------
1 | use colored::Colorize;
2 | use core::fmt::Display;
3 | use std::fmt::Debug;
4 |
5 | #[derive(Debug)]
6 | pub enum RswInfo {
7 | SplitLine,
8 | RswTomlOk,
9 | RswTomExist,
10 | RunWatch(String),
11 | CrateLink(String, String),
12 | CrateFail(String, String),
13 | CrateOk(String, String, String),
14 | CrateChange(std::path::PathBuf),
15 | CrateNewOk(String),
16 | CrateNewExist(String),
17 | ConfigNewDir(String, std::path::PathBuf),
18 | Clean(String, String),
19 | LoadCrate(String),
20 | }
21 |
22 | impl Display for RswInfo {
23 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 | match self {
25 | RswInfo::CrateLink(cli, name) => {
26 | write!(
27 | f,
28 | "{} {} {}",
29 | "[🔗 rsw::link]".green().on_black(),
30 | cli,
31 | name.yellow()
32 | )
33 | }
34 | RswInfo::Clean(a, b) => {
35 | write!(
36 | f,
37 | "{} {} {}",
38 | "[🗑 rsw::clean]".green().on_black(),
39 | a,
40 | b.yellow()
41 | )
42 | }
43 | RswInfo::CrateOk(name, mode, version) => {
44 | let rsw_tip = match *mode == "watch" {
45 | true => "[👀 rsw::watch]",
46 | false => "[✨ rsw::build]",
47 | };
48 | write!(
49 | f,
50 | "{} {} {}",
51 | rsw_tip.green().on_black(),
52 | name.purple(),
53 | version.yellow(),
54 | )
55 | }
56 | RswInfo::CrateFail(name, mode) => {
57 | let rsw_tip = format!("[💢 rsw::{}]", mode);
58 | write!(f, "{} {}", rsw_tip.red().on_black(), name)
59 | }
60 | RswInfo::SplitLine => {
61 | write!(f, "\n{}\n", "◼◻".repeat(24).yellow())
62 | }
63 | RswInfo::CrateChange(path) => {
64 | write!(
65 | f,
66 | "{} {}",
67 | "[📝 rsw::crate]".yellow().on_black(),
68 | path.display(),
69 | )
70 | }
71 | RswInfo::RunWatch(name) => {
72 | write!(
73 | f,
74 | "{} {}",
75 | "[🦀 rsw::watch]".yellow().on_black(),
76 | name.purple(),
77 | )
78 | }
79 | RswInfo::RswTomExist => {
80 | write!(
81 | f,
82 | "{} {} already exists",
83 | "[⚙️ rsw.toml]".red().on_black(),
84 | "rsw.toml".yellow(),
85 | )
86 | }
87 | RswInfo::RswTomlOk => {
88 | write!(
89 | f,
90 | "{} {} created successfully",
91 | "[⚙️ rsw.toml]".green().on_black(),
92 | "rsw.toml".yellow(),
93 | )
94 | }
95 | RswInfo::CrateNewOk(name) => {
96 | write!(
97 | f,
98 | "{} {} created successfully, please add the following code to `rsw.toml`\n\n{}",
99 | "[🦀 rsw::crate]".green().on_black(),
100 | name.yellow(),
101 | format!("[[crates]]\nname = {:?}", name).yellow(),
102 | )
103 | }
104 | RswInfo::CrateNewExist(name) => {
105 | write!(
106 | f,
107 | "{} {} already exists",
108 | "[🦀 rsw::crate]".red().on_black(),
109 | name.yellow(),
110 | )
111 | }
112 | RswInfo::ConfigNewDir(template, path) => {
113 | write!(
114 | f,
115 | "{} [new] dir = \"{}\"\n{:?} No such file or director",
116 | "[⚙️ rsw.toml]".red().on_black(),
117 | template.yellow(),
118 | path.display(),
119 | )
120 | }
121 | RswInfo::LoadCrate(mode) => {
122 | let rsw_tip = match *mode == "watch" {
123 | true => "[👀 rsw::watch]",
124 | false => "[✨ rsw::build]",
125 | };
126 | write!(
127 | f,
128 | "{} No crates found, configure in [[crates]] in `{}` and set\n\n{}",
129 | rsw_tip.red().on_black(),
130 | "rsw.toml".yellow(),
131 | format!(
132 | "[[crates]]\nname = \"npm_package_name\"\n[crates.{}]\nrun = true",
133 | mode
134 | )
135 | .green(),
136 | )
137 | }
138 | }
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/src/core/init.rs:
--------------------------------------------------------------------------------
1 | //! rsw init
2 |
3 | use std::fs::File;
4 | use std::io::prelude::*;
5 | use std::path::Path;
6 |
7 | use crate::core::RswInfo;
8 | use crate::{
9 | config, template,
10 | utils::{path_exists, print},
11 | };
12 |
13 | pub struct Init;
14 |
15 | impl Init {
16 | pub fn init() -> std::io::Result<()> {
17 | if !path_exists(Path::new(config::RSW_FILE)) {
18 | File::create(config::RSW_FILE)?.write_all(template::RSW_TOML)?;
19 | print(RswInfo::RswTomlOk);
20 | } else {
21 | print(RswInfo::RswTomExist);
22 | }
23 |
24 | Ok(())
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/core/link.rs:
--------------------------------------------------------------------------------
1 | //! rsw link
2 |
3 | use std::path::PathBuf;
4 |
5 | use crate::core::RswInfo;
6 | use crate::utils::{get_root, os_cli, print};
7 |
8 | pub struct Link {
9 | cli: String,
10 | name: String,
11 | cwd: PathBuf,
12 | }
13 |
14 | impl Link {
15 | pub fn new(cli: String, cwd: PathBuf, name: String) -> Link {
16 | Link { cli, cwd, name }
17 | }
18 | pub fn init(self) {
19 | if self.cli == "yarn" {
20 | self.yarn_link();
21 | }
22 | if self.cli == "pnpm" {
23 | self.pnpm_link();
24 | }
25 | }
26 | pub fn npm_link(cli: String, crates: Vec) {
27 | os_cli(cli, [&["link".into()], &crates[..]].concat(), get_root());
28 | print(RswInfo::CrateLink("npm link".into(), crates.join(" ")));
29 | }
30 |
31 | pub fn yarn_link(&self) {
32 | // register package
33 | // 1. cd /
34 | // 2. yarn link
35 | os_cli(self.cli.clone(), ["link".into()].to_vec(), &self.cwd);
36 |
37 | // yarn link
38 | os_cli(
39 | self.cli.clone(),
40 | ["link".into(), self.name.clone()].to_vec(),
41 | get_root(),
42 | );
43 |
44 | print(RswInfo::CrateLink(
45 | "yarn link".into(),
46 | self.name.to_string(),
47 | ));
48 | }
49 |
50 | pub fn pnpm_link(&self) {
51 | // pnpm link --dir
52 | let dir = get_root().to_string_lossy().to_string();
53 | os_cli(
54 | self.cli.clone(),
55 | ["link".into(), "--dir".into(), dir].to_vec(),
56 | &self.cwd,
57 | );
58 |
59 | print(RswInfo::CrateLink(
60 | "pnpm link".into(),
61 | self.name.to_string(),
62 | ));
63 | }
64 |
65 | pub fn unlink(cli: &String, crates: Vec) {
66 | let root = get_root();
67 |
68 | // unlink foo bar
69 | os_cli(
70 | cli.clone(),
71 | [&["unlink".into()], &crates[..]].concat(),
72 | &root,
73 | );
74 |
75 | if cli == "npm" {
76 | // npm unlink -g foo bar
77 | os_cli(
78 | cli.clone(),
79 | [&["unlink".into(), "-g".into()], &crates[..]].concat(),
80 | root,
81 | );
82 | }
83 |
84 | print(RswInfo::Clean(format!("{} unlink", cli), crates.join(" ")));
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/core/mod.rs:
--------------------------------------------------------------------------------
1 | //! rsw cli
2 |
3 | mod build;
4 | mod clean;
5 | mod cli;
6 | mod create;
7 | mod error;
8 | mod info;
9 | mod init;
10 | mod link;
11 | mod watch;
12 |
13 | pub use self::build::Build;
14 | pub use self::clean::Clean;
15 | pub use self::cli::Cli;
16 | pub use self::create::Create;
17 | pub use self::error::RswErr;
18 | pub use self::info::RswInfo;
19 | pub use self::init::Init;
20 | pub use self::link::Link;
21 | pub use self::watch::Watch;
22 |
--------------------------------------------------------------------------------
/src/core/watch.rs:
--------------------------------------------------------------------------------
1 | //! rsw watch
2 |
3 | use ignore::gitignore::Gitignore;
4 | use notify::{DebouncedEvent::*, RecursiveMode::*, Watcher};
5 | use path_clean::PathClean;
6 | use std::{
7 | collections::HashMap, path::PathBuf, sync::mpsc::channel, sync::Arc, thread::sleep,
8 | time::Duration,
9 | };
10 |
11 | use crate::config::{CrateConfig, RswConfig};
12 | use crate::core::{Build, RswErr, RswInfo};
13 |
14 | use crate::utils::{get_root, print};
15 |
16 | pub struct Watch {
17 | config: Arc,
18 | callback: Arc,
19 | }
20 |
21 | impl Watch {
22 | pub fn new(
23 | config: Arc,
24 | callback: Arc,
25 | ) -> Watch {
26 | Watch { config, callback }
27 | }
28 | pub fn init(self) {
29 | let config = self.config;
30 | let caller = self.callback;
31 | let mut crate_map = HashMap::new();
32 | let mut path_map = HashMap::new();
33 | let (tx, rx) = channel();
34 | // Keep the root as a path instead
35 | let cwd = get_root();
36 |
37 | let mut watcher = match notify::watcher(tx, Duration::from_secs(1)) {
38 | Ok(w) => w,
39 | Err(e) => {
40 | print(RswErr::WatchFile(e));
41 | std::process::exit(1);
42 | }
43 | };
44 |
45 | for i in &config.crates {
46 | // TODO: local deps watch
47 |
48 | // fix: https://github.com/rwasm/rsw-rs/issues/5#issuecomment-1242822143
49 | // Make sure the crate root path is absolute
50 | let crate_root = {
51 | let cr = PathBuf::from(i.root.as_deref().unwrap()).join(&i.name);
52 | if cr.is_relative() {
53 | cwd.to_owned().join(cr)
54 | } else {
55 | cwd.to_owned()
56 | }
57 | };
58 |
59 | let _ = watcher.watch(crate_root.join("src"), Recursive);
60 | let _ = watcher.watch(crate_root.join("Cargo.toml"), NonRecursive);
61 |
62 | crate_map.insert(&i.name, i);
63 | path_map.insert(&i.name, crate_root.clean());
64 |
65 | if i.watch.as_ref().unwrap().run.unwrap() {
66 | print(RswInfo::RunWatch(i.name.clone()));
67 | }
68 | }
69 |
70 | print(RswInfo::SplitLine);
71 |
72 | let (gitignore, _) = Gitignore::new("./.watchignore");
73 | let mut build_task_join_handle: Option> = None;
74 |
75 | loop {
76 | let first_event = rx.recv().unwrap();
77 | sleep(Duration::from_millis(config.interval.unwrap()));
78 | let other_events = rx.try_iter();
79 |
80 | let all_events = std::iter::once(first_event).chain(other_events);
81 | for event in all_events {
82 | debug!("{:?}", event);
83 |
84 | match event {
85 | Write(path) | Remove(path) | Rename(_, path) => {
86 | // Simplify gitignore matching code
87 | // strip_prefix is simpler to use
88 | let project_path = match path.strip_prefix(&cwd) {
89 | Ok(p) => p,
90 | Err(_) => continue,
91 | };
92 |
93 | if gitignore.matched(project_path, false).is_ignore() {
94 | continue;
95 | }
96 |
97 | for (key, val) in &path_map {
98 | // Use starts_with instead of regex comparing strings
99 | // This way we avoid potential issues with extra slashes
100 | if !path.starts_with(val) {
101 | continue;
102 | }
103 |
104 | let crate_config = (*crate_map.get(key).unwrap()).clone();
105 | print(RswInfo::CrateChange(path.clone().to_path_buf()));
106 |
107 | if let Some(join_handle) = build_task_join_handle {
108 | debug!("abort building task");
109 | join_handle.abort();
110 | }
111 |
112 | let config = config.clone();
113 | let caller = caller.clone();
114 | let path = path.clone();
115 | let join_handle = tokio::spawn(async move {
116 | let is_ok = Build::new(
117 | crate_config.clone(),
118 | "watch",
119 | config.cli.to_owned().unwrap(),
120 | false,
121 | )
122 | .init();
123 |
124 | if is_ok {
125 | caller(&crate_config, path.clone());
126 | }
127 | });
128 |
129 | build_task_join_handle = Some(join_handle);
130 |
131 | break;
132 | }
133 | }
134 | _ => (),
135 | }
136 | }
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! Usage
2 | //!
3 | //! ```bash
4 | //! #! help
5 | //! rsw -h
6 | //!
7 | //! #! new help
8 | //! rsw new -h
9 | //!
10 | //! #! dev
11 | //! rsw watch
12 | //!
13 | //! #! release
14 | //! rsw build
15 | //!
16 | //! #! generate a project quickly
17 | //! rsw new
18 | //!
19 | //! #! clean - link & build
20 | //! rsw clean
21 | //! ```
22 | //!
23 | //! ----------
24 | //!
25 | //! rsw.toml
26 | //!
27 | //!
28 | //!
29 | //! ```toml
30 | //! name = "rsw"
31 | //! version = "0.1.0"
32 | //!
33 | //! #! time interval for file changes to trigger wasm-pack build, default `50` milliseconds
34 | //! interval = 50
35 | //!
36 | //! #! link
37 | //! #! npm link @see https://docs.npmjs.com/cli/v8/commands/npm-link
38 | //! #! yarn link @see https://classic.yarnpkg.com/en/docs/cli/link
39 | //! #! pnpm link @see https://pnpm.io/cli/link
40 | //! #! The link command will only be executed if `[[crates]] link = true`
41 | //! #! cli: `npm` | `yarn` | `pnpm`, default is `npm`
42 | //! cli = "npm"
43 | //!
44 | //! #! ---------------------------
45 | //!
46 | //! #! rsw new
47 | //! [new]
48 | //! #! @see https://rustwasm.github.io/docs/wasm-pack/commands/new.html
49 | //! #! using: `wasm-pack` | `rsw` | `user`, default is `wasm-pack`
50 | //! #! 1. wasm-pack: `rsw new --template --mode `
51 | //! #! 2. rsw: `rsw new `, built-in templates
52 | //! #! 3. user: `rsw new `, if `dir` is not configured, use `wasm-pack new ` to initialize the project
53 | //! using = "wasm-pack"
54 | //! #! this field needs to be configured when `using = "user"`
55 | //! #! `using = "wasm-pack"` or `using = "rsw"`, this field will be ignored
56 | //! #! copy all files in this directory
57 | //! dir = "my-template"
58 | //!
59 | //! #! ################# NPM Package #################
60 | //!
61 | //! #! When there is only `name`, other fields will use the default configuration
62 | //! #! -------- package: rsw-hello --------
63 | //! [[crates]]
64 | //! #! npm package name
65 | //! name = "rsw-hello"
66 | //! #! run `npm link`: `true` | `false`, default is `false`
67 | //! link = false
68 | //!
69 | //! #! =======================================================
70 | //!
71 | //! #! -------- package: @rsw/hello --------
72 | //! # [[crates]]
73 | //! # #! npm package name
74 | //! # name = "@rsw/hello"
75 | //! # #! default is `.`
76 | //! # root = "."
77 | //! # #! default is `pkg`
78 | //! # out-dir = "pkg"
79 | //! # #! target: bundler | nodejs | web | no-modules, default is `web`
80 | //! # target = "web"
81 | //! #! run `npm link`: `true` | `false`, default is `false`
82 | //! # link = false
83 | //! # #! rsw watch
84 | //! # [crates.watch]
85 | //! # #! default is `true`
86 | //! # run = false
87 | //! # #! profile: `dev` | `profiling`, default is `dev`
88 | //! # profile = "dev"
89 | //! # #! rsw build
90 | //! # [crates.build]
91 | //! # run = false
92 | //! # #! profile: `release` | `profiling`, default is `release`
93 | //! # profile = "release"
94 | //! ```
95 |
96 | pub mod config;
97 | pub mod core;
98 | pub mod template;
99 | pub mod utils;
100 |
101 | #[macro_use]
102 | extern crate serde_derive;
103 | #[macro_use]
104 | extern crate log;
105 |
106 | use crate::core::RswErr;
107 | use crate::utils::print;
108 |
109 | pub use crate::core::Cli;
110 |
111 | pub fn rsw_cli() {
112 | utils::init_logger();
113 |
114 | if !utils::check_env_cmd("wasm-pack") {
115 | // TODO: ask if you want to install `wasm-pack` now
116 | print(RswErr::WasmPack);
117 | std::process::exit(1);
118 | }
119 |
120 | Cli::init();
121 | }
122 |
--------------------------------------------------------------------------------
/src/main.rs:
--------------------------------------------------------------------------------
1 | #[tokio::main]
2 | async fn main() {
3 | rsw::rsw_cli();
4 | }
5 |
--------------------------------------------------------------------------------
/src/template/mod.rs:
--------------------------------------------------------------------------------
1 | //! rsw template
2 |
3 | use std::path::Path;
4 |
5 | use crate::utils::load_file_contents;
6 |
7 | // config
8 | pub static RSW_TOML: &[u8] = include_bytes!("rsw.toml");
9 |
10 | // crate
11 | pub static CARGO_TOML: &[u8] = include_bytes!("rsw_cargo.toml");
12 | pub static LIB_RS: &[u8] = include_bytes!("rsw_lib.rs");
13 | pub static README: &[u8] = include_bytes!("rsw_readme.md");
14 |
15 | #[derive(Debug)]
16 | pub struct Template {
17 | pub cargo: Vec,
18 | pub readme: Vec,
19 | pub lib: Vec,
20 | }
21 |
22 | impl Template {
23 | pub fn new>(template_dir: P) -> Self {
24 | // Creates a `Template` from the given `template_dir`.
25 | // If a file is found in the template dir, it will override the default version.
26 | let template_dir = template_dir.as_ref();
27 | let mut template = Template::default();
28 |
29 | // If the theme directory doesn't exist there's no point continuing...
30 | if !template_dir.exists() || !template_dir.is_dir() {
31 | return template;
32 | }
33 |
34 | // Check for individual files, if they exist copy them across
35 | {
36 | let files = vec![
37 | (template_dir.join("Cargo.tmol"), &mut template.cargo),
38 | (template_dir.join("README.md"), &mut template.readme),
39 | (template_dir.join("src/lib.rs"), &mut template.lib),
40 | ];
41 |
42 | let load_with_warn = |filename: &Path, dest| {
43 | if !filename.exists() {
44 | // Don't warn if the file doesn't exist.
45 | return false;
46 | }
47 | if let Err(e) = load_file_contents(filename, dest) {
48 | println!("Couldn't load custom file, {}: {}", filename.display(), e);
49 | false
50 | } else {
51 | true
52 | }
53 | };
54 |
55 | for (filename, dest) in files {
56 | load_with_warn(&filename, dest);
57 | }
58 | }
59 |
60 | template
61 | }
62 | }
63 |
64 | impl Default for Template {
65 | fn default() -> Template {
66 | Template {
67 | cargo: CARGO_TOML.to_owned(),
68 | readme: README.to_owned(),
69 | lib: LIB_RS.to_owned(),
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/template/rsw.toml:
--------------------------------------------------------------------------------
1 | name = "rsw"
2 | version = "0.1.0"
3 |
4 | #! time interval for file changes to trigger wasm-pack build, default `50` milliseconds
5 | interval = 50
6 |
7 | #! link
8 | #! npm link @see https://docs.npmjs.com/cli/v8/commands/npm-link
9 | #! yarn link @see https://classic.yarnpkg.com/en/docs/cli/link
10 | #! pnpm link @see https://pnpm.io/cli/link
11 | #! The link command will only be executed if `[[crates]] link = true`
12 | #! cli: `npm` | `yarn` | `pnpm`, default is `npm`
13 | cli = "npm"
14 |
15 | #! ---------------------------
16 |
17 | #! rsw new
18 | [new]
19 | #! @see https://rustwasm.github.io/docs/wasm-pack/commands/new.html
20 | #! using: `wasm-pack` | `rsw` | `user`, default is `wasm-pack`
21 | #! 1. wasm-pack: `rsw new --template --mode `
22 | #! 2. rsw: `rsw new `, built-in templates
23 | #! 3. user: `rsw new `, if `dir` is not configured, use `wasm-pack new ` to initialize the project
24 | using = "wasm-pack"
25 | #! this field needs to be configured when `using = "user"`
26 | #! `using = "wasm-pack"` or `using = "rsw"`, this field will be ignored
27 | #! copy all files in this directory
28 | dir = "my-template"
29 |
30 | #! ################# NPM Package #################
31 |
32 | #! When there is only `name`, other fields will use the default configuration
33 |
34 | #! 📦 -------- package: rsw-hello --------
35 | # [[crates]]
36 | # #! npm package name (path: $ROOT/rsw-hello)
37 | # name = "rsw-hello"
38 | # #! run `npm link`: `true` | `false`, default is `false`
39 | # link = false
40 |
41 | #! 📦 -------- package: @rsw/utils --------
42 | # [[crates]]
43 | # #! npm package name (path: $ROOT/utils)
44 | # name = "utils"
45 | # # #! scope: npm org
46 | # scope = "rsw"
47 | # #! run `npm link`: `true` | `false`, default is `false`
48 | # link = false
49 |
50 | #! 📦 -------- package: @rsw/hello --------
51 | # [[crates]]
52 | # #! npm package name (path: $ROOT/@rsw/hello)
53 | # name = "@rsw/hello"
54 | # #! default is `.`
55 | # root = "."
56 | # #! default is `pkg`
57 | # out-dir = "pkg"
58 | # #! target: bundler | nodejs | web | no-modules, default is `web`
59 | # target = "web"
60 | # #! run `npm link`: `true` | `false`, default is `false`
61 | # link = false
62 | # #! rsw watch
63 | # [crates.watch]
64 | # #! default is `true`
65 | # run = true
66 | # #! profile: `dev` | `profiling`, default is `dev`
67 | # profile = "dev"
68 | # #! rsw build
69 | # [crates.build]
70 | # #! default is `true`
71 | # run = true
72 | # #! profile: `release` | `profiling`, default is `release`
73 | # profile = "release"
74 |
--------------------------------------------------------------------------------
/src/template/rsw_cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "rsw-template"
3 | version = "0.1.0"
4 | authors = []
5 | edition = "2021"
6 |
7 | [lib]
8 | crate-type = ["cdylib", "rlib"]
9 |
10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
11 |
12 | [dependencies]
13 | wasm-bindgen = "0.2.83"
14 |
--------------------------------------------------------------------------------
/src/template/rsw_lib.rs:
--------------------------------------------------------------------------------
1 | use wasm_bindgen::prelude::*;
2 |
3 | // Import the \`window.alert\` function from the Web.
4 | #[wasm_bindgen]
5 | extern "C" {
6 | fn alert(s: &str);
7 | }
8 |
9 | // Export a \`greet\` function from Rust to JavaScript, that alerts a
10 | // hello message.
11 | #[wasm_bindgen]
12 | pub fn greet(name: &str) {
13 | alert(&format!("[rsw] Hello, {}!", name));
14 | }
15 |
--------------------------------------------------------------------------------
/src/template/rsw_readme.md:
--------------------------------------------------------------------------------
1 | # rsw-template
2 |
3 | ## Usage
4 |
5 | [rsw-rs doc](https://github.com/lencx/rsw-rs)
6 |
7 | ```bash
8 | # install rsw
9 | cargo install rsw
10 |
11 | # --- help ---
12 | # rsw help
13 | rsw -h
14 | # new help
15 | rsw new -h
16 |
17 | # --- usage ---
18 | # dev
19 | rsw watch
20 |
21 | # production
22 | rsw build
23 | ```
24 |
--------------------------------------------------------------------------------
/src/utils.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use regex::Regex;
3 | use std::{
4 | env,
5 | fs::{self, File},
6 | io::{Read, Write},
7 | path::{Path, PathBuf},
8 | process::Command,
9 | };
10 | use toml::Value;
11 | use which::which;
12 |
13 | use crate::config;
14 | use crate::core::RswErr;
15 |
16 | pub fn check_env_cmd(program: &str) -> bool {
17 | let result = which(program);
18 | match result {
19 | Err(e) => {
20 | if is_program_in_path(program) {
21 | true
22 | } else {
23 | eprintln!("{}", e);
24 | false
25 | }
26 | }
27 | _ => true,
28 | }
29 | }
30 |
31 | pub fn is_program_in_path(program: &str) -> bool {
32 | if let Ok(path) = env::var("PATH") {
33 | for p in path.split(':') {
34 | let p_str = format!("{}/{}", p, program);
35 | if fs::metadata(p_str).is_ok() {
36 | return true;
37 | }
38 | }
39 | }
40 | false
41 | }
42 |
43 | // get fields from `Cargo.toml`
44 | pub fn get_crate_metadata(name: &str, root: PathBuf) -> Value {
45 | let crate_root = root.join("Cargo.toml");
46 | let content = fs::read_to_string(crate_root).unwrap_or_else(|e| {
47 | // TODO: create crate
48 | print(RswErr::Crate(name.into(), e));
49 | std::process::exit(1);
50 | });
51 | content.parse::().unwrap()
52 | }
53 |
54 | pub fn path_exists(path: &Path) -> bool {
55 | fs::metadata(path).is_ok()
56 | }
57 |
58 | // https://docs.npmjs.com/creating-a-package-json-file#required-name-and-version-fields
59 | pub fn get_pkg(name: &str) -> (String, String) {
60 | // example: @rsw/test | rsw-test | wasm123
61 | let re = Regex::new(r"(?x)(@([\w\d_-]+)/)?((?P[\w\d_-]+))").unwrap();
62 | let caps = re.captures(name).unwrap();
63 | let mut scope = "".into();
64 | if caps.get(2).is_some() {
65 | scope = caps.get(2).unwrap().as_str().into();
66 | }
67 |
68 | (caps["pkg_name"].to_owned(), scope)
69 | }
70 |
71 | // Checks if a file exists, if so, the destination buffer will be filled with
72 | // its contents.
73 | pub fn load_file_contents>(filename: P, dest: &mut Vec) -> Result<()> {
74 | let filename = filename.as_ref();
75 |
76 | let mut buffer = Vec::new();
77 | File::open(filename)?.read_to_end(&mut buffer)?;
78 |
79 | // We needed the buffer so we'd only overwrite the existing content if we
80 | // could successfully load the file into memory.
81 | dest.clear();
82 | dest.append(&mut buffer);
83 |
84 | Ok(())
85 | }
86 |
87 | // @see: https://github.com/rust-lang/mdBook/blob/2213312938/src/utils/fs.rs#L61-L72
88 | // This function creates a file and returns it. But before creating the file
89 | // it checks every directory in the path to see if it exists,
90 | // and if it does not it will be created.
91 | pub fn create_file(path: &Path) -> Result {
92 | debug!("Creating {}", path.display());
93 |
94 | // Construct path
95 | if let Some(p) = path.parent() {
96 | trace!("Parent directory is: {:?}", p);
97 | fs::create_dir_all(p)?;
98 | }
99 |
100 | File::create(path).map_err(Into::into)
101 | }
102 |
103 | // @see: https://github.com/rust-lang/mdBook/blob/2213312938/src/utils/fs.rs#L16-L20
104 | // Write the given data to a file, creating it first if necessary
105 | pub fn write_file>(build_dir: &Path, filename: P, content: &[u8]) -> Result<()> {
106 | let path = build_dir.join(filename);
107 | create_file(&path)?.write_all(content).map_err(Into::into)
108 | }
109 |
110 | // recursive copy folder
111 | pub fn copy_dirs>(source: P, target: P) -> std::io::Result<()> {
112 | fs::create_dir_all(&target)?;
113 | for entry in fs::read_dir(source)? {
114 | let entry = entry?;
115 | let entry_target = target.as_ref().join(entry.file_name());
116 | trace!(
117 | "Copy {} to {}",
118 | entry.path().display(),
119 | entry_target.display()
120 | );
121 | if entry.file_type()?.is_dir() {
122 | copy_dirs(entry.path(), entry_target)?;
123 | } else {
124 | fs::copy(entry.path(), entry_target)?;
125 | }
126 | }
127 | Ok(())
128 | }
129 |
130 | // rsw log
131 | pub fn init_logger() {
132 | use colored::Colorize;
133 | use env_logger::Builder;
134 | use log::Level;
135 | use log::LevelFilter;
136 |
137 | let mut builder = Builder::new();
138 |
139 | builder.format(|formatter, record| {
140 | let level = record.level().as_str();
141 | let log_level = match record.level() {
142 | Level::Info => level.blue(),
143 | Level::Debug => level.magenta(),
144 | Level::Trace => level.green(),
145 | Level::Error => level.red(),
146 | Level::Warn => level.yellow(),
147 | };
148 |
149 | let log_target = match record.level() {
150 | Level::Info | Level::Error => String::new(),
151 | _ => format!(" {}", record.target().yellow()),
152 | };
153 |
154 | let rsw_log = format!("[rsw::{}]", log_level);
155 | writeln!(
156 | formatter,
157 | "{}{} {}",
158 | rsw_log.bold().on_black(),
159 | log_target,
160 | record.args()
161 | )
162 | });
163 |
164 | if let Ok(var) = env::var("RUST_LOG") {
165 | builder.parse_filters(&var);
166 | } else {
167 | // if no RUST_LOG provided, default to logging at the Info level
168 | builder.filter(None, LevelFilter::Info);
169 | }
170 |
171 | builder.init();
172 | }
173 |
174 | pub fn print(a: T) {
175 | println!("{}", a)
176 | }
177 |
178 | pub fn get_root() -> PathBuf {
179 | std::env::current_dir().unwrap()
180 | }
181 |
182 | pub fn dot_rsw_dir() -> PathBuf {
183 | get_root().join(config::RSW_DIR)
184 | }
185 |
186 | pub fn init_rsw_crates(content: &[u8]) -> Result<()> {
187 | let rsw_root = dot_rsw_dir();
188 | if !path_exists(rsw_root.as_path()) {
189 | std::fs::create_dir(&rsw_root)?;
190 | }
191 |
192 | let rsw_crates = rsw_root.join(config::RSW_CRATES);
193 | if !path_exists(rsw_crates.as_path()) {
194 | File::create(&rsw_crates)?;
195 | }
196 | fs::write(rsw_crates, content)?;
197 |
198 | Ok(())
199 | }
200 |
201 | pub fn rsw_watch_file(info_content: &[u8], err_content: &[u8], file_type: String) -> Result<()> {
202 | let rsw_root = dot_rsw_dir();
203 | let rsw_info = rsw_root.join(config::RSW_INFO);
204 | let rsw_err = rsw_root.join(config::RSW_ERR);
205 | if !path_exists(rsw_info.as_path()) {
206 | File::create(&rsw_info)?;
207 | }
208 | if !path_exists(rsw_err.as_path()) {
209 | File::create(&rsw_err)?;
210 | }
211 | fs::write(&rsw_info, info_content)?;
212 | if file_type == "info" {
213 | fs::write(&rsw_err, "".as_bytes())?;
214 | }
215 | if file_type == "err" {
216 | fs::write(rsw_err, err_content)?;
217 | }
218 |
219 | Ok(())
220 | }
221 |
222 | pub fn os_cli>(cli: String, args: Vec, path: P) {
223 | if cfg!(target_os = "windows") {
224 | Command::new("cmd")
225 | .arg("/C")
226 | .arg(cli)
227 | .args(args)
228 | .current_dir(path)
229 | .status()
230 | .unwrap();
231 | } else {
232 | Command::new(cli)
233 | .args(args)
234 | .current_dir(path)
235 | .status()
236 | .unwrap();
237 | }
238 | }
239 |
240 | // https://www.reddit.com/r/learnrust/comments/h82em8/best_way_to_create_a_vecstring_from_str/
241 | pub fn vec_of_str(v: &[&str]) -> Vec {
242 | v.iter().map(|&x| x.into()).collect()
243 | }
244 |
245 | #[cfg(test)]
246 | mod pkg_name_tests {
247 | use super::*;
248 |
249 | #[test]
250 | fn pkg_word() {
251 | assert_eq!(get_pkg("@rsw/test"), ("test".into(), "rsw".into()));
252 | }
253 |
254 | #[test]
255 | fn pkg_word_num() {
256 | assert_eq!(get_pkg("wasm123"), ("wasm123".into(), "".into()));
257 | }
258 |
259 | #[test]
260 | fn pkg_word_num_line() {
261 | assert_eq!(
262 | get_pkg("@rsw-org/my_wasm"),
263 | ("my_wasm".into(), "rsw-org".into())
264 | );
265 | }
266 | }
267 |
--------------------------------------------------------------------------------