@take-out/scripts 0.0.93 → 0.0.95

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@take-out/scripts",
3
- "version": "0.0.93",
3
+ "version": "0.0.95",
4
4
  "type": "module",
5
5
  "main": "./src/run.ts",
6
6
  "sideEffects": false,
@@ -28,10 +28,12 @@
28
28
  "access": "public"
29
29
  },
30
30
  "dependencies": {
31
- "@take-out/helpers": "0.0.93"
31
+ "@clack/prompts": "^0.8.2",
32
+ "@take-out/helpers": "0.0.95",
33
+ "picocolors": "^1.1.1"
32
34
  },
33
35
  "peerDependencies": {
34
- "vxrn": "^1.4.15"
36
+ "vxrn": "^1.6.1"
35
37
  },
36
38
  "peerDependenciesMeta": {
37
39
  "vxrn": {
@@ -39,6 +41,6 @@
39
41
  }
40
42
  },
41
43
  "devDependencies": {
42
- "vxrn": "1.4.15"
44
+ "vxrn": "1.6.1"
43
45
  }
44
46
  }
@@ -1,51 +1,47 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
- /**
4
- * @description Bootstrap project workspace and build initial packages. This is
5
- * mostly important for Takeout starter kit itself.
6
- */
3
+ import { cmd } from './cmd'
7
4
 
8
- import { existsSync, mkdirSync, readFileSync, symlinkSync } from 'node:fs'
9
- import { exists } from 'node:fs/promises'
10
- import { join } from 'node:path'
5
+ await cmd`bootstrap project workspace and build initial packages`.run(
6
+ async ({ $, fs, path }) => {
7
+ const { existsSync, mkdirSync, readFileSync, symlinkSync } = await import('node:fs')
8
+ const { exists } = fs
11
9
 
12
- import { $ } from 'bun'
10
+ const hasPackages = await exists(`./packages`)
13
11
 
14
- const hasPackages = await exists(`./packages`)
15
-
16
- // only run once:
17
- if (!(await exists(`./node_modules/.bin/tko`))) {
18
- if (hasPackages) {
19
- symlinkBins()
20
- }
21
- }
12
+ // only run once
13
+ if (!(await exists(`./node_modules/.bin/tko`))) {
14
+ if (hasPackages) {
15
+ symlinkBins()
16
+ }
17
+ }
22
18
 
23
- // check if critical packages are built - both helpers and cli are needed for tko to work
24
- if (hasPackages) {
25
- const needsBuild =
26
- !(await exists(`./packages/helpers/dist`)) ||
27
- !(await exists(`./packages/cli/dist/esm`))
19
+ // check if critical packages are built
20
+ if (hasPackages) {
21
+ const needsBuild =
22
+ !(await exists(`./packages/helpers/dist`)) ||
23
+ !(await exists(`./packages/cli/dist/esm`))
28
24
 
29
- if (needsBuild) {
30
- // build helpers first as other packages depend on it
31
- await $`cd packages/helpers && bun run build`
25
+ if (needsBuild) {
26
+ // build helpers first as other packages depend on it
27
+ await $`cd packages/helpers && bun run build`
32
28
 
33
- // then build all other packages in parallel
34
- await $`bun ./packages/scripts/src/run.ts build --no-root`
35
- }
36
- }
29
+ // then build all other packages in parallel
30
+ await $`bun ./packages/scripts/src/run.ts build --no-root`
31
+ }
32
+ }
37
33
 
38
- // show welcome message if not onboarded
39
- checkAndShowWelcome()
34
+ // show welcome message if not onboarded
35
+ checkAndShowWelcome()
40
36
 
41
- function checkAndShowWelcome(cwd: string = process.cwd()): void {
42
- try {
43
- const packagePath = join(cwd, 'package.json')
44
- const pkg = JSON.parse(readFileSync(packagePath, 'utf-8'))
37
+ function checkAndShowWelcome(cwd: string = process.cwd()): void {
38
+ try {
39
+ const packagePath = path.join(cwd, 'package.json')
40
+ const pkg = JSON.parse(readFileSync(packagePath, 'utf-8'))
45
41
 
46
- if (pkg.takeout?.onboarded === false) {
47
- console.info()
48
- console.info(`
42
+ if (pkg.takeout?.onboarded === false) {
43
+ console.info()
44
+ console.info(`
49
45
  ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā•— ā–ˆā–ˆā•—ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā•— ā–ˆā–ˆā•—ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•—
50
46
  ā•šā•ā•ā–ˆā–ˆā•”ā•ā•ā•ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—ā–ˆā–ˆā•‘ ā–ˆā–ˆā•”ā•ā–ˆā–ˆā•”ā•ā•ā•ā•ā•ā–ˆā–ˆā•”ā•ā•ā•ā–ˆā–ˆā•—ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ā•šā•ā•ā–ˆā–ˆā•”ā•ā•ā•
51
47
  ā–ˆā–ˆā•‘ ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•‘ā–ˆā–ˆā–ˆā–ˆā–ˆā•”ā• ā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘
@@ -54,51 +50,50 @@ function checkAndShowWelcome(cwd: string = process.cwd()): void {
54
50
  ā•šā•ā• ā•šā•ā• ā•šā•ā•ā•šā•ā• ā•šā•ā•ā•šā•ā•ā•ā•ā•ā•ā• ā•šā•ā•ā•ā•ā•ā• ā•šā•ā•ā•ā•ā•ā• ā•šā•ā•
55
51
  éŗµ 碼 飯
56
52
  `)
57
- console.info()
58
- console.info(' welcome to takeout! 🄔')
59
- console.info()
60
- console.info(' run \x1b[32mbun onboard\x1b[0m to get things set up')
61
- console.info()
53
+ console.info()
54
+ console.info(' welcome to takeout! 🄔')
55
+ console.info()
56
+ console.info(' run \x1b[32mbun onboard\x1b[0m to get things set up')
57
+ console.info()
58
+ }
59
+ } catch {
60
+ // silently fail if package.json doesn't exist or is malformed
61
+ }
62
62
  }
63
- } catch {
64
- // silently fail if package.json doesn't exist or is malformed
65
- }
66
- }
67
-
68
- function symlinkBins() {
69
- // workaround for https://github.com/oven-sh/bun/issues/19782
70
- // bun doesn't create symlinks for workspace packages properly
71
- const packagesWithCLI = [
72
- { name: 'cli', cliFile: 'cli.mjs', alias: 'tko' },
73
- { name: 'postgres', cliFile: 'cli.cjs' },
74
- ]
75
-
76
- const binDir = join(process.cwd(), 'node_modules', '.bin')
77
63
 
78
- if (!existsSync(binDir)) {
79
- mkdirSync(binDir, { recursive: true })
80
- }
81
-
82
- for (const pkg of packagesWithCLI) {
83
- const binPath = join(binDir, pkg.name)
84
- const sourcePath = join(process.cwd(), 'packages', pkg.name, pkg.cliFile)
85
- symlinkTo(sourcePath, binPath)
86
-
87
- if (pkg.alias) {
88
- const aliasPath = join(binDir, pkg.alias)
89
- // create alias symlink pointing to the source
90
- symlinkTo(sourcePath, aliasPath)
64
+ function symlinkBins() {
65
+ // workaround for https://github.com/oven-sh/bun/issues/19782
66
+ const packagesWithCLI = [
67
+ { name: 'cli', cliFile: 'cli.mjs', alias: 'tko' },
68
+ { name: 'postgres', cliFile: 'cli.cjs' },
69
+ ]
70
+
71
+ const binDir = path.join(process.cwd(), 'node_modules', '.bin')
72
+
73
+ if (!existsSync(binDir)) {
74
+ mkdirSync(binDir, { recursive: true })
75
+ }
76
+
77
+ for (const pkg of packagesWithCLI) {
78
+ const binPath = path.join(binDir, pkg.name)
79
+ const sourcePath = path.join(process.cwd(), 'packages', pkg.name, pkg.cliFile)
80
+ symlinkTo(sourcePath, binPath)
81
+
82
+ if (pkg.alias) {
83
+ const aliasPath = path.join(binDir, pkg.alias)
84
+ symlinkTo(sourcePath, aliasPath)
85
+ }
86
+ }
87
+
88
+ function symlinkTo(source: string, target: string): void {
89
+ if (existsSync(target)) {
90
+ console.info(`āœ“ Symlink already exists: ${target}`)
91
+ return
92
+ }
93
+
94
+ symlinkSync(source, target)
95
+ console.info(`→ Created symlink: ${source} ⇢ ${target}`)
96
+ }
91
97
  }
92
98
  }
93
-
94
- // helper function to create symlink with existence check
95
- function symlinkTo(source: string, target: string): void {
96
- if (existsSync(target)) {
97
- console.info(`āœ“ Symlink already exists: ${target}`)
98
- return
99
- }
100
-
101
- symlinkSync(source, target)
102
- console.info(`→ Created symlink: ${source} ⇢ ${target}`)
103
- }
104
- }
99
+ )
package/src/clean.ts CHANGED
@@ -1,29 +1,29 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
- /**
4
- * @description Clean build artifacts and temporary files
5
- */
3
+ import { cmd } from './cmd'
6
4
 
7
- import { $ } from 'bun'
5
+ await cmd`clean build artifacts and temporary files`
6
+ .args('--full boolean --modules boolean --native boolean')
7
+ .run(async ({ args, $ }) => {
8
+ const full = args.full
9
+ const modules = full || args.modules
10
+ const native = full || args.native
8
11
 
9
- const full = process.argv.includes('--full')
10
- const modules = full || process.argv.includes('--modules')
11
- const native = full || process.argv.includes('--native')
12
+ await $`rm -rf dist types .tamagui .vite node_modules/.cache`
12
13
 
13
- await $`rm -rf dist types .tamagui .vite node_modules/.cache`
14
+ if (modules) {
15
+ console.info('removing all node_modules...')
16
+ await $`find . -name 'node_modules' -type d -prune -exec rm -rf {} +`
17
+ }
14
18
 
15
- if (modules) {
16
- console.info('removing all node_modules...')
17
- await $`find . -name 'node_modules' -type d -prune -exec rm -rf {} +`
18
- }
19
+ if (native) {
20
+ console.info('removing ios and android folders...')
21
+ await $`rm -rf ios android`
22
+ }
19
23
 
20
- if (native) {
21
- console.info('removing ios and android folders...')
22
- await $`rm -rf ios android`
23
- }
24
+ console.info('cleanup complete!')
24
25
 
25
- console.info('cleanup complete!')
26
-
27
- if (!full) {
28
- console.info('use --modules, --native, or --full for deeper cleaning')
29
- }
26
+ if (!full) {
27
+ console.info('use --modules, --native, or --full for deeper cleaning')
28
+ }
29
+ })
package/src/cmd.ts ADDED
@@ -0,0 +1,85 @@
1
+ type Prettify<T> = { [K in keyof T]: T[K] } & {}
2
+
3
+ type CmdContext = {
4
+ $: typeof import('bun').$
5
+ colors: typeof import('picocolors')
6
+ prompt: typeof import('@clack/prompts')
7
+ run: typeof import('./helpers/run').run
8
+ fs: typeof import('node:fs/promises')
9
+ path: typeof import('node:path')
10
+ os: typeof import('node:os')
11
+ }
12
+
13
+ type Args<S extends string> = import('./helpers/args').Args<S>
14
+
15
+ type RunFn<S extends string> = (
16
+ fn: (ctx: Prettify<{ args: Args<S> } & CmdContext>) => Promise<void> | void
17
+ ) => Promise<void>
18
+
19
+ let _interceptCmd: ((info: { description: string; args?: string }) => void) | undefined
20
+
21
+ export function setInterceptCmd(
22
+ fn: ((info: { description: string; args?: string }) => void) | undefined
23
+ ) {
24
+ _interceptCmd = fn
25
+ }
26
+
27
+ function createCmd(description: string) {
28
+ let argsSpec: string | undefined
29
+
30
+ function makeRun<S extends string>(spec: S): RunFn<S> {
31
+ return async (fn) => {
32
+ if (_interceptCmd) {
33
+ _interceptCmd({ description, args: argsSpec })
34
+ return
35
+ }
36
+
37
+ const colors = (await import(`picocolors`)).default
38
+
39
+ if (process.argv.includes(`--help`) || process.argv.includes(`-h`)) {
40
+ console.info()
41
+ console.info(colors.bold(description))
42
+ if (argsSpec) {
43
+ console.info()
44
+ const flags = [...argsSpec.matchAll(/--?([a-z-]+)\s+(string|number|boolean)/gi)]
45
+ for (const [, flag, type] of flags) {
46
+ console.info(` --${flag} ${colors.dim(type!)}`)
47
+ }
48
+ }
49
+ console.info()
50
+ process.exit(0)
51
+ }
52
+
53
+ const [{ $ }, prompt, { run }, { args: parseArgs }, fs, path, os] =
54
+ await Promise.all([
55
+ import(`bun`),
56
+ import(`@clack/prompts`),
57
+ import(`./helpers/run`),
58
+ import(`./helpers/args`),
59
+ import(`node:fs/promises`),
60
+ import(`node:path`),
61
+ import(`node:os`),
62
+ ])
63
+
64
+ const args = spec ? parseArgs(spec) : ({ rest: [] } as Args<S>)
65
+ await fn({ args, $, colors, prompt, run, fs, path, os } as Prettify<
66
+ { args: Args<S> } & CmdContext
67
+ >)
68
+ }
69
+ }
70
+
71
+ return {
72
+ args<const S extends string>(spec: S) {
73
+ argsSpec = spec
74
+ return { run: makeRun(spec) }
75
+ },
76
+ run: makeRun(`` as ``),
77
+ }
78
+ }
79
+
80
+ export function cmd(description: string): ReturnType<typeof createCmd>
81
+ export function cmd(strings: TemplateStringsArray): ReturnType<typeof createCmd>
82
+ export function cmd(input: string | TemplateStringsArray) {
83
+ const description = typeof input === `string` ? input : input[0] || ``
84
+ return createCmd(description)
85
+ }
package/src/dev-tunnel.ts CHANGED
@@ -1,179 +1,161 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
4
- import { homedir } from 'node:os'
5
- import { join } from 'node:path'
6
-
7
- import { handleProcessExit } from './helpers/handleProcessExit'
8
- import { run } from './helpers/run'
9
-
10
- handleProcessExit()
11
-
12
- const TUNNEL_CONFIG_DIR = join(homedir(), '.onechat-tunnel')
13
- const TUNNEL_ID_FILE = join(TUNNEL_CONFIG_DIR, 'tunnel-id.txt')
14
-
15
- async function ensureCloudflared(): Promise<boolean> {
16
- try {
17
- // check if cloudflared is installed
18
- await run('cloudflared --version', { silent: true })
19
- return true
20
- } catch {
21
- // install cloudflared using npm
22
- try {
23
- await run('npm install -g cloudflared')
24
- return true
25
- } catch (error) {
26
- console.error('Error installing cloudflared:', error)
27
- return false
28
- }
29
- }
30
- }
31
-
32
- async function ensureAuthenticated(): Promise<boolean> {
33
- // check if we have credentials
34
- const certPath = join(homedir(), '.cloudflared', 'cert.pem')
35
- if (existsSync(certPath)) {
36
- return true
37
- }
38
-
39
- try {
40
- await run('cloudflared tunnel login')
41
- return true
42
- } catch {
43
- console.error('\nāŒ Authentication failed')
44
- return false
45
- }
46
- }
47
-
48
- async function getOrCreateTunnel(): Promise<string | null> {
49
- // create config directory if it doesn't exist
50
- if (!existsSync(TUNNEL_CONFIG_DIR)) {
51
- mkdirSync(TUNNEL_CONFIG_DIR, { recursive: true })
52
- }
53
-
54
- // check if we have a saved tunnel ID
55
- if (existsSync(TUNNEL_ID_FILE)) {
56
- const tunnelId = readFileSync(TUNNEL_ID_FILE, 'utf-8').trim()
57
- return tunnelId
58
- }
59
-
60
- // create a new tunnel with a stable name
61
- const tunnelName = `onechat-dev-${process.env.USER || 'tunnel'}`
62
-
63
- try {
64
- const { stdout, stderr } = await run(`cloudflared tunnel create ${tunnelName}`, {
65
- captureOutput: true,
66
- })
67
- const output = stdout + stderr
3
+ import { cmd } from './cmd'
4
+
5
+ await cmd`set up cloudflare dev tunnel for local development`
6
+ .args('--port number')
7
+ .run(async ({ args, run, os, path }) => {
8
+ const { existsSync, mkdirSync, readFileSync, writeFileSync } = await import('node:fs')
9
+ const { handleProcessExit } = await import('./helpers/handleProcessExit')
68
10
 
69
- // extract tunnel ID from output - try multiple patterns
70
- const match1 = output.match(/Created tunnel .+ with id ([a-f0-9-]+)/i)
71
- const match2 = output.match(/Tunnel ([a-f0-9-]+) created/i)
72
- const match3 = output.match(
73
- /([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})/i
74
- )
11
+ handleProcessExit()
75
12
 
76
- const tunnelId = match1?.[1] || match2?.[1] || match3?.[1]
13
+ const TUNNEL_CONFIG_DIR = path.join(os.homedir(), '.onechat-tunnel')
14
+ const TUNNEL_ID_FILE = path.join(TUNNEL_CONFIG_DIR, 'tunnel-id.txt')
77
15
 
78
- if (tunnelId) {
79
- writeFileSync(TUNNEL_ID_FILE, tunnelId)
80
- return tunnelId
16
+ async function ensureCloudflared(): Promise<boolean> {
17
+ try {
18
+ await run('cloudflared --version', { silent: true })
19
+ return true
20
+ } catch {
21
+ try {
22
+ await run('npm install -g cloudflared')
23
+ return true
24
+ } catch (error) {
25
+ console.error('Error installing cloudflared:', error)
26
+ return false
27
+ }
28
+ }
81
29
  }
82
30
 
83
- console.error('Failed to extract tunnel ID from output')
84
- console.error('Output was:', output)
85
- return null
86
- } catch (error: any) {
87
- // check if tunnel already exists
88
- if (error.message?.includes('already exists')) {
31
+ async function ensureAuthenticated(): Promise<boolean> {
32
+ const certPath = path.join(os.homedir(), '.cloudflared', 'cert.pem')
33
+ if (existsSync(certPath)) {
34
+ return true
35
+ }
36
+
89
37
  try {
90
- const { stdout } = await run(
91
- `cloudflared tunnel list --name ${tunnelName} --output json`,
92
- { captureOutput: true }
38
+ await run('cloudflared tunnel login')
39
+ return true
40
+ } catch {
41
+ console.error('\nāŒ Authentication failed')
42
+ return false
43
+ }
44
+ }
45
+
46
+ async function getOrCreateTunnel(): Promise<string | null> {
47
+ if (!existsSync(TUNNEL_CONFIG_DIR)) {
48
+ mkdirSync(TUNNEL_CONFIG_DIR, { recursive: true })
49
+ }
50
+
51
+ if (existsSync(TUNNEL_ID_FILE)) {
52
+ const tunnelId = readFileSync(TUNNEL_ID_FILE, 'utf-8').trim()
53
+ return tunnelId
54
+ }
55
+
56
+ const tunnelName = `onechat-dev-${process.env.USER || 'tunnel'}`
57
+
58
+ try {
59
+ const { stdout, stderr } = await run(`cloudflared tunnel create ${tunnelName}`, {
60
+ captureOutput: true,
61
+ })
62
+ const output = stdout + stderr
63
+
64
+ const match1 = output.match(/Created tunnel .+ with id ([a-f0-9-]+)/i)
65
+ const match2 = output.match(/Tunnel ([a-f0-9-]+) created/i)
66
+ const match3 = output.match(
67
+ /([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})/i
93
68
  )
94
- const tunnels = JSON.parse(stdout)
95
- if (tunnels.length > 0) {
96
- const tunnelId = tunnels[0].id
69
+
70
+ const tunnelId = match1?.[1] || match2?.[1] || match3?.[1]
71
+
72
+ if (tunnelId) {
97
73
  writeFileSync(TUNNEL_ID_FILE, tunnelId)
98
74
  return tunnelId
99
75
  }
100
- } catch (e) {
101
- console.error('Failed to parse tunnel list:', e)
76
+
77
+ console.error('Failed to extract tunnel ID from output')
78
+ console.error('Output was:', output)
79
+ return null
80
+ } catch (error: any) {
81
+ if (error.message?.includes('already exists')) {
82
+ try {
83
+ const { stdout } = await run(
84
+ `cloudflared tunnel list --name ${tunnelName} --output json`,
85
+ { captureOutput: true }
86
+ )
87
+ const tunnels = JSON.parse(stdout)
88
+ if (tunnels.length > 0) {
89
+ const tunnelId = tunnels[0].id
90
+ writeFileSync(TUNNEL_ID_FILE, tunnelId)
91
+ return tunnelId
92
+ }
93
+ } catch (e) {
94
+ console.error('Failed to parse tunnel list:', e)
95
+ }
96
+ } else {
97
+ console.error('Failed to create tunnel:', error)
98
+ }
99
+ return null
102
100
  }
103
- } else {
104
- console.error('Failed to create tunnel:', error)
105
101
  }
106
- return null
107
- }
108
- }
109
-
110
- async function runTunnel(port: number = 8081) {
111
- // ensure cloudflared is installed
112
- const isInstalled = await ensureCloudflared()
113
- if (!isInstalled) {
114
- console.error('Failed to install cloudflared')
115
- process.exit(1)
116
- }
117
-
118
- // ensure authenticated
119
- const isAuthenticated = await ensureAuthenticated()
120
- if (!isAuthenticated) {
121
- console.error('Failed to authenticate with Cloudflare')
122
- process.exit(1)
123
- }
124
-
125
- // get or create tunnel
126
- const tunnelId = await getOrCreateTunnel()
127
- if (!tunnelId) {
128
- console.error('Failed to get or create tunnel')
129
- process.exit(1)
130
- }
131
-
132
- // save the expected URL immediately so it's available right away
133
- const expectedUrl = `https://${tunnelId}.cfargotunnel.com`
134
- writeFileSync(join(TUNNEL_CONFIG_DIR, 'tunnel-url.txt'), expectedUrl)
135
- console.info(`\n🌐 Tunnel URL: ${expectedUrl}`)
136
-
137
- // get the public URL in the background
138
- setTimeout(async () => {
139
- try {
140
- const { stdout } = await run(`cloudflared tunnel info ${tunnelId} --output json`, {
141
- captureOutput: true,
142
- silent: true,
143
- })
144
102
 
103
+ const port = args.port ?? 8081
104
+
105
+ // ensure cloudflared is installed
106
+ const isInstalled = await ensureCloudflared()
107
+ if (!isInstalled) {
108
+ console.error('Failed to install cloudflared')
109
+ process.exit(1)
110
+ }
111
+
112
+ // ensure authenticated
113
+ const isAuthenticated = await ensureAuthenticated()
114
+ if (!isAuthenticated) {
115
+ console.error('Failed to authenticate with Cloudflare')
116
+ process.exit(1)
117
+ }
118
+
119
+ // get or create tunnel
120
+ const tunnelId = await getOrCreateTunnel()
121
+ if (!tunnelId) {
122
+ console.error('Failed to get or create tunnel')
123
+ process.exit(1)
124
+ }
125
+
126
+ // save the expected url immediately so it's available right away
127
+ const expectedUrl = `https://${tunnelId}.cfargotunnel.com`
128
+ writeFileSync(path.join(TUNNEL_CONFIG_DIR, 'tunnel-url.txt'), expectedUrl)
129
+ console.info(`\n🌐 Tunnel URL: ${expectedUrl}`)
130
+
131
+ // get the public url in the background
132
+ setTimeout(async () => {
145
133
  try {
146
- const info = JSON.parse(stdout)
147
- const hostname = info.hostname || `${tunnelId}.cfargotunnel.com`
148
- writeFileSync(join(TUNNEL_CONFIG_DIR, 'tunnel-url.txt'), `https://${hostname}`)
134
+ const { stdout } = await run(
135
+ `cloudflared tunnel info ${tunnelId} --output json`,
136
+ {
137
+ captureOutput: true,
138
+ silent: true,
139
+ }
140
+ )
141
+
142
+ try {
143
+ const info = JSON.parse(stdout)
144
+ const hostname = info.hostname || `${tunnelId}.cfargotunnel.com`
145
+ writeFileSync(
146
+ path.join(TUNNEL_CONFIG_DIR, 'tunnel-url.txt'),
147
+ `https://${hostname}`
148
+ )
149
+ } catch (e) {
150
+ // use fallback url already saved
151
+ }
149
152
  } catch (e) {
150
- // use fallback URL already saved
153
+ // use fallback url already saved
151
154
  }
152
- } catch (e) {
153
- // use fallback URL already saved
154
- }
155
- }, 3000)
155
+ }, 3000)
156
156
 
157
- // run tunnel in detached mode so it keeps running and gets managed by handleProcessExit
158
- await run(`cloudflared tunnel run --url http://localhost:${port} ${tunnelId}`, {
159
- detached: false,
157
+ // run tunnel - managed by handleProcessExit
158
+ await run(`cloudflared tunnel run --url http://localhost:${port} ${tunnelId}`, {
159
+ detached: false,
160
+ })
160
161
  })
161
- }
162
-
163
- // Parse command line arguments
164
- const args = process.argv.slice(2)
165
- let port = 8081
166
-
167
- for (let i = 0; i < args.length; i++) {
168
- if (args[i] === '--port' && args[i + 1]) {
169
- const portArg = args[i + 1]
170
- if (portArg) {
171
- const parsedPort = parseInt(portArg, 10)
172
- if (!Number.isNaN(parsedPort)) {
173
- port = parsedPort
174
- }
175
- }
176
- }
177
- }
178
-
179
- runTunnel(port)