1+ import { existsSync } from 'node:fs'
2+ import { homedir } from 'node:os'
3+ import { join } from 'node:path'
4+
15export interface Desktop {
26 app : unknown
37 core : unknown
@@ -49,10 +53,30 @@ export interface OpenDevWindowOptions {
4953 sidebarConfig ?: unknown
5054 devTools ?: boolean
5155 craftBin ?: string
56+ systemTray ?: boolean
57+ hideDockIcon ?: boolean
58+ menubarOnly ?: boolean
5259}
5360
5461export type CraftLauncher = ( command : string [ ] ) => void | Promise < void >
5562
63+ export function resolveCraftBinary ( explicit : string | undefined = process . env . CRAFT_BIN ) : string {
64+ if ( explicit ) {
65+ if ( ! existsSync ( explicit ) )
66+ throw new Error ( `Craft binary not found: ${ explicit } ` )
67+ return explicit
68+ }
69+
70+ const craftRoot = join ( homedir ( ) , 'Code/Tools/craft' )
71+ const candidates = [
72+ join ( craftRoot , 'packages/zig/zig-out/bin/craft' ) ,
73+ join ( craftRoot , 'craft' ) ,
74+ join ( craftRoot , 'bin/craft' ) ,
75+ ]
76+
77+ return candidates . find ( candidate => existsSync ( candidate ) ) || 'craft'
78+ }
79+
5680export function resolveDevWindowUrl ( port : number , options : OpenDevWindowOptions = { } ) : string {
5781 if ( ! Number . isInteger ( port ) || port < 1 || port > 65_535 )
5882 throw new RangeError ( `Invalid desktop development port: ${ port } ` )
@@ -67,7 +91,7 @@ export function resolveDevWindowUrl(port: number, options: OpenDevWindowOptions
6791
6892export function craftDevCommand ( port : number , options : OpenDevWindowOptions = { } ) : string [ ] {
6993 const command = [
70- options . craftBin || process . env . CRAFT_BIN || 'craft' ,
94+ resolveCraftBinary ( options . craftBin ) ,
7195 resolveDevWindowUrl ( port , options ) ,
7296 '--title' ,
7397 options . title || 'Stacks' ,
@@ -78,12 +102,46 @@ export function craftDevCommand(port: number, options: OpenDevWindowOptions = {}
78102 ]
79103
80104 if ( options . hotReload !== false ) command . push ( '--hot-reload' )
81- if ( options . devTools !== false ) command . push ( '--dev-tools' )
82- if ( options . darkMode ) command . push ( '--dark-mode' )
105+ if ( options . devTools === false ) command . push ( '--no-devtools' )
106+ if ( options . darkMode ) command . push ( '--dark' )
107+ if ( options . systemTray ) command . push ( '--system-tray' )
108+ if ( options . hideDockIcon ) command . push ( '--hide-dock-icon' )
109+ if ( options . menubarOnly ) command . push ( '--menubar-only' )
83110
84111 return command
85112}
86113
114+ export interface InviteLinkOptions {
115+ email ?: string
116+ team ?: string
117+ role ?: string
118+ expiresAt ?: Date | string
119+ }
120+
121+ export function createInviteLink ( baseUrl : string , token : string , options : InviteLinkOptions = { } ) : string {
122+ if ( ! token . trim ( ) )
123+ throw new Error ( 'Invite token is required' )
124+
125+ const url = new URL ( '/invite' , / ^ h t t p s ? : \/ \/ / . test ( baseUrl ) ? baseUrl : `https://${ baseUrl } ` )
126+ url . searchParams . set ( 'token' , token )
127+ if ( options . email ) url . searchParams . set ( 'email' , options . email )
128+ if ( options . team ) url . searchParams . set ( 'team' , options . team )
129+ if ( options . role ) url . searchParams . set ( 'role' , options . role )
130+ if ( options . expiresAt ) {
131+ const date = options . expiresAt instanceof Date ? options . expiresAt : new Date ( options . expiresAt )
132+ if ( Number . isNaN ( date . valueOf ( ) ) )
133+ throw new Error ( 'Invite expiration must be a valid date' )
134+ url . searchParams . set ( 'expires' , date . toISOString ( ) )
135+ }
136+ return url . toString ( )
137+ }
138+
139+ export function createUpdateManifestUrl ( baseUrl : string , channel = 'stable' ) : string {
140+ if ( ! / ^ [ a - z 0 - 9 - ] + $ / i. test ( channel ) )
141+ throw new Error ( 'Update channel may only contain letters, numbers, and hyphens' )
142+ return new URL ( `/desktop/updates/${ channel } .json` , / ^ h t t p s ? : \/ \/ / . test ( baseUrl ) ? baseUrl : `https://${ baseUrl } ` ) . toString ( )
143+ }
144+
87145async function launchCraft ( command : string [ ] ) : Promise < void > {
88146 const process = Bun . spawn ( command , {
89147 stdin : 'inherit' ,
0 commit comments