@tishlang/tish-desktop 1.0.0

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.
Files changed (37) hide show
  1. package/LICENSE +13 -0
  2. package/bin/mode.js +73 -0
  3. package/package.json +45 -0
  4. package/src/commands/build.tish +55 -0
  5. package/src/commands/dev.tish +222 -0
  6. package/src/commands/distribute.tish +56 -0
  7. package/src/commands/doctor.tish +91 -0
  8. package/src/commands/icon.tish +42 -0
  9. package/src/commands/info.tish +68 -0
  10. package/src/commands/init.tish +198 -0
  11. package/src/commands/ios.tish +124 -0
  12. package/src/main.tish +105 -0
  13. package/src/paths.tish +215 -0
  14. package/templates/app/bridge-boot.js +6 -0
  15. package/templates/app/index.html +20 -0
  16. package/templates/app/package.json +32 -0
  17. package/templates/app/package.registry.json +32 -0
  18. package/templates/app/public/assets/.gitkeep +0 -0
  19. package/templates/app/scripts/build-css.tish +25 -0
  20. package/templates/app/src/main.tish +27 -0
  21. package/templates/app/ui/main.tish +43 -0
  22. package/templates/app/vite.config.mjs +38 -0
  23. package/templates/app/vite.registry.config.mjs +14 -0
  24. package/templates/bare/app/App.tish +27 -0
  25. package/templates/bare/app/Button.tish +11 -0
  26. package/templates/bare/app/Button.web.tish +11 -0
  27. package/templates/bare/app/Button.webview.tish +11 -0
  28. package/templates/bare/bridge-boot.js +6 -0
  29. package/templates/bare/index.html +20 -0
  30. package/templates/bare/package.json +31 -0
  31. package/templates/bare/package.registry.json +31 -0
  32. package/templates/bare/public/assets/.gitkeep +0 -0
  33. package/templates/bare/scripts/build-css.tish +35 -0
  34. package/templates/bare/src/main.tish +34 -0
  35. package/templates/bare/ui/main.tish +8 -0
  36. package/templates/bare/vite.config.mjs +39 -0
  37. package/templates/bare/vite.registry.config.mjs +19 -0
@@ -0,0 +1,198 @@
1
+ import { exit, cwd } from "tish:process"
2
+ import { mkdir, readFile, writeFile, readDir, isDir } from "tish:fs"
3
+ import {
4
+ basename,
5
+ cliPackageVersion,
6
+ cliRoot,
7
+ firstPositional,
8
+ flagValue,
9
+ inMonorepoCheckout,
10
+ joinPath,
11
+ pathExists,
12
+ q,
13
+ repoRoot,
14
+ } from "../paths.tish"
15
+
16
+ fn relPath(fromDir, toDir) {
17
+ if (fromDir === toDir) return "."
18
+ let prefix = fromDir
19
+ if (!prefix.endsWith("/")) prefix = prefix + "/"
20
+ if (String(toDir).startsWith(prefix)) {
21
+ return String(toDir).slice(prefix.length)
22
+ }
23
+ // Prefer absolute when the scaffold is outside the repo tree.
24
+ let rootPrefix = toDir
25
+ if (!rootPrefix.endsWith("/")) rootPrefix = rootPrefix + "/"
26
+ if (!String(fromDir).startsWith(rootPrefix) && !String(fromDir).startsWith(String(toDir))) {
27
+ return toDir
28
+ }
29
+ let fromParts = String(fromDir).split("/")
30
+ let toParts = String(toDir).split("/")
31
+ let i = 0
32
+ while (i < fromParts.length && i < toParts.length && fromParts[i] === toParts[i]) {
33
+ i = i + 1
34
+ }
35
+ let parts = []
36
+ let ups = 0
37
+ let u = i
38
+ while (u < fromParts.length) {
39
+ if (fromParts[u] !== "") {
40
+ parts.push("..")
41
+ ups = ups + 1
42
+ }
43
+ u = u + 1
44
+ }
45
+ // Too many hops → absolute (avoids ../../Users/... from /tmp).
46
+ if (ups > 3) return toDir
47
+ let d = i
48
+ while (d < toParts.length) {
49
+ if (toParts[d] !== "") parts.push(toParts[d])
50
+ d = d + 1
51
+ }
52
+ if (parts.length === 0) return "."
53
+ return parts.join("/")
54
+ }
55
+
56
+ fn replaceAll(text, from, to) {
57
+ return String(text).split(from).join(to)
58
+ }
59
+
60
+ fn applyReplacements(text, appName, relRoot, shellOut, port, pkgVersion) {
61
+ let out = String(text)
62
+ out = replaceAll(out, "__APP_NAME__", appName)
63
+ out = replaceAll(out, "__ROOT__", relRoot)
64
+ out = replaceAll(out, "__SHELL_OUT__", shellOut)
65
+ out = replaceAll(out, "__DEV_PORT__", port)
66
+ out = replaceAll(out, "__PKG_VERSION__", pkgVersion)
67
+ return out
68
+ }
69
+
70
+ fn copyTemplateFile(src, dest, appName, relRoot, shellOut, port, pkgVersion) {
71
+ let text = readFile(src)
72
+ if (text === null || String(text).startsWith("Error:")) {
73
+ console.error("init: failed to read template " + src)
74
+ exit(1)
75
+ }
76
+ let out = applyReplacements(text, appName, relRoot, shellOut, port, pkgVersion)
77
+ let slash = dest.lastIndexOf("/")
78
+ if (slash > 0) {
79
+ let parent = dest.slice(0, slash)
80
+ if (parent !== "" && !pathExists(parent)) {
81
+ mkdir(parent, true)
82
+ }
83
+ }
84
+ writeFile(dest, out)
85
+ console.log(" wrote " + dest)
86
+ }
87
+
88
+ fn walkCopy(srcDir, destDir, appName, relRoot, shellOut, port, pkgVersion, skipRegistryExtras) {
89
+ if (!pathExists(srcDir)) {
90
+ console.error("init: template missing: " + srcDir)
91
+ exit(1)
92
+ }
93
+ mkdir(destDir, true)
94
+ let entries = readDir(srcDir)
95
+ if (entries === null) {
96
+ console.error("init: cannot readDir " + srcDir)
97
+ exit(1)
98
+ }
99
+ let i = 0
100
+ while (i < entries.length) {
101
+ let name = String(entries[i])
102
+ if (name === "." || name === "..") {
103
+ i = i + 1
104
+ continue
105
+ }
106
+ // Registry-only stubs are applied separately; skip in the tree walk.
107
+ if (skipRegistryExtras && (name === "package.registry.json" || name === "vite.registry.config.mjs")) {
108
+ i = i + 1
109
+ continue
110
+ }
111
+ let src = joinPath(srcDir, name)
112
+ let dest = joinPath(destDir, name)
113
+ if (isDir(src)) {
114
+ walkCopy(src, dest, appName, relRoot, shellOut, port, pkgVersion, skipRegistryExtras)
115
+ } else {
116
+ copyTemplateFile(src, dest, appName, relRoot, shellOut, port, pkgVersion)
117
+ }
118
+ i = i + 1
119
+ }
120
+ }
121
+
122
+ fn applyRegistryOverlay(templates, target, appName, shellOut, port, pkgVersion) {
123
+ let pkgSrc = joinPath(templates, "package.registry.json")
124
+ let viteSrc = joinPath(templates, "vite.registry.config.mjs")
125
+ if (!pathExists(pkgSrc) || !pathExists(viteSrc)) {
126
+ console.error("init: registry templates missing under " + templates)
127
+ exit(1)
128
+ }
129
+ copyTemplateFile(pkgSrc, joinPath(target, "package.json"), appName, ".", shellOut, port, pkgVersion)
130
+ copyTemplateFile(viteSrc, joinPath(target, "vite.config.mjs"), appName, ".", shellOut, port, pkgVersion)
131
+ }
132
+
133
+ export fn runInit(args) {
134
+ let dirArg = firstPositional(args)
135
+ if (dirArg === null) dirArg = "my-desktop-app"
136
+ let here = cwd()
137
+ let target = dirArg
138
+ if (!String(dirArg).startsWith("/")) {
139
+ target = joinPath(here, dirArg)
140
+ }
141
+ if (pathExists(target) && isDir(target)) {
142
+ let pkg = joinPath(target, "package.json")
143
+ if (pathExists(pkg)) {
144
+ console.error("init: directory already has package.json: " + target)
145
+ exit(1)
146
+ }
147
+ }
148
+
149
+ let root = repoRoot()
150
+ let monorepo = inMonorepoCheckout()
151
+ let pkgVersion = cliPackageVersion()
152
+ // Plan packages-dx: `init --ui none` == BYO bare (no lattish). Alias of `--template bare`.
153
+ let templateName = flagValue(args, "--template")
154
+ let uiName = flagValue(args, "--ui")
155
+ if (uiName !== null) {
156
+ if (templateName !== null && templateName !== uiName) {
157
+ console.error("init: conflicting --template " + templateName + " and --ui " + uiName)
158
+ exit(1)
159
+ }
160
+ templateName = uiName
161
+ }
162
+ if (templateName === null) templateName = "lattish"
163
+ let templateDir = "templates/app"
164
+ if (templateName === "bare" || templateName === "none" || templateName === "byo") {
165
+ templateDir = "templates/bare"
166
+ } else if (templateName === "lattish" || templateName === "app" || templateName === "default") {
167
+ templateDir = "templates/app"
168
+ } else {
169
+ console.error("init: unknown --template/--ui " + templateName + " (use lattish|bare|none)")
170
+ exit(1)
171
+ }
172
+ let templates = joinPath(cliRoot(), templateDir)
173
+ let appName = basename(target)
174
+ if (appName === "" || appName === "." || appName === "/") appName = "my-desktop-app"
175
+
176
+ let relRoot = monorepo ? relPath(target, root) : "."
177
+ let shellOut = "dist/" + appName + "-shell"
178
+ let port = "5173"
179
+
180
+ console.log("[init] scaffolding " + target + " (template=" + templateName + ")")
181
+ if (monorepo) {
182
+ console.log("[init] monorepo mode — path deps → " + relRoot)
183
+ } else {
184
+ console.log("[init] registry mode — @tishlang/tish-desktop*@" + pkgVersion + " (outside tish-mode checkout)")
185
+ }
186
+ mkdir(target, true)
187
+ walkCopy(templates, target, appName, relRoot, shellOut, port, pkgVersion, true)
188
+ if (!monorepo) {
189
+ applyRegistryOverlay(templates, target, appName, shellOut, port, pkgVersion)
190
+ }
191
+
192
+ console.log("")
193
+ console.log("Next:")
194
+ console.log(" cd " + q(target))
195
+ console.log(" npm install")
196
+ console.log(" npx mode dev")
197
+ console.log(" # or: npm run dev")
198
+ }
@@ -0,0 +1,124 @@
1
+ // Launch iOS examples on the Simulator (no Xcode UI).
2
+ // Examples live in tish-desktop; the UIKit host comes from a sibling tish-apple checkout.
3
+ //
4
+ // mode ios --example hello-ios
5
+ // mode ios --example hello-ios --launch-only
6
+ // mode ios --example hello-ios --device "iPhone 16"
7
+ import { exit, cwd, exec, env } from "tish:process"
8
+ import {
9
+ flagValue,
10
+ firstPositional,
11
+ hasFlag,
12
+ joinPath,
13
+ pathExists,
14
+ q,
15
+ repoRoot,
16
+ } from "../paths.tish"
17
+
18
+ fn usage() {
19
+ console.log("mode ios — build + launch an iOS example on the Simulator")
20
+ console.log("")
21
+ console.log("Usage: mode ios [options]")
22
+ console.log("")
23
+ console.log("Options:")
24
+ console.log(" --example <name> hello-ios (default) — under examples/")
25
+ console.log(" --device <name> Preferred simulator (IOS_SIM_DEVICE)")
26
+ console.log(" --launch-only Skip rebuild; only simctl install/launch")
27
+ console.log(" --build-only Rust staticlib + xcodebuild (no launch)")
28
+ console.log(" --apple <path> Path to tish-apple (or TISH_APPLE); required for tish-ios dep")
29
+ console.log("")
30
+ console.log("Requires Xcode CLI tools (xcodebuild, simctl). Does not open Xcode.app.")
31
+ console.log("Examples are owned by tish-desktop; tish-apple provides the host crate only.")
32
+ }
33
+
34
+ fn dirnameOf(p) {
35
+ let s = String(p)
36
+ let i = s.lastIndexOf("/")
37
+ if (i < 0) return "."
38
+ if (i === 0) return "/"
39
+ return s.slice(0, i)
40
+ }
41
+
42
+ fn resolveAppleRoot(args) {
43
+ let fromFlag = flagValue(args, "--apple")
44
+ if (fromFlag !== null) return fromFlag
45
+ let fromEnv = env !== null ? env["TISH_APPLE"] : null
46
+ if (fromEnv !== null && fromEnv !== undefined && fromEnv !== "") return fromEnv
47
+ let root = repoRoot()
48
+ let sibling = joinPath(dirnameOf(root), "tish-apple")
49
+ if (pathExists(joinPath(sibling, "crates/tish-ios/Cargo.toml"))) {
50
+ return sibling
51
+ }
52
+ let alt = joinPath(dirnameOf(dirnameOf(root)), "tish-apple")
53
+ if (pathExists(joinPath(alt, "crates/tish-ios/Cargo.toml"))) {
54
+ return alt
55
+ }
56
+ return null
57
+ }
58
+
59
+ export fn runIos(args) {
60
+ if (hasFlag(args, "--help") || hasFlag(args, "-h") || firstPositional(args) === "help") {
61
+ usage()
62
+ exit(0)
63
+ }
64
+
65
+ let example = flagValue(args, "--example")
66
+ if (example === null) {
67
+ let pos = firstPositional(args)
68
+ if (pos !== null && pos !== "help") {
69
+ example = pos
70
+ } else {
71
+ example = "hello-ios"
72
+ }
73
+ }
74
+
75
+ let root = repoRoot()
76
+ let exampleDir = joinPath(root, "examples/" + example)
77
+ if (!pathExists(joinPath(exampleDir, "package.json"))) {
78
+ console.error("example not found: " + exampleDir)
79
+ console.error("iOS demos live under tish-desktop/examples/ (not tish-apple).")
80
+ exit(1)
81
+ }
82
+ if (!pathExists(joinPath(exampleDir, "scripts/run-sim.sh"))) {
83
+ console.error("example has no scripts/run-sim.sh: " + example)
84
+ exit(1)
85
+ }
86
+
87
+ let apple = resolveAppleRoot(args)
88
+ if (apple === null) {
89
+ console.error("tish-apple checkout not found (needed for tish-ios host crate).")
90
+ console.error("Set TISH_APPLE or pass --apple /path/to/tish-apple")
91
+ exit(1)
92
+ }
93
+ // Ensure npm file: dep resolves (package.json expects ../../../tish-apple from examples/*).
94
+ let expectedApple = joinPath(dirnameOf(root), "tish-apple")
95
+ if (apple !== expectedApple && !pathExists(joinPath(exampleDir, "node_modules/tish-ios"))) {
96
+ console.log("note: tish-apple at " + apple)
97
+ console.log("package.json file: dep expects sibling " + expectedApple)
98
+ console.log("If npm install fails, symlink or set the path accordingly.")
99
+ }
100
+
101
+ let device = flagValue(args, "--device")
102
+ let envPrefix = ""
103
+ if (device !== null) {
104
+ envPrefix = "IOS_SIM_DEVICE=" + q(device) + " "
105
+ }
106
+
107
+ console.log("example: " + exampleDir)
108
+ console.log("tish-apple: " + apple)
109
+
110
+ exec("cd " + q(exampleDir) + " && npm install --silent")
111
+
112
+ if (hasFlag(args, "--launch-only")) {
113
+ exec("cd " + q(exampleDir) + " && " + envPrefix + "npm run launch:sim")
114
+ exit(0)
115
+ }
116
+ if (hasFlag(args, "--build-only")) {
117
+ exec("cd " + q(exampleDir) + " && npm run build")
118
+ exec("cd " + q(exampleDir) + " && " + envPrefix + "npm run xcodebuild:sim")
119
+ exit(0)
120
+ }
121
+
122
+ exec("cd " + q(exampleDir) + " && " + envPrefix + "npm run run")
123
+ exit(0)
124
+ }
package/src/main.tish ADDED
@@ -0,0 +1,105 @@
1
+ // mode CLI entry — Tauri-like subcommands, Tish-first (scii-style).
2
+ //
3
+ // tish run src/main.tish --feature process,fs -- <cmd> [args]
4
+ // ./dist/mode <cmd> [args]
5
+ // npx mode <cmd> [args]
6
+ import { exit } from "tish:process"
7
+ import { userArgs } from "./paths.tish"
8
+ import { runInit } from "./commands/init.tish"
9
+ import { runDev } from "./commands/dev.tish"
10
+ import { runBuild } from "./commands/build.tish"
11
+ import { runInfo } from "./commands/info.tish"
12
+ import { runIcon } from "./commands/icon.tish"
13
+ import { runDistribute } from "./commands/distribute.tish"
14
+ import { runDoctor } from "./commands/doctor.tish"
15
+ import { runIos } from "./commands/ios.tish"
16
+
17
+ fn usage() {
18
+ console.log("mode — Tish Mode / Desktop CLI (@tishlang/tish-desktop)")
19
+ console.log("")
20
+ console.log("Usage: mode <command> [options]")
21
+ console.log("")
22
+ console.log("Commands:")
23
+ console.log(" init [dir] Scaffold a desktop app")
24
+ console.log(" dev [--example name] Vite UI + native shell (parity with scripts/dev-example.mjs)")
25
+ console.log(" build [dir] Production UI + native shell → dist/")
26
+ console.log(" ios [--example name] Build + launch iOS example on Simulator (no Xcode UI)")
27
+ console.log(" doctor Platform/surface, resolve probe, cap matrix (≡ tish app doctor)")
28
+ console.log(" info Print tish / rustc / platform summary")
29
+ console.log(" icon [dir] Generate icon set into project icons/")
30
+ console.log(" distribute <step> Run scripts/distribute/* (build|sign|notarize|updater|release)")
31
+ console.log(" help Show this message")
32
+ console.log("")
33
+ console.log("Init options:")
34
+ console.log(" --template lattish|bare UI pack (default lattish; bare = BYO, no lattish)")
35
+ console.log(" --ui none|lattish alias of --template (none = bare BYO)")
36
+ console.log("")
37
+ console.log("Doctor options:")
38
+ console.log(" --platform <name> macos|ios|windows|linux|web (or TISH_PLATFORM)")
39
+ console.log(" --surface <name> native|webview|web (or TISH_SURFACE)")
40
+ console.log(" --resolve <spec> relative import to probe (default ./Button)")
41
+ console.log(" Note: plan docs say `tish app doctor` — use `mode doctor`.")
42
+ console.log("")
43
+ console.log("Dev options:")
44
+ console.log(" --example <name> basic|file-browser|native-chrome|byo-ui|hybrid")
45
+ console.log(" --rebuild Force rebuild the native shell")
46
+ console.log(" --legacy-node Transitional: call node scripts/dev-example.mjs")
47
+ console.log("")
48
+ console.log("iOS options:")
49
+ console.log(" --example <name> hello-ios (default); requires sibling tish-apple")
50
+ console.log(" --device <name> Preferred simulator (or IOS_SIM_DEVICE)")
51
+ console.log(" --launch-only Skip rebuild")
52
+ console.log(" --build-only Staticlib + xcodebuild only")
53
+ console.log(" --apple <path> tish-apple checkout (or TISH_APPLE)")
54
+ }
55
+
56
+ let args = userArgs()
57
+ let cmd = args.length > 0 ? args[0] : "help"
58
+ let rest = []
59
+ let i = 1
60
+ while (i < args.length) {
61
+ rest.push(args[i])
62
+ i = i + 1
63
+ }
64
+
65
+ if (cmd === "help" || cmd === "--help" || cmd === "-h") {
66
+ usage()
67
+ exit(0)
68
+ }
69
+
70
+ if (cmd === "init") {
71
+ runInit(rest)
72
+ exit(0)
73
+ }
74
+ if (cmd === "dev") {
75
+ runDev(rest)
76
+ exit(0)
77
+ }
78
+ if (cmd === "build") {
79
+ runBuild(rest)
80
+ exit(0)
81
+ }
82
+ if (cmd === "ios") {
83
+ runIos(rest)
84
+ exit(0)
85
+ }
86
+ if (cmd === "doctor") {
87
+ runDoctor(rest)
88
+ exit(0)
89
+ }
90
+ if (cmd === "info") {
91
+ runInfo(rest)
92
+ exit(0)
93
+ }
94
+ if (cmd === "icon") {
95
+ runIcon(rest)
96
+ exit(0)
97
+ }
98
+ if (cmd === "distribute") {
99
+ runDistribute(rest)
100
+ exit(0)
101
+ }
102
+
103
+ console.error("unknown command: " + cmd)
104
+ usage()
105
+ exit(1)
package/src/paths.tish ADDED
@@ -0,0 +1,215 @@
1
+ // Path / argv helpers for the CLI.
2
+ import { argv, cwd, env } from "tish:process"
3
+ import { fileExists, isDir, readFile } from "tish:fs"
4
+
5
+ export fn pathExists(p) {
6
+ return fileExists(p)
7
+ }
8
+
9
+ export fn joinPath(a, b) {
10
+ if (a === null || a === "") return b
11
+ if (b === null || b === "") return a
12
+ if (a === ".") return b
13
+ if (a.endsWith("/")) return a + b
14
+ return a + "/" + b
15
+ }
16
+
17
+ export fn dirname(p) {
18
+ if (p === null || p === "") return "."
19
+ let i = p.lastIndexOf("/")
20
+ if (i < 0) return "."
21
+ if (i === 0) return "/"
22
+ return p.slice(0, i)
23
+ }
24
+
25
+ export fn basename(p) {
26
+ if (p === null || p === "") return ""
27
+ let i = p.lastIndexOf("/")
28
+ if (i < 0) return p
29
+ return p.slice(i + 1)
30
+ }
31
+
32
+ export fn q(s) {
33
+ return "'" + String(s).split("'").join("'\\''") + "'"
34
+ }
35
+
36
+ /** User-facing args after the binary / script path. */
37
+ export fn userArgs() {
38
+ let out = []
39
+ let i = 1
40
+ if (argv.length >= 2 && argv[1] !== null && String(argv[1]).endsWith(".tish")) {
41
+ i = 2
42
+ }
43
+ while (i < argv.length) {
44
+ out.push(argv[i])
45
+ i = i + 1
46
+ }
47
+ return out
48
+ }
49
+
50
+ export fn hasFlag(args, flag) {
51
+ let i = 0
52
+ while (i < args.length) {
53
+ if (args[i] === flag) return true
54
+ i = i + 1
55
+ }
56
+ return false
57
+ }
58
+
59
+ export fn flagValue(args, flag) {
60
+ let i = 0
61
+ while (i < args.length) {
62
+ if (args[i] === flag && i + 1 < args.length) return args[i + 1]
63
+ if (String(args[i]).startsWith(flag + "=")) {
64
+ return String(args[i]).slice(flag.length + 1)
65
+ }
66
+ i = i + 1
67
+ }
68
+ return null
69
+ }
70
+
71
+ export fn firstPositional(args) {
72
+ let i = 0
73
+ while (i < args.length) {
74
+ let a = args[i]
75
+ if (!String(a).startsWith("-")) return a
76
+ if ((a === "--example" || a === "--dir" || a === "--out" || a === "--template" || a === "--ui" || a === "--platform" || a === "--surface" || a === "--desk-platform" || a === "--desk-surface" || a === "--resolve" || a === "--importer" || a === "--device" || a === "--apple") && i + 1 < args.length) {
77
+ i = i + 2
78
+ continue
79
+ }
80
+ i = i + 1
81
+ }
82
+ return null
83
+ }
84
+
85
+ fn looksLikeCliRoot(dir) {
86
+ let pkg = joinPath(dir, "package.json")
87
+ if (!pathExists(pkg)) return false
88
+ let text = readFile(pkg)
89
+ if (text === null || String(text).startsWith("Error:")) return false
90
+ return String(text).indexOf("@tishlang/tish-desktop") >= 0
91
+ }
92
+
93
+ fn looksLikeRepoRoot(dir) {
94
+ return pathExists(joinPath(dir, "crates/tish_desktop")) && pathExists(joinPath(dir, "examples"))
95
+ }
96
+
97
+ /** True when running inside a tish-mode / tish-desktop checkout (path deps available). */
98
+ export fn inMonorepoCheckout() {
99
+ return looksLikeRepoRoot(repoRoot())
100
+ }
101
+
102
+ /** CLI package version (for registry scaffolds outside the monorepo). */
103
+ export fn cliPackageVersion() {
104
+ let pkg = joinPath(cliRoot(), "package.json")
105
+ let text = readFile(pkg)
106
+ if (text === null || String(text).startsWith("Error:")) return "0.1.0"
107
+ let s = String(text)
108
+ let key = "\"version\""
109
+ let i = s.indexOf(key)
110
+ if (i < 0) return "0.1.0"
111
+ let colon = s.indexOf(":", i + key.length)
112
+ if (colon < 0) return "0.1.0"
113
+ let q1 = s.indexOf("\"", colon + 1)
114
+ if (q1 < 0) return "0.1.0"
115
+ let q2 = s.indexOf("\"", q1 + 1)
116
+ if (q2 < 0) return "0.1.0"
117
+ let ver = s.slice(q1 + 1, q2)
118
+ if (ver === "") return "0.1.0"
119
+ return ver
120
+ }
121
+
122
+ /** Resolve the cli/ package directory (absolute when possible). */
123
+ export fn cliRoot() {
124
+ let here = cwd()
125
+ let candidate = null
126
+ if (argv.length >= 2 && argv[1] !== null && String(argv[1]).endsWith(".tish")) {
127
+ let script = String(argv[1])
128
+ if (!script.startsWith("/")) script = joinPath(here, script)
129
+ let srcDir = dirname(script)
130
+ let root = dirname(srcDir)
131
+ if (looksLikeCliRoot(root)) candidate = root
132
+ }
133
+ if (candidate === null && argv.length >= 1 && argv[0] !== null) {
134
+ let exe = String(argv[0])
135
+ if (exe.indexOf("mode") >= 0 || exe.indexOf("tish-desktop") >= 0) {
136
+ let dist = dirname(exe)
137
+ let root = dirname(dist)
138
+ if (looksLikeCliRoot(root)) candidate = root
139
+ }
140
+ }
141
+ if (candidate === null) {
142
+ if (looksLikeCliRoot(here)) candidate = here
143
+ else if (looksLikeCliRoot(joinPath(here, "cli"))) candidate = joinPath(here, "cli")
144
+ }
145
+ if (candidate === null) {
146
+ let walk = here
147
+ let n = 0
148
+ while (n < 8) {
149
+ if (looksLikeCliRoot(walk)) {
150
+ candidate = walk
151
+ break
152
+ }
153
+ if (looksLikeCliRoot(joinPath(walk, "cli"))) {
154
+ candidate = joinPath(walk, "cli")
155
+ break
156
+ }
157
+ let parent = dirname(walk)
158
+ if (parent === walk) break
159
+ walk = parent
160
+ n = n + 1
161
+ }
162
+ }
163
+ if (candidate === null) candidate = here
164
+ if (candidate === "." || !String(candidate).startsWith("/")) {
165
+ if (candidate === "." || candidate === "") return here
166
+ return joinPath(here, candidate)
167
+ }
168
+ return candidate
169
+ }
170
+
171
+ /** Resolve the tish-desktop repo root (parent of cli/). */
172
+ export fn repoRoot() {
173
+ let cli = cliRoot()
174
+ let parent = dirname(cli)
175
+ if (looksLikeRepoRoot(parent)) return parent
176
+ let here = cwd()
177
+ if (looksLikeRepoRoot(here)) return here
178
+ let walk = here
179
+ let n = 0
180
+ while (n < 8) {
181
+ if (looksLikeRepoRoot(walk)) return walk
182
+ let parent2 = dirname(walk)
183
+ if (parent2 === walk) break
184
+ walk = parent2
185
+ n = n + 1
186
+ }
187
+ return parent
188
+ }
189
+
190
+ export fn resolveTish() {
191
+ // Prefer explicit override, then sibling monorepo debug build (has --platform / resolve-id).
192
+ if (env !== null) {
193
+ let custom = env["TISH_PATH"]
194
+ if (custom !== null && custom !== "" && fileExists(String(custom))) return String(custom)
195
+ }
196
+ let root = repoRoot()
197
+ let localDebug = joinPath(root, "../tish/target/debug/tish")
198
+ if (fileExists(localDebug)) return localDebug
199
+ let localRelease = joinPath(root, "../tish/target/release/tish")
200
+ if (fileExists(localRelease)) return localRelease
201
+ let home = ""
202
+ if (env !== null) {
203
+ let h = env["HOME"]
204
+ if (h !== null && h !== "") home = String(h)
205
+ }
206
+ if (home !== "") {
207
+ let cargoTish = home + "/.cargo/bin/tish"
208
+ if (fileExists(cargoTish)) return cargoTish
209
+ }
210
+ return "tish"
211
+ }
212
+
213
+ export fn isDirPath(p) {
214
+ return isDir(p)
215
+ }
@@ -0,0 +1,6 @@
1
+ import { installBridge } from "@tishlang/tish-desktop-api/bridge"
2
+
3
+ installBridge()
4
+ if (import.meta.hot) {
5
+ import.meta.hot.accept(() => installBridge())
6
+ }
@@ -0,0 +1,20 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>__APP_NAME__</title>
7
+ <style>
8
+ html, body, #root { margin: 0; min-height: 100%; }
9
+ </style>
10
+ <link rel="stylesheet" href="/node_modules/@tishlang/tish-desktop-ui-theme/src/fonts.css" />
11
+ <link rel="stylesheet" href="/node_modules/@tishlang/tish-desktop-ui-theme/src/theme.css" />
12
+ <link rel="stylesheet" href="/node_modules/@tishlang/tish-desktop-ui-theme/src/theme-utilities.css" />
13
+ <link rel="stylesheet" href="/assets/app.css" />
14
+ </head>
15
+ <body>
16
+ <div id="root"></div>
17
+ <script type="module" src="/bridge-boot.js"></script>
18
+ <script type="module" src="/ui/main.tish"></script>
19
+ </body>
20
+ </html>