@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.
- package/LICENSE +13 -0
- package/bin/mode.js +73 -0
- package/package.json +45 -0
- package/src/commands/build.tish +55 -0
- package/src/commands/dev.tish +222 -0
- package/src/commands/distribute.tish +56 -0
- package/src/commands/doctor.tish +91 -0
- package/src/commands/icon.tish +42 -0
- package/src/commands/info.tish +68 -0
- package/src/commands/init.tish +198 -0
- package/src/commands/ios.tish +124 -0
- package/src/main.tish +105 -0
- package/src/paths.tish +215 -0
- package/templates/app/bridge-boot.js +6 -0
- package/templates/app/index.html +20 -0
- package/templates/app/package.json +32 -0
- package/templates/app/package.registry.json +32 -0
- package/templates/app/public/assets/.gitkeep +0 -0
- package/templates/app/scripts/build-css.tish +25 -0
- package/templates/app/src/main.tish +27 -0
- package/templates/app/ui/main.tish +43 -0
- package/templates/app/vite.config.mjs +38 -0
- package/templates/app/vite.registry.config.mjs +14 -0
- package/templates/bare/app/App.tish +27 -0
- package/templates/bare/app/Button.tish +11 -0
- package/templates/bare/app/Button.web.tish +11 -0
- package/templates/bare/app/Button.webview.tish +11 -0
- package/templates/bare/bridge-boot.js +6 -0
- package/templates/bare/index.html +20 -0
- package/templates/bare/package.json +31 -0
- package/templates/bare/package.registry.json +31 -0
- package/templates/bare/public/assets/.gitkeep +0 -0
- package/templates/bare/scripts/build-css.tish +35 -0
- package/templates/bare/src/main.tish +34 -0
- package/templates/bare/ui/main.tish +8 -0
- package/templates/bare/vite.config.mjs +39 -0
- 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,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>
|