@hamedb89/localghost 0.1.8 → 0.1.10
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 +56 -327
- package/apps/macos-widget/LocalghostWidget.entitlements +12 -0
- package/apps/macos-widget/LocalghostWidget.swift +701 -48
- package/apps/macos-widget/Resources/localghost-logo-source.png +0 -0
- package/apps/macos-widget/Resources/localghost-widget-ui-reference.png +0 -0
- package/apps/macos-widget/Shared/LocalghostWidgetSnapshot.swift +50 -0
- package/apps/macos-widget/WidgetExtension/LocalghostDesktopWidget.swift +176 -0
- package/apps/macos-widget/WidgetExtension/LocalghostDesktopWidgetExtension.entitlements +12 -0
- package/apps/macos-widget/build.sh +8 -1
- package/apps/macos-widget/project.yml +40 -0
- package/assets/ghost-tunnel-app-icon.png +0 -0
- package/assets/ghost-tunnel-portal.png +0 -0
- package/assets/ghost-tunnel-wordmark.png +0 -0
- package/dist/cli.js +528 -63
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +130 -5
- package/dist/index.js +765 -31
- package/dist/index.js.map +1 -1
- package/dist/tunnel-DzfLXZ8O.d.ts +126 -0
- package/dist/vite.d.ts +3 -1
- package/dist/vite.js +660 -36
- package/dist/vite.js.map +1 -1
- package/docs/flows.md +19 -0
- package/docs/ghost-tunnel.md +293 -0
- package/docs/github.md +6 -6
- package/docs/localghost.1.md +10 -4
- package/docs/macos-widget.md +68 -6
- package/package.json +6 -2
- package/dist/config-Cde1Bich.d.ts +0 -31
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
let localghostWidgetAppGroupIdentifier = "group.app.localghost"
|
|
4
|
+
let localghostWidgetSnapshotFileName = "LocalghostWidgetSnapshot.json"
|
|
5
|
+
|
|
6
|
+
struct LocalghostWidgetSnapshot: Codable {
|
|
7
|
+
var generatedAt: String
|
|
8
|
+
var instances: [LocalghostWidgetInstance]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
struct LocalghostWidgetInstance: Codable, Identifiable {
|
|
12
|
+
var id: String
|
|
13
|
+
var projectName: String
|
|
14
|
+
var cwd: String
|
|
15
|
+
var running: Bool
|
|
16
|
+
var mode: String
|
|
17
|
+
var routes: [LocalghostWidgetRoute]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
struct LocalghostWidgetRoute: Codable, Identifiable {
|
|
21
|
+
var id: String { "\(host):\(port)" }
|
|
22
|
+
var host: String
|
|
23
|
+
var port: Int
|
|
24
|
+
var listening: Bool
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
enum LocalghostWidgetSharedStore {
|
|
28
|
+
static func snapshotURL() -> URL {
|
|
29
|
+
if let appGroupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: localghostWidgetAppGroupIdentifier) {
|
|
30
|
+
return appGroupURL.appendingPathComponent(localghostWidgetSnapshotFileName)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let fallback = FileManager.default
|
|
34
|
+
.homeDirectoryForCurrentUser
|
|
35
|
+
.appendingPathComponent("Library/Application Support/Localghost", isDirectory: true)
|
|
36
|
+
return fallback.appendingPathComponent(localghostWidgetSnapshotFileName)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static func readSnapshot() throws -> LocalghostWidgetSnapshot {
|
|
40
|
+
let data = try Data(contentsOf: snapshotURL())
|
|
41
|
+
return try JSONDecoder().decode(LocalghostWidgetSnapshot.self, from: data)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static func writeSnapshot(_ snapshot: LocalghostWidgetSnapshot) throws {
|
|
45
|
+
let url = snapshotURL()
|
|
46
|
+
try FileManager.default.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true)
|
|
47
|
+
let data = try JSONEncoder().encode(snapshot)
|
|
48
|
+
try data.write(to: url, options: [.atomic])
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
import WidgetKit
|
|
3
|
+
|
|
4
|
+
struct LocalghostWidgetEntry: TimelineEntry {
|
|
5
|
+
let date: Date
|
|
6
|
+
let snapshot: LocalghostWidgetSnapshot
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
struct LocalghostWidgetProvider: TimelineProvider {
|
|
10
|
+
func placeholder(in context: Context) -> LocalghostWidgetEntry {
|
|
11
|
+
LocalghostWidgetEntry(date: Date(), snapshot: .placeholder)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
func getSnapshot(in context: Context, completion: @escaping (LocalghostWidgetEntry) -> Void) {
|
|
15
|
+
completion(LocalghostWidgetEntry(date: Date(), snapshot: loadSnapshot()))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func getTimeline(in context: Context, completion: @escaping (Timeline<LocalghostWidgetEntry>) -> Void) {
|
|
19
|
+
let entry = LocalghostWidgetEntry(date: Date(), snapshot: loadSnapshot())
|
|
20
|
+
let nextRefresh = Calendar.current.date(byAdding: .minute, value: 5, to: Date()) ?? Date().addingTimeInterval(300)
|
|
21
|
+
completion(Timeline(entries: [entry], policy: .after(nextRefresh)))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private func loadSnapshot() -> LocalghostWidgetSnapshot {
|
|
25
|
+
(try? LocalghostWidgetSharedStore.readSnapshot()) ?? .placeholder
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
struct LocalghostDesktopWidgetView: View {
|
|
30
|
+
let entry: LocalghostWidgetEntry
|
|
31
|
+
|
|
32
|
+
private var instances: [LocalghostWidgetInstance] {
|
|
33
|
+
entry.snapshot.instances
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private var routes: [LocalghostWidgetRoute] {
|
|
37
|
+
instances.flatMap(\.routes)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private var onlineCount: Int {
|
|
41
|
+
routes.filter(\.listening).count
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
var body: some View {
|
|
45
|
+
ZStack {
|
|
46
|
+
RoundedRectangle(cornerRadius: 24, style: .continuous)
|
|
47
|
+
.fill(.ultraThinMaterial)
|
|
48
|
+
.overlay(
|
|
49
|
+
LinearGradient(
|
|
50
|
+
colors: [
|
|
51
|
+
Color(red: 0.68, green: 0.56, blue: 1.0).opacity(0.18),
|
|
52
|
+
Color(red: 0.03, green: 0.04, blue: 0.16).opacity(0.48)
|
|
53
|
+
],
|
|
54
|
+
startPoint: .topLeading,
|
|
55
|
+
endPoint: .bottomTrailing
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
.overlay(
|
|
59
|
+
RoundedRectangle(cornerRadius: 24, style: .continuous)
|
|
60
|
+
.stroke(.white.opacity(0.18), lineWidth: 1)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
VStack(alignment: .leading, spacing: 14) {
|
|
64
|
+
HStack(spacing: 12) {
|
|
65
|
+
Text(">_")
|
|
66
|
+
.font(.system(size: 24, weight: .bold, design: .monospaced))
|
|
67
|
+
.foregroundStyle(Color(red: 0.68, green: 0.48, blue: 1.0))
|
|
68
|
+
|
|
69
|
+
Text("Localghost")
|
|
70
|
+
.font(.system(size: 24, weight: .bold))
|
|
71
|
+
.foregroundStyle(.white)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
HStack(spacing: 8) {
|
|
75
|
+
Circle()
|
|
76
|
+
.fill(Color(red: 0.36, green: 0.95, blue: 0.58))
|
|
77
|
+
.frame(width: 8, height: 8)
|
|
78
|
+
|
|
79
|
+
Text("\(onlineCount) \(onlineCount == 1 ? "host" : "hosts") online")
|
|
80
|
+
.font(.system(size: 13, weight: .semibold))
|
|
81
|
+
.foregroundStyle(.white.opacity(0.72))
|
|
82
|
+
}
|
|
83
|
+
.padding(.horizontal, 12)
|
|
84
|
+
.padding(.vertical, 7)
|
|
85
|
+
.background(.white.opacity(0.08), in: Capsule())
|
|
86
|
+
|
|
87
|
+
Divider().overlay(.white.opacity(0.12))
|
|
88
|
+
|
|
89
|
+
if routes.isEmpty {
|
|
90
|
+
emptyState
|
|
91
|
+
} else {
|
|
92
|
+
VStack(spacing: 8) {
|
|
93
|
+
ForEach(routes.prefix(4)) { route in
|
|
94
|
+
routeRow(route)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
Spacer(minLength: 0)
|
|
100
|
+
}
|
|
101
|
+
.padding(22)
|
|
102
|
+
}
|
|
103
|
+
.containerBackground(.clear, for: .widget)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private var emptyState: some View {
|
|
107
|
+
VStack(alignment: .leading, spacing: 8) {
|
|
108
|
+
Text("No hosts online")
|
|
109
|
+
.font(.system(size: 18, weight: .semibold))
|
|
110
|
+
.foregroundStyle(.white)
|
|
111
|
+
|
|
112
|
+
Text("Configured setups will stay here when idle.")
|
|
113
|
+
.font(.system(size: 13, weight: .medium))
|
|
114
|
+
.foregroundStyle(.white.opacity(0.64))
|
|
115
|
+
}
|
|
116
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
117
|
+
.padding(16)
|
|
118
|
+
.background(.white.opacity(0.08), in: RoundedRectangle(cornerRadius: 12, style: .continuous))
|
|
119
|
+
.overlay(
|
|
120
|
+
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
|
121
|
+
.stroke(.white.opacity(0.10), lineWidth: 1)
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private func routeRow(_ route: LocalghostWidgetRoute) -> some View {
|
|
126
|
+
HStack(spacing: 10) {
|
|
127
|
+
Circle()
|
|
128
|
+
.fill(route.listening ? Color(red: 0.36, green: 0.95, blue: 0.58) : Color(red: 1.0, green: 0.42, blue: 0.50))
|
|
129
|
+
.frame(width: 8, height: 8)
|
|
130
|
+
|
|
131
|
+
Text(route.host)
|
|
132
|
+
.font(.system(size: 13, weight: .semibold))
|
|
133
|
+
.foregroundStyle(.white)
|
|
134
|
+
.lineLimit(1)
|
|
135
|
+
|
|
136
|
+
Spacer()
|
|
137
|
+
|
|
138
|
+
Text(String(route.port))
|
|
139
|
+
.font(.system(size: 13, weight: .medium, design: .monospaced))
|
|
140
|
+
.foregroundStyle(.white.opacity(0.62))
|
|
141
|
+
}
|
|
142
|
+
.padding(.horizontal, 12)
|
|
143
|
+
.padding(.vertical, 9)
|
|
144
|
+
.background(.white.opacity(0.08), in: RoundedRectangle(cornerRadius: 10, style: .continuous))
|
|
145
|
+
.overlay(
|
|
146
|
+
RoundedRectangle(cornerRadius: 10, style: .continuous)
|
|
147
|
+
.stroke(.white.opacity(0.10), lineWidth: 1)
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
@main
|
|
153
|
+
struct LocalghostDesktopWidgetBundle: WidgetBundle {
|
|
154
|
+
var body: some Widget {
|
|
155
|
+
LocalghostDesktopWidget()
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
struct LocalghostDesktopWidget: Widget {
|
|
160
|
+
let kind = "LocalghostDesktopWidget"
|
|
161
|
+
|
|
162
|
+
var body: some WidgetConfiguration {
|
|
163
|
+
StaticConfiguration(kind: kind, provider: LocalghostWidgetProvider()) { entry in
|
|
164
|
+
LocalghostDesktopWidgetView(entry: entry)
|
|
165
|
+
}
|
|
166
|
+
.configurationDisplayName("Localghost")
|
|
167
|
+
.description("Shows Localghost setup and running local hosts.")
|
|
168
|
+
.supportedFamilies([.systemMedium])
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
extension LocalghostWidgetSnapshot {
|
|
173
|
+
static var placeholder: LocalghostWidgetSnapshot {
|
|
174
|
+
LocalghostWidgetSnapshot(generatedAt: ISO8601DateFormatter().string(from: Date()), instances: [])
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>com.apple.security.app-sandbox</key>
|
|
6
|
+
<true/>
|
|
7
|
+
<key>com.apple.security.application-groups</key>
|
|
8
|
+
<array>
|
|
9
|
+
<string>group.app.localghost</string>
|
|
10
|
+
</array>
|
|
11
|
+
</dict>
|
|
12
|
+
</plist>
|
|
@@ -5,19 +5,26 @@ ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
|
5
5
|
APP_DIR="$ROOT_DIR/dist/LocalghostWidget.app"
|
|
6
6
|
CONTENTS_DIR="$APP_DIR/Contents"
|
|
7
7
|
MACOS_DIR="$CONTENTS_DIR/MacOS"
|
|
8
|
+
RESOURCES_DIR="$CONTENTS_DIR/Resources"
|
|
8
9
|
SOURCE_FILE="$ROOT_DIR/apps/macos-widget/LocalghostWidget.swift"
|
|
10
|
+
SHARED_SOURCE_FILE="$ROOT_DIR/apps/macos-widget/Shared/LocalghostWidgetSnapshot.swift"
|
|
11
|
+
SOURCE_RESOURCES_DIR="$ROOT_DIR/apps/macos-widget/Resources"
|
|
9
12
|
EXECUTABLE="$MACOS_DIR/LocalghostWidget"
|
|
10
13
|
MODULE_CACHE_DIR="${TMPDIR:-/tmp}/localghost-swift-module-cache"
|
|
11
14
|
|
|
12
15
|
rm -rf "$APP_DIR"
|
|
13
|
-
mkdir -p "$MACOS_DIR"
|
|
16
|
+
mkdir -p "$MACOS_DIR" "$RESOURCES_DIR"
|
|
14
17
|
mkdir -p "$MODULE_CACHE_DIR"
|
|
15
18
|
|
|
19
|
+
cp "$SOURCE_RESOURCES_DIR/localghost-logo-source.png" "$RESOURCES_DIR/localghost-logo-source.png"
|
|
20
|
+
cp "$SOURCE_RESOURCES_DIR/localghost-widget-ui-reference.png" "$RESOURCES_DIR/localghost-widget-ui-reference.png"
|
|
21
|
+
|
|
16
22
|
swiftc \
|
|
17
23
|
-parse-as-library \
|
|
18
24
|
-O \
|
|
19
25
|
-module-cache-path "$MODULE_CACHE_DIR" \
|
|
20
26
|
-framework AppKit \
|
|
27
|
+
"$SHARED_SOURCE_FILE" \
|
|
21
28
|
"$SOURCE_FILE" \
|
|
22
29
|
-o "$EXECUTABLE"
|
|
23
30
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: LocalghostWidget
|
|
2
|
+
options:
|
|
3
|
+
bundleIdPrefix: app.localghost
|
|
4
|
+
deploymentTarget:
|
|
5
|
+
macOS: "14.0"
|
|
6
|
+
settings:
|
|
7
|
+
base:
|
|
8
|
+
SWIFT_VERSION: "6.0"
|
|
9
|
+
DEVELOPMENT_TEAM: ""
|
|
10
|
+
targets:
|
|
11
|
+
LocalghostWidget:
|
|
12
|
+
type: application
|
|
13
|
+
platform: macOS
|
|
14
|
+
deploymentTarget: "14.0"
|
|
15
|
+
sources:
|
|
16
|
+
- path: LocalghostWidget.swift
|
|
17
|
+
- path: Shared/LocalghostWidgetSnapshot.swift
|
|
18
|
+
- path: Resources
|
|
19
|
+
entitlements:
|
|
20
|
+
path: LocalghostWidget.entitlements
|
|
21
|
+
settings:
|
|
22
|
+
base:
|
|
23
|
+
PRODUCT_BUNDLE_IDENTIFIER: app.localghost.widget
|
|
24
|
+
INFOPLIST_KEY_CFBundleDisplayName: Localghost Widget
|
|
25
|
+
INFOPLIST_KEY_LSUIElement: true
|
|
26
|
+
dependencies:
|
|
27
|
+
- target: LocalghostDesktopWidgetExtension
|
|
28
|
+
LocalghostDesktopWidgetExtension:
|
|
29
|
+
type: app-extension
|
|
30
|
+
platform: macOS
|
|
31
|
+
deploymentTarget: "14.0"
|
|
32
|
+
sources:
|
|
33
|
+
- path: WidgetExtension/LocalghostDesktopWidget.swift
|
|
34
|
+
- path: Shared/LocalghostWidgetSnapshot.swift
|
|
35
|
+
entitlements:
|
|
36
|
+
path: WidgetExtension/LocalghostDesktopWidgetExtension.entitlements
|
|
37
|
+
settings:
|
|
38
|
+
base:
|
|
39
|
+
PRODUCT_BUNDLE_IDENTIFIER: app.localghost.widget.desktop-extension
|
|
40
|
+
INFOPLIST_KEY_NSExtension_NSExtensionPointIdentifier: com.apple.widgetkit-extension
|
|
Binary file
|
|
Binary file
|
|
Binary file
|