misku-native-view-cli 0.1.0-ci.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,195 @@
1
+ # Misku Native Views
2
+
3
+ Misku Native Views is a small Windows-first launcher that turns configured web URLs into lightweight desktop apps using Rust, Tauri 2, and Microsoft Edge WebView2.
4
+
5
+ It is inspired by tools like Pake, but keeps the workflow simple: edit `apps.toml`, build portable executables, and optionally install Start Menu shortcuts.
6
+
7
+ ## Features
8
+
9
+ - Multiple web apps from one `apps.toml` file.
10
+ - One portable `.exe` copy per app id.
11
+ - Isolated WebView2 profile directories per app.
12
+ - Custom window and shortcut icons.
13
+ - CLI command to add or update apps by URL.
14
+ - Optional Start Menu shortcut installation.
15
+ - Cargo target output outside the repo to avoid OneDrive lock/sync issues.
16
+
17
+ ## Requirements
18
+
19
+ - Windows 11.
20
+ - Rust stable with the MSVC toolchain.
21
+ - Microsoft C++ Build Tools with Desktop development with C++.
22
+ - Microsoft Edge WebView2 Evergreen Runtime.
23
+ - Python + Pillow only if you want to convert `.webp`, `.png`, or `.jpg` files into `.ico` icons.
24
+
25
+ ## Configure Apps
26
+
27
+ Apps live in `apps.toml`:
28
+
29
+ ```toml
30
+ [[apps]]
31
+ id = "tftacademy"
32
+ name = "TFT Academy"
33
+ url = "https://tftacademy.com/tierlist/comps/"
34
+ icon = "icons/tftacademy.ico"
35
+ width = 1280
36
+ height = 860
37
+ min_width = 900
38
+ min_height = 640
39
+ isolated_profile = true
40
+ devtools = false
41
+ resizable = true
42
+ zoom_hotkeys_enabled = true
43
+ ```
44
+
45
+ Supported fields:
46
+
47
+ - `id`: short identifier used by the CLI and executable name.
48
+ - `name`: window title.
49
+ - `url`: `https://` or `http://` URL.
50
+ - `icon`: optional `.ico` or `.png`, relative to `apps.toml`.
51
+ - `width`, `height`: initial window size.
52
+ - `min_width`, `min_height`: minimum window size.
53
+ - `isolated_profile`: separates cookies, cache, and sessions per app.
54
+ - `devtools`: enables DevTools for that app.
55
+ - `user_agent`: optional custom user agent.
56
+ - `resizable`: allows resizing the window.
57
+ - `zoom_hotkeys_enabled`: enables native zoom shortcuts.
58
+
59
+ ## CLI
60
+
61
+ Use the helper script during development:
62
+
63
+ ```powershell
64
+ .\scripts\misku.ps1 --list
65
+ .\scripts\misku.ps1 add https://chatgpt.com
66
+ .\scripts\misku.ps1 add --id tftacademy --name "TFT Academy" https://tftacademy.com/tierlist/comps/
67
+ .\scripts\misku.ps1 --app tftacademy
68
+ ```
69
+
70
+ `add` creates a new profile when the app does not exist. If the generated `id`, explicit `--id`, or exact URL already exists, it updates the existing profile instead of creating a duplicate.
71
+
72
+ For raw Cargo commands, use `scripts/dev.ps1` so Visual Studio Build Tools are loaded and Cargo output goes to `%LOCALAPPDATA%`:
73
+
74
+ ```powershell
75
+ .\scripts\dev.ps1 check
76
+ .\scripts\dev.ps1 run -p misku-native-views "--" --list
77
+ ```
78
+
79
+ ## Build Portable Apps
80
+
81
+ Generate `portable/` with one executable per configured app:
82
+
83
+ ```powershell
84
+ .\scripts\build-portable.ps1
85
+ ```
86
+
87
+ Run an app directly:
88
+
89
+ ```powershell
90
+ .\portable\tftacademy.exe
91
+ ```
92
+
93
+ If a profile defines `icon`, the build script also creates a matching `.lnk` shortcut with that icon. The `.exe` keeps the base embedded app icon; use the generated shortcut for the per-app Explorer/Start Menu icon.
94
+
95
+ ## Package And Install With pnpm
96
+
97
+ Build the CLI package and create a local pnpm-installable tarball:
98
+
99
+ ```powershell
100
+ pnpm pack
101
+ ```
102
+
103
+ `pnpm pack` runs `scripts/build-pnpm-package.ps1` first. The packed package includes the `misku-nv` wrapper, the prebuilt native executable, the default `apps.toml`, icons. The `misku-nv` wrapper prepares `%APPDATA%\Misku Native Views` on first use. End users do not need Rust, Cargo, or Visual Studio Build Tools to install the CLI package.
104
+
105
+ Install the published package globally:
106
+
107
+ ```powershell
108
+ pnpm install -g misku-native-view-cli
109
+ ```
110
+
111
+ Or install the local tarball generated by `pnpm pack`:
112
+
113
+ ```powershell
114
+ pnpm install -g .\misku-native-view-cli-0.1.0.tgz
115
+ ```
116
+
117
+ Use the installed CLI:
118
+
119
+ ```powershell
120
+ misku-nv https://github.com --name GitHub
121
+ misku-nv --list
122
+ misku-nv github
123
+ ```
124
+
125
+ The URL shorthand creates or updates a profile and opens it immediately. Add `--no-open` if you only want to save the profile:
126
+
127
+ ```powershell
128
+ misku-nv https://github.com --name GitHub --no-open
129
+ ```
130
+
131
+ When installed through pnpm, `misku-nv` stores the default editable config in `%APPDATA%\Misku Native Views\apps.toml`. If you run it from a folder that already contains `apps.toml`, or pass `--config <path>`, that config is used instead.
132
+
133
+ ## GitHub CI/CD
134
+
135
+ The repository includes two GitHub Actions workflows:
136
+
137
+ - `CI`: runs on every push, pull request, and manual dispatch. It builds the Rust/Tauri executable on Windows, packs the pnpm package, installs the generated tarball globally with pnpm, and smoke-tests `misku-nv`.
138
+ - `Publish package`: runs on pushes to `main` or `master`, tags matching `v*`, and manual dispatch. It builds and smoke-tests the package, publishes it to the npm registry, then downloads the published version with pnpm and smoke-tests `misku-nv`.
139
+
140
+ To enable publishing, create a GitHub repository secret named `NPM_TOKEN` with an npm automation token that can publish `misku-native-view-cli`.
141
+
142
+ Pushes to `main` or `master` publish an automatic version like `0.1.0-ci.<run>.<attempt>` with the `latest` dist-tag, so this command installs the newest pushed build:
143
+
144
+ ```powershell
145
+ pnpm install -g misku-native-view-cli
146
+ ```
147
+
148
+ Tags such as `v0.1.0` publish that exact stable version as `latest`.
149
+
150
+ ## Start Menu Shortcuts
151
+
152
+ Install generated shortcuts for the current Windows user:
153
+
154
+ ```powershell
155
+ .\scripts\install-start-menu.ps1
156
+ ```
157
+
158
+ Remove them:
159
+
160
+ ```powershell
161
+ .\scripts\install-start-menu.ps1 -Uninstall
162
+ ```
163
+
164
+ ## Icons
165
+
166
+ Convert image files into multi-size Windows `.ico` files:
167
+
168
+ ```powershell
169
+ .\scripts\convert-icon.ps1 .\downloads\logo.webp .\icons\myapp.ico
170
+ ```
171
+
172
+ Use a solid padding background when needed:
173
+
174
+ ```powershell
175
+ .\scripts\convert-icon.ps1 .\downloads\logo.webp .\icons\myapp.ico -Background "#FFFFFF"
176
+ ```
177
+
178
+ Then reference the icon from `apps.toml`:
179
+
180
+ ```toml
181
+ icon = "icons/myapp.ico"
182
+ ```
183
+
184
+ ## Publishing Notes
185
+
186
+ The repository intentionally ignores generated and local-only files such as:
187
+
188
+ - `target/`
189
+ - `src-tauri/target/`
190
+ - `portable/`
191
+ - `screenshots/`
192
+ - `.env` and `.env.*`
193
+ - logs
194
+
195
+ Do not commit private sessions, generated portable executables, local screenshots, or environment files.
package/bin/misku.js ADDED
@@ -0,0 +1,289 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { spawn, spawnSync } = require("node:child_process");
4
+ const fs = require("node:fs");
5
+ const os = require("node:os");
6
+ const path = require("node:path");
7
+
8
+ const packageRoot = path.resolve(__dirname, "..");
9
+ const portableDir = path.join(packageRoot, "portable");
10
+ const nativeExe = path.join(portableDir, "misku-native-views.exe");
11
+ const packageConfig = path.join(portableDir, "apps.toml");
12
+ const packageIcons = path.join(portableDir, "icons");
13
+
14
+ function fail(message) {
15
+ console.error(`misku-nv: ${message}`);
16
+ process.exit(1);
17
+ }
18
+
19
+ function printHelp() {
20
+ console.log(`Misku Native Views CLI
21
+
22
+ Uso:
23
+ misku-nv <url> [--name <nombre>] [--id <id>] [--icon <ruta>] [--no-open]
24
+ misku-nv <id>
25
+ misku-nv --list
26
+ misku-nv add [opciones] <url> [url...]
27
+
28
+ Ejemplos:
29
+ misku-nv https://github.com --name GitHub
30
+ misku-nv github
31
+ misku-nv --list
32
+
33
+ Opciones rapidas:
34
+ -n, --name <nombre> Nombre visible de la app.
35
+ -i, --id <id> Id del perfil. Si ya existe, se actualiza.
36
+ --icon <ruta> Icono .ico o .png relativo a apps.toml.
37
+ --no-open Crea o actualiza el perfil sin abrirlo.
38
+ -c, --config <ruta> Archivo apps.toml alternativo.`);
39
+ }
40
+
41
+ function getExplicitConfigArgs(args) {
42
+ for (let index = 0; index < args.length; index += 1) {
43
+ const arg = args[index];
44
+
45
+ if (arg === "--config" || arg === "-c") {
46
+ const value = args[index + 1];
47
+ return value ? ["--config", value] : null;
48
+ }
49
+
50
+ if (arg.startsWith("--config=")) {
51
+ return ["--config", arg.slice("--config=".length)];
52
+ }
53
+ }
54
+
55
+ return null;
56
+ }
57
+
58
+ function hasExplicitConfig(args) {
59
+ return getExplicitConfigArgs(args) !== null;
60
+ }
61
+
62
+ function getUserConfigDir() {
63
+ const appData = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
64
+ return path.join(appData, "Misku Native Views");
65
+ }
66
+
67
+ function copyDirectoryIfMissing(source, destination) {
68
+ if (!fs.existsSync(source) || fs.existsSync(destination)) {
69
+ return;
70
+ }
71
+
72
+ fs.mkdirSync(path.dirname(destination), { recursive: true });
73
+ fs.cpSync(source, destination, {
74
+ recursive: true,
75
+ errorOnExist: false,
76
+ force: false
77
+ });
78
+ }
79
+
80
+ function ensureUserConfig() {
81
+ const userConfigDir = getUserConfigDir();
82
+ const userConfig = path.join(userConfigDir, "apps.toml");
83
+ fs.mkdirSync(userConfigDir, { recursive: true });
84
+
85
+ if (!fs.existsSync(userConfig)) {
86
+ if (!fs.existsSync(packageConfig)) {
87
+ fail(`no encontre la configuracion incluida en ${packageConfig}`);
88
+ }
89
+ fs.copyFileSync(packageConfig, userConfig);
90
+ }
91
+
92
+ copyDirectoryIfMissing(packageIcons, path.join(userConfigDir, "icons"));
93
+
94
+ return userConfig;
95
+ }
96
+
97
+ function withDefaultConfig(args, rawArgs) {
98
+ if (hasExplicitConfig(args)) {
99
+ return args;
100
+ }
101
+
102
+ const explicitConfigArgs = getExplicitConfigArgs(rawArgs);
103
+ if (explicitConfigArgs) {
104
+ return [...explicitConfigArgs, ...args];
105
+ }
106
+
107
+ const cwdConfig = path.join(process.cwd(), "apps.toml");
108
+ if (fs.existsSync(cwdConfig)) {
109
+ return args;
110
+ }
111
+
112
+ return ["--config", ensureUserConfig(), ...args];
113
+ }
114
+
115
+ function looksLikeUrl(value) {
116
+ return /^https?:\/\//i.test(value)
117
+ || /^localhost(?::\d+)?(?:\/|$)/i.test(value)
118
+ || /^[^\s.]+\.[^\s]+/.test(value);
119
+ }
120
+
121
+ function pushOptionWithValue(target, args, index, canonicalName) {
122
+ const value = args[index + 1];
123
+ if (!value) {
124
+ fail(`falta el valor despues de ${args[index]}`);
125
+ }
126
+ target.push(canonicalName || args[index], value);
127
+ return index + 1;
128
+ }
129
+
130
+ function parseFriendlyUrlCommand(rawArgs) {
131
+ let url = null;
132
+ let openAfterAdd = true;
133
+ const addArgs = [];
134
+
135
+ for (let index = 0; index < rawArgs.length; index += 1) {
136
+ const arg = rawArgs[index];
137
+
138
+ if (arg === "--no-open") {
139
+ openAfterAdd = false;
140
+ continue;
141
+ }
142
+
143
+ if (arg === "--open") {
144
+ openAfterAdd = true;
145
+ continue;
146
+ }
147
+
148
+ if (arg === "--name" || arg === "-n") {
149
+ index = pushOptionWithValue(addArgs, rawArgs, index, arg);
150
+ continue;
151
+ }
152
+
153
+ if (arg === "--id" || arg === "-i") {
154
+ index = pushOptionWithValue(addArgs, rawArgs, index, arg);
155
+ continue;
156
+ }
157
+
158
+ if (arg === "--icon") {
159
+ index = pushOptionWithValue(addArgs, rawArgs, index, arg);
160
+ continue;
161
+ }
162
+
163
+ if (arg === "--config" || arg === "-c") {
164
+ index = pushOptionWithValue(addArgs, rawArgs, index, arg);
165
+ continue;
166
+ }
167
+
168
+ if (arg.startsWith("--name=")) {
169
+ addArgs.push("--name", arg.slice("--name=".length));
170
+ continue;
171
+ }
172
+
173
+ if (arg.startsWith("--id=")) {
174
+ addArgs.push("--id", arg.slice("--id=".length));
175
+ continue;
176
+ }
177
+
178
+ if (arg.startsWith("--icon=")) {
179
+ addArgs.push("--icon", arg.slice("--icon=".length));
180
+ continue;
181
+ }
182
+
183
+ if (arg.startsWith("--config=")) {
184
+ addArgs.push("--config", arg.slice("--config=".length));
185
+ continue;
186
+ }
187
+
188
+ if (!arg.startsWith("-") && !url && looksLikeUrl(arg)) {
189
+ url = arg;
190
+ addArgs.push(arg);
191
+ continue;
192
+ }
193
+
194
+ return null;
195
+ }
196
+
197
+ if (!url) {
198
+ return null;
199
+ }
200
+
201
+ return { addArgs, openAfterAdd };
202
+ }
203
+
204
+ function runNative(args, options = {}) {
205
+ const result = spawnSync(nativeExe, args, {
206
+ cwd: process.cwd(),
207
+ encoding: "utf8",
208
+ stdio: options.capture ? "pipe" : "inherit",
209
+ windowsHide: false
210
+ });
211
+
212
+ if (result.error) {
213
+ fail(result.error.message);
214
+ }
215
+
216
+ if (result.signal) {
217
+ fail(`proceso terminado por ${result.signal}`);
218
+ }
219
+
220
+ return result;
221
+ }
222
+
223
+ function openNative(args) {
224
+ const child = spawn(nativeExe, args, {
225
+ cwd: process.cwd(),
226
+ detached: true,
227
+ stdio: "ignore",
228
+ windowsHide: false
229
+ });
230
+
231
+ child.unref();
232
+ }
233
+
234
+ function parseUpsertedId(output) {
235
+ const match = output.match(/^(?:creado|actualizado):\s+([^\s]+)\s+->/m);
236
+ return match ? match[1] : null;
237
+ }
238
+
239
+ if (process.platform !== "win32") {
240
+ fail("esta version empaquetada solo soporta Windows.");
241
+ }
242
+
243
+ if (!fs.existsSync(nativeExe)) {
244
+ fail(`no encontre el binario nativo en ${nativeExe}. Ejecuta "pnpm run build:package" antes de empaquetar.`);
245
+ }
246
+
247
+ const rawArgs = process.argv.slice(2);
248
+
249
+ if (rawArgs.length === 0 || (rawArgs.length === 1 && (rawArgs[0] === "--help" || rawArgs[0] === "-h"))) {
250
+ printHelp();
251
+ process.exit(0);
252
+ }
253
+
254
+ const friendlyCommand = parseFriendlyUrlCommand(rawArgs);
255
+ if (friendlyCommand) {
256
+ const addArgs = withDefaultConfig(["add", ...friendlyCommand.addArgs], rawArgs);
257
+ const addResult = runNative(addArgs, { capture: friendlyCommand.openAfterAdd });
258
+
259
+ if (friendlyCommand.openAfterAdd) {
260
+ if (addResult.stdout) {
261
+ process.stdout.write(addResult.stdout);
262
+ }
263
+ if (addResult.stderr) {
264
+ process.stderr.write(addResult.stderr);
265
+ }
266
+ }
267
+
268
+ if (addResult.status !== 0) {
269
+ process.exit(addResult.status ?? 1);
270
+ }
271
+
272
+ if (!friendlyCommand.openAfterAdd) {
273
+ process.exit(0);
274
+ }
275
+
276
+ const appId = parseUpsertedId(addResult.stdout || "");
277
+ if (!appId) {
278
+ fail("no pude detectar el id del perfil creado.");
279
+ }
280
+
281
+ openNative(withDefaultConfig(["--app", appId], rawArgs));
282
+ process.exit(0);
283
+ }
284
+
285
+ const args = withDefaultConfig(rawArgs, rawArgs);
286
+ const result = runNative(args);
287
+ process.exit(result.status ?? 1);
288
+
289
+
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "misku-native-view-cli",
3
+ "version": "0.1.0-ci.1.2",
4
+ "description": "Installable CLI for launching websites as native Windows WebView apps",
5
+ "license": "UNLICENSED",
6
+ "type": "commonjs",
7
+ "os": [
8
+ "win32"
9
+ ],
10
+ "cpu": [
11
+ "x64"
12
+ ],
13
+ "engines": {
14
+ "node": ">=18"
15
+ },
16
+ "bin": {
17
+ "misku-nv": "./bin/misku.js",
18
+ "misku": "./bin/misku.js",
19
+ "misku-native-views": "./bin/misku.js"
20
+ },
21
+ "files": [
22
+ "bin/",
23
+ "portable/misku-native-views.exe",
24
+ "portable/apps.toml",
25
+ "portable/icons/",
26
+ "README.md"
27
+ ],
28
+ "keywords": [
29
+ "tauri",
30
+ "webview",
31
+ "windows",
32
+ "cli",
33
+ "desktop"
34
+ ],
35
+ "publishConfig": {
36
+ "registry": "https://registry.npmjs.org/",
37
+ "access": "public"
38
+ },
39
+ "scripts": {}
40
+ }
@@ -0,0 +1,27 @@
1
+ [[apps]]
2
+ id = "tftacademy"
3
+ name = "TFT Academy"
4
+ url = "https://tftacademy.com/tierlist/comps/"
5
+ icon = "icons/tftacademy.ico"
6
+ width = 1280
7
+ height = 860
8
+ min_width = 900
9
+ min_height = 640
10
+ isolated_profile = true
11
+ devtools = true
12
+ resizable = true
13
+ zoom_hotkeys_enabled = true
14
+
15
+ [[apps]]
16
+ id = "x"
17
+ name = "X"
18
+ url = "https://x.com/home"
19
+ icon = "icons/twitter.ico"
20
+ width = 1280
21
+ height = 860
22
+ min_width = 900
23
+ min_height = 420
24
+ isolated_profile = true
25
+ devtools = false
26
+ resizable = true
27
+ zoom_hotkeys_enabled = false
Binary file
Binary file
Binary file